@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,362 @@
1
+ import { nanoid } from "@fungi.computer/cairn";
2
+ import { parseModelPreference } from "./model-preference.js";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Option from "effect/Option";
5
+ import * as Stream from "effect/Stream";
6
+ import { ShiitakeError } from "../errors.js";
7
+ import { decodeClientReadOptions, decodeClientSessionCommand, decodeClientSessionId, decodeClientSubscribeOptions, } from "./client-boundary.js";
8
+ import { mergeObservationSignals, openObservationKernel, publishOwnerObservation, } from "./observation-kernel.js";
9
+ import { checkHostMeta } from "./host-meta.js";
10
+ import { readSessionActivity } from "./session-status.js";
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";
13
+ /** Generic Agent-scope extension controls over the runtime's one call door. */
14
+ function agentExtensions(runtime) {
15
+ return Object.freeze({
16
+ list: runtime.assertOpen.pipe(Effect.map(() => runtime.extensions.list())),
17
+ resume: (extensionId) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.resume(extensionId))),
18
+ call: (extensionId, command, input) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "agent" }, extensionId, command, input))),
19
+ });
20
+ }
21
+ function currentSessionActivity(options, runtime, sessionId) {
22
+ return runtime
23
+ .readQueueHead(sessionId)
24
+ .pipe(Effect.flatMap((head) => (head.state === "queued"
25
+ ? runtime.runningWork(head.jobId)
26
+ : Effect.succeed(null)).pipe(Effect.map((runningWork) => readSessionActivity(options.store, sessionId, head, runningWork)))));
27
+ }
28
+ /** Carry the Session's context use on a baseline, when it has one. */
29
+ function withContext(snapshot, context) {
30
+ return context === undefined
31
+ ? snapshot
32
+ : Object.freeze({ ...snapshot, context });
33
+ }
34
+ /** Read the baseline and its context use for one Session. */
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, runtime, sessionId).pipe(Effect.map((context) => withContext(snapshot, context)))));
37
+ }
38
+ function sessionOwnerOperation(runtime, sessionId, operation) {
39
+ return runtime.assertOpen.pipe(Effect.andThen(Effect.try({
40
+ try: () => operation(decodeClientSessionId(sessionId)),
41
+ catch: (error) => error instanceof ShiitakeError
42
+ ? error
43
+ : new ShiitakeError("store", "rejected", "Session owner operation failed"),
44
+ })));
45
+ }
46
+ function decodeSessionCommand(sessionId, input) {
47
+ return Effect.try({
48
+ try: () => {
49
+ const decodedCommand = decodeClientSessionCommand(input);
50
+ if (decodedCommand._tag === "Failure")
51
+ throw new ShiitakeError("contract", "invalid", "Session command is invalid");
52
+ return Object.freeze({
53
+ sessionId: decodeClientSessionId(sessionId),
54
+ command: decodedCommand.success,
55
+ });
56
+ },
57
+ catch: (error) => error instanceof ShiitakeError
58
+ ? error
59
+ : new ShiitakeError("contract", "invalid", "Session command is invalid"),
60
+ });
61
+ }
62
+ function decodeSubscriptionInput(sessionId, input) {
63
+ return Effect.try({
64
+ try: () => Object.freeze({
65
+ sessionId: decodeClientSessionId(sessionId),
66
+ options: decodeClientSubscribeOptions(input),
67
+ }),
68
+ catch: (error) => error instanceof ShiitakeError
69
+ ? error
70
+ : new ShiitakeError("contract", "invalid", "Subscribe options are invalid"),
71
+ });
72
+ }
73
+ function resetFrame(options, runtime, sessionId, includeSandboxFailures = false) {
74
+ return runtime.assertOpen.pipe(Effect.andThen(readSessionWithContext(options, runtime, sessionId, {
75
+ includeSandboxFailures,
76
+ })), Effect.map((baseline) => Object.freeze({
77
+ kind: "reset",
78
+ reason: "history_unavailable",
79
+ cursor: baseline.cursor,
80
+ baseline,
81
+ })));
82
+ }
83
+ function continueFromReset(frame) {
84
+ return [
85
+ [frame],
86
+ Option.some({
87
+ after: frame.cursor ?? undefined,
88
+ delivery: frame.baseline.hasMore ? "replay" : "live",
89
+ awaitSignal: !frame.baseline.hasMore,
90
+ }),
91
+ ];
92
+ }
93
+ function subscriptionFrames(options, runtime, registration, sessionId, after, delivery, includeSandboxFailures = false) {
94
+ const initial = {
95
+ after,
96
+ delivery,
97
+ awaitSignal: false,
98
+ };
99
+ return Stream.paginate(initial, (state) => (state.awaitSignal
100
+ ? registration.awaitSignal.pipe(Effect.flatMap((signal) => registration.pollSignal.pipe(Effect.map((pending) => pending === undefined
101
+ ? signal
102
+ : mergeObservationSignals(signal, pending)))))
103
+ : registration.pollSignal).pipe(Effect.flatMap((signal) => {
104
+ if (signal === "closed")
105
+ return Effect.succeed([[], Option.none()]);
106
+ if (signal === "reset")
107
+ return resetFrame(options, runtime, sessionId, includeSandboxFailures).pipe(Effect.map(continueFromReset));
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) {
112
+ return Effect.succeed([
113
+ [
114
+ {
115
+ kind: "progress",
116
+ progress: activity.progress,
117
+ activity: activity.facts,
118
+ },
119
+ ],
120
+ Option.some({ ...state, awaitSignal: true }),
121
+ ]);
122
+ }
123
+ return readSessionReplayPage(options.store, sessionId, state.after, state.delivery, activity, () => runtime.extensions.project(sessionId), includeSandboxFailures).pipe(Effect.flatMap((page) => {
124
+ if (page.kind === "reset")
125
+ return resetFrame(options, runtime, sessionId, includeSandboxFailures).pipe(Effect.map(continueFromReset));
126
+ const latestCursor = page.frames
127
+ .flatMap((frame) => frame.kind === "progress" ? [] : [frame.cursor])
128
+ .at(-1);
129
+ const next = {
130
+ after: latestCursor ?? state.after,
131
+ delivery: page.hasMore ? state.delivery : "live",
132
+ awaitSignal: !page.hasMore,
133
+ };
134
+ // Every update carries the context use it settles on.
135
+ return (page.frames.some((frame) => frame.kind === "update")
136
+ ? readSessionContext(options, runtime, sessionId)
137
+ : Effect.succeed(undefined)).pipe(Effect.map((context) => [
138
+ context === undefined
139
+ ? page.frames
140
+ : page.frames.map((frame) => frame.kind === "update"
141
+ ? Object.freeze({ ...frame, context })
142
+ : frame),
143
+ Option.some(next),
144
+ ]));
145
+ }));
146
+ }));
147
+ })));
148
+ }
149
+ function subscribeSession(options, runtime, observation, sessionId, input) {
150
+ return Stream.unwrap(runtime.assertOpen.pipe(Effect.andThen(decodeSubscriptionInput(sessionId, input)), Effect.flatMap((decoded) => Effect.acquireRelease(observation.register(decoded.sessionId), (registration) => Effect.sync(registration.close)).pipe(Effect.map((registration) => subscriptionFrames(options, runtime, registration, decoded.sessionId, decoded.options.after, "replay", decoded.options.includeSandboxFailures === true))))));
151
+ }
152
+ function decodeWatchInput(sessionId, input) {
153
+ return Effect.try({
154
+ try: () => Object.freeze({
155
+ sessionId: decodeClientSessionId(sessionId),
156
+ options: decodeClientReadOptions(input),
157
+ }),
158
+ catch: (error) => error instanceof ShiitakeError
159
+ ? error
160
+ : new ShiitakeError("contract", "invalid", "Watch options are invalid"),
161
+ });
162
+ }
163
+ function watchSession(options, runtime, observation, sessionId, input) {
164
+ return runtime.assertOpen.pipe(Effect.andThen(decodeWatchInput(sessionId, input)), Effect.flatMap((decoded) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
165
+ const registration = yield* observation.register(decoded.sessionId);
166
+ return yield* restore(runtime.assertOpen.pipe(Effect.andThen(readSessionWithContext(options, runtime, decoded.sessionId, decoded.options)), Effect.map((snapshot) => Object.freeze({
167
+ snapshot,
168
+ changes: subscriptionFrames(options, runtime, registration, decoded.sessionId, snapshot.cursor ?? undefined, "live", decoded.options.includeSandboxFailures === true).pipe(Stream.ensuring(Effect.sync(registration.close))),
169
+ close: Effect.sync(registration.close),
170
+ })))).pipe(Effect.onError(() => Effect.sync(registration.close)));
171
+ }))));
172
+ }
173
+ /**
174
+ * Admit one user prompt, mail prompt or compaction request through the
175
+ * accepted-command door.
176
+ */
177
+ function admitUserWork(runtime, sessionId, input, meta, mail, reply) {
178
+ let request = {
179
+ sessionId,
180
+ command: input,
181
+ when: "now",
182
+ origin: { kind: "user" },
183
+ };
184
+ if (meta !== undefined)
185
+ request = { ...request, meta };
186
+ if (mail !== undefined)
187
+ request = { ...request, mail };
188
+ if (reply !== undefined)
189
+ request = { ...request, reply };
190
+ return runtime.assertOpen.pipe(Effect.andThen(runtime.door.admit(request)), Effect.flatMap((admission) => admission.status === "accepted"
191
+ ? Effect.succeed({
192
+ status: "accepted",
193
+ commandId: admission.commandId,
194
+ })
195
+ : Effect.fail(new ShiitakeError("runtime", "unavailable", "Session command was not admitted"))));
196
+ }
197
+ function cancelRequestedSession(options, runtime, sessionId, input, meta) {
198
+ return runtime.assertOpen.pipe(Effect.andThen(cancelSession(options.store, sessionId, input.commandId, input.onlyIfPending, () => runtime.readQueueHeadTransactional(sessionId), runtime.recordAcceptedCancellation, meta)), Effect.flatMap((cancellation) => cancellation === undefined
199
+ ? Effect.succeed({
200
+ status: "too_late",
201
+ commandId: input.commandId,
202
+ })
203
+ : (cancellation.active
204
+ ? runtime.signalCancellation(cancellation.accepted.commandId, cancellation.accepted.entryId)
205
+ : runtime.requestWake).pipe(Effect.as({
206
+ status: "accepted",
207
+ commandId: cancellation.accepted.commandId,
208
+ }))));
209
+ }
210
+ function abortCurrentSession(options, runtime, sessionId, meta) {
211
+ return runtime.assertOpen.pipe(Effect.andThen(runtime.commit(abortSession(options.store, sessionId, () => runtime.readQueueHeadTransactional(sessionId), runtime.recordAcceptedCancellation, meta))), Effect.flatMap((cancellation) => cancellation === undefined
212
+ ? runtime.requestWake
213
+ : runtime.signalCancellation(cancellation.commandId, cancellation.entryId)));
214
+ }
215
+ function selectBranch(options, runtime, sessionId, input) {
216
+ return runtime.assertOpen.pipe(Effect.andThen(branchSession(options.store, {
217
+ sessionId,
218
+ type: "branch",
219
+ entryId: input.entryId,
220
+ assertIdleSession: runtime.assertIdleSession,
221
+ })), Effect.as(undefined));
222
+ }
223
+ function forkBranch(options, runtime, sessionId, input) {
224
+ return runtime.assertOpen.pipe(Effect.andThen(forkSession(options.store, {
225
+ sessionId,
226
+ type: "fork",
227
+ entryId: input.entryId,
228
+ assertIdleSession: runtime.assertIdleSession,
229
+ })));
230
+ }
231
+ function executeSessionCommand(options, runtime, sessionId, meta, input) {
232
+ return runtime.assertOpen.pipe(Effect.andThen(decodeSessionCommand(sessionId, input)),
233
+ // The one path every command's host metadata takes: checked once against
234
+ // the host's schema, then stored on whatever entry the command writes.
235
+ Effect.flatMap((decoded) => checkCommandMeta(options, meta).pipe(Effect.map((checked) => ({ ...decoded, checked })))), Effect.flatMap(({ sessionId: decodedSessionId, command, checked, }) => {
236
+ switch (command.type) {
237
+ case "prompt":
238
+ case "compact":
239
+ return admitUserWork(runtime, decodedSessionId, command, checked);
240
+ case "abort":
241
+ return abortCurrentSession(options, runtime, decodedSessionId, checked).pipe(Effect.as(undefined));
242
+ case "cancel":
243
+ return cancelRequestedSession(options, runtime, decodedSessionId, command, checked);
244
+ case "branch":
245
+ return selectBranch(options, runtime, decodedSessionId, command);
246
+ case "fork":
247
+ return forkBranch(options, runtime, decodedSessionId, command);
248
+ case "operator":
249
+ return runtime.assertOpen.pipe(Effect.andThen(recordOperatorLine(options.store, {
250
+ sessionId: decodedSessionId,
251
+ requestId: command.requestId,
252
+ author: command.author,
253
+ meta: checked,
254
+ text: command.text,
255
+ assertIdleSession: runtime.assertIdleSession,
256
+ })), Effect.as(undefined));
257
+ case "title":
258
+ return runtime
259
+ .commit(sessionOwnerOperation(runtime, decodedSessionId, (decoded) => {
260
+ setSessionTitle(options.store, decoded, command.title);
261
+ }))
262
+ .pipe(Effect.as(undefined));
263
+ }
264
+ }));
265
+ }
266
+ /** Check one command's host metadata against the host's schema, once. */
267
+ function checkCommandMeta(options, meta) {
268
+ return Effect.try({
269
+ try: () => meta === undefined ? undefined : checkHostMeta(options.meta, meta),
270
+ catch: (cause) => cause instanceof ShiitakeError
271
+ ? cause
272
+ : new ShiitakeError("contract", "invalid", "Host metadata is invalid"),
273
+ });
274
+ }
275
+ /** Construct the Effect-native coordinator over the private owner modules. */
276
+ export function openCoordinator(options, runtime) {
277
+ const observation = openObservationKernel(options.store);
278
+ /** One Session handle; `meta` is the host metadata its prompts carry. */
279
+ const sessionFor = (sessionId, meta) => {
280
+ function command(input) {
281
+ return executeSessionCommand(options, runtime, sessionId, meta, input);
282
+ }
283
+ return Object.freeze({
284
+ command,
285
+ abort: command({ type: "abort" }),
286
+ send: (text, input) => Effect.sync(() => input?.requestId ?? nanoid()).pipe(Effect.flatMap((requestId) => {
287
+ const prompt = {
288
+ type: "prompt",
289
+ delivery: input?.delivery ?? "steer",
290
+ requestId,
291
+ text,
292
+ };
293
+ return command(input?.images === undefined
294
+ ? prompt
295
+ : { ...prompt, images: input.images }).pipe(Effect.mapError((error) => error.requestId === requestId
296
+ ? error
297
+ : new ShiitakeError(error.boundary, error.cause, error.message, requestId)));
298
+ })),
299
+ cancel: (input) => command({ type: "cancel", ...input }),
300
+ modelPreference: Object.freeze({
301
+ read: sessionOwnerOperation(runtime, sessionId, (decoded) => readSessionModelPreference(options.store, decoded)),
302
+ set: (preference) => Effect.try({
303
+ try: () => parseModelPreference(preference),
304
+ catch: () => new ShiitakeError("contract", "invalid", "Model preference is invalid"),
305
+ }).pipe(Effect.flatMap((parsed) => runtime
306
+ .commit(sessionOwnerOperation(runtime, sessionId, (decoded) => {
307
+ setSessionModelPreference(options.store, decoded, parsed);
308
+ return decoded;
309
+ }))
310
+ .pipe(
311
+ // Once committed, observers re-read the Session: the
312
+ // selection's window may have changed.
313
+ Effect.tap((decoded) => Effect.sync(() => {
314
+ publishOwnerObservation(options.store, decoded);
315
+ })), Effect.asVoid))),
316
+ }),
317
+ extension: (extensionId) => Object.freeze({
318
+ call: (command, input) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "session", sessionId }, extensionId, command, input))),
319
+ }),
320
+ read: (input) => runtime.assertOpen.pipe(Effect.andThen(readSessionWithContext(options, runtime, sessionId, input))),
321
+ subscribe: (input) => subscribeSession(options, runtime, observation, sessionId, input),
322
+ watch: (input) => watchSession(options, runtime, observation, sessionId, input),
323
+ });
324
+ };
325
+ const agent = Object.freeze({
326
+ createSession: (requestId) => runtime.assertOpen.pipe(Effect.andThen(createSession(options.store, requestId, runtime.storage.admitNew))),
327
+ deleteSession: (sessionId) => runtime.assertOpen.pipe(Effect.andThen(deleteSession(options.store, {
328
+ sessionId,
329
+ assertIdleSession: runtime.assertIdleSession,
330
+ }))),
331
+ listSessions: (after) => runtime.assertOpen.pipe(Effect.andThen(readSessionInbox(options.store, after))),
332
+ extensions: agentExtensions(runtime),
333
+ session: (sessionId) => sessionFor(sessionId, undefined),
334
+ close: runtime.beginClose.pipe(Effect.andThen(observation.close)),
335
+ });
336
+ return Object.freeze({
337
+ ...agent,
338
+ grants: Object.freeze({
339
+ // A synchronous owner write: a delivery lands before any other work
340
+ // can interleave, so the very next tool call reads it.
341
+ deliver: (sessionId, delivery) => sessionOwnerOperation(runtime, sessionId, (decoded) => options.store.transactionSync(() => deliverSessionGrants(options.store, decoded, delivery))),
342
+ read: (sessionId) => sessionOwnerOperation(runtime, sessionId, (decoded) => readSessionGrants(options.store, decoded)),
343
+ }),
344
+ withMeta: (meta) => Object.freeze({
345
+ ...agent,
346
+ session: (sessionId) => sessionFor(sessionId, meta),
347
+ }),
348
+ });
349
+ }
350
+ /**
351
+ * Accept one addressed mail envelope; the door decodes it as a prompt. `reply`
352
+ * is the host's authentication of a delegated Agent's reply; only then does
353
+ * the model see the line inside the verified-reply tag.
354
+ */
355
+ export function acceptAddressedMail(runtime, mail, meta, reply) {
356
+ return admitUserWork(runtime, mail.to.sessionId, {
357
+ requestId: mail.key,
358
+ delivery: "followUp",
359
+ type: "prompt",
360
+ text: mail.content,
361
+ }, meta, mail, reply);
362
+ }
@@ -0,0 +1,11 @@
1
+ import * as Schema from "effect/Schema";
2
+ import type { DocumentsV0, DocumentSpecV0 } from "../session-documents.js";
3
+ /** The declared schema owns typed values; the JSON decoder owns their encoding. */
4
+ export declare function encodeDocument<A, I>(spec: DocumentSpecV0<A, I>, value: A): string;
5
+ /** Validate policy, bound and initial state before admitting any extension. */
6
+ export declare function documentDescriptor(spec: DocumentsV0[string]): {
7
+ schema: Schema.Json;
8
+ initial: string;
9
+ fork: "inherit-at-fork-point" | "current" | "fresh";
10
+ maxBytes: number;
11
+ };
@@ -0,0 +1,39 @@
1
+ import * as Schema from "effect/Schema";
2
+ import * as SchemaRepresentation from "effect/SchemaRepresentation";
3
+ import { ExtensionStateError } from "../extensions.js";
4
+ import { JsonValueSchema } from "./json-value.js";
5
+ const Declaration = Schema.Struct({
6
+ fork: Schema.Literals(["inherit-at-fork-point", "current", "fresh"]),
7
+ maxBytes: Schema.Int.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER))),
8
+ });
9
+ /** The declared schema owns typed values; the JSON decoder owns their encoding. */
10
+ export function encodeDocument(spec, value) {
11
+ const result = Schema.encodeUnknownResult(spec.schema)(value);
12
+ if (result._tag === "Failure")
13
+ throw new ExtensionStateError({
14
+ reason: "encode",
15
+ message: "Session document does not match its schema",
16
+ });
17
+ const json = Schema.decodeUnknownResult(JsonValueSchema)(result.success);
18
+ if (json._tag === "Failure")
19
+ throw new ExtensionStateError({
20
+ reason: "encode",
21
+ message: "Session document must encode finite JSON",
22
+ });
23
+ const text = JSON.stringify(json.success);
24
+ if (new TextEncoder().encode(text).byteLength > spec.maxBytes)
25
+ throw new ExtensionStateError({
26
+ reason: "bound",
27
+ message: "Session document exceeds maxBytes",
28
+ });
29
+ return text;
30
+ }
31
+ /** Validate policy, bound and initial state before admitting any extension. */
32
+ export function documentDescriptor(spec) {
33
+ const declaration = Schema.decodeUnknownSync(Declaration)(spec);
34
+ return {
35
+ ...declaration,
36
+ schema: SchemaRepresentation.toJson(Schema.toRepresentation(spec.schema)),
37
+ initial: encodeDocument(spec, spec.initial),
38
+ };
39
+ }
@@ -0,0 +1,13 @@
1
+ import * as Effect from "effect/Effect";
2
+ import type { ShiitakeAgent } from "../client.js";
3
+ import type { ShiitakeEffectAgent, ShiitakeEffectOwner } from "./effect-contracts.js";
4
+ import type { ShiitakeOwnerAgent } from "./contracts.js";
5
+ /** Run one Shiitake Effect through its sole Promise error adaptation. */
6
+ export declare function runShiitakePromise<A, E>(effect: Effect.Effect<A, E>): Promise<A>;
7
+ /** Adapt one canonical Effect agent to Shiitake's ordinary web interface. */
8
+ export declare function toPromiseAgent(agent: ShiitakeEffectAgent, suppliedAddress?: string): ShiitakeAgent;
9
+ /**
10
+ * Adapt the in-process owner, keeping its host-only grants and metadata
11
+ * surfaces; its metadata views share one address.
12
+ */
13
+ export declare function toPromiseOwner(owner: ShiitakeEffectOwner): ShiitakeOwnerAgent;
@@ -0,0 +1,102 @@
1
+ import { nanoid } from "@fungi.computer/cairn";
2
+ import * as Cause from "effect/Cause";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Exit from "effect/Exit";
5
+ import * as Option from "effect/Option";
6
+ import * as Stream from "effect/Stream";
7
+ import { ShiitakeError } from "../errors.js";
8
+ /** Run one Shiitake Effect through its sole Promise error adaptation. */
9
+ export function runShiitakePromise(effect) {
10
+ return Effect.runPromiseExit(effect).then((exit) => {
11
+ if (Exit.isSuccess(exit))
12
+ return exit.value;
13
+ const failure = Option.getOrUndefined(Cause.findErrorOption(exit.cause));
14
+ const error = failure ?? Cause.squash(exit.cause);
15
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
16
+ });
17
+ }
18
+ /**
19
+ * Run one synchronous owner Effect and return or throw its result. Only
20
+ * store operations that never suspend use it.
21
+ */
22
+ function runShiitakeSync(effect) {
23
+ const exit = Effect.runSyncExit(effect);
24
+ if (Exit.isSuccess(exit))
25
+ return exit.value;
26
+ const failure = Option.getOrUndefined(Cause.findErrorOption(exit.cause));
27
+ const error = failure ?? Cause.squash(exit.cause);
28
+ throw error instanceof Error ? error : new Error(String(error));
29
+ }
30
+ function observationAsyncIterable(stream) {
31
+ const iterable = Stream.toAsyncIterable(stream);
32
+ return Object.freeze({
33
+ [Symbol.asyncIterator]: () => {
34
+ const iterator = iterable[Symbol.asyncIterator]();
35
+ return {
36
+ next: () => iterator.next(),
37
+ return: () => iterator.return?.() ??
38
+ Promise.resolve({ done: true, value: undefined }),
39
+ };
40
+ },
41
+ });
42
+ }
43
+ function sessionFacade(address, session) {
44
+ function command(input) {
45
+ return runShiitakePromise(session.command(input));
46
+ }
47
+ return Object.freeze({
48
+ address,
49
+ command,
50
+ abort: () => runShiitakePromise(session.abort),
51
+ send: (text, options) => runShiitakePromise(session.send(text, options)),
52
+ cancel: (input) => runShiitakePromise(session.cancel(input)),
53
+ modelPreference: Object.freeze({
54
+ read: () => runShiitakePromise(session.modelPreference.read),
55
+ set: (preference) => runShiitakePromise(session.modelPreference.set(preference)),
56
+ }),
57
+ extension: (extensionId) => {
58
+ const extension = session.extension(extensionId);
59
+ return Object.freeze({
60
+ call: (command, input) => runShiitakePromise(extension.call(command, input)),
61
+ });
62
+ },
63
+ read: (input) => runShiitakePromise(session.read(input)),
64
+ subscribe: (input) => observationAsyncIterable(session.subscribe(input)),
65
+ watch: (input) => runShiitakePromise(session.watch(input)).then((watch) => Object.freeze({
66
+ snapshot: watch.snapshot,
67
+ changes: observationAsyncIterable(watch.changes),
68
+ close: () => runShiitakePromise(watch.close),
69
+ })),
70
+ });
71
+ }
72
+ /** Adapt one canonical Effect agent to Shiitake's ordinary web interface. */
73
+ export function toPromiseAgent(agent, suppliedAddress) {
74
+ const agentAddress = suppliedAddress ?? `local:${nanoid()}`;
75
+ return Object.freeze({
76
+ createSession: (requestId) => runShiitakePromise(agent.createSession(requestId)),
77
+ deleteSession: (sessionId) => runShiitakePromise(agent.deleteSession(sessionId)),
78
+ listSessions: (after) => runShiitakePromise(agent.listSessions(after)),
79
+ extensions: Object.freeze({
80
+ list: () => runShiitakePromise(agent.extensions.list),
81
+ resume: (extensionId) => runShiitakePromise(agent.extensions.resume(extensionId)),
82
+ call: (extensionId, command, input) => runShiitakePromise(agent.extensions.call(extensionId, command, input)),
83
+ }),
84
+ session: (sessionId) => sessionFacade(`${agentAddress}/sessions/${encodeURIComponent(sessionId)}`, agent.session(sessionId)),
85
+ close: () => runShiitakePromise(agent.close),
86
+ });
87
+ }
88
+ /**
89
+ * Adapt the in-process owner, keeping its host-only grants and metadata
90
+ * surfaces; its metadata views share one address.
91
+ */
92
+ export function toPromiseOwner(owner) {
93
+ const address = `local:${nanoid()}`;
94
+ return Object.freeze({
95
+ ...toPromiseAgent(owner, address),
96
+ grants: Object.freeze({
97
+ deliver: (sessionId, delivery) => runShiitakeSync(owner.grants.deliver(sessionId, delivery)),
98
+ read: (sessionId) => runShiitakeSync(owner.grants.read(sessionId)),
99
+ }),
100
+ withMeta: (meta) => toPromiseAgent(owner.withMeta(meta), address),
101
+ });
102
+ }