@fungi.computer/shiitake 0.1.0 → 0.3.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 +155 -34
- package/dist/agent-control.d.ts +78 -11
- package/dist/agent-control.js +273 -70
- package/dist/client-entry.d.ts +15 -8
- package/dist/client-entry.js +7 -5
- package/dist/client-wire.d.ts +24 -8
- package/dist/client-wire.js +53 -11
- package/dist/client.d.ts +56 -11
- package/dist/client.js +1 -1
- package/dist/cloudflare.d.ts +42 -23
- package/dist/cloudflare.js +76 -54
- package/dist/cold-sessions.d.ts +28 -0
- package/dist/cold-sessions.js +1 -0
- package/dist/delegation.d.ts +214 -0
- package/dist/delegation.js +319 -0
- package/dist/effect.d.ts +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -2
- package/dist/extension-contract.d.ts +10 -2
- package/dist/extensions.d.ts +6 -2
- package/dist/fleet.d.ts +52 -34
- package/dist/fleet.js +101 -63
- package/dist/hosted.js +1 -0
- package/dist/http-routes.d.ts +106 -22
- package/dist/http-routes.js +18 -1
- package/dist/index.d.ts +3 -2
- package/dist/internal/accepted-run-executor.d.ts +5 -0
- package/dist/internal/accepted-run-executor.js +10 -4
- package/dist/internal/admission-door.d.ts +3 -0
- package/dist/internal/admission-door.js +12 -2
- package/dist/internal/client-boundary.js +7 -1
- package/dist/internal/compaction/compaction-slot.d.ts +36 -0
- package/dist/internal/compaction/compaction-slot.js +41 -0
- package/dist/internal/compaction/compaction.d.ts +7 -4
- package/dist/internal/compaction/compaction.js +59 -30
- package/dist/internal/compaction/session-compaction.d.ts +8 -16
- package/dist/internal/compaction/session-compaction.js +358 -119
- package/dist/internal/compaction/settings.js +5 -0
- package/dist/internal/compaction/utils.d.ts +19 -16
- package/dist/internal/compaction/utils.js +30 -84
- package/dist/internal/contracts.d.ts +40 -13
- package/dist/internal/coordinator.js +28 -10
- package/dist/internal/effect-client.d.ts +4 -2
- package/dist/internal/effect-client.js +29 -0
- package/dist/internal/effect-contracts.d.ts +17 -4
- package/dist/internal/extension-runtime.d.ts +5 -0
- package/dist/internal/extension-runtime.js +184 -89
- package/dist/internal/host-meta.d.ts +7 -0
- package/dist/internal/model-acquisition.js +7 -1
- package/dist/internal/model-preference.d.ts +2 -0
- package/dist/internal/model-preference.js +2 -1
- package/dist/internal/promise-facade.js +4 -16
- package/dist/internal/run-failure-reason.d.ts +10 -0
- package/dist/internal/run-failure-reason.js +19 -0
- package/dist/internal/run-scope.d.ts +4 -1
- package/dist/internal/run-scope.js +38 -5
- package/dist/internal/runtime-coordinator.d.ts +2 -1
- package/dist/internal/runtime-coordinator.js +65 -4
- package/dist/internal/runtime-core.js +1 -0
- package/dist/internal/runtime-reconciliation.js +42 -6
- package/dist/internal/session-context.d.ts +1 -2
- package/dist/internal/session-context.js +8 -30
- package/dist/internal/session-status.d.ts +2 -1
- package/dist/internal/session-status.js +4 -1
- package/dist/internal/woodstock/client-projection.d.ts +6 -5
- package/dist/internal/woodstock/client-projection.js +79 -53
- package/dist/internal/woodstock/cold-sessions.d.ts +4 -0
- package/dist/internal/woodstock/cold-sessions.js +90 -0
- package/dist/internal/woodstock/extension-store.d.ts +2 -0
- package/dist/internal/woodstock/extension-store.js +4 -0
- package/dist/internal/woodstock/fleet-store.d.ts +3 -3
- package/dist/internal/woodstock/fleet-store.js +16 -11
- package/dist/internal/woodstock/index.d.ts +11 -3
- package/dist/internal/woodstock/index.js +33 -14
- package/dist/internal/woodstock/progress.d.ts +14 -1
- package/dist/internal/woodstock/progress.js +97 -19
- package/dist/internal/woodstock/run-recovery.js +6 -4
- package/dist/internal/woodstock/session-attention.d.ts +1 -1
- package/dist/internal/woodstock/session-attention.js +2 -2
- package/dist/internal/woodstock/session-entry.d.ts +109 -42
- package/dist/internal/woodstock/session-entry.js +63 -20
- package/dist/internal/woodstock/session-inbox.js +2 -0
- package/dist/internal/woodstock/session-store.d.ts +104 -24
- package/dist/internal/woodstock/session-store.js +937 -246
- package/dist/internal/woodstock/storage-admission.d.ts +8 -8
- package/dist/internal/woodstock/storage-admission.js +8 -34
- package/dist/internal/woodstock/storage-conformance.d.ts +15 -0
- package/dist/internal/woodstock/storage-conformance.js +489 -0
- package/dist/model-preference.d.ts +2 -2
- package/dist/model-preference.js +2 -2
- package/dist/node.js +1 -1
- package/dist/remote-client.js +8 -1
- package/dist/session-attention.d.ts +8 -1
- package/dist/session-attention.js +6 -1
- package/dist/testing.js +1 -0
- package/docs/api/README.md +18 -16
- package/docs/api/agent-control.md +172 -2
- package/docs/api/client-entry.md +26 -10
- package/docs/api/client.md +153 -37
- package/docs/api/cloudflare.md +76 -23
- package/docs/api/delegation.md +485 -0
- package/docs/api/effect.md +20 -2
- package/docs/api/extensions.md +21 -9
- package/docs/api/fleet.md +149 -122
- package/docs/api/http.md +4 -1
- package/docs/api/index.md +116 -20
- package/docs/api/internal/woodstock/storage-conformance.md +81 -0
- package/docs/api/model-preference.md +17 -0
- package/docs/api/testing.md +16 -4
- package/package.json +26 -14
- package/dist/internal/browser-sessions.d.ts +0 -31
- package/dist/internal/browser-sessions.js +0 -173
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapted from the maintained Pi v0.84.4 coding-agent compaction utilities.
|
|
3
|
+
* Token estimates are pi-ai's own (`@earendil-works/pi-ai/utils/estimate`).
|
|
3
4
|
* The only destination delta is carrying Pi's native Message values directly.
|
|
4
5
|
*/
|
|
5
|
-
import type { AssistantMessage, Message } from "@earendil-works/pi-ai";
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
import type { AssistantMessage, Message, UserMessage } from "@earendil-works/pi-ai";
|
|
7
|
+
/**
|
|
8
|
+
* Pi's compaction entry model message (Pi Durable `compaction.ts`): the
|
|
9
|
+
* summary as the user message the model reads, built once when the entry is
|
|
10
|
+
* appended and stored with it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function compactionSummaryMessage(summary: string, timestamp: number): UserMessage;
|
|
13
|
+
/** The summary text inside a stored compaction model message. */
|
|
14
|
+
export declare function compactionSummaryText(message: UserMessage): string;
|
|
8
15
|
export type FileOperations = {
|
|
9
16
|
edited: Set<string>;
|
|
10
17
|
read: Set<string>;
|
|
@@ -18,24 +25,20 @@ export declare function computeFileLists(fileOps: FileOperations): {
|
|
|
18
25
|
};
|
|
19
26
|
export declare function formatFileOperations(readFiles: readonly string[], modifiedFiles: readonly string[]): string;
|
|
20
27
|
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
28
|
export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* A background compaction starts above the blocking threshold minus
|
|
31
|
+
* `backgroundTokens` and at or below the blocking threshold; past that the
|
|
32
|
+
* blocking check owns it. Token-based only: byte limits are Zookeeper's.
|
|
33
|
+
* Unclamped like Pi Durable generation.ts:322-339 (Levi, 2026-10-17: match Pi); keepRecentTokens gates small windows.
|
|
34
|
+
*/
|
|
35
|
+
export declare function shouldCompactInBackground(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
|
|
35
36
|
export type CompactionSettings = {
|
|
36
37
|
enabled: boolean;
|
|
37
38
|
reserveTokens: number;
|
|
38
39
|
keepRecentTokens: number;
|
|
40
|
+
/** Tokens before the blocking threshold at which a background compaction starts; 0 turns it off. */
|
|
41
|
+
backgroundTokens: number;
|
|
39
42
|
};
|
|
40
43
|
export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
|
|
41
44
|
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.";
|
|
@@ -6,7 +6,11 @@ const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point wa
|
|
|
6
6
|
`;
|
|
7
7
|
const COMPACTION_SUMMARY_SUFFIX = `
|
|
8
8
|
</summary>`;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Pi's compaction entry model message (Pi Durable `compaction.ts`): the
|
|
11
|
+
* summary as the user message the model reads, built once when the entry is
|
|
12
|
+
* appended and stored with it.
|
|
13
|
+
*/
|
|
10
14
|
export function compactionSummaryMessage(summary, timestamp) {
|
|
11
15
|
return {
|
|
12
16
|
role: "user",
|
|
@@ -19,6 +23,16 @@ export function compactionSummaryMessage(summary, timestamp) {
|
|
|
19
23
|
timestamp,
|
|
20
24
|
};
|
|
21
25
|
}
|
|
26
|
+
/** The summary text inside a stored compaction model message. */
|
|
27
|
+
export function compactionSummaryText(message) {
|
|
28
|
+
const block = Array.isArray(message.content) ? message.content[0] : undefined;
|
|
29
|
+
const text = block?.type === "text" ? block.text : undefined;
|
|
30
|
+
if (text === undefined ||
|
|
31
|
+
!text.startsWith(COMPACTION_SUMMARY_PREFIX) ||
|
|
32
|
+
!text.endsWith(COMPACTION_SUMMARY_SUFFIX))
|
|
33
|
+
throw new Error("Compaction model message is not a summary");
|
|
34
|
+
return text.slice(COMPACTION_SUMMARY_PREFIX.length, text.length - COMPACTION_SUMMARY_SUFFIX.length);
|
|
35
|
+
}
|
|
22
36
|
export function createFileOps() {
|
|
23
37
|
return { edited: new Set(), read: new Set(), written: new Set() };
|
|
24
38
|
}
|
|
@@ -76,14 +90,11 @@ function serializeUserMessage(message) {
|
|
|
76
90
|
}
|
|
77
91
|
function serializeAssistantMessage(message) {
|
|
78
92
|
const text = [];
|
|
79
|
-
const thinking = [];
|
|
80
93
|
const calls = [];
|
|
81
94
|
for (const block of message.content) {
|
|
82
95
|
if (block.type === "text")
|
|
83
96
|
text.push(block.text);
|
|
84
|
-
else if (block.type === "
|
|
85
|
-
thinking.push(block.thinking);
|
|
86
|
-
else {
|
|
97
|
+
else if (block.type === "toolCall") {
|
|
87
98
|
const args = Object.entries(block.arguments)
|
|
88
99
|
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
|
|
89
100
|
.join(", ");
|
|
@@ -91,8 +102,6 @@ function serializeAssistantMessage(message) {
|
|
|
91
102
|
}
|
|
92
103
|
}
|
|
93
104
|
const parts = [];
|
|
94
|
-
if (thinking.length > 0)
|
|
95
|
-
parts.push(`[Assistant thinking]: ${thinking.join("\n")}`);
|
|
96
105
|
if (text.length > 0)
|
|
97
106
|
parts.push(`[Assistant]: ${text.join("\n")}`);
|
|
98
107
|
if (calls.length > 0)
|
|
@@ -129,92 +138,29 @@ export function serializeConversation(messages) {
|
|
|
129
138
|
}
|
|
130
139
|
return parts.join("\n\n");
|
|
131
140
|
}
|
|
132
|
-
const ESTIMATED_IMAGE_CHARS = 4800;
|
|
133
|
-
function estimateTextAndImageContentChars(content) {
|
|
134
|
-
if (isString(content))
|
|
135
|
-
return content.length;
|
|
136
|
-
return content.reduce((chars, block) => chars +
|
|
137
|
-
(block.type === "text"
|
|
138
|
-
? (block.text?.length ?? 0)
|
|
139
|
-
: block.type === "image"
|
|
140
|
-
? ESTIMATED_IMAGE_CHARS
|
|
141
|
-
: 0), 0);
|
|
142
|
-
}
|
|
143
|
-
/** Pi's chars/4 estimator, including its image and tool-call constants. */
|
|
144
|
-
export function estimateTokens(message) {
|
|
145
|
-
let chars = 0;
|
|
146
|
-
switch (message.role) {
|
|
147
|
-
case "system":
|
|
148
|
-
return Math.ceil(estimateTextAndImageContentChars(message.content) / 4);
|
|
149
|
-
case "user":
|
|
150
|
-
return Math.ceil(estimateTextAndImageContentChars(message.content) / 4);
|
|
151
|
-
case "assistant":
|
|
152
|
-
for (const block of message.content) {
|
|
153
|
-
if (block.type === "text")
|
|
154
|
-
chars += block.text.length;
|
|
155
|
-
else if (block.type === "thinking")
|
|
156
|
-
chars += block.thinking.length;
|
|
157
|
-
else
|
|
158
|
-
chars += block.name.length + JSON.stringify(block.arguments).length;
|
|
159
|
-
}
|
|
160
|
-
return Math.ceil(chars / 4);
|
|
161
|
-
case "toolResult":
|
|
162
|
-
return Math.ceil(estimateTextAndImageContentChars(message.content) / 4);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
function calculateContextTokens(usage) {
|
|
166
|
-
return (usage.totalTokens ||
|
|
167
|
-
usage.input + usage.output + usage.cacheRead + usage.cacheWrite);
|
|
168
|
-
}
|
|
169
|
-
function validAssistantUsage(message) {
|
|
170
|
-
if (message.role !== "assistant")
|
|
171
|
-
return undefined;
|
|
172
|
-
if (message.stopReason === "aborted" || message.stopReason === "error")
|
|
173
|
-
return undefined;
|
|
174
|
-
return calculateContextTokens(message.usage) > 0 ? message.usage : undefined;
|
|
175
|
-
}
|
|
176
|
-
export function estimateContextTokens(messages) {
|
|
177
|
-
let index = null;
|
|
178
|
-
let usageTokens = 0;
|
|
179
|
-
for (let cursor = messages.length - 1; cursor >= 0; cursor -= 1) {
|
|
180
|
-
const message = messages[cursor];
|
|
181
|
-
if (message === undefined)
|
|
182
|
-
continue;
|
|
183
|
-
const usage = validAssistantUsage(message);
|
|
184
|
-
if (usage !== undefined) {
|
|
185
|
-
index = cursor;
|
|
186
|
-
usageTokens = calculateContextTokens(usage);
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
if (index === null) {
|
|
191
|
-
const tokens = messages.reduce((sum, message) => sum + estimateTokens(message), 0);
|
|
192
|
-
return {
|
|
193
|
-
tokens,
|
|
194
|
-
usageTokens: 0,
|
|
195
|
-
trailingTokens: tokens,
|
|
196
|
-
lastUsageIndex: null,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
const trailingTokens = messages
|
|
200
|
-
.slice(index + 1)
|
|
201
|
-
.reduce((sum, message) => sum + estimateTokens(message), 0);
|
|
202
|
-
return {
|
|
203
|
-
tokens: usageTokens + trailingTokens,
|
|
204
|
-
usageTokens,
|
|
205
|
-
trailingTokens,
|
|
206
|
-
lastUsageIndex: index,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
141
|
export function shouldCompact(contextTokens, contextWindow, settings) {
|
|
210
142
|
if (!settings.enabled)
|
|
211
143
|
return false;
|
|
212
144
|
return contextTokens > contextWindow - settings.reserveTokens;
|
|
213
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* A background compaction starts above the blocking threshold minus
|
|
148
|
+
* `backgroundTokens` and at or below the blocking threshold; past that the
|
|
149
|
+
* blocking check owns it. Token-based only: byte limits are Zookeeper's.
|
|
150
|
+
* Unclamped like Pi Durable generation.ts:322-339 (Levi, 2026-10-17: match Pi); keepRecentTokens gates small windows.
|
|
151
|
+
*/
|
|
152
|
+
export function shouldCompactInBackground(contextTokens, contextWindow, settings) {
|
|
153
|
+
if (!settings.enabled || settings.backgroundTokens === 0)
|
|
154
|
+
return false;
|
|
155
|
+
const blocking = contextWindow - settings.reserveTokens;
|
|
156
|
+
return (contextTokens > blocking - settings.backgroundTokens &&
|
|
157
|
+
!shouldCompact(contextTokens, contextWindow, settings));
|
|
158
|
+
}
|
|
214
159
|
export const DEFAULT_COMPACTION_SETTINGS = {
|
|
215
160
|
enabled: true,
|
|
216
161
|
reserveTokens: 16384,
|
|
217
162
|
keepRecentTokens: 20000,
|
|
163
|
+
backgroundTokens: 32768,
|
|
218
164
|
};
|
|
219
165
|
export 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.
|
|
220
166
|
|
|
@@ -24,6 +24,12 @@ export type SqliteOwner = Omit<WatchdogSqliteOwner, "sql"> & Readonly<{
|
|
|
24
24
|
/** Execute a statement with SQLite scalar bindings. */
|
|
25
25
|
exec: (statement: string, ...bindings: SqliteValue[]) => SqliteCursor;
|
|
26
26
|
}>;
|
|
27
|
+
/**
|
|
28
|
+
* How the host names this store when Woodstock refuses part of it, so its
|
|
29
|
+
* operator knows which store to act on: its Durable Object or its file.
|
|
30
|
+
* Absent is "Woodstock store".
|
|
31
|
+
*/
|
|
32
|
+
storeName?: string;
|
|
27
33
|
}>;
|
|
28
34
|
/** Acquired Pi model and optional explicit Pi stream function. */
|
|
29
35
|
export type ModelAcquisition = Readonly<{
|
|
@@ -31,8 +37,16 @@ export type ModelAcquisition = Readonly<{
|
|
|
31
37
|
model: Model<Api>;
|
|
32
38
|
/** Optional explicit Pi stream function for custom providers and tests. */
|
|
33
39
|
stream?: MuleStreamFunction;
|
|
40
|
+
/** Typed retry decision for this door's exact message; unknown means false. */
|
|
41
|
+
retryable: (message: AssistantMessage) => boolean;
|
|
34
42
|
/** Provider's bounded Retry-After for a clean HTTP refusal, if reported. */
|
|
35
43
|
retryAfterMs?: (message: AssistantMessage) => number | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Describe only a failure message produced by this acquired model door.
|
|
46
|
+
* Return its owner-authored safe reason, never arbitrary SDK error prose.
|
|
47
|
+
* Unknown messages must return undefined.
|
|
48
|
+
*/
|
|
49
|
+
failureReason?: (message: AssistantMessage) => string | undefined;
|
|
36
50
|
/** Release the acquired model resources. */
|
|
37
51
|
release: () => Promise<void>;
|
|
38
52
|
}>;
|
|
@@ -124,18 +138,19 @@ export type RuntimeHost = Readonly<{
|
|
|
124
138
|
}>>;
|
|
125
139
|
}>;
|
|
126
140
|
/**
|
|
127
|
-
* A host's
|
|
128
|
-
*
|
|
129
|
-
*
|
|
141
|
+
* A host's storage-pressure check. Shiitake owns the refusal door and the
|
|
142
|
+
* typed `storage_pressure` error; the host owns the numbers behind it, such
|
|
143
|
+
* as Zookeeper's budget and thresholds. While the check reports pressure,
|
|
144
|
+
* new Sessions and new prompts are refused and admitted work finishes.
|
|
130
145
|
*/
|
|
131
|
-
export type
|
|
146
|
+
export type StoragePressureCheck = Readonly<{
|
|
132
147
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
148
|
+
* Whether the owner is under storage pressure now. Called only for a new
|
|
149
|
+
* Session or a new prompt, never for a replay or for writes that finish
|
|
150
|
+
* admitted work. A throw, such as an unreadable measurement, refuses the
|
|
151
|
+
* new work with that error.
|
|
135
152
|
*/
|
|
136
|
-
|
|
137
|
-
/** The host's budget for those bytes. */
|
|
138
|
-
budgetBytes: number;
|
|
153
|
+
underPressure: () => boolean;
|
|
139
154
|
}>;
|
|
140
155
|
/** One model's compaction token budgets; omitted fields fall back. */
|
|
141
156
|
export type CompactionBudget = Readonly<{
|
|
@@ -143,11 +158,13 @@ export type CompactionBudget = Readonly<{
|
|
|
143
158
|
reserveTokens?: number;
|
|
144
159
|
/** Recent tokens kept verbatim instead of summarized. */
|
|
145
160
|
keepRecentTokens?: number;
|
|
161
|
+
/** Tokens before the blocking threshold at which compaction starts in the background; 0 turns it off (default 32768). */
|
|
162
|
+
backgroundTokens?: number;
|
|
146
163
|
}>;
|
|
147
164
|
/**
|
|
148
165
|
* Pi's compaction settings. Each budget resolves through the exact
|
|
149
166
|
* `provider/modelId` override, then the ordinary value, then Pi's default
|
|
150
|
-
* (16384 reserve, 20000 kept). `enabled` is global and governs automatic
|
|
167
|
+
* (16384 reserve, 20000 kept, 32768 background). `enabled` is global and governs automatic
|
|
151
168
|
* compaction only; a manual compaction always runs.
|
|
152
169
|
*/
|
|
153
170
|
export type CompactionOptions = CompactionBudget & Readonly<{
|
|
@@ -159,13 +176,14 @@ export type CompactionOptions = CompactionBudget & Readonly<{
|
|
|
159
176
|
export type ShiitakeOptions = Readonly<{
|
|
160
177
|
/**
|
|
161
178
|
* The schema of the opaque metadata this host attaches to prompts and
|
|
162
|
-
* operator lines. Shiitake checks, stores and returns it;
|
|
179
|
+
* operator lines. Shiitake checks, stores and returns it; the host owns its
|
|
180
|
+
* grammar and may select a principal identity path for recall authorization.
|
|
163
181
|
*/
|
|
164
182
|
meta?: HostMetaOptions;
|
|
165
183
|
/** Native owner storage and synchronous transaction capability. */
|
|
166
184
|
store: SqliteOwner;
|
|
167
|
-
/**
|
|
168
|
-
|
|
185
|
+
/** Host storage-pressure check; absent never refuses for storage pressure. */
|
|
186
|
+
storagePressure?: StoragePressureCheck;
|
|
169
187
|
/** Compaction budgets; absent uses Pi's defaults for every model. */
|
|
170
188
|
compaction?: CompactionOptions;
|
|
171
189
|
/** Runtime host that schedules the opaque durable reconciler. */
|
|
@@ -202,6 +220,13 @@ export type ToolAccess = Pick<AccessOptions,
|
|
|
202
220
|
* remote client can.
|
|
203
221
|
*/
|
|
204
222
|
export type ShiitakeOwnerAgent = ShiitakeAgent & Readonly<{
|
|
223
|
+
/** Stage a configuration without disturbing an in-flight turn. */
|
|
224
|
+
prepareConfiguration: (configuration: Pick<ShiitakeOptions, "extensions" | "tools">) => Promise<Readonly<{
|
|
225
|
+
/** Publish synchronously; the next turn applies the prepared roster. */
|
|
226
|
+
commit: () => void;
|
|
227
|
+
/** Drop the staged configuration without applying it. */
|
|
228
|
+
discard: () => Promise<void>;
|
|
229
|
+
}>>;
|
|
205
230
|
/** Each Session's delivered tool grants, read at every call. */
|
|
206
231
|
grants: Readonly<{
|
|
207
232
|
/**
|
|
@@ -224,6 +249,8 @@ export type { GrantsDelivery, GrantsDeliveryResult, } from "./woodstock/index.js
|
|
|
224
249
|
export type { HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, } from "./host-meta.js";
|
|
225
250
|
/** Exact accepted-work facts, independent of the current Session branch or page. */
|
|
226
251
|
export type CommandSettlement = Readonly<{
|
|
252
|
+
/** Original Agent sender recorded at acceptance, never the terminal's canceller. */
|
|
253
|
+
acceptedSender: string | null;
|
|
227
254
|
/** Durable terminal and the command that ended the work's opaque metadata. */
|
|
228
255
|
terminal: SessionTerminal;
|
|
229
256
|
/** Last completed assistant text; cancelled work returns no answer. */
|
|
@@ -9,7 +9,7 @@ import { mergeObservationSignals, openObservationKernel, publishOwnerObservation
|
|
|
9
9
|
import { checkHostMeta } from "./host-meta.js";
|
|
10
10
|
import { readSessionActivity } from "./session-status.js";
|
|
11
11
|
import { readSessionContext } from "./session-context.js";
|
|
12
|
-
import { abortSession, cancelSession, branchSession, recordOperatorLine, forkSession, readSession, readSessionModelPreference, readSessionReplayPage, readSessionInbox, createSession, deleteSession, deliverSessionGrants, readSessionGrants, setSessionModelPreference, setSessionTitle, } from "./woodstock/index.js";
|
|
12
|
+
import { abortSession, cancelSession, branchSession, recordOperatorLine, forkSession, readSession, readSessionModelPreference, readSessionReplayPage, readSessionInbox, readLatestPendingPrompt, createSession, deleteSession, deliverSessionGrants, readSessionGrants, setSessionModelPreference, setSessionTitle, readAdmissionIdentity, assertAdmissionIdentity, } from "./woodstock/index.js";
|
|
13
13
|
/** Generic Agent-scope extension controls over the runtime's one call door. */
|
|
14
14
|
function agentExtensions(runtime) {
|
|
15
15
|
return Object.freeze({
|
|
@@ -33,7 +33,7 @@ function withContext(snapshot, context) {
|
|
|
33
33
|
}
|
|
34
34
|
/** Read the baseline and its context use for one Session. */
|
|
35
35
|
function readSessionWithContext(options, runtime, sessionId, input) {
|
|
36
|
-
return currentSessionActivity(options, runtime, sessionId).pipe(Effect.flatMap((activity) => readSession(options.store, sessionId, activity, input, () => runtime.extensions.project(sessionId))), Effect.flatMap((snapshot) => readSessionContext(options,
|
|
36
|
+
return currentSessionActivity(options, runtime, sessionId).pipe(Effect.flatMap((activity) => readSession(options.store, sessionId, activity, input, () => runtime.extensions.project(sessionId))), Effect.flatMap((snapshot) => readSessionContext(options, sessionId).pipe(Effect.map((context) => withContext(snapshot, context)))));
|
|
37
37
|
}
|
|
38
38
|
function sessionOwnerOperation(runtime, sessionId, operation) {
|
|
39
39
|
return runtime.assertOpen.pipe(Effect.andThen(Effect.try({
|
|
@@ -106,14 +106,21 @@ function subscriptionFrames(options, runtime, registration, sessionId, after, de
|
|
|
106
106
|
if (signal === "reset")
|
|
107
107
|
return resetFrame(options, runtime, sessionId, includeSandboxFailures).pipe(Effect.map(continueFromReset));
|
|
108
108
|
return runtime.assertOpen.pipe(Effect.andThen(currentSessionActivity(options, runtime, sessionId)), Effect.flatMap((activity) => {
|
|
109
|
-
if (signal === "progress" &&
|
|
110
|
-
state.delivery === "live" &&
|
|
111
|
-
activity.progress !== null) {
|
|
109
|
+
if (signal === "progress" && state.delivery === "live") {
|
|
112
110
|
return Effect.succeed([
|
|
113
111
|
[
|
|
112
|
+
...(activity.progress === null
|
|
113
|
+
? []
|
|
114
|
+
: [
|
|
115
|
+
{
|
|
116
|
+
kind: "progress",
|
|
117
|
+
progress: activity.progress,
|
|
118
|
+
activity: activity.facts,
|
|
119
|
+
},
|
|
120
|
+
]),
|
|
114
121
|
{
|
|
115
|
-
kind: "
|
|
116
|
-
|
|
122
|
+
kind: "tool_output",
|
|
123
|
+
toolOutput: activity.toolOutput,
|
|
117
124
|
activity: activity.facts,
|
|
118
125
|
},
|
|
119
126
|
],
|
|
@@ -124,7 +131,10 @@ function subscriptionFrames(options, runtime, registration, sessionId, after, de
|
|
|
124
131
|
if (page.kind === "reset")
|
|
125
132
|
return resetFrame(options, runtime, sessionId, includeSandboxFailures).pipe(Effect.map(continueFromReset));
|
|
126
133
|
const latestCursor = page.frames
|
|
127
|
-
.flatMap((frame) => frame.kind === "progress"
|
|
134
|
+
.flatMap((frame) => frame.kind === "progress" ||
|
|
135
|
+
frame.kind === "tool_output"
|
|
136
|
+
? []
|
|
137
|
+
: [frame.cursor])
|
|
128
138
|
.at(-1);
|
|
129
139
|
const next = {
|
|
130
140
|
after: latestCursor ?? state.after,
|
|
@@ -133,7 +143,7 @@ function subscriptionFrames(options, runtime, registration, sessionId, after, de
|
|
|
133
143
|
};
|
|
134
144
|
// Every update carries the context use it settles on.
|
|
135
145
|
return (page.frames.some((frame) => frame.kind === "update")
|
|
136
|
-
? readSessionContext(options,
|
|
146
|
+
? readSessionContext(options, sessionId)
|
|
137
147
|
: Effect.succeed(undefined)).pipe(Effect.map((context) => [
|
|
138
148
|
context === undefined
|
|
139
149
|
? page.frames
|
|
@@ -195,7 +205,10 @@ function admitUserWork(runtime, sessionId, input, meta, mail, reply) {
|
|
|
195
205
|
: Effect.fail(new ShiitakeError("runtime", "unavailable", "Session command was not admitted"))));
|
|
196
206
|
}
|
|
197
207
|
function cancelRequestedSession(options, runtime, sessionId, input, meta) {
|
|
198
|
-
return runtime.assertOpen.pipe(Effect.andThen(
|
|
208
|
+
return runtime.assertOpen.pipe(Effect.andThen(sessionOwnerOperation(runtime, sessionId, (decoded) => {
|
|
209
|
+
if (options.meta?.identityPath !== undefined && meta !== undefined)
|
|
210
|
+
assertAdmissionIdentity(options.store, decoded, input.commandId, readAdmissionIdentity(options.store, options.meta.identityPath, meta));
|
|
211
|
+
})), Effect.andThen(cancelSession(options.store, sessionId, input.commandId, input.onlyIfPending, () => runtime.readQueueHeadTransactional(sessionId), runtime.recordAcceptedCancellation, meta)), Effect.flatMap((cancellation) => cancellation === undefined
|
|
199
212
|
? Effect.succeed({
|
|
200
213
|
status: "too_late",
|
|
201
214
|
commandId: input.commandId,
|
|
@@ -282,6 +295,7 @@ export function openCoordinator(options, runtime) {
|
|
|
282
295
|
}
|
|
283
296
|
return Object.freeze({
|
|
284
297
|
command,
|
|
298
|
+
withdraw: (requestId) => runtime.assertOpen.pipe(Effect.andThen(runtime.door.withdraw(sessionId, requestId))),
|
|
285
299
|
abort: command({ type: "abort" }),
|
|
286
300
|
send: (text, input) => Effect.sync(() => input?.requestId ?? nanoid()).pipe(Effect.flatMap((requestId) => {
|
|
287
301
|
const prompt = {
|
|
@@ -297,6 +311,9 @@ export function openCoordinator(options, runtime) {
|
|
|
297
311
|
: new ShiitakeError(error.boundary, error.cause, error.message, requestId)));
|
|
298
312
|
})),
|
|
299
313
|
cancel: (input) => command({ type: "cancel", ...input }),
|
|
314
|
+
latestPending: sessionOwnerOperation(runtime, sessionId, (decoded) => readLatestPendingPrompt(options.store, decoded, options.meta?.identityPath === undefined || meta === undefined
|
|
315
|
+
? undefined
|
|
316
|
+
: readAdmissionIdentity(options.store, options.meta.identityPath, meta))),
|
|
300
317
|
modelPreference: Object.freeze({
|
|
301
318
|
read: sessionOwnerOperation(runtime, sessionId, (decoded) => readSessionModelPreference(options.store, decoded)),
|
|
302
319
|
set: (preference) => Effect.try({
|
|
@@ -335,6 +352,7 @@ export function openCoordinator(options, runtime) {
|
|
|
335
352
|
});
|
|
336
353
|
return Object.freeze({
|
|
337
354
|
...agent,
|
|
355
|
+
prepareConfiguration: runtime.prepareConfiguration,
|
|
338
356
|
grants: Object.freeze({
|
|
339
357
|
// A synchronous owner write: a delivery lands before any other work
|
|
340
358
|
// can interleave, so the very next tool call reads it.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import type { ShiitakeAgent } from "../client.js";
|
|
3
|
-
import type { ShiitakeEffectAgent, ShiitakeEffectOwner } from "./effect-contracts.js";
|
|
4
|
-
import type { ShiitakeOwnerAgent } from "./contracts.js";
|
|
3
|
+
import type { ShiitakeEffectAgent, ShiitakeEffectOwner, ShiitakeEffectOptions } from "./effect-contracts.js";
|
|
4
|
+
import type { ShiitakeOwnerAgent, ShiitakeOptions } from "./contracts.js";
|
|
5
|
+
/** Adapt a Promise tool configuration for both initial open and reload. */
|
|
6
|
+
export declare function liftTools(toolSource: NonNullable<ShiitakeOptions["tools"]>): NonNullable<ShiitakeEffectOptions["tools"]>;
|
|
5
7
|
/** Run one Shiitake Effect through its sole Promise error adaptation. */
|
|
6
8
|
export declare function runShiitakePromise<A, E>(effect: Effect.Effect<A, E>): Promise<A>;
|
|
7
9
|
/** Adapt one canonical Effect agent to Shiitake's ordinary web interface. */
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { nanoid } from "@fungi.computer/cairn";
|
|
2
|
+
import { fromTypedSandbox } from "@fungi.computer/mycelium/effect";
|
|
2
3
|
import * as Cause from "effect/Cause";
|
|
3
4
|
import * as Effect from "effect/Effect";
|
|
4
5
|
import * as Exit from "effect/Exit";
|
|
5
6
|
import * as Option from "effect/Option";
|
|
6
7
|
import * as Stream from "effect/Stream";
|
|
7
8
|
import { ShiitakeError } from "../errors.js";
|
|
9
|
+
/** Adapt a Promise tool configuration for both initial open and reload. */
|
|
10
|
+
export function liftTools(toolSource) {
|
|
11
|
+
const tools = {
|
|
12
|
+
modules: toolSource.modules ?? [],
|
|
13
|
+
sources: toolSource.sources ?? [],
|
|
14
|
+
sandbox: fromTypedSandbox(toolSource.sandbox),
|
|
15
|
+
access: toolSource.access ?? {},
|
|
16
|
+
};
|
|
17
|
+
return toolSource.execution === undefined
|
|
18
|
+
? tools
|
|
19
|
+
: { ...tools, execution: toolSource.execution };
|
|
20
|
+
}
|
|
8
21
|
/** Run one Shiitake Effect through its sole Promise error adaptation. */
|
|
9
22
|
export function runShiitakePromise(effect) {
|
|
10
23
|
return Effect.runPromiseExit(effect).then((exit) => {
|
|
@@ -47,9 +60,11 @@ function sessionFacade(address, session) {
|
|
|
47
60
|
return Object.freeze({
|
|
48
61
|
address,
|
|
49
62
|
command,
|
|
63
|
+
withdraw: (requestId) => runShiitakePromise(session.withdraw(requestId)),
|
|
50
64
|
abort: () => runShiitakePromise(session.abort),
|
|
51
65
|
send: (text, options) => runShiitakePromise(session.send(text, options)),
|
|
52
66
|
cancel: (input) => runShiitakePromise(session.cancel(input)),
|
|
67
|
+
latestPending: () => runShiitakePromise(session.latestPending),
|
|
53
68
|
modelPreference: Object.freeze({
|
|
54
69
|
read: () => runShiitakePromise(session.modelPreference.read),
|
|
55
70
|
set: (preference) => runShiitakePromise(session.modelPreference.set(preference)),
|
|
@@ -93,6 +108,20 @@ export function toPromiseOwner(owner) {
|
|
|
93
108
|
const address = `local:${nanoid()}`;
|
|
94
109
|
return Object.freeze({
|
|
95
110
|
...toPromiseAgent(owner, address),
|
|
111
|
+
prepareConfiguration: async (configuration) => {
|
|
112
|
+
const next = {
|
|
113
|
+
extensions: configuration.extensions,
|
|
114
|
+
};
|
|
115
|
+
if (configuration.tools !== undefined)
|
|
116
|
+
Object.assign(next, { tools: liftTools(configuration.tools) });
|
|
117
|
+
const prepared = await runShiitakePromise(owner.prepareConfiguration(next));
|
|
118
|
+
return {
|
|
119
|
+
commit: () => {
|
|
120
|
+
runShiitakeSync(prepared.commit);
|
|
121
|
+
},
|
|
122
|
+
discard: () => runShiitakePromise(prepared.discard),
|
|
123
|
+
};
|
|
124
|
+
},
|
|
96
125
|
grants: Object.freeze({
|
|
97
126
|
deliver: (sessionId, delivery) => runShiitakeSync(owner.grants.deliver(sessionId, delivery)),
|
|
98
127
|
read: (sessionId) => runShiitakeSync(owner.grants.read(sessionId)),
|
|
@@ -9,10 +9,10 @@ import * as Stream from "effect/Stream";
|
|
|
9
9
|
import type * as Scope from "effect/Scope";
|
|
10
10
|
import type { WatchdogError } from "@fungi.computer/watchdog";
|
|
11
11
|
import type { WatchdogExecutionDecision, WatchdogRunningJob, ConcurrentWatchdogRuntimeEffect } from "@fungi.computer/watchdog/effect";
|
|
12
|
-
import type { AbortCommand, Accepted, BranchCommand, OperatorCommand, TitleCommand, CancelCommand, CancelResult, ForkCommand, ForkedSession, CompactCommand, PromptCommand, PromptDelivery, ReadOptions, SessionCommand, SessionFrame, SessionSnapshot, SessionSummaryPage, SubscribeOptions, WatchOptions } from "../client.js";
|
|
12
|
+
import type { AbortCommand, Accepted, BranchCommand, OperatorCommand, TitleCommand, CancelCommand, CancelResult, ForkCommand, ForkedSession, CompactCommand, PromptCommand, PromptDelivery, ReadOptions, SessionCommand, SessionFrame, SessionSnapshot, SessionPendingPrompt, SessionSummaryPage, SubscribeOptions, WatchOptions } from "../client.js";
|
|
13
13
|
import type { ShiitakeError } from "../errors.js";
|
|
14
14
|
import type { ExtensionStatusV0, Json } from "../extensions.js";
|
|
15
|
-
import type { InstructionSnapshot, ModelDescription, RuntimePendingWork, SqliteOwner,
|
|
15
|
+
import type { InstructionSnapshot, ModelDescription, RuntimePendingWork, SqliteOwner, StoragePressureCheck, CompactionOptions } from "./contracts.js";
|
|
16
16
|
import type { ModelPreference } from "./model-preference.js";
|
|
17
17
|
/** Effect-native acquired Pi model and finalizer. */
|
|
18
18
|
export type ModelAcquisitionEffect = Readonly<{
|
|
@@ -22,6 +22,10 @@ export type ModelAcquisitionEffect = Readonly<{
|
|
|
22
22
|
stream?: MuleStreamFunction;
|
|
23
23
|
/** Provider’s bounded Retry-After for a clean HTTP refusal, if reported. */
|
|
24
24
|
retryAfterMs?: (message: AssistantMessage) => number | undefined;
|
|
25
|
+
/** Typed retry decision for this door's exact message; unknown means false. */
|
|
26
|
+
retryable: (message: AssistantMessage) => boolean;
|
|
27
|
+
/** Owner-authored reason for this door's failure message, never SDK prose. */
|
|
28
|
+
failureReason?: (message: AssistantMessage) => string | undefined;
|
|
25
29
|
/** Effect finalizer for acquired model resources. */
|
|
26
30
|
release: Effect.Effect<void, ShiitakeError>;
|
|
27
31
|
}>;
|
|
@@ -91,8 +95,8 @@ export type ShiitakeEffectOptions = Readonly<{
|
|
|
91
95
|
meta?: HostMetaOptions;
|
|
92
96
|
/** Native owner storage and synchronous transaction capability. */
|
|
93
97
|
store: SqliteOwner;
|
|
94
|
-
/**
|
|
95
|
-
|
|
98
|
+
/** Host storage-pressure check; absent never refuses for storage pressure. */
|
|
99
|
+
storagePressure?: StoragePressureCheck;
|
|
96
100
|
/** Compaction budgets; absent uses Pi's defaults for every model. */
|
|
97
101
|
compaction?: CompactionOptions;
|
|
98
102
|
/** One host lane in Shiitake's existing Watchdog, opened before recovery. */
|
|
@@ -161,6 +165,8 @@ export type ShiitakeEffectSendOptions = Readonly<{
|
|
|
161
165
|
export type ShiitakeEffectSession = Readonly<{
|
|
162
166
|
/** Execute one retained type-discriminated Session command. */
|
|
163
167
|
command: ShiitakeEffectCommandHandler;
|
|
168
|
+
/** Resolve admission or durably fence a request that has not been admitted. */
|
|
169
|
+
withdraw: (requestId: string) => Effect.Effect<import("../client.js").WithdrawResult, ShiitakeError>;
|
|
164
170
|
/** Cancel the running turn and all queued prompts, retaining their text. */
|
|
165
171
|
abort: Effect.Effect<void, ShiitakeError>;
|
|
166
172
|
/** Send text with one generated or explicitly supplied request identity. */
|
|
@@ -170,6 +176,8 @@ export type ShiitakeEffectSession = Readonly<{
|
|
|
170
176
|
commandId: string;
|
|
171
177
|
onlyIfPending?: boolean;
|
|
172
178
|
}) => Effect.Effect<CancelResult, ShiitakeError>;
|
|
179
|
+
/** Newest pending prompt scoped by the host's bound identity. */
|
|
180
|
+
latestPending: Effect.Effect<SessionPendingPrompt | null, ShiitakeError>;
|
|
173
181
|
/** Address one rostered extension's Session-scope commands. */
|
|
174
182
|
extension: (extensionId: string) => ShiitakeEffectSessionExtension;
|
|
175
183
|
/** Lineage-local selection used when the next run acquires its model. */
|
|
@@ -221,6 +229,11 @@ export type ShiitakeEffectAgent = Readonly<{
|
|
|
221
229
|
* remote client can.
|
|
222
230
|
*/
|
|
223
231
|
export type ShiitakeEffectOwner = ShiitakeEffectAgent & Readonly<{
|
|
232
|
+
/** Stage a configuration without disturbing an in-flight turn. */
|
|
233
|
+
prepareConfiguration: (configuration: Pick<ShiitakeEffectOptions, "extensions" | "tools">) => Effect.Effect<Readonly<{
|
|
234
|
+
commit: Effect.Effect<void, ShiitakeError>;
|
|
235
|
+
discard: Effect.Effect<void>;
|
|
236
|
+
}>, ShiitakeError>;
|
|
224
237
|
/** Each Session's delivered tool grants, read at every call. */
|
|
225
238
|
grants: Readonly<{
|
|
226
239
|
/** Store one versioned delivery; versions only move forward. */
|
|
@@ -11,6 +11,11 @@ import type { AdmittedExtensionV0 } from "./extension-roster.js";
|
|
|
11
11
|
export declare const coreConsumer = "core";
|
|
12
12
|
/** Core's generic view of rostered v1 extensions. */
|
|
13
13
|
export type ExtensionRuntime = Readonly<{
|
|
14
|
+
/** Prepare additions without replacing any live member; apply only between turns. */
|
|
15
|
+
stage: (admitted: readonly AdmittedExtensionV0[]) => Effect.Effect<Readonly<{
|
|
16
|
+
apply: Effect.Effect<void, ShiitakeError>;
|
|
17
|
+
discard: Effect.Effect<void>;
|
|
18
|
+
}>, ShiitakeError>;
|
|
14
19
|
/** Running extensions that participate in runs, with their fingerprints. */
|
|
15
20
|
runParticipants: () => readonly (readonly [
|
|
16
21
|
ExtensionId,
|