@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.
- package/LICENSE +21 -0
- package/README.md +454 -2
- package/dist/agar-extension.d.ts +9 -0
- package/dist/agar-extension.js +38 -0
- package/dist/agent-control.d.ts +72 -0
- package/dist/agent-control.js +431 -0
- package/dist/client-entry.d.ts +211 -0
- package/dist/client-entry.js +101 -0
- package/dist/client-wire.d.ts +199 -0
- package/dist/client-wire.js +271 -0
- package/dist/client.d.ts +439 -0
- package/dist/client.js +4 -0
- package/dist/cloudflare.d.ts +220 -0
- package/dist/cloudflare.js +531 -0
- package/dist/effect.d.ts +42 -0
- package/dist/effect.js +20 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.js +81 -0
- package/dist/extension-client.d.ts +38 -0
- package/dist/extension-client.js +56 -0
- package/dist/extension-contract.d.ts +73 -0
- package/dist/extension-contract.js +11 -0
- package/dist/extensions.d.ts +589 -0
- package/dist/extensions.js +263 -0
- package/dist/fleet.d.ts +297 -0
- package/dist/fleet.js +515 -0
- package/dist/hosted.d.ts +133 -0
- package/dist/hosted.js +265 -0
- package/dist/http-routes.d.ts +772 -0
- package/dist/http-routes.js +242 -0
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +13 -0
- package/dist/internal/accepted-run-executor.d.ts +23 -0
- package/dist/internal/accepted-run-executor.js +129 -0
- package/dist/internal/admission-door.d.ts +75 -0
- package/dist/internal/admission-door.js +105 -0
- package/dist/internal/agent-close.d.ts +4 -0
- package/dist/internal/agent-close.js +42 -0
- package/dist/internal/browser-sessions.d.ts +31 -0
- package/dist/internal/browser-sessions.js +173 -0
- package/dist/internal/client-boundary.d.ts +262 -0
- package/dist/internal/client-boundary.js +202 -0
- package/dist/internal/compaction/compaction.d.ts +54 -0
- package/dist/internal/compaction/compaction.js +292 -0
- package/dist/internal/compaction/session-compaction.d.ts +69 -0
- package/dist/internal/compaction/session-compaction.js +330 -0
- package/dist/internal/compaction/settings.d.ts +11 -0
- package/dist/internal/compaction/settings.js +40 -0
- package/dist/internal/compaction/utils.d.ts +45 -0
- package/dist/internal/compaction/utils.js +319 -0
- package/dist/internal/contracts.d.ts +235 -0
- package/dist/internal/contracts.js +1 -0
- package/dist/internal/coordinator.d.ts +18 -0
- package/dist/internal/coordinator.js +362 -0
- package/dist/internal/document-codec.d.ts +11 -0
- package/dist/internal/document-codec.js +39 -0
- package/dist/internal/effect-client.d.ts +13 -0
- package/dist/internal/effect-client.js +102 -0
- package/dist/internal/effect-contracts.d.ts +236 -0
- package/dist/internal/effect-contracts.js +2 -0
- package/dist/internal/extension-command-registry.d.ts +11 -0
- package/dist/internal/extension-command-registry.js +29 -0
- package/dist/internal/extension-command-tools.d.ts +17 -0
- package/dist/internal/extension-command-tools.js +50 -0
- package/dist/internal/extension-context.d.ts +25 -0
- package/dist/internal/extension-context.js +35 -0
- package/dist/internal/extension-definitions.d.ts +5 -0
- package/dist/internal/extension-definitions.js +6 -0
- package/dist/internal/extension-hook.d.ts +25 -0
- package/dist/internal/extension-hook.js +46 -0
- package/dist/internal/extension-roster.d.ts +23 -0
- package/dist/internal/extension-roster.js +228 -0
- package/dist/internal/extension-runtime.d.ts +63 -0
- package/dist/internal/extension-runtime.js +1004 -0
- package/dist/internal/failure.d.ts +2 -0
- package/dist/internal/failure.js +9 -0
- package/dist/internal/host-meta.d.ts +41 -0
- package/dist/internal/host-meta.js +39 -0
- package/dist/internal/json-value.d.ts +4 -0
- package/dist/internal/json-value.js +3 -0
- package/dist/internal/model-acquisition.d.ts +21 -0
- package/dist/internal/model-acquisition.js +98 -0
- package/dist/internal/model-preference.d.ts +21 -0
- package/dist/internal/model-preference.js +20 -0
- package/dist/internal/observation-kernel.d.ts +28 -0
- package/dist/internal/observation-kernel.js +169 -0
- package/dist/internal/promise-facade.d.ts +18 -0
- package/dist/internal/promise-facade.js +172 -0
- package/dist/internal/run-scope.d.ts +27 -0
- package/dist/internal/run-scope.js +214 -0
- package/dist/internal/runtime-coordinator.d.ts +34 -0
- package/dist/internal/runtime-coordinator.js +233 -0
- package/dist/internal/runtime-core.d.ts +19 -0
- package/dist/internal/runtime-core.js +39 -0
- package/dist/internal/runtime-reconciliation.d.ts +18 -0
- package/dist/internal/runtime-reconciliation.js +162 -0
- package/dist/internal/session-context.d.ts +11 -0
- package/dist/internal/session-context.js +67 -0
- package/dist/internal/session-status.d.ts +10 -0
- package/dist/internal/session-status.js +52 -0
- package/dist/internal/woodstock/agent-reply.d.ts +25 -0
- package/dist/internal/woodstock/agent-reply.js +67 -0
- package/dist/internal/woodstock/client-projection.d.ts +29 -0
- package/dist/internal/woodstock/client-projection.js +512 -0
- package/dist/internal/woodstock/compaction-activity.d.ts +17 -0
- package/dist/internal/woodstock/compaction-activity.js +32 -0
- package/dist/internal/woodstock/cursor.d.ts +9 -0
- package/dist/internal/woodstock/cursor.js +44 -0
- package/dist/internal/woodstock/extension-store.d.ts +72 -0
- package/dist/internal/woodstock/extension-store.js +262 -0
- package/dist/internal/woodstock/fleet-store.d.ts +32 -0
- package/dist/internal/woodstock/fleet-store.js +93 -0
- package/dist/internal/woodstock/identities.d.ts +6 -0
- package/dist/internal/woodstock/identities.js +11 -0
- package/dist/internal/woodstock/index.d.ts +170 -0
- package/dist/internal/woodstock/index.js +546 -0
- package/dist/internal/woodstock/progress.d.ts +32 -0
- package/dist/internal/woodstock/progress.js +89 -0
- package/dist/internal/woodstock/run-recovery.d.ts +8 -0
- package/dist/internal/woodstock/run-recovery.js +95 -0
- package/dist/internal/woodstock/session-attention.d.ts +16 -0
- package/dist/internal/woodstock/session-attention.js +88 -0
- package/dist/internal/woodstock/session-documents.d.ts +18 -0
- package/dist/internal/woodstock/session-documents.js +51 -0
- package/dist/internal/woodstock/session-entry.d.ts +478 -0
- package/dist/internal/woodstock/session-entry.js +484 -0
- package/dist/internal/woodstock/session-grants.d.ts +24 -0
- package/dist/internal/woodstock/session-grants.js +84 -0
- package/dist/internal/woodstock/session-inbox.d.ts +56 -0
- package/dist/internal/woodstock/session-inbox.js +307 -0
- package/dist/internal/woodstock/session-store.d.ts +432 -0
- package/dist/internal/woodstock/session-store.js +2006 -0
- package/dist/internal/woodstock/storage-admission.d.ts +14 -0
- package/dist/internal/woodstock/storage-admission.js +44 -0
- package/dist/model-preference.d.ts +20 -0
- package/dist/model-preference.js +30 -0
- package/dist/node.d.ts +64 -0
- package/dist/node.js +256 -0
- package/dist/remote-client.d.ts +11 -0
- package/dist/remote-client.js +298 -0
- package/dist/session-attention.d.ts +35 -0
- package/dist/session-attention.js +22 -0
- package/dist/session-documents.d.ts +28 -0
- package/dist/session-documents.js +1 -0
- package/dist/testing.d.ts +54 -0
- package/dist/testing.js +215 -0
- package/docs/api/README.md +26 -0
- package/docs/api/agar-extension.md +40 -0
- package/docs/api/agent-control.md +151 -0
- package/docs/api/client-entry.md +344 -0
- package/docs/api/client.md +1112 -0
- package/docs/api/cloudflare.md +500 -0
- package/docs/api/effect.md +904 -0
- package/docs/api/extensions.md +2003 -0
- package/docs/api/fleet.md +706 -0
- package/docs/api/hosted.md +382 -0
- package/docs/api/http.md +111 -0
- package/docs/api/index.md +1116 -0
- package/docs/api/model-preference.md +151 -0
- package/docs/api/node.md +142 -0
- package/docs/api/testing.md +250 -0
- package/package.json +130 -3
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { reportCatchFailure } from "./internal/failure.js";
|
|
2
|
+
import { parseModelPreference } from "./internal/model-preference.js";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { streamSSE, } from "hono/streaming";
|
|
5
|
+
import { validator } from "hono/validator";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import { ShiitakeError, toShiitakeErrorWire } from "./errors.js";
|
|
8
|
+
import { JsonValueSchema } from "./internal/json-value.js";
|
|
9
|
+
import { decodeClientIdentity, decodeClientEventsQuery, decodeClientReadQuery, decodeClientSessionCommandRequest, decodeClientSessionCreationRequest, decodeClientPageCursor, decodeClientSessionId, } from "./internal/client-boundary.js";
|
|
10
|
+
const decodeJsonValue = Schema.decodeUnknownResult(Schema.fromJsonString(JsonValueSchema));
|
|
11
|
+
/** One encoded extension output; its contract, not Hono, types the value. */
|
|
12
|
+
function jsonResponse(value) {
|
|
13
|
+
return new Response(JSON.stringify(value), {
|
|
14
|
+
status: 200,
|
|
15
|
+
headers: { "Content-Type": "application/json" },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** The raw request body text; JSON decoding belongs to `decodeJsonValue`. */
|
|
19
|
+
function readJsonBody(request) {
|
|
20
|
+
return request.text();
|
|
21
|
+
}
|
|
22
|
+
function invalidRequest(message, cause) {
|
|
23
|
+
return new ShiitakeError("contract", "invalid", message, undefined, cause);
|
|
24
|
+
}
|
|
25
|
+
/** Build the exact public read options named by a decoded read query. */
|
|
26
|
+
function readOptionsFromQuery(query) {
|
|
27
|
+
let options = {};
|
|
28
|
+
if (query.limit !== undefined)
|
|
29
|
+
options = { ...options, limit: query.limit };
|
|
30
|
+
if (query.before !== undefined)
|
|
31
|
+
options = { ...options, before: query.before };
|
|
32
|
+
if (query.includeSandboxFailures === "true")
|
|
33
|
+
options = { ...options, includeSandboxFailures: true };
|
|
34
|
+
return options;
|
|
35
|
+
}
|
|
36
|
+
function closeOnce(close) {
|
|
37
|
+
let closed = false;
|
|
38
|
+
return async () => {
|
|
39
|
+
if (closed)
|
|
40
|
+
return;
|
|
41
|
+
closed = true;
|
|
42
|
+
await close();
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function openSessionEvents(session, request) {
|
|
46
|
+
if (request.subscribe) {
|
|
47
|
+
let options = {};
|
|
48
|
+
if (request.after !== undefined)
|
|
49
|
+
options = { ...options, after: request.after };
|
|
50
|
+
if (request.includeSandboxFailures === true)
|
|
51
|
+
options = { ...options, includeSandboxFailures: true };
|
|
52
|
+
const frames = session.subscribe(options);
|
|
53
|
+
const iterator = frames[Symbol.asyncIterator]();
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
snapshot: undefined,
|
|
56
|
+
iterator,
|
|
57
|
+
close: async () => {
|
|
58
|
+
await iterator.return?.();
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
let options = {};
|
|
63
|
+
if (request.limit !== undefined)
|
|
64
|
+
options = { ...options, limit: request.limit };
|
|
65
|
+
if (request.includeSandboxFailures === true)
|
|
66
|
+
options = { ...options, includeSandboxFailures: true };
|
|
67
|
+
const watch = await session.watch(options);
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
snapshot: watch.snapshot,
|
|
70
|
+
iterator: watch.changes[Symbol.asyncIterator](),
|
|
71
|
+
close: watch.close,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function snapshotEvent(snapshot) {
|
|
75
|
+
const event = {
|
|
76
|
+
event: "snapshot",
|
|
77
|
+
data: JSON.stringify(snapshot),
|
|
78
|
+
};
|
|
79
|
+
return snapshot.cursor === null ? event : { ...event, id: snapshot.cursor };
|
|
80
|
+
}
|
|
81
|
+
function frameEvent(frame) {
|
|
82
|
+
const id = frame.kind === "update"
|
|
83
|
+
? (frame.cursor ?? "")
|
|
84
|
+
: frame.kind === "reset"
|
|
85
|
+
? (frame.cursor ?? "")
|
|
86
|
+
: undefined;
|
|
87
|
+
const event = { event: "frame", data: JSON.stringify(frame) };
|
|
88
|
+
return id === undefined ? event : { ...event, id };
|
|
89
|
+
}
|
|
90
|
+
async function writeSessionEvents(stream, session, request) {
|
|
91
|
+
let close = () => Promise.resolve();
|
|
92
|
+
try {
|
|
93
|
+
const opened = await openSessionEvents(session, request);
|
|
94
|
+
close = closeOnce(opened.close);
|
|
95
|
+
stream.onAbort(close);
|
|
96
|
+
if (!stream.aborted && opened.snapshot !== undefined)
|
|
97
|
+
await stream.writeSSE(snapshotEvent(opened.snapshot));
|
|
98
|
+
while (!stream.aborted) {
|
|
99
|
+
const next = await opened.iterator.next();
|
|
100
|
+
if (next.done === true)
|
|
101
|
+
break;
|
|
102
|
+
await stream.writeSSE(frameEvent(next.value));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
reportCatchFailure("session.events", error);
|
|
107
|
+
if (!stream.aborted)
|
|
108
|
+
await stream.writeSSE({
|
|
109
|
+
event: "runtime-error",
|
|
110
|
+
data: JSON.stringify(toShiitakeErrorWire(error)),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
await close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Build Shiitake's typed HTTP routes around an existing Session owner. */
|
|
118
|
+
export function createShiitakeHttpRoutes(resolveSession, listSessions, createSession, deleteSession, resolveExtensions) {
|
|
119
|
+
return new Hono()
|
|
120
|
+
.get("/extensions", async (context) => context.json(await (await resolveExtensions()).list(), 200))
|
|
121
|
+
.post("/extensions/:extensionId/resume", async (context) => {
|
|
122
|
+
const id = decodeClientIdentity(context.req.param("extensionId"));
|
|
123
|
+
if (id._tag === "Failure")
|
|
124
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Extension id is invalid")), 400);
|
|
125
|
+
return context.json(await (await resolveExtensions()).resume(id.success), 200);
|
|
126
|
+
})
|
|
127
|
+
.post("/extensions/:extensionId/commands/:command", async (context) => {
|
|
128
|
+
const id = decodeClientIdentity(context.req.param("extensionId"));
|
|
129
|
+
const command = decodeClientIdentity(context.req.param("command"));
|
|
130
|
+
const input = decodeJsonValue(await readJsonBody(context.req.raw));
|
|
131
|
+
if (id._tag === "Failure" ||
|
|
132
|
+
command._tag === "Failure" ||
|
|
133
|
+
input._tag === "Failure")
|
|
134
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Extension command is invalid")), 400);
|
|
135
|
+
return jsonResponse(await (await resolveExtensions()).call(id.success, command.success, input.success));
|
|
136
|
+
})
|
|
137
|
+
.post("/sessions/:sessionId/extensions/:extensionId/commands/:command", async (context) => {
|
|
138
|
+
const sessionId = decodeClientSessionId(context.req.param("sessionId"));
|
|
139
|
+
const id = decodeClientIdentity(context.req.param("extensionId"));
|
|
140
|
+
const command = decodeClientIdentity(context.req.param("command"));
|
|
141
|
+
const input = decodeJsonValue(await readJsonBody(context.req.raw));
|
|
142
|
+
if (id._tag === "Failure" ||
|
|
143
|
+
command._tag === "Failure" ||
|
|
144
|
+
input._tag === "Failure")
|
|
145
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Extension command is invalid")), 400);
|
|
146
|
+
const session = await resolveSession(sessionId, context.get("shiitakeMeta"));
|
|
147
|
+
return jsonResponse(await session
|
|
148
|
+
.extension(id.success)
|
|
149
|
+
.call(command.success, input.success));
|
|
150
|
+
})
|
|
151
|
+
.post("/sessions", validator("json", (value, context) => {
|
|
152
|
+
const decoded = decodeClientSessionCreationRequest(value);
|
|
153
|
+
if (decoded._tag === "Failure")
|
|
154
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Session creation request is invalid")), 400);
|
|
155
|
+
return decoded.success;
|
|
156
|
+
}), async (context) => context.json({
|
|
157
|
+
sessionId: await createSession(context.req.valid("json").requestId),
|
|
158
|
+
}, 201))
|
|
159
|
+
.delete("/sessions/:sessionId", async (context) => context.json({
|
|
160
|
+
deleted: await deleteSession(decodeClientSessionId(context.req.param("sessionId"))),
|
|
161
|
+
}, 200))
|
|
162
|
+
.get("/sessions", async (context) => {
|
|
163
|
+
const after = context.req.query("after");
|
|
164
|
+
let cursor;
|
|
165
|
+
try {
|
|
166
|
+
cursor =
|
|
167
|
+
after === undefined ? undefined : decodeClientPageCursor(after);
|
|
168
|
+
}
|
|
169
|
+
catch (cause) {
|
|
170
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Session cursor is invalid", cause)), 400);
|
|
171
|
+
}
|
|
172
|
+
const page = await listSessions(cursor);
|
|
173
|
+
return context.json(page, 200);
|
|
174
|
+
})
|
|
175
|
+
.get("/sessions/:sessionId/model-preference", async (context) => {
|
|
176
|
+
const session = await resolveSession(decodeClientSessionId(context.req.param("sessionId")), context.get("shiitakeMeta"));
|
|
177
|
+
return context.json(await session.modelPreference.read(), 200);
|
|
178
|
+
})
|
|
179
|
+
.put("/sessions/:sessionId/model-preference", validator("json", (value, context) => {
|
|
180
|
+
try {
|
|
181
|
+
return parseModelPreference(value);
|
|
182
|
+
}
|
|
183
|
+
catch (cause) {
|
|
184
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Model preference is invalid", cause)), 400);
|
|
185
|
+
}
|
|
186
|
+
}), async (context) => {
|
|
187
|
+
const session = await resolveSession(decodeClientSessionId(context.req.param("sessionId")), context.get("shiitakeMeta"));
|
|
188
|
+
await session.modelPreference.set(context.req.valid("json"));
|
|
189
|
+
return context.body(null, 204);
|
|
190
|
+
})
|
|
191
|
+
.post("/sessions/:sessionId/command", validator("json", (value, context) => {
|
|
192
|
+
const decoded = decodeClientSessionCommandRequest(value);
|
|
193
|
+
if (decoded._tag === "Failure")
|
|
194
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Command is invalid")), 400);
|
|
195
|
+
return decoded.success;
|
|
196
|
+
}), async (context) => {
|
|
197
|
+
const sessionId = decodeClientSessionId(context.req.param("sessionId"));
|
|
198
|
+
const session = await resolveSession(sessionId, context.get("shiitakeMeta"));
|
|
199
|
+
const command = context.req.valid("json").command;
|
|
200
|
+
if (command.type === "abort" ||
|
|
201
|
+
command.type === "branch" ||
|
|
202
|
+
command.type === "operator" ||
|
|
203
|
+
command.type === "title") {
|
|
204
|
+
await session.command(command);
|
|
205
|
+
return context.body(null, 204);
|
|
206
|
+
}
|
|
207
|
+
return context.json(await session.command(command), 200);
|
|
208
|
+
})
|
|
209
|
+
.get("/sessions/:sessionId", validator("query", (value, context) => {
|
|
210
|
+
const decoded = decodeClientReadQuery(value);
|
|
211
|
+
if (decoded._tag === "Failure")
|
|
212
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Read options are invalid")), 400);
|
|
213
|
+
return decoded.success;
|
|
214
|
+
}), async (context) => {
|
|
215
|
+
const sessionId = decodeClientSessionId(context.req.param("sessionId"));
|
|
216
|
+
const session = await resolveSession(sessionId, context.get("shiitakeMeta"));
|
|
217
|
+
const query = context.req.valid("query");
|
|
218
|
+
return context.json(await session.read(readOptionsFromQuery(query)), 200);
|
|
219
|
+
})
|
|
220
|
+
.get("/sessions/:sessionId/events", validator("query", (value, context) => {
|
|
221
|
+
const decoded = decodeClientEventsQuery(value);
|
|
222
|
+
if (decoded._tag === "Failure")
|
|
223
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Event options are invalid")), 400);
|
|
224
|
+
return decoded.success;
|
|
225
|
+
}), async (context) => {
|
|
226
|
+
const sessionId = decodeClientSessionId(context.req.param("sessionId"));
|
|
227
|
+
const session = await resolveSession(sessionId, context.get("shiitakeMeta"));
|
|
228
|
+
const query = context.req.valid("query");
|
|
229
|
+
const lastEventId = context.req.header("Last-Event-ID");
|
|
230
|
+
const after = lastEventId ?? query.after;
|
|
231
|
+
const shouldSubscribe = lastEventId !== undefined || query.mode === "subscribe";
|
|
232
|
+
context.header("Content-Encoding", "Identity");
|
|
233
|
+
context.header("Cache-Control", "no-cache");
|
|
234
|
+
return streamSSE(context, (stream) => writeSessionEvents(stream, session, {
|
|
235
|
+
after,
|
|
236
|
+
limit: query.limit,
|
|
237
|
+
subscribe: shouldSubscribe,
|
|
238
|
+
includeSandboxFailures: query.includeSandboxFailures === "true",
|
|
239
|
+
}));
|
|
240
|
+
})
|
|
241
|
+
.onError((error, context) => context.json(toShiitakeErrorWire(error), 503));
|
|
242
|
+
}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createShiitakeHttpRoutes, type ShiitakeHttpEnv, type ShiitakeSessionResolver, } from "./http-routes.js";
|
package/dist/http.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createShiitakeHttpRoutes, } from "./http-routes.js";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CommandSettlement } from "./internal/contracts.js";
|
|
2
|
+
import type { Json as HostJson } from "./extensions.js";
|
|
3
|
+
import type { BirdDogMail } from "@fungi.computer/bird-dog";
|
|
4
|
+
import type { VerifiedAgentReply } from "./internal/woodstock/index.js";
|
|
5
|
+
import type { BirdDogEffect } from "@fungi.computer/bird-dog/effect";
|
|
6
|
+
import type { Accepted } from "./client.js";
|
|
7
|
+
import type { ShiitakeOptions, ShiitakeOwnerAgent } from "./internal/contracts.js";
|
|
8
|
+
export type { AbortCommand, AgentExtensionControls, BranchCommand, CompactCommand, CancelCommand, CancelResult, Accepted, ForkCommand, ForkedSession, PromptCommand, PromptDelivery, SendOptions, ReadOptions, SessionCommand, SessionCommandHandler, SessionExtensionControls, SessionExtensionSlots, SessionFrame, SessionMessage, SessionProgress, SessionContext, SessionSnapshot, SessionInboxState, SessionSummary, SessionSummaryPage, SessionSummaryRow, SessionActivity, AgentRunningWork, SessionTerminal, SessionCompactionMarker, SessionCompactionNotice, SessionCompactionTrigger, ShiitakeEventSourceConstructor, SessionWatch, ShiitakeAgent, ShiitakeSession, SubscribeOptions, TooLate, WatchOptions, } from "./client.js";
|
|
9
|
+
export { parseForkedSession, parseSessionFrame, parseSessionSnapshot, } from "./client.js";
|
|
10
|
+
export type { InstructionSnapshot, InstructionSource, ModelAcquisition, ModelDescription, ModelSource, RuntimeHost, RuntimePendingWork, SqliteCursor, SqliteOwner, SqliteValue, SqliteRow, StorageBudget, CompactionBudget, CompactionOptions, GrantsDelivery, GrantsDeliveryResult, ShiitakeOptions, ShiitakeOwnerAgent, ToolAccess, HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, ToolSource, } from "./internal/contracts.js";
|
|
11
|
+
export type { BorrowedCompactionViewV0, CompactionHandoffErrorV0, CompactionHandoffResultV0, CompactionHandoffV0, ExtensionCapabilityV0, ExtensionGrantV0, ExtensionRosterV0, ExtensionSlotV0, ExtensionStatusV0, RosteredExtensionV0, } from "./extensions.js";
|
|
12
|
+
export { ShiitakeError, type ShiitakeErrorBoundary, type ShiitakeErrorCause, } from "./errors.js";
|
|
13
|
+
/** Public construction namespace for the Promise-facing durable harness. */
|
|
14
|
+
export type ShiitakeApi = Readonly<{
|
|
15
|
+
/** Construct a Promise-facing durable harness. */
|
|
16
|
+
make: (options: ShiitakeOptions) => Promise<ShiitakeOwnerAgent>;
|
|
17
|
+
}>;
|
|
18
|
+
/** Promise-facing host composition with addressed mail kept off Session. */
|
|
19
|
+
export type ShiitakeHost = Readonly<{
|
|
20
|
+
/** Exact settled command facts; never a selected-lineage transcript query. */
|
|
21
|
+
readSettlement: (sessionId: string, commandId: string) => Promise<CommandSettlement | undefined>;
|
|
22
|
+
agent: ShiitakeOwnerAgent;
|
|
23
|
+
/** Durably admit one addressed mail item through the host owner. */
|
|
24
|
+
acceptMail: (mail: BirdDogMail, meta?: HostJson, reply?: VerifiedAgentReply) => Promise<Accepted>;
|
|
25
|
+
}>;
|
|
26
|
+
/** Construct the host-only Promise facade over the canonical Effect program. */
|
|
27
|
+
export declare function makeHost(options: ShiitakeOptions, mailRoom: BirdDogEffect): Promise<ShiitakeHost>;
|
|
28
|
+
/** Construct the Promise facade over the canonical Effect program. */
|
|
29
|
+
export declare const Shiitake: ShiitakeApi;
|
|
30
|
+
export type { ModelPreference } from "./internal/model-preference.js";
|
|
31
|
+
export type { DocumentSpecV0, DocumentsV0, DocumentForkPolicyV0, DocumentHandleV0, DocumentHandlesV0, } from "./session-documents.js";
|
|
32
|
+
export { sqliteRowSchema } from "./internal/contracts.js";
|
|
33
|
+
export type { CommandSettlement } from "./internal/contracts.js";
|
|
34
|
+
/** A delegated Agent reply after the admitting host authenticated it. */
|
|
35
|
+
export type { VerifiedAgentReply } from "./internal/woodstock/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { make as makeEffect, openHost as openEffectHost } from "./effect.js";
|
|
2
|
+
import { openPromiseAgent, openPromiseHost, } from "./internal/promise-facade.js";
|
|
3
|
+
export { parseForkedSession, parseSessionFrame, parseSessionSnapshot, } from "./client.js";
|
|
4
|
+
export { ShiitakeError, } from "./errors.js";
|
|
5
|
+
/** Construct the host-only Promise facade over the canonical Effect program. */
|
|
6
|
+
export function makeHost(options, mailRoom) {
|
|
7
|
+
return openPromiseHost(options, mailRoom, openEffectHost);
|
|
8
|
+
}
|
|
9
|
+
/** Construct the Promise facade over the canonical Effect program. */
|
|
10
|
+
export const Shiitake = Object.freeze({
|
|
11
|
+
make: (options) => openPromiseAgent(options, makeEffect),
|
|
12
|
+
});
|
|
13
|
+
export { sqliteRowSchema } from "./internal/contracts.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type WatchdogRunningJob } from "@fungi.computer/watchdog";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import type { ShiitakeEffectOptions } from "./effect-contracts.js";
|
|
4
|
+
import type { ExtensionRuntime } from "./extension-runtime.js";
|
|
5
|
+
/** Bounded process-loss recovery for every accepted Agent turn. */
|
|
6
|
+
export declare const agentTurnRecovery: Readonly<{
|
|
7
|
+
maxRecoveries: 2;
|
|
8
|
+
}>;
|
|
9
|
+
/** Execute one claimed opaque job through Shiitake's accepted run scope. */
|
|
10
|
+
export declare function executeAcceptedRun(options: ShiitakeEffectOptions, coreDigest: string, job: WatchdogRunningJob, extensions: ExtensionRuntime | undefined): Effect.Effect<{
|
|
11
|
+
readonly kind: "completed";
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "failed";
|
|
14
|
+
readonly reason?: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: "cancelled";
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "outcome_unknown";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "failed" | "cancelled";
|
|
21
|
+
} | {
|
|
22
|
+
kind: "completed";
|
|
23
|
+
}, never, never>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { decodeAgentJob } from "@fungi.computer/bird-dog/effect";
|
|
2
|
+
import {} from "@fungi.computer/watchdog";
|
|
3
|
+
import { Mycelium } from "@fungi.computer/mycelium";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Redacted from "effect/Redacted";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import { ShiitakeError } from "../errors.js";
|
|
8
|
+
import { ExtensionId } from "../extension-contract.js";
|
|
9
|
+
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
10
|
+
import { coreProtocolDigest } from "./extension-roster.js";
|
|
11
|
+
import { runAcceptedCommand, runAcceptedCompaction, ToolSetupError, } from "./run-scope.js";
|
|
12
|
+
import { CompactionCommitError } from "./compaction/session-compaction.js";
|
|
13
|
+
import { isCompactCommand, loadAcceptedRun, readExtensionAdmissionOrigin, recordWorkSettlement, } from "./woodstock/index.js";
|
|
14
|
+
/** Bounded process-loss recovery for every accepted Agent turn. */
|
|
15
|
+
export const agentTurnRecovery = Object.freeze({ maxRecoveries: 2 });
|
|
16
|
+
function runtimeFailure(message) {
|
|
17
|
+
return new ShiitakeError("runtime", "rejected", message);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Settle a run that could not finish. The durable job records the sanitized
|
|
21
|
+
* boundary and category, plus a safe model-host or closed tool-setup diagnostic.
|
|
22
|
+
* Original provider causes stay in the server log only.
|
|
23
|
+
*/
|
|
24
|
+
function failedDecision(error) {
|
|
25
|
+
const category = `${error.boundary}:${error.cause}`;
|
|
26
|
+
const diagnostic = error instanceof ToolSetupError || error instanceof CompactionCommitError
|
|
27
|
+
? error.diagnostic
|
|
28
|
+
: error.boundary === "model"
|
|
29
|
+
? error.message
|
|
30
|
+
: undefined;
|
|
31
|
+
return {
|
|
32
|
+
kind: "failed",
|
|
33
|
+
reason: diagnostic === undefined
|
|
34
|
+
? category
|
|
35
|
+
: `${category}: ${diagnostic}`.slice(0, 200),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Log a run failure with its sanitized reason and underlying cause. */
|
|
39
|
+
function logRunFailed(workId, error) {
|
|
40
|
+
return Effect.logError("shiitake_run_failed", Redacted.make(error)).pipe(Effect.annotateLogs({
|
|
41
|
+
workId,
|
|
42
|
+
reason: `${error.boundary}:${error.cause}`,
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
/** Execute one claimed opaque job through Shiitake's accepted run scope. */
|
|
46
|
+
export function executeAcceptedRun(options, coreDigest, job, extensions) {
|
|
47
|
+
return Effect.gen(function* () {
|
|
48
|
+
publishOwnerObservation(options.store, job.queue);
|
|
49
|
+
const payload = yield* Effect.try({
|
|
50
|
+
try: () => decodeAgentJob(job),
|
|
51
|
+
catch: () => runtimeFailure("Agent work payload is invalid"),
|
|
52
|
+
});
|
|
53
|
+
const prepared = yield* loadAcceptedRun(options.store, payload.work);
|
|
54
|
+
if (prepared.abnormalTerminal !== undefined)
|
|
55
|
+
return { kind: prepared.abnormalTerminal };
|
|
56
|
+
const incompatible = extensions?.incompatibleMigrations()[0];
|
|
57
|
+
if (incompatible !== undefined) {
|
|
58
|
+
yield* Effect.logError("shiitake_run_contract_changed").pipe(Effect.annotateLogs({
|
|
59
|
+
workId: job.jobId,
|
|
60
|
+
terminal: `contract_changed: ${incompatible}`,
|
|
61
|
+
}));
|
|
62
|
+
return yield* Effect.fail(runtimeFailure(`contract_changed: ${incompatible}`));
|
|
63
|
+
}
|
|
64
|
+
// A cold reopen selects its current admitted code at the next execution
|
|
65
|
+
// boundary. The command's admission contract remains an audit fact.
|
|
66
|
+
const participants = new Set(extensions?.runParticipants().map(([id]) => id) ?? []);
|
|
67
|
+
// A compaction request runs the Session's one compaction commit with the
|
|
68
|
+
// run's model and handoff, and no model turn. A failure settles through
|
|
69
|
+
// the ordinary failed decision with its owner-closed diagnostic.
|
|
70
|
+
// "Nothing to compact" completes the work; the Session records it as a
|
|
71
|
+
// compaction notice, which clients show as the request's result.
|
|
72
|
+
if (isCompactCommand(prepared.command.entry)) {
|
|
73
|
+
yield* runAcceptedCompaction(options, prepared, extensions?.compactionHandoff(participants));
|
|
74
|
+
yield* recordWorkSettlement(options.store, {
|
|
75
|
+
sessionId: prepared.command.sessionId,
|
|
76
|
+
commandFactId: payload.work.commandFactId,
|
|
77
|
+
workId: prepared.command.entry.workId,
|
|
78
|
+
outcome: "completed",
|
|
79
|
+
});
|
|
80
|
+
return { kind: "completed" };
|
|
81
|
+
}
|
|
82
|
+
const origin = yield* Effect.try({
|
|
83
|
+
try: () => readExtensionAdmissionOrigin(options.store, payload.work.sessionId, payload.work.commandFactId, prepared.command.entry.workId),
|
|
84
|
+
catch: () => runtimeFailure("Accepted run origin is unavailable"),
|
|
85
|
+
});
|
|
86
|
+
const runIdentity = {
|
|
87
|
+
sessionId: prepared.command.sessionId,
|
|
88
|
+
workId: prepared.command.entry.workId,
|
|
89
|
+
origin: origin === null
|
|
90
|
+
? null
|
|
91
|
+
: {
|
|
92
|
+
extensionId: Schema.decodeUnknownSync(ExtensionId)(origin.extensionId),
|
|
93
|
+
correlation: origin.correlation,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const fileTools = extensions === undefined
|
|
97
|
+
? []
|
|
98
|
+
: yield* extensions.filesForRun(runIdentity, participants);
|
|
99
|
+
// Every participant offers its tools under the grant key its host grant
|
|
100
|
+
// names (its id by default); Mycelium decides each call.
|
|
101
|
+
const extensionModules = extensions === undefined
|
|
102
|
+
? []
|
|
103
|
+
: yield* Effect.forEach([...participants], (extensionId) => extensions.modulesForRun(runIdentity, new Set([extensionId])).pipe(Effect.flatMap((modules) => Effect.try({
|
|
104
|
+
try: () => {
|
|
105
|
+
const grantKey = options.extensions.grants.find((grant) => grant.id === extensionId)?.grantKey ?? extensionId;
|
|
106
|
+
return modules.map((module) => Mycelium.grantAs(module, grantKey));
|
|
107
|
+
},
|
|
108
|
+
catch: () => runtimeFailure("Extension tools are invalid"),
|
|
109
|
+
})))).pipe(Effect.map((groups) => groups.flat()));
|
|
110
|
+
const extensionHandoff = extensions?.compactionHandoff(participants);
|
|
111
|
+
const executionContract = {
|
|
112
|
+
core: fileTools.length === 0
|
|
113
|
+
? coreDigest
|
|
114
|
+
: yield* coreProtocolDigest(fileTools.map((tool) => tool.descriptor.name)),
|
|
115
|
+
extensions: extensions?.runParticipants() ?? [],
|
|
116
|
+
};
|
|
117
|
+
const run = yield* runAcceptedCommand(options, prepared, executionContract, extensionModules, fileTools, extensionHandoff, extensions === undefined
|
|
118
|
+
? undefined
|
|
119
|
+
: () => extensions.promptSections(runIdentity, participants));
|
|
120
|
+
switch (run.status) {
|
|
121
|
+
case "completed":
|
|
122
|
+
return { kind: "completed" };
|
|
123
|
+
case "cancelled":
|
|
124
|
+
return { kind: "cancelled" };
|
|
125
|
+
case "failed":
|
|
126
|
+
return { kind: "failed" };
|
|
127
|
+
}
|
|
128
|
+
}).pipe(Effect.catch((error) => logRunFailed(job.jobId, error).pipe(Effect.as(failedDecision(error)))));
|
|
129
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { BirdDogMail } from "@fungi.computer/bird-dog";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import type { AcceptedWork } from "./client-boundary.js";
|
|
4
|
+
import type { Json } from "../extensions.js";
|
|
5
|
+
import { type HostMetaOptions } from "./host-meta.js";
|
|
6
|
+
import { ShiitakeError } from "../errors.js";
|
|
7
|
+
import type { SqliteOwner } from "./contracts.js";
|
|
8
|
+
import { type AdmissionOrigin, type EnqueueAcceptedWork, type RunContract, type SessionQueueHead, type VerifiedAgentReply } from "./woodstock/index.js";
|
|
9
|
+
export type { AdmissionOrigin } from "./woodstock/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* `now` admits immediately (a steer targets the running job). `idle` admits
|
|
12
|
+
* only while the Session queue head is idle and otherwise reports `not_ready`
|
|
13
|
+
* without writing anything; a retried request id still replays.
|
|
14
|
+
*/
|
|
15
|
+
export type AdmitWhen = "now" | "idle";
|
|
16
|
+
/** One prompt or compaction request submitted to the door, still encoded. */
|
|
17
|
+
export type AdmitRequest = Readonly<{
|
|
18
|
+
sessionId: string;
|
|
19
|
+
/** Checked atomically before admission; an absent target fails closed. */
|
|
20
|
+
requireExistingSession?: boolean;
|
|
21
|
+
/** Encoded accepted work, decoded once by the public command schema. */
|
|
22
|
+
command: AcceptedWork;
|
|
23
|
+
when: AdmitWhen;
|
|
24
|
+
origin: AdmissionOrigin;
|
|
25
|
+
/** Addressed BirdDog mail whose reply settles with this work. */
|
|
26
|
+
mail?: BirdDogMail;
|
|
27
|
+
/** Host metadata stored beside the prompt; only the in-process host sets it. */
|
|
28
|
+
meta?: Json;
|
|
29
|
+
/**
|
|
30
|
+
* The host's authentication of `mail` as a delegated Agent's reply. Only the
|
|
31
|
+
* host passes it, after checking the reply against the mail it sent.
|
|
32
|
+
*/
|
|
33
|
+
reply?: VerifiedAgentReply;
|
|
34
|
+
}>;
|
|
35
|
+
/** Outcome of one door admission. */
|
|
36
|
+
export type AdmitResult = Readonly<{
|
|
37
|
+
status: "accepted";
|
|
38
|
+
commandId: string;
|
|
39
|
+
delivery: "new" | "replay";
|
|
40
|
+
}> | Readonly<{
|
|
41
|
+
status: "not_ready";
|
|
42
|
+
}>;
|
|
43
|
+
/** Admit inside the door's open owner transaction. Throws `ShiitakeError`. */
|
|
44
|
+
export type AdmitWithin = (request: AdmitRequest) => AdmitResult;
|
|
45
|
+
/**
|
|
46
|
+
* Where a door transaction runs. `request` commits through the host so the
|
|
47
|
+
* wake guarantee is durable with the mutation; `reconcile` is already inside
|
|
48
|
+
* the host's reconciliation and commits on the owner alone.
|
|
49
|
+
*/
|
|
50
|
+
export type AdmissionBoundary = "request" | "reconcile";
|
|
51
|
+
/** The one accepted-command door. Nothing else writes accepted commands. */
|
|
52
|
+
export type AdmissionDoor = Readonly<{
|
|
53
|
+
/** Admit one prompt in its own transaction. */
|
|
54
|
+
admit: (request: AdmitRequest, boundary?: AdmissionBoundary) => Effect.Effect<AdmitResult, ShiitakeError>;
|
|
55
|
+
/**
|
|
56
|
+
* Run one owner transaction that may admit through `admit`. After commit the
|
|
57
|
+
* door publishes each Session with new work and requests one wake.
|
|
58
|
+
*/
|
|
59
|
+
transact: <A>(body: (admit: AdmitWithin) => A, boundary?: AdmissionBoundary) => Effect.Effect<A, ShiitakeError>;
|
|
60
|
+
}>;
|
|
61
|
+
/** Bind the door to one owner, run contract, Watchdog queue and host wake. */
|
|
62
|
+
export declare function openAdmissionDoor(input: Readonly<{
|
|
63
|
+
owner: SqliteOwner;
|
|
64
|
+
/** The host's metadata schema, checked before any write. */
|
|
65
|
+
hostMeta?: HostMetaOptions | undefined;
|
|
66
|
+
/** Refuses a new prompt under storage pressure; replays skip it. */
|
|
67
|
+
admitNew: () => void;
|
|
68
|
+
/** The run contract captured at this instant (participants may change). */
|
|
69
|
+
contract: () => RunContract;
|
|
70
|
+
enqueue: (work: Parameters<EnqueueAcceptedWork>[0], mail?: BirdDogMail) => void;
|
|
71
|
+
/** Transactional queue-head projection for the steer and idle decisions. */
|
|
72
|
+
readQueueHead: (sessionId: string) => SessionQueueHead;
|
|
73
|
+
commit: <A>(mutation: Effect.Effect<A, ShiitakeError>) => Effect.Effect<A, ShiitakeError>;
|
|
74
|
+
requestWake: Effect.Effect<void, ShiitakeError>;
|
|
75
|
+
}>): AdmissionDoor;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
import { checkHostMeta } from "./host-meta.js";
|
|
4
|
+
import { ShiitakeError } from "../errors.js";
|
|
5
|
+
import { decodeClientAcceptedCommand, decodeClientSessionId, requireDecoded, } from "./client-boundary.js";
|
|
6
|
+
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
7
|
+
import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, hasStoredSession, VerifiedAgentReplySchema, } from "./woodstock/index.js";
|
|
8
|
+
const decodeOrigin = Schema.decodeUnknownResult(AdmissionOriginSchema, {
|
|
9
|
+
onExcessProperty: "error",
|
|
10
|
+
});
|
|
11
|
+
const decodeExistingSessionCondition = Schema.decodeUnknownResult(Schema.UndefinedOr(Schema.Boolean));
|
|
12
|
+
const decodeVerifiedReply = Schema.decodeUnknownResult(VerifiedAgentReplySchema, { onExcessProperty: "error" });
|
|
13
|
+
/** A verified reply names the mail it arrived on and that mail's sender. */
|
|
14
|
+
function decodeReply(request) {
|
|
15
|
+
if (request.reply === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
const reply = requireDecoded(decodeVerifiedReply(request.reply), "Verified Agent reply is invalid");
|
|
18
|
+
const mail = request.mail;
|
|
19
|
+
if (mail?.replyTo.length !== 0 ||
|
|
20
|
+
mail.from.agentId !== reply.agentId ||
|
|
21
|
+
mail.from.sessionId !== reply.sessionId ||
|
|
22
|
+
request.command.type !== "prompt" ||
|
|
23
|
+
request.command.delivery !== "followUp")
|
|
24
|
+
throw new ShiitakeError("contract", "invalid", "Verified Agent reply does not match its mail");
|
|
25
|
+
return reply;
|
|
26
|
+
}
|
|
27
|
+
/** Bind the door to one owner, run contract, Watchdog queue and host wake. */
|
|
28
|
+
export function openAdmissionDoor(input) {
|
|
29
|
+
/** The door's one decode, done before any owner operation when possible. */
|
|
30
|
+
const decode = (request) => ({
|
|
31
|
+
sessionId: decodeClientSessionId(request.sessionId),
|
|
32
|
+
requireExistingSession: requireDecoded(decodeExistingSessionCondition(request.requireExistingSession), "Session admission condition is invalid") ?? false,
|
|
33
|
+
command: requireDecoded(decodeClientAcceptedCommand(request.command), "Session command is invalid"),
|
|
34
|
+
origin: requireDecoded(decodeOrigin(request.origin), "Session command origin is invalid"),
|
|
35
|
+
when: request.when,
|
|
36
|
+
mail: request.mail,
|
|
37
|
+
reply: decodeReply(request),
|
|
38
|
+
meta: request.meta === undefined
|
|
39
|
+
? undefined
|
|
40
|
+
: checkHostMeta(input.hostMeta, request.meta),
|
|
41
|
+
});
|
|
42
|
+
const admitDecoded = (request, touched) => {
|
|
43
|
+
const { sessionId, origin } = request;
|
|
44
|
+
const command = request.command;
|
|
45
|
+
if (request.requireExistingSession &&
|
|
46
|
+
!hasStoredSession(input.owner, sessionId))
|
|
47
|
+
throw new ShiitakeError("contract", "closed", "Session target is unavailable");
|
|
48
|
+
const head = input.readQueueHead(sessionId);
|
|
49
|
+
if (request.when === "idle" &&
|
|
50
|
+
head.state !== "idle" &&
|
|
51
|
+
readSessionCommandAdmissionByRequest(input.owner, sessionId, command.requestId) === undefined)
|
|
52
|
+
return Object.freeze({ status: "not_ready" });
|
|
53
|
+
// Storage pressure refuses new prompts. A compaction request adds no
|
|
54
|
+
// prompt and frees model context, so it is admitted.
|
|
55
|
+
if (command.type === "prompt" &&
|
|
56
|
+
readSessionCommandAdmissionByRequest(input.owner, sessionId, command.requestId) === undefined)
|
|
57
|
+
input.admitNew();
|
|
58
|
+
// Only a steering prompt targets the running job; a compaction request
|
|
59
|
+
// always queues behind running work.
|
|
60
|
+
const targetWorkId = command.type === "prompt" &&
|
|
61
|
+
command.delivery === "steer" &&
|
|
62
|
+
head.state === "running" &&
|
|
63
|
+
!head.cancellationRequested
|
|
64
|
+
? head.jobId
|
|
65
|
+
: undefined;
|
|
66
|
+
const accepted = acceptSessionCommandOwned(input.owner, sessionId, command, request.meta, origin, input.contract(), targetWorkId === undefined
|
|
67
|
+
? (work) => {
|
|
68
|
+
input.enqueue(work, request.mail);
|
|
69
|
+
}
|
|
70
|
+
: undefined, targetWorkId, request.reply);
|
|
71
|
+
touched.set(sessionId, (touched.get(sessionId) ?? false) || accepted.delivery === "new");
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
status: "accepted",
|
|
74
|
+
commandId: accepted.accepted.commandId,
|
|
75
|
+
delivery: accepted.delivery,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const run = (body, boundary) => Effect.suspend(() => {
|
|
79
|
+
const touched = new Map();
|
|
80
|
+
const mutation = Effect.try({
|
|
81
|
+
try: () => {
|
|
82
|
+
touched.clear();
|
|
83
|
+
return input.owner.transactionSync(() => body((request) => admitDecoded(request, touched)));
|
|
84
|
+
},
|
|
85
|
+
catch: (error) => error instanceof ShiitakeError
|
|
86
|
+
? error
|
|
87
|
+
: new ShiitakeError("store", "unavailable", "Session command admission is unavailable"),
|
|
88
|
+
});
|
|
89
|
+
return (boundary === "request" ? input.commit(mutation) : mutation).pipe(Effect.tap(() => Effect.sync(() => {
|
|
90
|
+
for (const [sessionId, isNew] of touched)
|
|
91
|
+
if (isNew)
|
|
92
|
+
publishOwnerObservation(input.owner, sessionId);
|
|
93
|
+
})), Effect.tap(() => (touched.size > 0 ? input.requestWake : Effect.void)));
|
|
94
|
+
});
|
|
95
|
+
const transact = (body, boundary = "request") => run((admit) => body((request) => admit(decode(request))), boundary);
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
transact,
|
|
98
|
+
admit: (request, boundary) => Effect.try({
|
|
99
|
+
try: () => decode(request),
|
|
100
|
+
catch: (error) => error instanceof ShiitakeError
|
|
101
|
+
? error
|
|
102
|
+
: new ShiitakeError("contract", "invalid", "Session command is invalid"),
|
|
103
|
+
}).pipe(Effect.flatMap((decoded) => run((admit) => admit(decoded), boundary ?? "request"))),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type { ShiitakeError } from "../errors.js";
|
|
3
|
+
/** Compose one idempotent close action for the constructed agent. */
|
|
4
|
+
export declare function composeAgentClose(coordinatorClose: Effect.Effect<void, ShiitakeError>, extensionClose: Effect.Effect<void, ShiitakeError>): Effect.Effect<void, ShiitakeError>;
|