@earendil-works/pi-agent-core 0.85.0 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -6
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +64 -22
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +5 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +15 -6
- package/dist/agent.js.map +1 -1
- package/dist/harness/config.d.ts +1 -1
- package/dist/harness/config.d.ts.map +1 -1
- package/dist/harness/config.js +10 -2
- package/dist/harness/config.js.map +1 -1
- package/dist/harness/env/nodejs.d.ts +2 -1
- package/dist/harness/env/nodejs.d.ts.map +1 -1
- package/dist/harness/env/nodejs.js +98 -27
- package/dist/harness/env/nodejs.js.map +1 -1
- package/dist/harness/execution/tools.d.ts +1 -1
- package/dist/harness/execution/tools.d.ts.map +1 -1
- package/dist/harness/execution/tools.js +0 -2
- package/dist/harness/execution/tools.js.map +1 -1
- package/dist/harness/messages.d.ts.map +1 -1
- package/dist/harness/messages.js +1 -0
- package/dist/harness/messages.js.map +1 -1
- package/dist/harness/pico3/bash.d.ts +10 -0
- package/dist/harness/pico3/bash.d.ts.map +1 -0
- package/dist/harness/pico3/bash.js +25 -0
- package/dist/harness/pico3/bash.js.map +1 -0
- package/dist/harness/pico3/bounded.d.ts +21 -0
- package/dist/harness/pico3/bounded.d.ts.map +1 -0
- package/dist/harness/pico3/bounded.js +98 -0
- package/dist/harness/pico3/bounded.js.map +1 -0
- package/dist/harness/pico3/chord.d.ts +41 -0
- package/dist/harness/pico3/chord.d.ts.map +1 -0
- package/dist/harness/pico3/chord.js +130 -0
- package/dist/harness/pico3/chord.js.map +1 -0
- package/dist/harness/pico3/context.d.ts +16 -0
- package/dist/harness/pico3/context.d.ts.map +1 -0
- package/dist/harness/pico3/context.js +88 -0
- package/dist/harness/pico3/context.js.map +1 -0
- package/dist/harness/pico3/harness.d.ts +197 -0
- package/dist/harness/pico3/harness.d.ts.map +1 -0
- package/dist/harness/pico3/harness.js +654 -0
- package/dist/harness/pico3/harness.js.map +1 -0
- package/dist/harness/pico3/hooks.d.ts +10 -0
- package/dist/harness/pico3/hooks.d.ts.map +1 -0
- package/dist/harness/pico3/hooks.js +33 -0
- package/dist/harness/pico3/hooks.js.map +1 -0
- package/dist/harness/pico3/index.d.ts +19 -0
- package/dist/harness/pico3/index.d.ts.map +1 -0
- package/dist/harness/pico3/index.js +15 -0
- package/dist/harness/pico3/index.js.map +1 -0
- package/dist/harness/pico3/jsonl.d.ts +58 -0
- package/dist/harness/pico3/jsonl.d.ts.map +1 -0
- package/dist/harness/pico3/jsonl.js +325 -0
- package/dist/harness/pico3/jsonl.js.map +1 -0
- package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
- package/dist/harness/pico3/kinds/collapse.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/collapse.js +191 -0
- package/dist/harness/pico3/kinds/collapse.js.map +1 -0
- package/dist/harness/pico3/kinds/entries.d.ts +78 -0
- package/dist/harness/pico3/kinds/entries.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/entries.js +13 -0
- package/dist/harness/pico3/kinds/entries.js.map +1 -0
- package/dist/harness/pico3/kinds/frames.d.ts +9 -0
- package/dist/harness/pico3/kinds/frames.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/frames.js +75 -0
- package/dist/harness/pico3/kinds/frames.js.map +1 -0
- package/dist/harness/pico3/kinds/generation.d.ts +94 -0
- package/dist/harness/pico3/kinds/generation.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/generation.js +504 -0
- package/dist/harness/pico3/kinds/generation.js.map +1 -0
- package/dist/harness/pico3/kinds/job.d.ts +44 -0
- package/dist/harness/pico3/kinds/job.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/job.js +143 -0
- package/dist/harness/pico3/kinds/job.js.map +1 -0
- package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
- package/dist/harness/pico3/kinds/plugin.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/plugin.js +35 -0
- package/dist/harness/pico3/kinds/plugin.js.map +1 -0
- package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
- package/dist/harness/pico3/kinds/post-tools.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/post-tools.js +145 -0
- package/dist/harness/pico3/kinds/post-tools.js.map +1 -0
- package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
- package/dist/harness/pico3/kinds/task-api.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/task-api.js +45 -0
- package/dist/harness/pico3/kinds/task-api.js.map +1 -0
- package/dist/harness/pico3/kinds/tool.d.ts +43 -0
- package/dist/harness/pico3/kinds/tool.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/tool.js +373 -0
- package/dist/harness/pico3/kinds/tool.js.map +1 -0
- package/dist/harness/pico3/membrane.d.ts +23 -0
- package/dist/harness/pico3/membrane.d.ts.map +1 -0
- package/dist/harness/pico3/membrane.js +141 -0
- package/dist/harness/pico3/membrane.js.map +1 -0
- package/dist/harness/pico3/memory.d.ts +53 -0
- package/dist/harness/pico3/memory.d.ts.map +1 -0
- package/dist/harness/pico3/memory.js +264 -0
- package/dist/harness/pico3/memory.js.map +1 -0
- package/dist/harness/pico3/scheduler.d.ts +46 -0
- package/dist/harness/pico3/scheduler.d.ts.map +1 -0
- package/dist/harness/pico3/scheduler.js +438 -0
- package/dist/harness/pico3/scheduler.js.map +1 -0
- package/dist/harness/pico3/session.d.ts +263 -0
- package/dist/harness/pico3/session.d.ts.map +1 -0
- package/dist/harness/pico3/session.js +1347 -0
- package/dist/harness/pico3/session.js.map +1 -0
- package/dist/harness/pico3/system.d.ts +126 -0
- package/dist/harness/pico3/system.d.ts.map +1 -0
- package/dist/harness/pico3/system.js +244 -0
- package/dist/harness/pico3/system.js.map +1 -0
- package/dist/harness/pico3/types.d.ts +973 -0
- package/dist/harness/pico3/types.d.ts.map +1 -0
- package/dist/harness/pico3/types.js +96 -0
- package/dist/harness/pico3/types.js.map +1 -0
- package/dist/harness/pico3/view.d.ts +34 -0
- package/dist/harness/pico3/view.d.ts.map +1 -0
- package/dist/harness/pico3/view.js +417 -0
- package/dist/harness/pico3/view.js.map +1 -0
- package/dist/harness/runtime/drive/boundary.d.ts.map +1 -1
- package/dist/harness/runtime/drive/boundary.js +6 -3
- package/dist/harness/runtime/drive/boundary.js.map +1 -1
- package/dist/harness/runtime/drive/response.d.ts.map +1 -1
- package/dist/harness/runtime/drive/response.js +4 -4
- package/dist/harness/runtime/drive/response.js.map +1 -1
- package/dist/harness/runtime/drive/retry.d.ts +2 -2
- package/dist/harness/runtime/drive/retry.d.ts.map +1 -1
- package/dist/harness/runtime/drive/retry.js +3 -6
- package/dist/harness/runtime/drive/retry.js.map +1 -1
- package/dist/harness/runtime/drive/structural.d.ts.map +1 -1
- package/dist/harness/runtime/drive/structural.js +5 -5
- package/dist/harness/runtime/drive/structural.js.map +1 -1
- package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -1
- package/dist/harness/runtime/drive/tool-placement.js +2 -27
- package/dist/harness/runtime/drive/tool-placement.js.map +1 -1
- package/dist/harness/runtime/drive/tools.d.ts.map +1 -1
- package/dist/harness/runtime/drive/tools.js.map +1 -1
- package/dist/harness/session/fork-policy.d.ts +20 -12
- package/dist/harness/session/fork-policy.d.ts.map +1 -1
- package/dist/harness/session/fork-policy.js +45 -12
- package/dist/harness/session/fork-policy.js.map +1 -1
- package/dist/harness/session/fork.d.ts +0 -2
- package/dist/harness/session/fork.d.ts.map +1 -1
- package/dist/harness/session/fork.js +20 -68
- package/dist/harness/session/fork.js.map +1 -1
- package/dist/harness/session/in-memory-storage-state.d.ts +5 -5
- package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -1
- package/dist/harness/session/in-memory-storage-state.js +99 -39
- package/dist/harness/session/in-memory-storage-state.js.map +1 -1
- package/dist/harness/session/index.d.ts +1 -1
- package/dist/harness/session/index.d.ts.map +1 -1
- package/dist/harness/session/index.js +1 -1
- package/dist/harness/session/index.js.map +1 -1
- package/dist/harness/session/jsonl/fork.d.ts +38 -0
- package/dist/harness/session/jsonl/fork.d.ts.map +1 -0
- package/dist/harness/session/jsonl/fork.js +264 -0
- package/dist/harness/session/jsonl/fork.js.map +1 -0
- package/dist/harness/session/jsonl/io.d.ts +14 -0
- package/dist/harness/session/jsonl/io.d.ts.map +1 -0
- package/dist/harness/session/jsonl/io.js +86 -0
- package/dist/harness/session/jsonl/io.js.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts +33 -8
- package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -1
- package/dist/harness/session/jsonl/legacy-v3.js +285 -206
- package/dist/harness/session/jsonl/legacy-v3.js.map +1 -1
- package/dist/harness/session/jsonl/repo.d.ts +2 -1
- package/dist/harness/session/jsonl/repo.d.ts.map +1 -1
- package/dist/harness/session/jsonl/repo.js +36 -20
- package/dist/harness/session/jsonl/repo.js.map +1 -1
- package/dist/harness/session/jsonl/storage.d.ts +4 -5
- package/dist/harness/session/jsonl/storage.d.ts.map +1 -1
- package/dist/harness/session/jsonl/storage.js +39 -113
- package/dist/harness/session/jsonl/storage.js.map +1 -1
- package/dist/harness/session/memory.d.ts +3 -5
- package/dist/harness/session/memory.d.ts.map +1 -1
- package/dist/harness/session/memory.js +9 -14
- package/dist/harness/session/memory.js.map +1 -1
- package/dist/harness/session/testing/conformance/session-repo.d.ts +2 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -1
- package/dist/harness/session/testing/conformance/session-repo.js +220 -1
- package/dist/harness/session/testing/conformance/session-repo.js.map +1 -1
- package/dist/harness/session/testing/index.d.ts +1 -1
- package/dist/harness/session/testing/index.d.ts.map +1 -1
- package/dist/harness/session/testing/index.js +1 -1
- package/dist/harness/session/testing/index.js.map +1 -1
- package/dist/harness/session/types.d.ts +1 -0
- package/dist/harness/session/types.d.ts.map +1 -1
- package/dist/harness/session/types.js.map +1 -1
- package/dist/harness/types.d.ts +14 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/proxy.d.ts +2 -2
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js.map +1 -1
- package/dist/types.d.ts +30 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -6
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { uuidv7 } from "@earendil-works/pi-ai/utils/uuid";
|
|
2
2
|
import { createBranchSummaryMessage, createCompactionSummaryMessage } from "../../messages.js";
|
|
3
3
|
import { addUsage, emptyUsage } from "../../utils/usage.js";
|
|
4
|
-
import { branchTip, entryLabel, laneConfig, laneState, sessionName } from "../values.js";
|
|
4
|
+
import { branchTip, entryLabel, laneConfig, laneState, sessionName, setValue } from "../values.js";
|
|
5
5
|
import { parseJsonlSessionHeader } from "./codec.js";
|
|
6
|
+
import { fileValue, readJsonlHeader } from "./io.js";
|
|
6
7
|
import { JSONL_FORMAT_VERSION, JSONL_STORAGE_VERSION, } from "./types.js";
|
|
7
8
|
async function resolveLegacyV3ParentSessionId(fileSystem, parentSessionPath, context) {
|
|
8
9
|
const lines = await fileSystem.readTextLines(parentSessionPath, { maxLines: 1 }, context);
|
|
@@ -34,15 +35,15 @@ export async function normalizeLegacyV3Header(fileSystem, header, context) {
|
|
|
34
35
|
...(await metadataFromLegacyV3Header(fileSystem, header, context)),
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
|
-
function parseLegacyV3Entry(line
|
|
38
|
+
function parseLegacyV3Entry(line) {
|
|
38
39
|
let entry;
|
|
39
40
|
try {
|
|
40
41
|
entry = JSON.parse(line);
|
|
41
42
|
}
|
|
42
43
|
catch (error) {
|
|
43
|
-
throw new Error(
|
|
44
|
+
throw new Error("Invalid legacy v3 JSONL record: not valid JSON", { cause: error });
|
|
44
45
|
}
|
|
45
|
-
const recordType = entry
|
|
46
|
+
const recordType = entry?.type;
|
|
46
47
|
if (recordType !== "message" &&
|
|
47
48
|
recordType !== "custom" &&
|
|
48
49
|
recordType !== "custom_message" &&
|
|
@@ -53,7 +54,7 @@ function parseLegacyV3Entry(line, lineNumber) {
|
|
|
53
54
|
recordType !== "active_tools_change" &&
|
|
54
55
|
recordType !== "session_info" &&
|
|
55
56
|
recordType !== "label") {
|
|
56
|
-
throw new Error(`Unsupported legacy v3 record type
|
|
57
|
+
throw new Error(`Unsupported legacy v3 record type: ${String(recordType)}`);
|
|
57
58
|
}
|
|
58
59
|
return entry;
|
|
59
60
|
}
|
|
@@ -76,110 +77,61 @@ function isRetainedEntry(entry) {
|
|
|
76
77
|
entry.type !== "session_info" &&
|
|
77
78
|
entry.type !== "label");
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
this.entriesById = entriesById;
|
|
90
|
-
this.remintedIds = remintedIds;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Resolve a legacy node to its reminted ID, or to its nearest retained ancestor when discarded.
|
|
94
|
-
* Returns null when the reference is null or no retained ancestor exists.
|
|
95
|
-
*/
|
|
96
|
-
resolve(legacyId) {
|
|
97
|
-
const traversedIds = [];
|
|
98
|
-
const visitedIds = new Set();
|
|
99
|
-
let currentId = legacyId;
|
|
100
|
-
let resolvedId = null;
|
|
101
|
-
while (currentId !== null) {
|
|
102
|
-
const remintedId = this.remintedIds.get(currentId);
|
|
103
|
-
if (remintedId !== undefined) {
|
|
104
|
-
resolvedId = remintedId;
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
if (this.resolvedIds.has(currentId)) {
|
|
108
|
-
resolvedId = this.resolvedIds.get(currentId) ?? null;
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
if (visitedIds.has(currentId))
|
|
112
|
-
throw new Error(`Cycle in legacy v3 parent chain at entry: ${currentId}`);
|
|
113
|
-
visitedIds.add(currentId);
|
|
114
|
-
const entry = this.entriesById.get(currentId);
|
|
115
|
-
if (entry === undefined)
|
|
116
|
-
throw new Error(`Missing legacy v3 entry reference: ${currentId}`);
|
|
117
|
-
traversedIds.push(currentId);
|
|
118
|
-
currentId = entry.parentId;
|
|
119
|
-
}
|
|
120
|
-
for (const traversedId of traversedIds)
|
|
121
|
-
this.resolvedIds.set(traversedId, resolvedId);
|
|
122
|
-
return resolvedId;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function requireRetainedId(resolver, legacyId) {
|
|
126
|
-
const importedId = resolver.resolve(legacyId);
|
|
127
|
-
if (importedId === null)
|
|
128
|
-
throw new Error(`Legacy v3 entry reference has no retained ancestor: ${legacyId}`);
|
|
129
|
-
return importedId;
|
|
80
|
+
/** Parent mappings are already folded during the scan; later references need only a lookup. */
|
|
81
|
+
function createLegacyIdResolver(entries) {
|
|
82
|
+
return (legacyId) => {
|
|
83
|
+
if (legacyId === null)
|
|
84
|
+
return null;
|
|
85
|
+
const mappedId = entries.get(legacyId)?.mappedId;
|
|
86
|
+
if (mappedId === undefined)
|
|
87
|
+
throw new Error(`Missing legacy v3 entry reference: ${legacyId}`);
|
|
88
|
+
return mappedId;
|
|
89
|
+
};
|
|
130
90
|
}
|
|
131
|
-
function resolveBranchSummaryFromId(
|
|
91
|
+
function resolveBranchSummaryFromId(resolveLegacyId, legacyFromId) {
|
|
132
92
|
// Legacy branchWithSummary() encoded a root source as the "root" sentinel instead of null.
|
|
133
|
-
return legacyFromId === "root" ? null :
|
|
93
|
+
return legacyFromId === "root" ? null : resolveLegacyId(legacyFromId);
|
|
134
94
|
}
|
|
135
|
-
function
|
|
95
|
+
function projectContextMessage(entry, resolveLegacyId) {
|
|
136
96
|
switch (entry.type) {
|
|
137
97
|
case "message":
|
|
138
|
-
return
|
|
98
|
+
return entry.message;
|
|
139
99
|
case "custom_message":
|
|
140
|
-
return
|
|
100
|
+
return importedCustomMessage(entry);
|
|
141
101
|
case "branch_summary":
|
|
142
102
|
return entry.summary
|
|
143
|
-
?
|
|
144
|
-
|
|
145
|
-
]
|
|
146
|
-
: [];
|
|
103
|
+
? createBranchSummaryMessage(entry.summary, resolveBranchSummaryFromId(resolveLegacyId, entry.fromId), entry.timestamp)
|
|
104
|
+
: undefined;
|
|
147
105
|
case "compaction":
|
|
148
|
-
return
|
|
106
|
+
return createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp);
|
|
149
107
|
case "custom":
|
|
150
108
|
case "model_change":
|
|
151
109
|
case "thinking_level_change":
|
|
152
110
|
case "active_tools_change":
|
|
153
111
|
case "session_info":
|
|
154
112
|
case "label":
|
|
155
|
-
return
|
|
113
|
+
return undefined;
|
|
156
114
|
}
|
|
157
115
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const visited = new Set();
|
|
116
|
+
/** Walk physical ancestry, including discarded nodes and the exact kept boundary. */
|
|
117
|
+
function* retainedTailStructure(compaction, entriesById) {
|
|
161
118
|
let currentId = compaction.parentId;
|
|
162
119
|
while (currentId !== null) {
|
|
163
|
-
|
|
164
|
-
throw new Error(`Cycle in legacy v3 parent chain at entry: ${currentId}`);
|
|
165
|
-
visited.add(currentId);
|
|
120
|
+
// The scan guarantees that every parent exists earlier in the file, so cycles are impossible.
|
|
166
121
|
const entry = entriesById.get(currentId);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (currentId === compaction.firstKeptEntryId) {
|
|
171
|
-
return reversedTail.reverse().flatMap((tailEntry) => projectContextMessages(tailEntry, resolver));
|
|
172
|
-
}
|
|
122
|
+
yield entry;
|
|
123
|
+
if (currentId === compaction.firstKeptEntryId)
|
|
124
|
+
return;
|
|
173
125
|
currentId = entry.parentId;
|
|
174
126
|
}
|
|
175
127
|
throw new Error(`Legacy v3 compaction ${compaction.id} firstKeptEntryId is not on its parent branch: ${compaction.firstKeptEntryId}`);
|
|
176
128
|
}
|
|
177
|
-
function normalizeRetainedEntry(entry,
|
|
129
|
+
function normalizeRetainedEntry(entry, indexed, retainedTail, resolveLegacyId) {
|
|
178
130
|
const committedBase = {
|
|
179
131
|
kind: "entry",
|
|
180
|
-
id:
|
|
181
|
-
parentId:
|
|
182
|
-
seq,
|
|
132
|
+
id: indexed.mappedId,
|
|
133
|
+
parentId: resolveLegacyId(indexed.parentId),
|
|
134
|
+
seq: indexed.seq,
|
|
183
135
|
timestamp: Date.parse(entry.timestamp),
|
|
184
136
|
};
|
|
185
137
|
if (entry.type === "message") {
|
|
@@ -196,7 +148,7 @@ function normalizeRetainedEntry(entry, seq, entriesById, resolver) {
|
|
|
196
148
|
return {
|
|
197
149
|
...committedBase,
|
|
198
150
|
type: "branch_summary",
|
|
199
|
-
fromId: resolveBranchSummaryFromId(
|
|
151
|
+
fromId: resolveBranchSummaryFromId(resolveLegacyId, entry.fromId),
|
|
200
152
|
summary: entry.summary,
|
|
201
153
|
details: entry.details,
|
|
202
154
|
usage: entry.usage,
|
|
@@ -208,7 +160,7 @@ function normalizeRetainedEntry(entry, seq, entriesById, resolver) {
|
|
|
208
160
|
...committedBase,
|
|
209
161
|
type: "compaction",
|
|
210
162
|
summary: entry.summary,
|
|
211
|
-
retainedTail
|
|
163
|
+
retainedTail,
|
|
212
164
|
tokensBefore: entry.tokensBefore,
|
|
213
165
|
details: entry.details,
|
|
214
166
|
usage: entry.usage,
|
|
@@ -222,42 +174,30 @@ function normalizeRetainedEntry(entry, seq, entriesById, resolver) {
|
|
|
222
174
|
data: entry.data,
|
|
223
175
|
};
|
|
224
176
|
}
|
|
225
|
-
const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
226
177
|
function selectedConfiguration(entriesById, selectedId) {
|
|
178
|
+
const remaining = new Set([
|
|
179
|
+
"model_change",
|
|
180
|
+
"thinking_level_change",
|
|
181
|
+
"active_tools_change",
|
|
182
|
+
]);
|
|
227
183
|
let model;
|
|
228
184
|
let thinkingLevel;
|
|
229
185
|
let activeToolNames;
|
|
230
|
-
let sawModel = false;
|
|
231
|
-
let sawThinkingLevel = false;
|
|
232
|
-
let sawActiveToolNames = false;
|
|
233
|
-
const visited = new Set();
|
|
234
186
|
let currentId = selectedId;
|
|
235
|
-
while (currentId !== null &&
|
|
236
|
-
if (visited.has(currentId))
|
|
237
|
-
throw new Error(`Cycle in legacy v3 parent chain at entry: ${currentId}`);
|
|
238
|
-
visited.add(currentId);
|
|
187
|
+
while (currentId !== null && remaining.size !== 0) {
|
|
239
188
|
const entry = entriesById.get(currentId);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
sawThinkingLevel = true;
|
|
253
|
-
if (typeof entry.thinkingLevel === "string" && THINKING_LEVELS.has(entry.thinkingLevel)) {
|
|
254
|
-
thinkingLevel = entry.thinkingLevel;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
else if (entry.type === "active_tools_change" && !sawActiveToolNames) {
|
|
258
|
-
sawActiveToolNames = true;
|
|
259
|
-
if (Array.isArray(entry.activeToolNames) && entry.activeToolNames.every((name) => typeof name === "string")) {
|
|
260
|
-
activeToolNames = [...entry.activeToolNames];
|
|
189
|
+
// Consume the nearest change even when invalid: older values must not become fallbacks.
|
|
190
|
+
if (remaining.delete(entry.type)) {
|
|
191
|
+
switch (entry.type) {
|
|
192
|
+
case "model_change":
|
|
193
|
+
model = { provider: entry.provider, modelId: entry.modelId };
|
|
194
|
+
break;
|
|
195
|
+
case "thinking_level_change":
|
|
196
|
+
thinkingLevel = entry.thinkingLevel;
|
|
197
|
+
break;
|
|
198
|
+
case "active_tools_change":
|
|
199
|
+
activeToolNames = [...entry.activeToolNames];
|
|
200
|
+
break;
|
|
261
201
|
}
|
|
262
202
|
}
|
|
263
203
|
currentId = entry.parentId;
|
|
@@ -266,118 +206,257 @@ function selectedConfiguration(entriesById, selectedId) {
|
|
|
266
206
|
return undefined;
|
|
267
207
|
return { model, thinkingLevel, activeToolNames: activeToolNames ?? [] };
|
|
268
208
|
}
|
|
269
|
-
function
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
latestSessionInfo = entry;
|
|
209
|
+
function indexLegacyV3Entry(entry, lineNumber, seq, entries) {
|
|
210
|
+
// SessionManager appends after an existing leaf and writes extracted branches in parent order.
|
|
211
|
+
const mappedParentId = entry.parentId === null ? null : entries.get(entry.parentId)?.mappedId;
|
|
212
|
+
if (mappedParentId === undefined) {
|
|
213
|
+
throw new Error(`Legacy v3 entry ${entry.id} has a missing or forward parent at line ${lineNumber}: ${entry.parentId}`);
|
|
275
214
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
215
|
+
const structure = {
|
|
216
|
+
id: entry.id,
|
|
217
|
+
parentId: entry.parentId,
|
|
218
|
+
mappedId: mappedParentId,
|
|
219
|
+
};
|
|
220
|
+
if (!isRetainedEntry(entry)) {
|
|
221
|
+
switch (entry.type) {
|
|
222
|
+
case "label":
|
|
223
|
+
return { ...structure, type: entry.type, targetId: entry.targetId, label: entry.label };
|
|
224
|
+
case "model_change":
|
|
225
|
+
return { ...structure, type: entry.type, provider: entry.provider, modelId: entry.modelId };
|
|
226
|
+
case "thinking_level_change":
|
|
227
|
+
return { ...structure, type: entry.type, thinkingLevel: entry.thinkingLevel };
|
|
228
|
+
case "active_tools_change":
|
|
229
|
+
return { ...structure, type: entry.type, activeToolNames: entry.activeToolNames };
|
|
230
|
+
case "session_info":
|
|
231
|
+
return { ...structure, type: entry.type };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Null denotes the root, not a retained node identity that can be reminted.
|
|
235
|
+
if (entry.id === null)
|
|
236
|
+
throw new Error("Legacy v3 entry reference has no retained ancestor: null");
|
|
237
|
+
const retained = {
|
|
238
|
+
...structure,
|
|
239
|
+
mappedId: uuidv7(Date.parse(entry.timestamp)),
|
|
240
|
+
seq,
|
|
241
|
+
};
|
|
242
|
+
switch (entry.type) {
|
|
243
|
+
case "branch_summary":
|
|
244
|
+
return { ...retained, type: entry.type, fromId: entry.fromId };
|
|
245
|
+
case "compaction":
|
|
246
|
+
return { ...retained, type: entry.type, firstKeptEntryId: entry.firstKeptEntryId };
|
|
247
|
+
default:
|
|
248
|
+
return { ...retained, type: entry.type };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function legacyEntryUsage(entry) {
|
|
252
|
+
switch (entry.type) {
|
|
253
|
+
case "message":
|
|
254
|
+
return entry.message.role === "assistant" || entry.message.role === "toolResult"
|
|
255
|
+
? entry.message.usage
|
|
256
|
+
: undefined;
|
|
257
|
+
case "compaction":
|
|
258
|
+
case "branch_summary":
|
|
259
|
+
return entry.usage;
|
|
260
|
+
default:
|
|
261
|
+
return undefined;
|
|
285
262
|
}
|
|
263
|
+
}
|
|
264
|
+
async function readLegacyV3Inventory(reader, context) {
|
|
265
|
+
const entries = new Map();
|
|
266
|
+
let nextSeq = 1;
|
|
267
|
+
let importedUsage = emptyUsage();
|
|
268
|
+
let name;
|
|
269
|
+
let finalId = null;
|
|
270
|
+
while (true) {
|
|
271
|
+
const line = fileValue(await reader.readLine(context), "Failed to read legacy v3 source");
|
|
272
|
+
if (line === undefined || !line.terminated)
|
|
273
|
+
break;
|
|
274
|
+
const lineNumber = entries.size + 2;
|
|
275
|
+
const entry = parseLegacyV3Entry(line.text);
|
|
276
|
+
if (entries.has(entry.id))
|
|
277
|
+
throw new Error(`Duplicate legacy v3 entry id: ${entry.id}`);
|
|
278
|
+
const indexed = indexLegacyV3Entry(entry, lineNumber, nextSeq, entries);
|
|
279
|
+
entries.set(entry.id, indexed);
|
|
280
|
+
if (isRetainedEntry(indexed))
|
|
281
|
+
nextSeq++;
|
|
282
|
+
finalId = entry.id;
|
|
283
|
+
if (entry.type === "session_info")
|
|
284
|
+
name = entry.name;
|
|
285
|
+
const usage = legacyEntryUsage(entry);
|
|
286
|
+
if (usage !== undefined)
|
|
287
|
+
importedUsage = addUsage(importedUsage, usage);
|
|
288
|
+
}
|
|
289
|
+
return { entries, nextSeq, importedUsage, name, finalId };
|
|
290
|
+
}
|
|
291
|
+
function normalizeLegacyV3Values(inventory) {
|
|
292
|
+
const { entries, name, finalId } = inventory;
|
|
293
|
+
const resolveLegacyId = createLegacyIdResolver(entries);
|
|
294
|
+
let nextSeq = inventory.nextSeq;
|
|
295
|
+
const values = [];
|
|
296
|
+
// session name
|
|
297
|
+
if (name)
|
|
298
|
+
values.push({ ...setValue(sessionName, name), seq: nextSeq++ });
|
|
299
|
+
// labels
|
|
286
300
|
const labels = new Map();
|
|
287
|
-
for (const entry of entries) {
|
|
301
|
+
for (const entry of entries.values()) {
|
|
288
302
|
if (entry.type !== "label")
|
|
289
303
|
continue;
|
|
290
|
-
const targetId =
|
|
291
|
-
// Labels have no current address when their target has no retained ancestor.
|
|
304
|
+
const targetId = resolveLegacyId(entry.targetId);
|
|
292
305
|
if (targetId === null)
|
|
293
306
|
continue;
|
|
294
|
-
// Legacy v3 treated both undefined and the empty string as clearing a label.
|
|
295
307
|
if (entry.label)
|
|
296
308
|
labels.set(targetId, entry.label);
|
|
297
309
|
else
|
|
298
310
|
labels.delete(targetId);
|
|
299
311
|
}
|
|
300
312
|
for (const [targetId, label] of labels) {
|
|
301
|
-
|
|
302
|
-
writes.push({
|
|
303
|
-
kind: "value",
|
|
304
|
-
op: "set",
|
|
305
|
-
seq: firstSeq + writes.length,
|
|
306
|
-
namespace: address.namespace,
|
|
307
|
-
key: address.key,
|
|
308
|
-
value: label,
|
|
309
|
-
});
|
|
313
|
+
values.push({ ...setValue(entryLabel(targetId), label), seq: nextSeq++ });
|
|
310
314
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
op: "set",
|
|
316
|
-
seq: firstSeq + writes.length,
|
|
317
|
-
namespace: tipAddress.namespace,
|
|
318
|
-
key: tipAddress.key,
|
|
319
|
-
value: finalEntry === undefined ? null : resolver.resolve(finalEntry.id),
|
|
320
|
-
});
|
|
321
|
-
const configuration = selectedConfiguration(entriesById, finalEntry?.id ?? null);
|
|
315
|
+
// branch tip
|
|
316
|
+
values.push({ ...setValue(branchTip("main"), resolveLegacyId(finalId)), seq: nextSeq++ });
|
|
317
|
+
// configuration
|
|
318
|
+
const configuration = selectedConfiguration(entries, finalId);
|
|
322
319
|
if (configuration !== undefined) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
seq: firstSeq + writes.length,
|
|
328
|
-
namespace: configAddress.namespace,
|
|
329
|
-
key: configAddress.key,
|
|
330
|
-
value: configuration,
|
|
331
|
-
});
|
|
332
|
-
const stateAddress = laneState("main");
|
|
333
|
-
writes.push({
|
|
334
|
-
kind: "value",
|
|
335
|
-
op: "set",
|
|
336
|
-
seq: firstSeq + writes.length,
|
|
337
|
-
namespace: stateAddress.namespace,
|
|
338
|
-
key: stateAddress.key,
|
|
339
|
-
value: { currentOperationId: null, lastOperationId: null, inbox: [] },
|
|
320
|
+
values.push({ ...setValue(laneConfig("main"), configuration), seq: nextSeq++ });
|
|
321
|
+
values.push({
|
|
322
|
+
...setValue(laneState("main"), { currentOperationId: null, lastOperationId: null, inbox: [] }),
|
|
323
|
+
seq: nextSeq++,
|
|
340
324
|
});
|
|
341
325
|
}
|
|
342
|
-
return
|
|
326
|
+
return values;
|
|
343
327
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
328
|
+
/**
|
|
329
|
+
* A captured legacy file exposed as repeatable logical v4 writes.
|
|
330
|
+
* Each pass reopens the path; callers must not replace or edit the source between passes.
|
|
331
|
+
* Structural indexes, label/configuration metadata, and derived current values survive between scans.
|
|
332
|
+
*/
|
|
333
|
+
export class LegacyV3Source {
|
|
334
|
+
header;
|
|
335
|
+
importedUsage;
|
|
336
|
+
nextSeq;
|
|
337
|
+
values;
|
|
338
|
+
fileSystem;
|
|
339
|
+
path;
|
|
340
|
+
entries;
|
|
341
|
+
resolveLegacyId;
|
|
342
|
+
constructor(fileSystem, path, header, entries, importedUsage, values, nextSeq) {
|
|
343
|
+
this.fileSystem = fileSystem;
|
|
344
|
+
this.path = path;
|
|
345
|
+
this.header = header;
|
|
346
|
+
this.entries = entries;
|
|
347
|
+
this.resolveLegacyId = createLegacyIdResolver(entries);
|
|
348
|
+
this.importedUsage = importedUsage;
|
|
349
|
+
this.values = values;
|
|
350
|
+
this.nextSeq = nextSeq;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Scan complete v3 records without modifying the file, ignoring an unterminated final line.
|
|
354
|
+
* Build parent mappings, assign IDs stable for this source instance, and derive current values
|
|
355
|
+
* and imported usage. Retain metadata, not conversation payloads or an open reader. writes()
|
|
356
|
+
* reopens the path to materialize captured records and resolve their payload-specific references.
|
|
357
|
+
*/
|
|
358
|
+
static async read(fileSystem, path, context) {
|
|
359
|
+
const reader = fileValue(await fileSystem.openTextLineReader(path, context), `Failed to open legacy v3 source ${path}`);
|
|
360
|
+
try {
|
|
361
|
+
const parsed = await readJsonlHeader(reader, path, context);
|
|
362
|
+
if (parsed.format !== "v3-legacy") {
|
|
363
|
+
throw new Error(`Invalid legacy v3 JSONL storage ${path}: expected format 3 header`);
|
|
364
|
+
}
|
|
365
|
+
const inventory = await readLegacyV3Inventory(reader, context);
|
|
366
|
+
const values = normalizeLegacyV3Values(inventory);
|
|
367
|
+
return new LegacyV3Source(fileSystem, path, await normalizeLegacyV3Header(fileSystem, parsed.header, context), inventory.entries, inventory.importedUsage, values, inventory.nextSeq + values.length);
|
|
353
368
|
}
|
|
354
|
-
|
|
355
|
-
|
|
369
|
+
finally {
|
|
370
|
+
await reader.close(context);
|
|
356
371
|
}
|
|
357
|
-
if (usage !== undefined)
|
|
358
|
-
aggregate = addUsage(aggregate, usage);
|
|
359
372
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
373
|
+
*entryStructures() {
|
|
374
|
+
for (const entry of this.entries.values()) {
|
|
375
|
+
if (!isRetainedEntry(entry))
|
|
376
|
+
continue;
|
|
377
|
+
yield { id: entry.mappedId, parentId: this.resolveLegacyId(entry.parentId), seq: entry.seq };
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
translateForkEntryId(legacyId) {
|
|
381
|
+
const entry = this.entries.get(legacyId);
|
|
382
|
+
if (entry === undefined)
|
|
383
|
+
throw new Error(`Legacy v3 fork entry does not exist: ${legacyId}`);
|
|
384
|
+
if (!isRetainedEntry(entry))
|
|
385
|
+
throw new Error(`Legacy v3 fork entry is not a retained entry: ${legacyId}`);
|
|
386
|
+
return entry.mappedId;
|
|
387
|
+
}
|
|
388
|
+
collectRequiredTailMessageIds(isEntrySelected) {
|
|
389
|
+
const requiredIds = new Set();
|
|
390
|
+
for (const entry of this.entries.values()) {
|
|
391
|
+
if (entry.type !== "compaction")
|
|
392
|
+
continue;
|
|
393
|
+
const compactionIsSelected = isEntrySelected === undefined || isEntrySelected(entry.mappedId);
|
|
394
|
+
if (!compactionIsSelected)
|
|
395
|
+
continue;
|
|
396
|
+
// Walk from the compaction's parent through firstKeptEntryId, inclusive.
|
|
397
|
+
for (const tailEntry of retainedTailStructure(entry, this.entries)) {
|
|
398
|
+
const canProduceContextMessage = isRetainedEntry(tailEntry) && tailEntry.type !== "custom";
|
|
399
|
+
if (canProduceContextMessage)
|
|
400
|
+
requiredIds.add(tailEntry.id);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return requiredIds;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Stream normalized v4 entries, optionally filtered by reminted ID, followed by derived current values.
|
|
407
|
+
* Each pass owns its reader and message cache, sharing only the captured IDs and metadata.
|
|
408
|
+
*/
|
|
409
|
+
async *writes(context, isEntrySelected) {
|
|
410
|
+
const requiredTailMessageIds = this.collectRequiredTailMessageIds(isEntrySelected);
|
|
411
|
+
// Keep needed context messages for this entire pass; tails may revisit old or shared branches.
|
|
412
|
+
const tailMessagesByLegacyId = new Map();
|
|
413
|
+
for await (const { entry, indexed } of this.readCapturedEntries(context)) {
|
|
414
|
+
if (requiredTailMessageIds.has(entry.id)) {
|
|
415
|
+
const message = projectContextMessage(entry, this.resolveLegacyId);
|
|
416
|
+
if (message !== undefined)
|
|
417
|
+
tailMessagesByLegacyId.set(entry.id, message);
|
|
418
|
+
}
|
|
419
|
+
if (!isRetainedEntry(indexed) || !isRetainedEntry(entry))
|
|
420
|
+
continue;
|
|
421
|
+
if (isEntrySelected !== undefined && !isEntrySelected(indexed.mappedId))
|
|
422
|
+
continue;
|
|
423
|
+
const retainedTail = [];
|
|
424
|
+
if (indexed.type === "compaction") {
|
|
425
|
+
for (const ancestor of retainedTailStructure(indexed, this.entries)) {
|
|
426
|
+
const message = tailMessagesByLegacyId.get(ancestor.id);
|
|
427
|
+
if (message !== undefined)
|
|
428
|
+
retainedTail.push(message);
|
|
429
|
+
}
|
|
430
|
+
retainedTail.reverse();
|
|
431
|
+
}
|
|
432
|
+
yield normalizeRetainedEntry(entry, indexed, retainedTail, this.resolveLegacyId);
|
|
433
|
+
}
|
|
434
|
+
yield* this.values;
|
|
435
|
+
}
|
|
436
|
+
/** Replay only the captured prefix and verify its physical identities before materialization. */
|
|
437
|
+
async *readCapturedEntries(context) {
|
|
438
|
+
const reader = fileValue(await this.fileSystem.openTextLineReader(this.path, context), `Failed to reopen legacy v3 source ${this.path}`);
|
|
439
|
+
try {
|
|
440
|
+
const parsed = await readJsonlHeader(reader, this.path, context);
|
|
441
|
+
if (parsed.format !== "v3-legacy" ||
|
|
442
|
+
parsed.header.id !== this.header.id ||
|
|
443
|
+
parsed.header.cwd !== this.header.cwd) {
|
|
444
|
+
throw new Error("Legacy v3 source header changed");
|
|
445
|
+
}
|
|
446
|
+
for (const indexed of this.entries.values()) {
|
|
447
|
+
const line = fileValue(await reader.readLine(context), "Failed to reread legacy v3 source");
|
|
448
|
+
if (line === undefined || !line.terminated)
|
|
449
|
+
throw new Error("Legacy v3 source ended before captured entries");
|
|
450
|
+
const entry = parseLegacyV3Entry(line.text);
|
|
451
|
+
if (entry.id !== indexed.id || entry.type !== indexed.type) {
|
|
452
|
+
throw new Error("Legacy v3 source changed");
|
|
453
|
+
}
|
|
454
|
+
yield { entry, indexed };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
finally {
|
|
458
|
+
await reader.close(context);
|
|
459
|
+
}
|
|
370
460
|
}
|
|
371
|
-
const retainedEntries = entries.filter(isRetainedEntry);
|
|
372
|
-
const remintedIds = new Map(retainedEntries.map((entry) => [entry.id, uuidv7(Date.parse(entry.timestamp))]));
|
|
373
|
-
const resolver = new RetainedIdResolver(entriesById, remintedIds);
|
|
374
|
-
const entryWrites = retainedEntries.map((entry, index) => normalizeRetainedEntry(entry, index + 1, entriesById, resolver));
|
|
375
|
-
const valueWrites = normalizeLegacyV3Values(entries, entriesById, resolver, entryWrites.length + 1);
|
|
376
|
-
const writes = [...entryWrites, ...valueWrites];
|
|
377
|
-
return {
|
|
378
|
-
writes,
|
|
379
|
-
importedUsage: aggregateImportedUsage(entries),
|
|
380
|
-
nextSeq: writes.length + 1,
|
|
381
|
-
};
|
|
382
461
|
}
|
|
383
462
|
//# sourceMappingURL=legacy-v3.js.map
|