ds4-context-engine 0.3.0-alpha.4 → 0.3.0-beta.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/README.md +25 -11
- package/docs/ADR/058-bounded-manifest-storage.md +31 -0
- package/docs/ADR/README.md +1 -0
- package/docs/ARCHITECTURE.md +14 -9
- package/docs/COMPACTION.md +8 -8
- package/docs/CONTEXT_MANIFEST.md +10 -1
- package/docs/CONTEXT_PERSISTENCE_TOOL.md +2 -2
- package/docs/DOGFOODING_0.3.0_ALPHA.md +36 -10
- package/docs/DOGFOODING_0.3.0_BETA.md +331 -0
- package/docs/RELEASE_READINESS_0.2.0.md +1 -1
- package/docs/RELEASING.md +5 -5
- package/docs/STORAGE.md +16 -2
- package/docs/STORAGE_MAINTENANCE.md +121 -0
- package/docs/releases/0.3.0-alpha.4.md +5 -3
- package/docs/releases/0.3.0-alpha.5.md +60 -0
- package/docs/releases/0.3.0-beta.1.md +81 -0
- package/package.json +6 -2
- package/scripts/ds4-context-storage.mjs +148 -0
- package/src/extension/commands.ts +83 -2
- package/src/extension/runtime.ts +81 -24
- package/src/pi-adapter/compaction-adapter.ts +107 -23
- package/src/pi-adapter/compaction-coordinator.ts +425 -117
- package/src/pi-adapter/summary-generator.ts +140 -22
- package/src/pi-adapter/version.ts +1 -1
|
@@ -19,14 +19,22 @@ interface SourceCandidate {
|
|
|
19
19
|
used: boolean;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
type CompactionMessage = SessionBeforeCompactEvent["preparation"]["messagesToSummarize"][number];
|
|
23
|
+
|
|
24
|
+
export interface PreparedCompactionSourceSlice {
|
|
25
|
+
messages: CompactionMessage[];
|
|
24
26
|
conversationText: string;
|
|
25
27
|
sourceText: string;
|
|
26
28
|
sourceEntryIds: string[];
|
|
27
29
|
sourceHash: string;
|
|
28
30
|
segmentReadFiles: string[];
|
|
29
31
|
segmentModifiedFiles: string[];
|
|
32
|
+
isSplitTurn: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PreparedCompactionSource extends PreparedCompactionSourceSlice {
|
|
36
|
+
messageEntryIds: string[];
|
|
37
|
+
turnPrefixStartIndex: number;
|
|
30
38
|
readFiles: string[];
|
|
31
39
|
modifiedFiles: string[];
|
|
32
40
|
previousSummary?: string;
|
|
@@ -53,7 +61,7 @@ function mapSourceEntryIds(messages: readonly unknown[], entries: readonly Sessi
|
|
|
53
61
|
const match = available.find((candidate) => !candidate.used && candidate.fingerprint === fingerprint(message));
|
|
54
62
|
if (!match) throw new Error("Compaction source message has no exact canonical Pi session entry");
|
|
55
63
|
match.used = true;
|
|
56
|
-
|
|
64
|
+
ids.push(match.entryId);
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
return ids;
|
|
@@ -69,8 +77,93 @@ function previousFiles(entries: readonly SessionEntry[]): { read: string[]; modi
|
|
|
69
77
|
return { read: [], modified: [] };
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
function unique(values: Iterable<string>): string[] {
|
|
81
|
+
return [...new Set(values)];
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
function sortedUnique(values: Iterable<string>): string[] {
|
|
73
|
-
return
|
|
85
|
+
return unique(values).sort((left, right) => left.localeCompare(right));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function segmentFiles(messages: readonly CompactionMessage[]): { readFiles: string[]; modifiedFiles: string[] } {
|
|
89
|
+
const read = new Set<string>();
|
|
90
|
+
const modified = new Set<string>();
|
|
91
|
+
for (const message of messages) {
|
|
92
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
93
|
+
for (const block of message.content) {
|
|
94
|
+
if (block.type !== "toolCall" || !block.arguments || typeof block.arguments !== "object") continue;
|
|
95
|
+
const path = "path" in block.arguments && typeof block.arguments.path === "string"
|
|
96
|
+
? block.arguments.path
|
|
97
|
+
: undefined;
|
|
98
|
+
if (!path) continue;
|
|
99
|
+
if (block.name === "read") read.add(path);
|
|
100
|
+
if (block.name === "write" || block.name === "edit") modified.add(path);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
readFiles: sortedUnique([...read].filter((path) => !modified.has(path))),
|
|
105
|
+
modifiedFiles: sortedUnique(modified),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function createSourceSlice(input: {
|
|
110
|
+
messages: CompactionMessage[];
|
|
111
|
+
sourceEntryIds: string[];
|
|
112
|
+
segmentReadFiles: string[];
|
|
113
|
+
segmentModifiedFiles: string[];
|
|
114
|
+
isSplitTurn: boolean;
|
|
115
|
+
}): PreparedCompactionSourceSlice {
|
|
116
|
+
const conversationText = serializeConversation(convertToLlm(input.messages));
|
|
117
|
+
const canonicalSourceText = stableStringify(input.messages);
|
|
118
|
+
const sourceEntryIds = unique(input.sourceEntryIds);
|
|
119
|
+
const sourceText = [
|
|
120
|
+
canonicalSourceText,
|
|
121
|
+
conversationText,
|
|
122
|
+
input.segmentReadFiles.join("\n"),
|
|
123
|
+
input.segmentModifiedFiles.join("\n"),
|
|
124
|
+
].filter(Boolean).join("\n\n");
|
|
125
|
+
return {
|
|
126
|
+
messages: input.messages,
|
|
127
|
+
conversationText,
|
|
128
|
+
sourceText,
|
|
129
|
+
sourceEntryIds,
|
|
130
|
+
sourceHash: computeSummarySourceHash({
|
|
131
|
+
conversationText: canonicalSourceText,
|
|
132
|
+
sourceEntryIds,
|
|
133
|
+
readFiles: input.segmentReadFiles,
|
|
134
|
+
modifiedFiles: input.segmentModifiedFiles,
|
|
135
|
+
}),
|
|
136
|
+
segmentReadFiles: input.segmentReadFiles,
|
|
137
|
+
segmentModifiedFiles: input.segmentModifiedFiles,
|
|
138
|
+
isSplitTurn: input.isSplitTurn,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function sliceCompactionSource(
|
|
143
|
+
source: PreparedCompactionSource,
|
|
144
|
+
messageIndices: readonly number[],
|
|
145
|
+
): PreparedCompactionSourceSlice {
|
|
146
|
+
const indices = [...new Set(messageIndices)].sort((left, right) => left - right);
|
|
147
|
+
if (indices.length === 0) throw new Error("Compaction segment contains no source messages");
|
|
148
|
+
for (let offset = 0; offset < indices.length; offset++) {
|
|
149
|
+
const index = indices[offset];
|
|
150
|
+
if (index === undefined || !source.messages[index] || !source.messageEntryIds[index]) {
|
|
151
|
+
throw new Error("Compaction segment references an unavailable source message");
|
|
152
|
+
}
|
|
153
|
+
if (offset > 0 && index !== (indices[offset - 1] ?? -1) + 1) {
|
|
154
|
+
throw new Error("Compaction segment source messages must be contiguous");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const messages = indices.map((index) => source.messages[index] as CompactionMessage);
|
|
158
|
+
const files = segmentFiles(messages);
|
|
159
|
+
return createSourceSlice({
|
|
160
|
+
messages,
|
|
161
|
+
sourceEntryIds: indices.map((index) => source.messageEntryIds[index] as string),
|
|
162
|
+
segmentReadFiles: files.readFiles,
|
|
163
|
+
segmentModifiedFiles: files.modifiedFiles,
|
|
164
|
+
isSplitTurn: source.isSplitTurn
|
|
165
|
+
&& indices.some((index) => index >= source.turnPrefixStartIndex),
|
|
166
|
+
});
|
|
74
167
|
}
|
|
75
168
|
|
|
76
169
|
export function findActiveBranchSummary(
|
|
@@ -109,7 +202,8 @@ export function prepareCompactionSource(event: SessionBeforeCompactEvent): Prepa
|
|
|
109
202
|
];
|
|
110
203
|
if (messages.length === 0) throw new Error("Pi compaction preparation contains no source messages");
|
|
111
204
|
|
|
112
|
-
const
|
|
205
|
+
const messageEntryIds = mapSourceEntryIds(messages, event.branchEntries);
|
|
206
|
+
const sourceEntryIds = unique(messageEntryIds);
|
|
113
207
|
const priorFiles = previousFiles(event.branchEntries);
|
|
114
208
|
const segmentReadFiles = sortedUnique(event.preparation.fileOps.read);
|
|
115
209
|
const segmentModifiedFiles = sortedUnique([
|
|
@@ -118,30 +212,20 @@ export function prepareCompactionSource(event: SessionBeforeCompactEvent): Prepa
|
|
|
118
212
|
]);
|
|
119
213
|
const readFiles = sortedUnique([...priorFiles.read, ...segmentReadFiles]);
|
|
120
214
|
const modifiedFiles = sortedUnique([...priorFiles.modified, ...segmentModifiedFiles]);
|
|
121
|
-
const conversationText = serializeConversation(convertToLlm(messages));
|
|
122
|
-
const canonicalSourceText = stableStringify(messages);
|
|
123
215
|
const previousSummary = event.preparation.previousSummary;
|
|
124
216
|
const previousNode = findActiveBranchSummary(event.branchEntries, previousSummary);
|
|
125
|
-
const
|
|
126
|
-
canonicalSourceText,
|
|
127
|
-
conversationText,
|
|
128
|
-
segmentReadFiles.join("\n"),
|
|
129
|
-
segmentModifiedFiles.join("\n"),
|
|
130
|
-
].filter(Boolean).join("\n\n");
|
|
131
|
-
|
|
132
|
-
return {
|
|
217
|
+
const prepared = createSourceSlice({
|
|
133
218
|
messages,
|
|
134
|
-
conversationText,
|
|
135
|
-
sourceText,
|
|
136
219
|
sourceEntryIds,
|
|
137
|
-
sourceHash: computeSummarySourceHash({
|
|
138
|
-
conversationText: canonicalSourceText,
|
|
139
|
-
sourceEntryIds,
|
|
140
|
-
readFiles: segmentReadFiles,
|
|
141
|
-
modifiedFiles: segmentModifiedFiles,
|
|
142
|
-
}),
|
|
143
220
|
segmentReadFiles,
|
|
144
221
|
segmentModifiedFiles,
|
|
222
|
+
isSplitTurn: event.preparation.isSplitTurn,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
...prepared,
|
|
227
|
+
messageEntryIds,
|
|
228
|
+
turnPrefixStartIndex: event.preparation.messagesToSummarize.length,
|
|
145
229
|
readFiles,
|
|
146
230
|
modifiedFiles,
|
|
147
231
|
...(previousSummary ? { previousSummary } : {}),
|