@fungi.computer/shiitake 0.0.0-stage → 0.0.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.
Files changed (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +454 -2
  3. package/dist/agar-extension.d.ts +9 -0
  4. package/dist/agar-extension.js +38 -0
  5. package/dist/agent-control.d.ts +72 -0
  6. package/dist/agent-control.js +431 -0
  7. package/dist/client-entry.d.ts +211 -0
  8. package/dist/client-entry.js +101 -0
  9. package/dist/client-wire.d.ts +199 -0
  10. package/dist/client-wire.js +271 -0
  11. package/dist/client.d.ts +439 -0
  12. package/dist/client.js +4 -0
  13. package/dist/cloudflare.d.ts +220 -0
  14. package/dist/cloudflare.js +531 -0
  15. package/dist/effect.d.ts +42 -0
  16. package/dist/effect.js +20 -0
  17. package/dist/errors.d.ts +50 -0
  18. package/dist/errors.js +81 -0
  19. package/dist/extension-client.d.ts +38 -0
  20. package/dist/extension-client.js +56 -0
  21. package/dist/extension-contract.d.ts +73 -0
  22. package/dist/extension-contract.js +11 -0
  23. package/dist/extensions.d.ts +589 -0
  24. package/dist/extensions.js +263 -0
  25. package/dist/fleet.d.ts +297 -0
  26. package/dist/fleet.js +515 -0
  27. package/dist/hosted.d.ts +133 -0
  28. package/dist/hosted.js +265 -0
  29. package/dist/http-routes.d.ts +772 -0
  30. package/dist/http-routes.js +242 -0
  31. package/dist/http.d.ts +1 -0
  32. package/dist/http.js +1 -0
  33. package/dist/index.d.ts +35 -0
  34. package/dist/index.js +13 -0
  35. package/dist/internal/accepted-run-executor.d.ts +23 -0
  36. package/dist/internal/accepted-run-executor.js +129 -0
  37. package/dist/internal/admission-door.d.ts +75 -0
  38. package/dist/internal/admission-door.js +105 -0
  39. package/dist/internal/agent-close.d.ts +4 -0
  40. package/dist/internal/agent-close.js +42 -0
  41. package/dist/internal/browser-sessions.d.ts +31 -0
  42. package/dist/internal/browser-sessions.js +173 -0
  43. package/dist/internal/client-boundary.d.ts +262 -0
  44. package/dist/internal/client-boundary.js +202 -0
  45. package/dist/internal/compaction/compaction.d.ts +54 -0
  46. package/dist/internal/compaction/compaction.js +292 -0
  47. package/dist/internal/compaction/session-compaction.d.ts +69 -0
  48. package/dist/internal/compaction/session-compaction.js +330 -0
  49. package/dist/internal/compaction/settings.d.ts +11 -0
  50. package/dist/internal/compaction/settings.js +40 -0
  51. package/dist/internal/compaction/utils.d.ts +45 -0
  52. package/dist/internal/compaction/utils.js +319 -0
  53. package/dist/internal/contracts.d.ts +235 -0
  54. package/dist/internal/contracts.js +1 -0
  55. package/dist/internal/coordinator.d.ts +18 -0
  56. package/dist/internal/coordinator.js +362 -0
  57. package/dist/internal/document-codec.d.ts +11 -0
  58. package/dist/internal/document-codec.js +39 -0
  59. package/dist/internal/effect-client.d.ts +13 -0
  60. package/dist/internal/effect-client.js +102 -0
  61. package/dist/internal/effect-contracts.d.ts +236 -0
  62. package/dist/internal/effect-contracts.js +2 -0
  63. package/dist/internal/extension-command-registry.d.ts +11 -0
  64. package/dist/internal/extension-command-registry.js +29 -0
  65. package/dist/internal/extension-command-tools.d.ts +17 -0
  66. package/dist/internal/extension-command-tools.js +50 -0
  67. package/dist/internal/extension-context.d.ts +25 -0
  68. package/dist/internal/extension-context.js +35 -0
  69. package/dist/internal/extension-definitions.d.ts +5 -0
  70. package/dist/internal/extension-definitions.js +6 -0
  71. package/dist/internal/extension-hook.d.ts +25 -0
  72. package/dist/internal/extension-hook.js +46 -0
  73. package/dist/internal/extension-roster.d.ts +23 -0
  74. package/dist/internal/extension-roster.js +228 -0
  75. package/dist/internal/extension-runtime.d.ts +63 -0
  76. package/dist/internal/extension-runtime.js +1004 -0
  77. package/dist/internal/failure.d.ts +2 -0
  78. package/dist/internal/failure.js +9 -0
  79. package/dist/internal/host-meta.d.ts +41 -0
  80. package/dist/internal/host-meta.js +39 -0
  81. package/dist/internal/json-value.d.ts +4 -0
  82. package/dist/internal/json-value.js +3 -0
  83. package/dist/internal/model-acquisition.d.ts +21 -0
  84. package/dist/internal/model-acquisition.js +98 -0
  85. package/dist/internal/model-preference.d.ts +21 -0
  86. package/dist/internal/model-preference.js +20 -0
  87. package/dist/internal/observation-kernel.d.ts +28 -0
  88. package/dist/internal/observation-kernel.js +169 -0
  89. package/dist/internal/promise-facade.d.ts +18 -0
  90. package/dist/internal/promise-facade.js +172 -0
  91. package/dist/internal/run-scope.d.ts +27 -0
  92. package/dist/internal/run-scope.js +214 -0
  93. package/dist/internal/runtime-coordinator.d.ts +34 -0
  94. package/dist/internal/runtime-coordinator.js +233 -0
  95. package/dist/internal/runtime-core.d.ts +19 -0
  96. package/dist/internal/runtime-core.js +39 -0
  97. package/dist/internal/runtime-reconciliation.d.ts +18 -0
  98. package/dist/internal/runtime-reconciliation.js +162 -0
  99. package/dist/internal/session-context.d.ts +11 -0
  100. package/dist/internal/session-context.js +67 -0
  101. package/dist/internal/session-status.d.ts +10 -0
  102. package/dist/internal/session-status.js +52 -0
  103. package/dist/internal/woodstock/agent-reply.d.ts +25 -0
  104. package/dist/internal/woodstock/agent-reply.js +67 -0
  105. package/dist/internal/woodstock/client-projection.d.ts +29 -0
  106. package/dist/internal/woodstock/client-projection.js +512 -0
  107. package/dist/internal/woodstock/compaction-activity.d.ts +17 -0
  108. package/dist/internal/woodstock/compaction-activity.js +32 -0
  109. package/dist/internal/woodstock/cursor.d.ts +9 -0
  110. package/dist/internal/woodstock/cursor.js +44 -0
  111. package/dist/internal/woodstock/extension-store.d.ts +72 -0
  112. package/dist/internal/woodstock/extension-store.js +262 -0
  113. package/dist/internal/woodstock/fleet-store.d.ts +32 -0
  114. package/dist/internal/woodstock/fleet-store.js +93 -0
  115. package/dist/internal/woodstock/identities.d.ts +6 -0
  116. package/dist/internal/woodstock/identities.js +11 -0
  117. package/dist/internal/woodstock/index.d.ts +170 -0
  118. package/dist/internal/woodstock/index.js +546 -0
  119. package/dist/internal/woodstock/progress.d.ts +32 -0
  120. package/dist/internal/woodstock/progress.js +89 -0
  121. package/dist/internal/woodstock/run-recovery.d.ts +8 -0
  122. package/dist/internal/woodstock/run-recovery.js +95 -0
  123. package/dist/internal/woodstock/session-attention.d.ts +16 -0
  124. package/dist/internal/woodstock/session-attention.js +88 -0
  125. package/dist/internal/woodstock/session-documents.d.ts +18 -0
  126. package/dist/internal/woodstock/session-documents.js +51 -0
  127. package/dist/internal/woodstock/session-entry.d.ts +478 -0
  128. package/dist/internal/woodstock/session-entry.js +484 -0
  129. package/dist/internal/woodstock/session-grants.d.ts +24 -0
  130. package/dist/internal/woodstock/session-grants.js +84 -0
  131. package/dist/internal/woodstock/session-inbox.d.ts +56 -0
  132. package/dist/internal/woodstock/session-inbox.js +307 -0
  133. package/dist/internal/woodstock/session-store.d.ts +432 -0
  134. package/dist/internal/woodstock/session-store.js +2006 -0
  135. package/dist/internal/woodstock/storage-admission.d.ts +14 -0
  136. package/dist/internal/woodstock/storage-admission.js +44 -0
  137. package/dist/model-preference.d.ts +20 -0
  138. package/dist/model-preference.js +30 -0
  139. package/dist/node.d.ts +64 -0
  140. package/dist/node.js +256 -0
  141. package/dist/remote-client.d.ts +11 -0
  142. package/dist/remote-client.js +298 -0
  143. package/dist/session-attention.d.ts +35 -0
  144. package/dist/session-attention.js +22 -0
  145. package/dist/session-documents.d.ts +28 -0
  146. package/dist/session-documents.js +1 -0
  147. package/dist/testing.d.ts +54 -0
  148. package/dist/testing.js +215 -0
  149. package/docs/api/README.md +26 -0
  150. package/docs/api/agar-extension.md +40 -0
  151. package/docs/api/agent-control.md +151 -0
  152. package/docs/api/client-entry.md +344 -0
  153. package/docs/api/client.md +1112 -0
  154. package/docs/api/cloudflare.md +500 -0
  155. package/docs/api/effect.md +904 -0
  156. package/docs/api/extensions.md +2003 -0
  157. package/docs/api/fleet.md +706 -0
  158. package/docs/api/hosted.md +382 -0
  159. package/docs/api/http.md +111 -0
  160. package/docs/api/index.md +1116 -0
  161. package/docs/api/model-preference.md +151 -0
  162. package/docs/api/node.md +142 -0
  163. package/docs/api/testing.md +250 -0
  164. package/package.json +130 -3
@@ -0,0 +1,330 @@
1
+ import { isContextOverflow, } from "@fungi.computer/mule/effect";
2
+ import { isRecoverableLength } from "@earendil-works/pi-ai";
3
+ import * as Effect from "effect/Effect";
4
+ import { ShiitakeError } from "../../errors.js";
5
+ import { borrowedView, compact, prepareCompaction, } from "./compaction.js";
6
+ import { estimateContextTokens, estimateTokens, shouldCompact, } from "./utils.js";
7
+ import { appendCompactionNotice, appendCompactionTransition, beginCompactionActivity, buildSessionContext, endCompactionActivity, withdrawnWorkIds, contextMessageIds, commandContextMessage, operatorContextMessage, promptCommand, readSessionContextPath, } from "../woodstock/index.js";
8
+ function runtimeFailure(message) {
9
+ return new ShiitakeError("runtime", "rejected", message);
10
+ }
11
+ const commitFailures = {
12
+ handoff: "The compaction handoff failed; the conversation context is unchanged",
13
+ conflict: "The Session changed during compaction; the conversation context is unchanged",
14
+ };
15
+ /**
16
+ * A commit that stopped before its CAS append took effect. The phase is
17
+ * owner-closed, so its diagnostic is safe to record and show.
18
+ */
19
+ export class CompactionCommitError extends ShiitakeError {
20
+ phase;
21
+ constructor(phase) {
22
+ super(phase === "conflict" ? "store" : "runtime", phase === "conflict" ? "conflict" : "rejected", commitFailures[phase]);
23
+ this.phase = phase;
24
+ }
25
+ get diagnostic() {
26
+ return commitFailures[this.phase];
27
+ }
28
+ }
29
+ function sourceEntries(path, activeWorkId) {
30
+ const visible = contextMessageIds(path, activeWorkId);
31
+ const withdrawn = withdrawnWorkIds(path);
32
+ const entries = [];
33
+ for (const row of path) {
34
+ const prompt = promptCommand(row.entry);
35
+ if (prompt !== undefined && !withdrawn.has(prompt.workId)) {
36
+ entries.push({
37
+ id: row.id,
38
+ kind: "message",
39
+ message: commandContextMessage(prompt),
40
+ });
41
+ continue;
42
+ }
43
+ if (row.entry.kind === "operator") {
44
+ entries.push({
45
+ id: row.id,
46
+ kind: "message",
47
+ message: operatorContextMessage(row.entry),
48
+ });
49
+ continue;
50
+ }
51
+ if (row.entry.kind === "message") {
52
+ if (visible.has(row.id))
53
+ entries.push({ id: row.id, kind: "message", message: row.entry.value });
54
+ continue;
55
+ }
56
+ if (row.entry.kind === "compaction") {
57
+ const compaction = {
58
+ id: row.id,
59
+ kind: "compaction",
60
+ timestamp: row.timestamp,
61
+ firstKeptEntryId: row.entry.firstKeptEntryId,
62
+ summary: row.entry.summary,
63
+ usage: row.entry.usage,
64
+ };
65
+ entries.push(row.entry.details === undefined
66
+ ? compaction
67
+ : { ...compaction, details: row.entry.details });
68
+ }
69
+ }
70
+ return entries;
71
+ }
72
+ /** The current context rows: the latest kept window, never the whole lineage. */
73
+ function readPath(store, sessionId, workId) {
74
+ return Effect.try({
75
+ try: () => readSessionContextPath(store, { sessionId, workId }),
76
+ catch: (error) => error instanceof ShiitakeError && error.cause === "context_too_large"
77
+ ? error
78
+ : runtimeFailure("Woodstock selected lineage is unavailable"),
79
+ });
80
+ }
81
+ /**
82
+ * The compaction owner's context estimate: Pi's usage-plus-trailing count,
83
+ * excluding provider usage that belongs before Woodstock's latest boundary.
84
+ *
85
+ * A provider's reported usage measures the context it actually read, so it
86
+ * is authoritative only while nothing has reshaped that context since. Once
87
+ * a compaction lands, every kept message's own usage figure is stale: it
88
+ * priced the pre-compaction history, not the summary that replaced it. The
89
+ * one safe fallback is a fresh chars/4 estimate of every message still in
90
+ * view, the same method `tokensAfterCompaction` uses when it records the
91
+ * marker's own `tokensAfter` at commit time; trailing-after-usage alone
92
+ * would undercount (even to zero, when the kept tail's own reply is the
93
+ * newest message and nothing followed it).
94
+ */
95
+ export function estimateThresholdContextTokens(path, messages) {
96
+ const boundary = path.reduce((latest, row) => (row.entry.kind === "compaction" ? row.timestamp : latest), undefined);
97
+ const estimate = estimateContextTokens(messages);
98
+ const usageIndex = estimate.lastUsageIndex;
99
+ const usageMessage = usageIndex === null ? undefined : messages[usageIndex];
100
+ if (boundary === undefined ||
101
+ usageMessage?.role !== "assistant" ||
102
+ usageMessage.timestamp > boundary)
103
+ return estimate.tokens;
104
+ return messages.reduce((sum, message) => sum + estimateTokens(message), 0);
105
+ }
106
+ /** The context the model reads once a compaction row becomes the boundary. */
107
+ function tokensAfterCompaction(path, expected, entry, workId) {
108
+ const boundary = {
109
+ ...expected,
110
+ id: `${expected.id}:compaction`,
111
+ parentId: expected.id,
112
+ sequence: expected.sequence + 1,
113
+ timestamp: Date.now(),
114
+ entry,
115
+ };
116
+ return buildSessionContext([...path, boundary], workId).reduce((sum, message) => sum + estimateTokens(message), 0);
117
+ }
118
+ /** One exact preparation, handoff, CAS append, and authoritative rebuild. */
119
+ function commitCompaction(input, path, request) {
120
+ return Effect.suspend(() => {
121
+ beginCompactionActivity(input.store, input.sessionId, request.trigger);
122
+ return commitCompactionEffect(input, path, request).pipe(Effect.ensuring(Effect.sync(() => {
123
+ endCompactionActivity(input.store, input.sessionId);
124
+ })));
125
+ });
126
+ }
127
+ /** The commit itself; `commitCompaction` wraps it with the live activity fact. */
128
+ function commitCompactionEffect(input, path, request) {
129
+ return Effect.gen(function* () {
130
+ const preparation = prepareCompaction(sourceEntries(path, input.workId), input.settings);
131
+ if (preparation === undefined)
132
+ return undefined;
133
+ const expected = path.at(-1);
134
+ if (expected === undefined)
135
+ return undefined;
136
+ const result = yield* compact(preparation, input.model, input.stream, request.instructions);
137
+ if (input.handoff !== undefined)
138
+ yield* input.handoff
139
+ .consume(borrowedView(preparation, result, expected.id))
140
+ .pipe(Effect.mapError(() => new CompactionCommitError("handoff")));
141
+ const tokensAfter = tokensAfterCompaction(path, expected, {
142
+ kind: "compaction",
143
+ resumesRun: request.resumesRun,
144
+ trigger: request.trigger,
145
+ workId: input.workId,
146
+ firstKeptEntryId: result.firstKeptEntryId,
147
+ summary: result.summary,
148
+ tokensBefore: result.tokensBefore,
149
+ usage: result.usage,
150
+ contextRevision: expected.id,
151
+ }, input.workId);
152
+ const committed = yield* Effect.try({
153
+ try: () => appendCompactionTransition(input.store, {
154
+ sessionId: input.sessionId,
155
+ resumesRun: request.resumesRun,
156
+ trigger: request.trigger,
157
+ tokensAfter,
158
+ expectedLeafId: expected.id,
159
+ workId: input.workId,
160
+ firstKeptEntryId: result.firstKeptEntryId,
161
+ summary: result.summary,
162
+ tokensBefore: result.tokensBefore,
163
+ usage: result.usage,
164
+ contextRevision: expected.id,
165
+ details: result.details,
166
+ }),
167
+ catch: (error) => error instanceof ShiitakeError
168
+ ? error.cause === "conflict"
169
+ ? new CompactionCommitError("conflict")
170
+ : error
171
+ : runtimeFailure("Woodstock compaction append failed"),
172
+ });
173
+ const committedPath = [...path, committed];
174
+ return {
175
+ path: committedPath,
176
+ messages: buildSessionContext(committedPath, input.workId),
177
+ };
178
+ });
179
+ }
180
+ /** A failure's display-safe reason: owner-closed text or the model's message. */
181
+ function failureDiagnostic(error) {
182
+ if (error instanceof CompactionCommitError)
183
+ return error.diagnostic;
184
+ if (error.boundary === "model")
185
+ return error.message;
186
+ return `Compaction failed (${error.boundary}: ${error.cause})`;
187
+ }
188
+ /** Record a compaction that committed nothing, so clients can show it. */
189
+ function recordNotice(input, trigger, failure) {
190
+ return Effect.try({
191
+ try: () => {
192
+ appendCompactionNotice(input.store, {
193
+ sessionId: input.sessionId,
194
+ workId: input.workId,
195
+ trigger,
196
+ result: failure === undefined ? "nothing_to_compact" : "failed",
197
+ diagnostic: failure === undefined ? undefined : failureDiagnostic(failure),
198
+ });
199
+ },
200
+ catch: (error) => error instanceof ShiitakeError
201
+ ? error
202
+ : runtimeFailure("Compaction notice could not be recorded"),
203
+ });
204
+ }
205
+ /**
206
+ * Run one automatic compaction. A failure is recorded as a notice and the
207
+ * caller keeps its current context; automatic compaction never fails a run.
208
+ */
209
+ function autoCompaction(input, path, request) {
210
+ return Effect.result(commitCompaction(input, path, request)).pipe(Effect.flatMap((result) => result._tag === "Success"
211
+ ? Effect.succeed(result.success)
212
+ : recordNotice(input, request.trigger, result.failure).pipe(Effect.as(undefined))));
213
+ }
214
+ /** Apply the pre-run threshold check, preserving unchanged input on failure. */
215
+ function preRunMessages(input) {
216
+ if (input.resuming === true ||
217
+ !shouldCompact(estimateThresholdContextTokens(input.path, input.messages), input.model.contextWindow, input.settings))
218
+ return Effect.succeed({ path: input.path, messages: input.messages });
219
+ return autoCompaction(input, input.path, {
220
+ trigger: "threshold",
221
+ resumesRun: false,
222
+ }).pipe(Effect.map((compacted) => compacted ?? { path: input.path, messages: input.messages }));
223
+ }
224
+ function classifyRecovery(outcome, model) {
225
+ const overflow = isContextOverflow(outcome.message, model.contextWindow);
226
+ const recoverableLength = isRecoverableLength(outcome.message, model.maxTokens);
227
+ return {
228
+ triggered: overflow || recoverableLength,
229
+ overflow,
230
+ retry: (overflow || recoverableLength) && outcome.message.stopReason !== "stop",
231
+ };
232
+ }
233
+ function removeFailedAssistant(messages) {
234
+ const last = messages.at(-1);
235
+ return last?.role === "assistant" &&
236
+ (last.stopReason === "error" || last.stopReason === "length")
237
+ ? messages.slice(0, -1)
238
+ : messages;
239
+ }
240
+ function postRunPath(input, working, outcome, recovery) {
241
+ if (!input.settings.enabled)
242
+ return Effect.succeed(undefined);
243
+ const threshold = shouldCompact(estimateThresholdContextTokens(working.path, [
244
+ ...working.messages,
245
+ ...outcome.messages.slice(working.newMessageCount),
246
+ ]), input.model.contextWindow, input.settings);
247
+ return recovery.triggered || threshold
248
+ ? readPath(input.store, input.sessionId, input.workId)
249
+ : Effect.succeed(undefined);
250
+ }
251
+ /**
252
+ * Pi's between-turn threshold check: when a running work's context has grown
253
+ * past the threshold, compact the durable lineage (which already holds this
254
+ * turn's tool results) and continue from the rebuilt context.
255
+ */
256
+ function betweenTurns(input, working) {
257
+ return (context, newMessageCount) => Effect.gen(function* () {
258
+ if (!shouldCompact(estimateThresholdContextTokens(working.path, context), input.model.contextWindow, input.settings))
259
+ return undefined;
260
+ const path = yield* readPath(input.store, input.sessionId, input.workId);
261
+ const compacted = yield* autoCompaction(input, path, {
262
+ trigger: "threshold",
263
+ resumesRun: false,
264
+ });
265
+ if (compacted === undefined)
266
+ return undefined;
267
+ working.path = compacted.path;
268
+ working.messages = compacted.messages;
269
+ working.newMessageCount = newMessageCount;
270
+ return compacted.messages;
271
+ });
272
+ }
273
+ /** Run terminal outcomes through post-threshold and one-time recovery policy. */
274
+ function postRunOutcome(input, initial) {
275
+ return Effect.gen(function* () {
276
+ let current = initial;
277
+ let recoveryAttempted = initial.path.some(({ entry }) => entry.kind === "compaction" &&
278
+ entry.workId === input.workId &&
279
+ entry.resumesRun === true);
280
+ for (;;) {
281
+ const working = {
282
+ path: current.path,
283
+ messages: current.messages,
284
+ newMessageCount: 0,
285
+ };
286
+ const outcome = yield* input.run(working.messages, betweenTurns(input, working));
287
+ const recovery = classifyRecovery(outcome, input.model);
288
+ const path = yield* postRunPath(input, working, outcome, recovery);
289
+ if (path === undefined)
290
+ return outcome;
291
+ if (recovery.retry && recoveryAttempted)
292
+ return outcome;
293
+ const compacted = yield* autoCompaction(input, path, {
294
+ trigger: recovery.overflow ? "overflow" : "threshold",
295
+ resumesRun: recovery.retry,
296
+ });
297
+ if (compacted === undefined || !recovery.retry)
298
+ return outcome;
299
+ current = {
300
+ path: compacted.path,
301
+ messages: removeFailedAssistant(compacted.messages),
302
+ };
303
+ recoveryAttempted = true;
304
+ }
305
+ });
306
+ }
307
+ /** Compose the retained pre-run, between-turn and post-run compaction phases. */
308
+ export function runWithCompaction(input) {
309
+ if (!input.settings.enabled)
310
+ return postRunOutcome(input, input);
311
+ return preRunMessages(input).pipe(Effect.flatMap((initial) => postRunOutcome(input, initial)));
312
+ }
313
+ /**
314
+ * Compact one Session now, as accepted work: the same commit the automatic
315
+ * triggers use, with `resumesRun: false`, the user's optional focus and no
316
+ * model turn. "Nothing to compact" and a failure are each recorded as a
317
+ * notice. A retried claim of the same work never compacts twice.
318
+ */
319
+ export function compactSession(input) {
320
+ if (input.path.some(({ entry }) => (entry.kind === "compaction" || entry.kind === "compaction_notice") &&
321
+ entry.workId === input.workId))
322
+ return Effect.succeed("compacted");
323
+ return commitCompaction(input, input.path, {
324
+ trigger: "manual",
325
+ resumesRun: false,
326
+ instructions: input.instructions,
327
+ }).pipe(Effect.tapError((error) => recordNotice(input, "manual", error)), Effect.flatMap((committed) => committed === undefined
328
+ ? recordNotice(input, "manual", undefined).pipe(Effect.as("nothing_to_compact"))
329
+ : Effect.succeed("compacted")));
330
+ }
@@ -0,0 +1,11 @@
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import type { CompactionOptions } from "../contracts.js";
3
+ import { type CompactionSettings } from "./utils.js";
4
+ /** Resolve one model's compaction settings from the host's validated options. */
5
+ export type CompactionSettingsFor = (model: Model<Api>) => CompactionSettings;
6
+ /**
7
+ * Validate the host's compaction options once, when the owner opens, and
8
+ * resolve each budget per model: exact `provider/modelId` override, then the
9
+ * ordinary value, then Pi's default.
10
+ */
11
+ export declare function openCompactionSettings(options: CompactionOptions | undefined): CompactionSettingsFor;
@@ -0,0 +1,40 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { ShiitakeError } from "../../errors.js";
3
+ import { DEFAULT_COMPACTION_SETTINGS, } from "./utils.js";
4
+ const tokens = Schema.Int.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(0)), Schema.check(Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER)));
5
+ const BudgetSchema = Schema.Struct({
6
+ reserveTokens: Schema.optionalKey(tokens),
7
+ keepRecentTokens: Schema.optionalKey(tokens),
8
+ });
9
+ const CompactionOptionsSchema = Schema.Struct({
10
+ enabled: Schema.optionalKey(Schema.Boolean),
11
+ reserveTokens: Schema.optionalKey(tokens),
12
+ keepRecentTokens: Schema.optionalKey(tokens),
13
+ modelOverrides: Schema.optionalKey(Schema.Record(Schema.String, BudgetSchema)),
14
+ });
15
+ const decodeOptions = Schema.decodeUnknownResult(CompactionOptionsSchema, {
16
+ onExcessProperty: "error",
17
+ });
18
+ /**
19
+ * Validate the host's compaction options once, when the owner opens, and
20
+ * resolve each budget per model: exact `provider/modelId` override, then the
21
+ * ordinary value, then Pi's default.
22
+ */
23
+ export function openCompactionSettings(options) {
24
+ const decoded = decodeOptions(options ?? {});
25
+ if (decoded._tag === "Failure")
26
+ throw new ShiitakeError("contract", "invalid", "Compaction settings must be non-negative whole token counts");
27
+ const settings = decoded.success;
28
+ return (model) => {
29
+ const override = settings.modelOverrides?.[`${model.provider}/${model.id}`];
30
+ return {
31
+ enabled: settings.enabled ?? DEFAULT_COMPACTION_SETTINGS.enabled,
32
+ reserveTokens: override?.reserveTokens ??
33
+ settings.reserveTokens ??
34
+ DEFAULT_COMPACTION_SETTINGS.reserveTokens,
35
+ keepRecentTokens: override?.keepRecentTokens ??
36
+ settings.keepRecentTokens ??
37
+ DEFAULT_COMPACTION_SETTINGS.keepRecentTokens,
38
+ };
39
+ };
40
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Adapted from the maintained Pi v0.84.4 coding-agent compaction utilities.
3
+ * The only destination delta is carrying Pi's native Message values directly.
4
+ */
5
+ import type { AssistantMessage, Message } from "@earendil-works/pi-ai";
6
+ /** Pi's native compaction boundary projection, retained without a custom role. */
7
+ export declare function compactionSummaryMessage(summary: string, timestamp: number): Message;
8
+ export type FileOperations = {
9
+ edited: Set<string>;
10
+ read: Set<string>;
11
+ written: Set<string>;
12
+ };
13
+ export declare function createFileOps(): FileOperations;
14
+ export declare function extractFileOpsFromMessage(message: Message, fileOps: FileOperations): void;
15
+ export declare function computeFileLists(fileOps: FileOperations): {
16
+ modifiedFiles: string[];
17
+ readFiles: string[];
18
+ };
19
+ export declare function formatFileOperations(readFiles: readonly string[], modifiedFiles: readonly string[]): string;
20
+ export declare function serializeConversation(messages: readonly Message[]): string;
21
+ /** Pi's chars/4 estimator, including its image and tool-call constants. */
22
+ export declare function estimateTokens(message: Message): number;
23
+ export declare function estimateContextTokens(messages: readonly Message[]): {
24
+ tokens: number;
25
+ usageTokens: number;
26
+ trailingTokens: number;
27
+ lastUsageIndex: null;
28
+ } | {
29
+ tokens: number;
30
+ usageTokens: number;
31
+ trailingTokens: number;
32
+ lastUsageIndex: number;
33
+ };
34
+ export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
35
+ export type CompactionSettings = {
36
+ enabled: boolean;
37
+ reserveTokens: number;
38
+ keepRecentTokens: number;
39
+ };
40
+ export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
41
+ export declare const SUMMARIZATION_SYSTEM_PROMPT = "You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.";
42
+ export declare const SUMMARIZATION_PROMPT = "The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.\n\nUse this EXACT format:\n\n## Goal\n[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned by user]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Ordered list of what should happen next]\n\n## Critical Context\n- [Any data, examples, or references needed to continue]\n- [Or \"(none)\" if not applicable]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.";
43
+ export declare const UPDATE_SUMMARIZATION_PROMPT = "The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, and context from the new messages\n- UPDATE the Progress section: move items from \"In Progress\" to \"Done\" when completed\n- UPDATE \"Next Steps\" based on what was accomplished\n- PRESERVE exact file paths, function names, and error messages\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Goal\n[Preserve existing goals, add new ones if the task expanded]\n\n## Constraints & Preferences\n- [Preserve existing, add new ones discovered]\n\n## Progress\n### Done\n- [x] [Include previously done items AND newly completed items]\n\n### In Progress\n- [ ] [Current work - update based on progress]\n\n### Blocked\n- [Current blockers - remove if resolved]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale] (preserve all previous, add new)\n\n## Next Steps\n1. [Update based on current state]\n\n## Critical Context\n- [Preserve important context, add new if needed]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.";
44
+ export declare const TURN_PREFIX_SUMMARIZATION_PROMPT = "The messages above are earlier context from an ongoing conversation. Later messages are stored separately and do not need to be reconstructed.\n\nCreate a concise checkpoint of the user's request and the progress shown above. This checkpoint will be placed before the later messages so the conversation can continue with the necessary context.\n\n## Original Request\n[What did the user ask for?]\n\n## Progress So Far\n- [Key decisions and work completed in these messages]\n\n## Context Needed to Continue\n- [Information from these messages needed to understand the later work]\n\nOnly summarize information explicitly present above. Do not infer or recreate later messages.";
45
+ export declare function summaryFailure(response: AssistantMessage, label: string): string | undefined;