@agimon-ai/po-autocompact 0.0.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vuong Ngo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # @agimon-ai/po-autocompact
2
+
3
+ Staged checkpoint summarization and iterative context compaction for Po.
4
+
5
+ Part of the [Po distribution](https://www.npmjs.com/package/@agimon-ai/po).
6
+
7
+ Instead of waiting for one final summary near the context limit, Autocompact creates up to three
8
+ asynchronous checkpoints and combines each checkpoint with subsequent messages.
9
+
10
+ > **Alpha:** thresholds and compaction steering may change between releases.
11
+
12
+ ## Requirements
13
+
14
+ - Node.js 24 or newer
15
+ - Po headless/server host
16
+ - Authentication for the model selected for summarization
17
+
18
+ ## Install
19
+
20
+ `po init` and `dpi init` include Autocompact in `default.packages` in `.po/modes.yaml`.
21
+ Remove it or move it to a named layer to change which major modes load it.
22
+ The native session facet is `@agimon-ai/po-autocompact/extensions/server`.
23
+ This package does not provide a Pi extension or terminal UI.
24
+
25
+ ## Checkpoint ladder
26
+
27
+ The native owner uses thresholds around 50%, 75%, and 95% of the usable context window,
28
+ subject to token caps and the host's native-compaction clamp:
29
+
30
+ 1. create an initial compact summary;
31
+ 2. combine it with later messages and allow the model to decide whether compaction is ready;
32
+ 3. combine again and force compaction on the final pass.
33
+
34
+ ## Per-model token checkpoints
35
+
36
+ Ratios scale with the window, which is usually what you want. On a very large window they can
37
+ also mean the first checkpoint waits far longer than you would like: half of a one-million-token
38
+ window is 500,000 tokens. `modes.autocompact.overrides` pins individual passes to an absolute
39
+ token count for the models you name, and leaves every other model on the ratio ladder.
40
+
41
+ ```yaml
42
+ modes:
43
+ autocompact:
44
+ overrides:
45
+ - model: 'claude-opus-4-[6-9]'
46
+ tokens:
47
+ pass1: 75000
48
+ pass2: 150000
49
+ pass3: 200000
50
+ ```
51
+
52
+ - Entries are tried in file order and the first match wins. A model matching nothing behaves
53
+ exactly as it does without this key.
54
+ - `model` is a glob supporting `*` and character classes such as `[6-9]`. A pattern containing
55
+ `/` is matched against `provider/id`, anything else against the bare model id, because the
56
+ wildcard does not cross the separator. Matching ignores case.
57
+ - The lower of the ratio and the token count wins, so an override can only bring a checkpoint
58
+ forward, never push one past the native compaction boundary.
59
+ - Counts are compared against total context usage, not against growth since the last compaction.
60
+ - A count at or below the current cycle's baseline is ignored and the pass falls back to its
61
+ ratio. Without that, a compaction landing above the count would satisfy the pass as soon as
62
+ the next cycle opened and re-fire it every turn.
63
+ - A pass never fires before the one before it. A list that puts a later pass lower is raised to
64
+ match, the same way a ratio below the previous pass is.
65
+ - A repository config replaces the global list whole rather than merging entry by entry.
66
+ - These keys are not on the cockpit settings page; edit `.po/config.yaml` directly.
67
+ Summarization runs asynchronously in the background, on the session's own provider, so a model
68
+ registered by a Pi provider extension summarizes through that extension. Checkpoints are
69
+ persisted as hidden session entries and used for later steering.
70
+
71
+ Po uses autocompact's configured model and thinking level, then planning subagent
72
+ configuration, then the active session model. Each checkpoint can make another provider request,
73
+ consume quota, and require credentials. Provider errors or missing authentication affect checkpoint
74
+ generation rather than granting a free local summary.
75
+
76
+ ## Coordination state
77
+
78
+ Autocompact does not own `@agimon-ai/po-task` or `@agimon-ai/po-team` persistence. The
79
+ native owner reads registered context-contribution snapshots from those packages when available, while Task's
80
+ `tasks.json` and Team's run and intercom state remain owned by their respective packages. Missing
81
+ snapshot data is reported as unavailable and must not be inferred from an Autocompact summary.
82
+
83
+ ## Public API
84
+
85
+ ```ts
86
+ import { thresholdTokens } from '@agimon-ai/po-autocompact';
87
+ import type { AutocompactPass } from '@agimon-ai/po-autocompact';
88
+ ```
89
+
90
+ The server host mounts the native facet and owns its lifecycle. `thresholdTokens` exposes
91
+ the threshold policy for hosts that need to preview or test checkpoint boundaries.
92
+
93
+ ## Development
94
+
95
+ ```bash
96
+ pnpm build
97
+ pnpm typecheck
98
+ pnpm test
99
+ pnpm lint
100
+ ```
101
+
102
+ Maintained by [Agimon](https://agimon.ai/about).
103
+
104
+ ## License
105
+
106
+ MIT
107
+
108
+ ## Extension lifecycle and source layout
109
+
110
+ The session root constructs one native owner. Routed server hooks drive checkpoints, context
111
+ projection, compaction observation, and navigation invalidation. Shutdown disposes the checkpoint
112
+ boundary, cancels summarization, and drains pending work before releasing telemetry.
113
+
114
+ The web entry declares settings sections. Public exports contain reusable policy capabilities.
@@ -0,0 +1,17 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ //#region src/schemas/apiContracts.ts
6
+ /** This facet contributes agent tools, commands or lifecycle only, with no separate HTTP or socket surface. */
7
+ const apiContracts = (0, require("@agimon-ai/po-core/apiContracts").defineApiContract)({
8
+ version: 1,
9
+ http: [],
10
+ sockets: [],
11
+ dynamic: []
12
+ });
13
+ //#endregion
14
+ exports.apiContracts = apiContracts;
15
+ exports.default = apiContracts;
16
+
17
+ //# sourceMappingURL=apiContracts.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiContracts.cjs","names":["defineApiContract"],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract } from '@agimon-ai/po-core/apiContracts';\n\n/** This facet contributes agent tools, commands or lifecycle only, with no separate HTTP or socket surface. */\nexport const apiContracts = defineApiContract({ version: 1, http: [], sockets: [], dynamic: [] });\nexport default apiContracts;\n"],"mappings":";;;;;;AAGA,MAAa,gBAAA,4CAAeA,CAAAA,CAAAA,kBAAAA,CAAkB;CAAE,SAAS;CAAG,MAAM,CAAC;CAAG,SAAS,CAAC;CAAG,SAAS,CAAC;AAAE,CAAC"}
@@ -0,0 +1,53 @@
1
+ //#region src/schemas/apiContracts.d.ts
2
+ /** This facet contributes agent tools, commands or lifecycle only, with no separate HTTP or socket surface. */
3
+ export declare const apiContracts: {
4
+ version: 1;
5
+ protocols?: Record<string, string> | undefined;
6
+ schemas?: Record<string, boolean | object> | undefined;
7
+ http: {
8
+ id: string;
9
+ scope: "global" | "session" | "workspace";
10
+ basePath?: string | undefined;
11
+ path: string;
12
+ method: "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT";
13
+ description: string;
14
+ availability?: string | undefined;
15
+ authentication: "device" | "none" | "owner";
16
+ parameters?: {
17
+ name: string;
18
+ in: "header" | "path" | "query";
19
+ required: boolean;
20
+ schema: boolean | object;
21
+ }[] | undefined;
22
+ body?: {
23
+ contentType: string;
24
+ contentTypes?: string[] | undefined;
25
+ required: boolean;
26
+ schema: boolean | object;
27
+ } | undefined;
28
+ responses: Record<string, {
29
+ description: string;
30
+ contentType?: string | undefined;
31
+ schema?: boolean | object | undefined;
32
+ events?: Record<string, boolean | object> | undefined;
33
+ }>;
34
+ }[];
35
+ sockets: {
36
+ id: string;
37
+ scope: "global" | "session" | "workspace";
38
+ description: string;
39
+ availability?: string | undefined;
40
+ service: string;
41
+ member: string;
42
+ direction: "client-to-server" | "server-to-client";
43
+ kind: "channel" | "method" | "state";
44
+ input: boolean | object;
45
+ output?: boolean | object | undefined;
46
+ errors?: boolean | object | undefined;
47
+ }[];
48
+ dynamic: string[];
49
+ gaps?: string[] | undefined;
50
+ };
51
+ //#endregion
52
+ export { apiContracts as default };
53
+ //# sourceMappingURL=apiContracts.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiContracts.d.mts","names":[],"sources":["../src/schemas/apiContracts.ts"],"mappings":";;qBAGa"}
@@ -0,0 +1,13 @@
1
+ import { defineApiContract } from "@agimon-ai/po-core/apiContracts";
2
+ //#region src/schemas/apiContracts.ts
3
+ /** This facet contributes agent tools, commands or lifecycle only, with no separate HTTP or socket surface. */
4
+ const apiContracts = defineApiContract({
5
+ version: 1,
6
+ http: [],
7
+ sockets: [],
8
+ dynamic: []
9
+ });
10
+ //#endregion
11
+ export { apiContracts, apiContracts as default };
12
+
13
+ //# sourceMappingURL=apiContracts.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiContracts.mjs","names":[],"sources":["../src/schemas/apiContracts.ts"],"sourcesContent":["import { defineApiContract } from '@agimon-ai/po-core/apiContracts';\n\n/** This facet contributes agent tools, commands or lifecycle only, with no separate HTTP or socket surface. */\nexport const apiContracts = defineApiContract({ version: 1, http: [], sockets: [], dynamic: [] });\nexport default apiContracts;\n"],"mappings":";;;AAGA,MAAa,eAAe,kBAAkB;CAAE,SAAS;CAAG,MAAM,CAAC;CAAG,SAAS,CAAC;CAAG,SAAS,CAAC;AAAE,CAAC"}
@@ -0,0 +1,459 @@
1
+ let _earendil_works_pi_coding_agent = require("@earendil-works/pi-coding-agent");
2
+ //#region src/constants/autocompact.ts
3
+ const STATE_CUSTOM_TYPE = "doom-autocompact-state";
4
+ const CHECKPOINT_MESSAGE_TYPE = "doom-autocompact-checkpoint";
5
+ const CONTEXT_MESSAGE_TYPE = "doom-autocompact-context";
6
+ const RESUME_MESSAGE_TYPE = "doom-autocompact-resume";
7
+ const RUNTIME_STATE_MESSAGE_TYPE = "doom-autocompact-runtime-state";
8
+ const COMPACTION_THRESHOLDS = {
9
+ 1: {
10
+ ratio: .5,
11
+ capTokens: 2e5
12
+ },
13
+ 2: {
14
+ ratio: .75,
15
+ capTokens: 5e5
16
+ },
17
+ 3: {
18
+ ratio: .95,
19
+ capTokens: 8e5
20
+ }
21
+ };
22
+ const CHECKPOINT_HEADINGS = [
23
+ "## Goal",
24
+ "## Constraints & Preferences",
25
+ "## Progress",
26
+ "### Done",
27
+ "### In Progress",
28
+ "### Blocked",
29
+ "## Key Decisions",
30
+ "## Next Steps",
31
+ "## Critical Context"
32
+ ];
33
+ //#endregion
34
+ //#region src/services/compactionPolicy/index.ts
35
+ function customMessage(entry) {
36
+ if (entry.type === "custom_message") return entry;
37
+ if (entry.type === "message" && entry.message.role === "custom") return entry.message;
38
+ }
39
+ const SHOULD_COMPACT_PATTERN = /<shouldCompact>\s*(true|false)\s*<\/shouldCompact>/i;
40
+ const FILE_SECTION_PATTERN = /\n*<(?:read-files|modified-files)>[\s\S]*?<\/(?:read-files|modified-files)>/g;
41
+ function isRecord(value) {
42
+ return typeof value === "object" && value !== null;
43
+ }
44
+ function isStringArray(value) {
45
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
46
+ }
47
+ function isPass(value) {
48
+ return value === 1 || value === 2 || value === 3;
49
+ }
50
+ function assistantText(entry) {
51
+ if (entry.type !== "message" || entry.message.role !== "assistant") return void 0;
52
+ return entry.message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n").trim() || void 0;
53
+ }
54
+ function isAutocompactMessage(value) {
55
+ return isRecord(value) && typeof value.role === "string";
56
+ }
57
+ function contextDetailsFromUnknown(value) {
58
+ if (!isRecord(value) || !isStringArray(value.readFiles) || !isStringArray(value.modifiedFiles)) return void 0;
59
+ if (!Array.isArray(value.retainedMessages) || !value.retainedMessages.every(isAutocompactMessage)) return void 0;
60
+ if (!isRecord(value.doomAutocompact)) return void 0;
61
+ const marker = value.doomAutocompact;
62
+ if (marker.version !== 2 || marker.pass !== 2 && marker.pass !== 3 || typeof marker.cycle !== "number" || typeof marker.requestId !== "string" || typeof marker.snapshotLeafId !== "string" || typeof marker.tokensBefore !== "number") return void 0;
63
+ return {
64
+ readFiles: [...value.readFiles],
65
+ modifiedFiles: [...value.modifiedFiles],
66
+ retainedMessages: [...value.retainedMessages],
67
+ doomAutocompact: {
68
+ version: 2,
69
+ cycle: marker.cycle,
70
+ pass: marker.pass,
71
+ requestId: marker.requestId,
72
+ snapshotLeafId: marker.snapshotLeafId,
73
+ tokensBefore: marker.tokensBefore
74
+ }
75
+ };
76
+ }
77
+ function createInitialState() {
78
+ return {
79
+ version: 2,
80
+ cycle: 1,
81
+ pass: 1,
82
+ phase: "waiting",
83
+ checkpointQueue: [],
84
+ exhaustedPasses: [],
85
+ invalidAttempts: 0,
86
+ baselineTokens: 0,
87
+ baselinePending: false,
88
+ readFiles: [],
89
+ modifiedFiles: []
90
+ };
91
+ }
92
+ /**
93
+ * The ratios each pass fires at, with configured values folded in.
94
+ *
95
+ * The ladder only works if it climbs, so a configured pass never sits below the
96
+ * one before it: a file that says pass 2 fires earlier than pass 1 gets pass 1's
97
+ * ratio for pass 2 rather than a pass that can never contribute.
98
+ */
99
+ function compactionRatios(configured = {}) {
100
+ let previous = 0;
101
+ const ratios = {};
102
+ for (const pass of [
103
+ 1,
104
+ 2,
105
+ 3
106
+ ]) {
107
+ const ratio = Math.max(configured[pass] ?? COMPACTION_THRESHOLDS[pass].ratio, previous);
108
+ ratios[pass] = ratio;
109
+ previous = ratio;
110
+ }
111
+ return ratios;
112
+ }
113
+ function thresholdTokens(pass, contextWindow, baselineTokens = 0, configured = {}) {
114
+ const threshold = COMPACTION_THRESHOLDS[pass];
115
+ const ratio = compactionRatios(configured)[pass];
116
+ const baseline = Math.max(0, Math.min(baselineTokens, contextWindow));
117
+ const remaining = contextWindow - baseline;
118
+ const staged = baseline + Math.min(Math.floor(remaining * ratio), threshold.capTokens);
119
+ const nativeTrigger = contextWindow - _earendil_works_pi_coding_agent.DEFAULT_COMPACTION_SETTINGS.reserveTokens;
120
+ return nativeTrigger > baseline ? Math.min(staged, nativeTrigger) : staged;
121
+ }
122
+ /**
123
+ * Where every pass fires, with a matching model's absolute token checkpoints folded in.
124
+ *
125
+ * A configured token count replaces the ratio only when it fires earlier, so an
126
+ * override can bring a checkpoint forward on a very large window without ever
127
+ * pushing one past the point Pi compacts natively.
128
+ *
129
+ * Two rules keep the ladder usable. A token count at or below the baseline is
130
+ * ignored, because a compaction that lands above it would otherwise satisfy that
131
+ * pass the moment the next cycle opens and re-fire it every turn. And a pass never
132
+ * sits below the one before it, for the same reason {@link compactionRatios}
133
+ * climbs: `evaluateUsage` skips any pass below the one already reached, so a later
134
+ * pass that fires first does not merely reorder the ladder, it locks the earlier
135
+ * passes out of the cycle and forces compaction with no summary ever written.
136
+ */
137
+ function effectiveThresholds(contextWindow, baselineTokens = 0, configured = {}, tokenOverrides = {}) {
138
+ let previous = 0;
139
+ const thresholds = {};
140
+ for (const pass of [
141
+ 1,
142
+ 2,
143
+ 3
144
+ ]) {
145
+ const ratioTokens = thresholdTokens(pass, contextWindow, baselineTokens, configured);
146
+ const absolute = tokenOverrides[pass];
147
+ thresholds[pass] = Math.max(absolute !== void 0 && absolute > baselineTokens ? Math.min(ratioTokens, absolute) : ratioTokens, previous);
148
+ previous = thresholds[pass];
149
+ }
150
+ return thresholds;
151
+ }
152
+ function latestCheckpointArtifactEntry(branchEntries) {
153
+ return branchEntries.findLast((entry) => entry.type === "compaction" || entry.type === "custom" && entry.customType === "doom-autocompact-checkpoint" || customMessage(entry)?.customType === "doom-autocompact-checkpoint" || customMessage(entry)?.customType === "doom-autocompact-context");
154
+ }
155
+ function baselineUsageIsSettled(branchEntries) {
156
+ const boundaryIndex = branchEntries.findLastIndex((entry) => entry.type === "compaction" || customMessage(entry)?.customType === "doom-autocompact-context");
157
+ return branchEntries.slice(boundaryIndex + 1).some((entry) => {
158
+ if (entry.type !== "message" || entry.message.role !== "assistant") return false;
159
+ const { stopReason, usage } = entry.message;
160
+ if (stopReason === "aborted" || stopReason === "error" || !usage) return false;
161
+ return (usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite) > 0;
162
+ });
163
+ }
164
+ function projectContextMessages(messages, branchEntries) {
165
+ const nativeCompactionIndex = branchEntries.findLastIndex((entry) => entry.type === "compaction");
166
+ const markerEntry = branchEntries.slice(nativeCompactionIndex + 1).findLast((entry) => customMessage(entry)?.customType === CONTEXT_MESSAGE_TYPE);
167
+ const markerMessage = markerEntry && customMessage(markerEntry);
168
+ if (!markerEntry || !markerMessage) return {
169
+ messages,
170
+ retainedMessageCount: 0
171
+ };
172
+ const details = contextDetailsFromUnknown(markerMessage.details);
173
+ const summary = checkpointSummaryFromEntry(markerEntry);
174
+ if (!details || !summary) return {
175
+ messages,
176
+ invalidMarker: true,
177
+ retainedMessageCount: 0
178
+ };
179
+ const markerMessageIndex = messages.findLastIndex((message) => message.role === "custom" && message.customType === "doom-autocompact-context" && contextDetailsFromUnknown(message.details)?.doomAutocompact.requestId === details.doomAutocompact.requestId);
180
+ if (markerMessageIndex === -1) return {
181
+ messages,
182
+ invalidMarker: true,
183
+ retainedMessageCount: 0
184
+ };
185
+ return {
186
+ messages: [
187
+ {
188
+ role: "compactionSummary",
189
+ summary,
190
+ tokensBefore: details.doomAutocompact.tokensBefore,
191
+ timestamp: messages[markerMessageIndex]?.timestamp ?? Date.now()
192
+ },
193
+ ...details.retainedMessages,
194
+ ...messages.slice(markerMessageIndex + 1)
195
+ ],
196
+ marker: details.doomAutocompact,
197
+ retainedMessageCount: details.retainedMessages.length
198
+ };
199
+ }
200
+ function fileOperationsFromMessages(messages) {
201
+ const fileOps = {
202
+ read: /* @__PURE__ */ new Set(),
203
+ written: /* @__PURE__ */ new Set(),
204
+ edited: /* @__PURE__ */ new Set()
205
+ };
206
+ for (const message of messages) {
207
+ if (message.role !== "assistant") continue;
208
+ for (const block of message.content) {
209
+ if (block.type !== "toolCall") continue;
210
+ const path = typeof block.arguments.path === "string" ? block.arguments.path : void 0;
211
+ if (!path) continue;
212
+ if (block.name === "read") fileOps.read.add(path);
213
+ else if (block.name === "write") fileOps.written.add(path);
214
+ else if (block.name === "edit") fileOps.edited.add(path);
215
+ }
216
+ }
217
+ return fileOps;
218
+ }
219
+ function parseState(value) {
220
+ if (!isRecord(value)) return void 0;
221
+ if (value.version !== 2 || typeof value.cycle !== "number" || !isPass(value.pass)) return void 0;
222
+ if (value.phase !== "waiting" && value.phase !== "checkpoint_pending" && value.phase !== "checkpoint_ready" && value.phase !== "compacting") return void 0;
223
+ const baselineTokens = value.baselineTokens ?? 0;
224
+ if (typeof baselineTokens !== "number" || baselineTokens < 0) return void 0;
225
+ const baselinePending = value.baselinePending ?? false;
226
+ if (typeof baselinePending !== "boolean") return void 0;
227
+ const checkpointQueue = value.checkpointQueue ?? [];
228
+ if (!Array.isArray(checkpointQueue) || !checkpointQueue.every(isPass) || checkpointQueue.some((pass, index) => index > 0 && pass <= checkpointQueue[index - 1])) return void 0;
229
+ const exhaustedPasses = value.exhaustedPasses ?? [];
230
+ if (!Array.isArray(exhaustedPasses) || !exhaustedPasses.every(isPass)) return void 0;
231
+ const invalidAttempts = value.invalidAttempts ?? 0;
232
+ if (typeof invalidAttempts !== "number" || invalidAttempts < 0) return void 0;
233
+ if (!isStringArray(value.readFiles) || !isStringArray(value.modifiedFiles)) return void 0;
234
+ if (value.requestId !== void 0 && typeof value.requestId !== "string") return void 0;
235
+ if (value.compactionPass !== void 0 && value.compactionPass !== 2 && value.compactionPass !== 3) return void 0;
236
+ if ("latestCheckpointEntryId" in value) return void 0;
237
+ if (value.latestCheckpointSnapshotLeafId !== void 0 && typeof value.latestCheckpointSnapshotLeafId !== "string") return void 0;
238
+ if (value.lastAttemptLeafId !== void 0 && typeof value.lastAttemptLeafId !== "string") return void 0;
239
+ if (value.snapshotLeafId !== void 0 && typeof value.snapshotLeafId !== "string") return void 0;
240
+ if (value.snapshotTokens !== void 0 && (typeof value.snapshotTokens !== "number" || value.snapshotTokens < 0)) return void 0;
241
+ if (value.pendingCheckpoint !== void 0 && typeof value.pendingCheckpoint !== "string") return void 0;
242
+ return {
243
+ version: 2,
244
+ cycle: value.cycle,
245
+ pass: value.pass,
246
+ phase: value.phase,
247
+ checkpointQueue: [...checkpointQueue],
248
+ exhaustedPasses: [...exhaustedPasses],
249
+ invalidAttempts,
250
+ baselineTokens,
251
+ baselinePending,
252
+ readFiles: [...value.readFiles],
253
+ modifiedFiles: [...value.modifiedFiles],
254
+ ...value.requestId ? { requestId: value.requestId } : {},
255
+ ...value.compactionPass ? { compactionPass: value.compactionPass } : {},
256
+ ...value.latestCheckpointSnapshotLeafId ? { latestCheckpointSnapshotLeafId: value.latestCheckpointSnapshotLeafId } : {},
257
+ ...value.lastAttemptLeafId ? { lastAttemptLeafId: value.lastAttemptLeafId } : {},
258
+ ...value.snapshotLeafId ? { snapshotLeafId: value.snapshotLeafId } : {},
259
+ ...value.snapshotTokens !== void 0 ? { snapshotTokens: value.snapshotTokens } : {},
260
+ ...value.pendingCheckpoint ? { pendingCheckpoint: value.pendingCheckpoint } : {}
261
+ };
262
+ }
263
+ function checkpointSummaryFromEntry(entry) {
264
+ if (!entry) return void 0;
265
+ if (entry.type === "compaction") return entry.summary.trim() || void 0;
266
+ if (entry.type === "custom") return isRecord(entry.data) && typeof entry.data.summary === "string" ? entry.data.summary.trim() || void 0 : void 0;
267
+ const marker = customMessage(entry);
268
+ if (marker) return (typeof marker.content === "string" ? marker.content : marker.content.filter((block) => block.type === "text").map((block) => block.text).join("\n")).trim() || void 0;
269
+ return assistantText(entry);
270
+ }
271
+ function parseCheckpointDecision(response) {
272
+ const match = response.match(SHOULD_COMPACT_PATTERN);
273
+ const summary = response.replace(SHOULD_COMPACT_PATTERN, "").trim();
274
+ if (!match) return { summary };
275
+ return {
276
+ summary,
277
+ shouldCompact: match[1]?.toLowerCase() === "true"
278
+ };
279
+ }
280
+ function isStructuredCheckpoint(summary) {
281
+ return CHECKPOINT_HEADINGS.every((heading) => summary.includes(heading));
282
+ }
283
+ function createRequestId(sessionId, cycle, pass, leafId) {
284
+ return `${sessionId}:${cycle}:${pass}:${leafId ?? "root"}`;
285
+ }
286
+ function checkpointRequestDetails(cycle, pass, requestId) {
287
+ return {
288
+ version: 2,
289
+ cycle,
290
+ pass,
291
+ requestId
292
+ };
293
+ }
294
+ function mergeFileDetails(current, fileOps, additional) {
295
+ const modified = /* @__PURE__ */ new Set([
296
+ ...current.modifiedFiles,
297
+ ...additional?.modifiedFiles ?? [],
298
+ ...fileOps ? [...fileOps.edited, ...fileOps.written] : []
299
+ ]);
300
+ const read = /* @__PURE__ */ new Set([
301
+ ...current.readFiles,
302
+ ...additional?.readFiles ?? [],
303
+ ...fileOps ? [...fileOps.read] : []
304
+ ]);
305
+ for (const path of modified) read.delete(path);
306
+ return {
307
+ readFiles: [...read].sort(),
308
+ modifiedFiles: [...modified].sort()
309
+ };
310
+ }
311
+ function fileDetailsFromUnknown(value) {
312
+ if (!isRecord(value) || !isStringArray(value.readFiles) || !isStringArray(value.modifiedFiles)) return void 0;
313
+ return {
314
+ readFiles: value.readFiles,
315
+ modifiedFiles: value.modifiedFiles
316
+ };
317
+ }
318
+ function withCanonicalFileSections(summary, details) {
319
+ const sections = [];
320
+ if (details.readFiles.length > 0) sections.push(`<read-files>\n${details.readFiles.join("\n")}\n</read-files>`);
321
+ if (details.modifiedFiles.length > 0) sections.push(`<modified-files>\n${details.modifiedFiles.join("\n")}\n</modified-files>`);
322
+ const normalized = summary.replace(FILE_SECTION_PATTERN, "").trim();
323
+ return sections.length > 0 ? `${normalized}\n\n${sections.join("\n\n")}` : normalized;
324
+ }
325
+ //#endregion
326
+ Object.defineProperty(exports, "CHECKPOINT_MESSAGE_TYPE", {
327
+ enumerable: true,
328
+ get: function() {
329
+ return CHECKPOINT_MESSAGE_TYPE;
330
+ }
331
+ });
332
+ Object.defineProperty(exports, "CONTEXT_MESSAGE_TYPE", {
333
+ enumerable: true,
334
+ get: function() {
335
+ return CONTEXT_MESSAGE_TYPE;
336
+ }
337
+ });
338
+ Object.defineProperty(exports, "RESUME_MESSAGE_TYPE", {
339
+ enumerable: true,
340
+ get: function() {
341
+ return RESUME_MESSAGE_TYPE;
342
+ }
343
+ });
344
+ Object.defineProperty(exports, "RUNTIME_STATE_MESSAGE_TYPE", {
345
+ enumerable: true,
346
+ get: function() {
347
+ return RUNTIME_STATE_MESSAGE_TYPE;
348
+ }
349
+ });
350
+ Object.defineProperty(exports, "STATE_CUSTOM_TYPE", {
351
+ enumerable: true,
352
+ get: function() {
353
+ return STATE_CUSTOM_TYPE;
354
+ }
355
+ });
356
+ Object.defineProperty(exports, "baselineUsageIsSettled", {
357
+ enumerable: true,
358
+ get: function() {
359
+ return baselineUsageIsSettled;
360
+ }
361
+ });
362
+ Object.defineProperty(exports, "checkpointRequestDetails", {
363
+ enumerable: true,
364
+ get: function() {
365
+ return checkpointRequestDetails;
366
+ }
367
+ });
368
+ Object.defineProperty(exports, "checkpointSummaryFromEntry", {
369
+ enumerable: true,
370
+ get: function() {
371
+ return checkpointSummaryFromEntry;
372
+ }
373
+ });
374
+ Object.defineProperty(exports, "contextDetailsFromUnknown", {
375
+ enumerable: true,
376
+ get: function() {
377
+ return contextDetailsFromUnknown;
378
+ }
379
+ });
380
+ Object.defineProperty(exports, "createInitialState", {
381
+ enumerable: true,
382
+ get: function() {
383
+ return createInitialState;
384
+ }
385
+ });
386
+ Object.defineProperty(exports, "createRequestId", {
387
+ enumerable: true,
388
+ get: function() {
389
+ return createRequestId;
390
+ }
391
+ });
392
+ Object.defineProperty(exports, "effectiveThresholds", {
393
+ enumerable: true,
394
+ get: function() {
395
+ return effectiveThresholds;
396
+ }
397
+ });
398
+ Object.defineProperty(exports, "fileDetailsFromUnknown", {
399
+ enumerable: true,
400
+ get: function() {
401
+ return fileDetailsFromUnknown;
402
+ }
403
+ });
404
+ Object.defineProperty(exports, "fileOperationsFromMessages", {
405
+ enumerable: true,
406
+ get: function() {
407
+ return fileOperationsFromMessages;
408
+ }
409
+ });
410
+ Object.defineProperty(exports, "isStructuredCheckpoint", {
411
+ enumerable: true,
412
+ get: function() {
413
+ return isStructuredCheckpoint;
414
+ }
415
+ });
416
+ Object.defineProperty(exports, "latestCheckpointArtifactEntry", {
417
+ enumerable: true,
418
+ get: function() {
419
+ return latestCheckpointArtifactEntry;
420
+ }
421
+ });
422
+ Object.defineProperty(exports, "mergeFileDetails", {
423
+ enumerable: true,
424
+ get: function() {
425
+ return mergeFileDetails;
426
+ }
427
+ });
428
+ Object.defineProperty(exports, "parseCheckpointDecision", {
429
+ enumerable: true,
430
+ get: function() {
431
+ return parseCheckpointDecision;
432
+ }
433
+ });
434
+ Object.defineProperty(exports, "parseState", {
435
+ enumerable: true,
436
+ get: function() {
437
+ return parseState;
438
+ }
439
+ });
440
+ Object.defineProperty(exports, "projectContextMessages", {
441
+ enumerable: true,
442
+ get: function() {
443
+ return projectContextMessages;
444
+ }
445
+ });
446
+ Object.defineProperty(exports, "thresholdTokens", {
447
+ enumerable: true,
448
+ get: function() {
449
+ return thresholdTokens;
450
+ }
451
+ });
452
+ Object.defineProperty(exports, "withCanonicalFileSections", {
453
+ enumerable: true,
454
+ get: function() {
455
+ return withCanonicalFileSections;
456
+ }
457
+ });
458
+
459
+ //# sourceMappingURL=compactionPolicy-BIJJHcBP.cjs.map