@fungi.computer/shiitake 0.5.0 → 0.7.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 +59 -21
- package/dist/agent-control.d.ts +94 -2
- package/dist/agent-control.js +147 -26
- package/dist/client-entry.d.ts +20 -0
- package/dist/client-entry.js +5 -0
- package/dist/client-visitor.d.ts +33 -0
- package/dist/client-visitor.js +231 -0
- package/dist/client-wire.d.ts +9 -0
- package/dist/client-wire.js +24 -3
- package/dist/client.d.ts +27 -6
- package/dist/client.js +23 -0
- package/dist/cloudflare.d.ts +20 -0
- package/dist/cloudflare.js +4 -1
- package/dist/delegation.d.ts +79 -53
- package/dist/delegation.js +26 -21
- package/dist/effect.d.ts +26 -1
- package/dist/effect.js +14 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -0
- package/dist/extensions.d.ts +22 -3
- package/dist/extensions.js +1 -1
- package/dist/fleet.d.ts +25 -2
- package/dist/fleet.js +108 -24
- package/dist/hosted.js +3 -0
- package/dist/http-routes.d.ts +23 -9
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/internal/accepted-run-executor.js +2 -1
- package/dist/internal/admission-door.d.ts +6 -1
- package/dist/internal/admission-door.js +33 -3
- package/dist/internal/contracts.d.ts +19 -1
- package/dist/internal/coordinator.js +115 -52
- package/dist/internal/effect-client.js +1 -1
- package/dist/internal/effect-contracts.d.ts +9 -2
- package/dist/internal/extension-command-registry.d.ts +2 -1
- package/dist/internal/extension-command-registry.js +3 -1
- package/dist/internal/extension-runtime.d.ts +3 -1
- package/dist/internal/extension-runtime.js +18 -8
- package/dist/internal/model-acquisition.js +2 -0
- package/dist/internal/observation-kernel.d.ts +2 -0
- package/dist/internal/observation-kernel.js +17 -6
- package/dist/internal/promise-facade.js +2 -0
- package/dist/internal/run-scope.d.ts +14 -0
- package/dist/internal/run-scope.js +58 -15
- package/dist/internal/runtime-coordinator.d.ts +14 -0
- package/dist/internal/runtime-coordinator.js +60 -11
- package/dist/internal/runtime-core.d.ts +3 -0
- package/dist/internal/runtime-core.js +1 -0
- package/dist/internal/runtime-reconciliation.js +123 -25
- package/dist/internal/session-context.d.ts +5 -5
- package/dist/internal/session-context.js +80 -83
- package/dist/internal/session-status.d.ts +2 -2
- package/dist/internal/session-status.js +5 -3
- package/dist/internal/woodstock/agent-reply.d.ts +5 -0
- package/dist/internal/woodstock/agent-reply.js +3 -0
- package/dist/internal/woodstock/client-projection.js +13 -11
- package/dist/internal/woodstock/fleet-store.d.ts +3 -2
- package/dist/internal/woodstock/fleet-store.js +24 -4
- package/dist/internal/woodstock/index.d.ts +7 -3
- package/dist/internal/woodstock/index.js +23 -6
- package/dist/internal/woodstock/progress.d.ts +11 -1
- package/dist/internal/woodstock/progress.js +126 -25
- package/dist/internal/woodstock/session-delegator.d.ts +13 -0
- package/dist/internal/woodstock/session-delegator.js +44 -0
- package/dist/internal/woodstock/session-entry.d.ts +12 -2
- package/dist/internal/woodstock/session-store.d.ts +16 -2
- package/dist/internal/woodstock/session-store.js +349 -66
- package/dist/internal/woodstock/work-senders.d.ts +32 -0
- package/dist/internal/woodstock/work-senders.js +70 -0
- package/dist/node.js +11 -2
- package/dist/remote-client.js +7 -2
- package/dist/server-visitor-stream.d.ts +13 -0
- package/dist/server-visitor-stream.js +124 -0
- package/dist/session-attention.d.ts +30 -0
- package/dist/session-attention.js +14 -0
- package/docs/api/agent-control.md +212 -15
- package/docs/api/client-entry.md +3 -0
- package/docs/api/client.md +209 -5
- package/docs/api/cloudflare.md +23 -1
- package/docs/api/delegation.md +284 -77
- package/docs/api/effect.md +100 -12
- package/docs/api/extensions.md +73 -12
- package/docs/api/fleet.md +138 -25
- package/docs/api/http.md +28 -0
- package/docs/api/index.md +43 -17
- package/docs/api/testing.md +9 -7
- package/package.json +10 -6
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { nanoid } from "@fungi.computer/cairn";
|
|
2
2
|
import { parseModelPreference } from "./model-preference.js";
|
|
3
3
|
import { acceptedNotice } from "@fungi.computer/bird-dog/effect";
|
|
4
|
+
import { FungiMessageMetaSchema } from "@fungi.computer/agar/agents";
|
|
4
5
|
import * as Effect from "effect/Effect";
|
|
5
6
|
import * as Option from "effect/Option";
|
|
6
7
|
import * as Stream from "effect/Stream";
|
|
7
8
|
import { ShiitakeError } from "../errors.js";
|
|
8
9
|
import { decodeClientReadOptions, decodeClientSettlementRequest, requireDecoded, decodeClientSessionCommand, decodeClientSessionId, decodeClientSubscribeOptions, } from "./client-boundary.js";
|
|
9
|
-
import { mergeObservationSignals, openObservationKernel, publishOwnerObservation, } from "./observation-kernel.js";
|
|
10
|
+
import { mergeObservationSignals, openObservationKernel, publishAgentExecutionChange, publishOwnerObservation, } from "./observation-kernel.js";
|
|
10
11
|
import { checkHostMeta } from "./host-meta.js";
|
|
11
|
-
import { readSessionActivity } from "./session-status.js";
|
|
12
|
-
import {
|
|
13
|
-
import { abortSession, cancelSession, branchSession, recordOperatorLine, forkSession, readSession, readCommandSettlement, readSessionModelPreference, readSessionReplayPage, readSessionInbox, readLatestPendingPrompt, createSession, deleteSession, deliverSessionGrants, readSessionGrants, setSessionModelPreference, setSessionTitle, readAdmissionIdentity, assertAdmissionIdentity, } from "./woodstock/index.js";
|
|
12
|
+
import { readSessionActivity, } from "./session-status.js";
|
|
13
|
+
import { prepareSessionContext } from "./session-context.js";
|
|
14
|
+
import { progressPosition, readProcessProgressDelta, abortSession, cancelSession, branchSession, recordOperatorLine, forkSession, readSession, readCommandSettlement, readSessionModelPreference, readSessionReplayPage, readSessionInbox, readLatestPendingPrompt, createSession, deleteSession, deliverSessionGrants, readSessionGrants, setSessionModelPreference, setSessionTitle, readAdmissionIdentity, assertAdmissionIdentity, } from "./woodstock/index.js";
|
|
14
15
|
/** Generic Agent-scope extension controls over the runtime's one call door. */
|
|
15
16
|
function agentExtensions(runtime) {
|
|
16
17
|
return Object.freeze({
|
|
@@ -19,12 +20,15 @@ function agentExtensions(runtime) {
|
|
|
19
20
|
call: (extensionId, command, input) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "agent" }, extensionId, command, input))),
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
+
function currentSessionWork(runtime, sessionId) {
|
|
23
24
|
return runtime
|
|
24
25
|
.readQueueHead(sessionId)
|
|
25
26
|
.pipe(Effect.flatMap((head) => (head.state === "queued"
|
|
26
27
|
? runtime.runningWork(head.jobId)
|
|
27
|
-
: Effect.succeed(null)).pipe(Effect.map((runningWork) =>
|
|
28
|
+
: Effect.succeed(null)).pipe(Effect.map((runningWork) => ({ head, runningWork })))));
|
|
29
|
+
}
|
|
30
|
+
function currentSessionActivity(options, runtime, sessionId) {
|
|
31
|
+
return currentSessionWork(runtime, sessionId).pipe(Effect.map(({ head, runningWork }) => readSessionActivity(options.store, sessionId, head, runningWork)));
|
|
28
32
|
}
|
|
29
33
|
/** Carry the Session's context use on a baseline, when it has one. */
|
|
30
34
|
function withContext(snapshot, context) {
|
|
@@ -34,7 +38,10 @@ function withContext(snapshot, context) {
|
|
|
34
38
|
}
|
|
35
39
|
/** Read the baseline and its context use for one Session. */
|
|
36
40
|
function readSessionWithContext(options, runtime, sessionId, input) {
|
|
37
|
-
return
|
|
41
|
+
return currentSessionWork(runtime, sessionId).pipe(Effect.flatMap(({ head, runningWork }) => readSession(options.store, sessionId, () => readSessionActivity(options.store, sessionId, head, runningWork), input, () => runtime.extensions.project(sessionId), (snapshot) => ({
|
|
42
|
+
snapshot,
|
|
43
|
+
context: prepareSessionContext(options, sessionId, snapshot.cursor, input?.before === undefined),
|
|
44
|
+
}))), Effect.flatMap(({ snapshot, context }) => context.pipe(Effect.map((value) => withContext(snapshot, value)))));
|
|
38
45
|
}
|
|
39
46
|
function sessionOwnerOperation(runtime, sessionId, operation) {
|
|
40
47
|
return runtime.assertOpen.pipe(Effect.andThen(Effect.try({
|
|
@@ -91,7 +98,46 @@ function continueFromReset(frame) {
|
|
|
91
98
|
}),
|
|
92
99
|
];
|
|
93
100
|
}
|
|
94
|
-
function subscriptionFrames(options, runtime, registration, sessionId, after, delivery, includeSandboxFailures = false) {
|
|
101
|
+
function subscriptionFrames(options, runtime, registration, sessionId, after, delivery, includeSandboxFailures = false, baselineProgress = null) {
|
|
102
|
+
let position = progressPosition(baselineProgress);
|
|
103
|
+
let toolOutput = [];
|
|
104
|
+
const reset = () => resetFrame(options, runtime, sessionId, includeSandboxFailures).pipe(Effect.map((frame) => {
|
|
105
|
+
position = progressPosition(frame.baseline.progress);
|
|
106
|
+
toolOutput = frame.baseline.toolOutput;
|
|
107
|
+
return continueFromReset(frame);
|
|
108
|
+
}));
|
|
109
|
+
const liveFrames = (activity) => {
|
|
110
|
+
const queue = activity.facts.queue;
|
|
111
|
+
const delta = queue.state !== "running"
|
|
112
|
+
? null
|
|
113
|
+
: readProcessProgressDelta(options.store, sessionId, queue.commandId, position);
|
|
114
|
+
if (delta === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
const frames = [];
|
|
117
|
+
if (delta !== null) {
|
|
118
|
+
frames.push({
|
|
119
|
+
kind: "progress",
|
|
120
|
+
progress: delta,
|
|
121
|
+
activity: activity.facts,
|
|
122
|
+
});
|
|
123
|
+
position = {
|
|
124
|
+
messageId: delta.messageId,
|
|
125
|
+
revision: delta.revision,
|
|
126
|
+
text: delta.text.offset + delta.text.append.length,
|
|
127
|
+
thinking: delta.thinking.offset + delta.thinking.append.length,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (toolOutput.length !== activity.toolOutput.length ||
|
|
131
|
+
toolOutput.some((slot, index) => slot !== activity.toolOutput[index])) {
|
|
132
|
+
toolOutput = activity.toolOutput;
|
|
133
|
+
frames.push({
|
|
134
|
+
kind: "tool_output",
|
|
135
|
+
toolOutput,
|
|
136
|
+
activity: activity.facts,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return frames;
|
|
140
|
+
};
|
|
95
141
|
const initial = {
|
|
96
142
|
after,
|
|
97
143
|
delivery,
|
|
@@ -105,64 +151,73 @@ function subscriptionFrames(options, runtime, registration, sessionId, after, de
|
|
|
105
151
|
if (signal === "closed")
|
|
106
152
|
return Effect.succeed([[], Option.none()]);
|
|
107
153
|
if (signal === "reset")
|
|
108
|
-
return
|
|
154
|
+
return reset();
|
|
109
155
|
return runtime.assertOpen.pipe(Effect.andThen(currentSessionActivity(options, runtime, sessionId)), Effect.flatMap((activity) => {
|
|
110
156
|
registration.setQueued(activity.facts.queue.state === "queued");
|
|
111
|
-
if (signal === "
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (signal === "progress" && state.delivery === "live") {
|
|
157
|
+
if ((signal === "progress" || signal === "activity") &&
|
|
158
|
+
state.delivery === "live") {
|
|
159
|
+
const frames = liveFrames(activity);
|
|
160
|
+
if (frames === undefined)
|
|
161
|
+
return reset();
|
|
117
162
|
return Effect.succeed([
|
|
118
163
|
[
|
|
119
|
-
...(
|
|
120
|
-
? [
|
|
121
|
-
: [
|
|
164
|
+
...(signal === "activity"
|
|
165
|
+
? [
|
|
122
166
|
{
|
|
123
|
-
kind: "
|
|
124
|
-
progress: activity.progress,
|
|
167
|
+
kind: "activity",
|
|
125
168
|
activity: activity.facts,
|
|
126
169
|
},
|
|
127
|
-
]
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
toolOutput: activity.toolOutput,
|
|
131
|
-
activity: activity.facts,
|
|
132
|
-
},
|
|
170
|
+
]
|
|
171
|
+
: []),
|
|
172
|
+
...frames,
|
|
133
173
|
],
|
|
134
174
|
Option.some({ ...state, awaitSignal: true }),
|
|
135
175
|
]);
|
|
136
176
|
}
|
|
137
|
-
return readSessionReplayPage(options.store, sessionId, state.after, state.delivery, activity, () => runtime.extensions.project(sessionId), includeSandboxFailures
|
|
177
|
+
return readSessionReplayPage(options.store, sessionId, state.after, state.delivery, activity, () => runtime.extensions.project(sessionId), includeSandboxFailures, (page) => {
|
|
178
|
+
const latestCursor = page.kind === "page"
|
|
179
|
+
? page.frames
|
|
180
|
+
.flatMap((frame) => frame.kind === "progress" ||
|
|
181
|
+
frame.kind === "activity" ||
|
|
182
|
+
frame.kind === "tool_output"
|
|
183
|
+
? []
|
|
184
|
+
: [frame.cursor])
|
|
185
|
+
.at(-1)
|
|
186
|
+
: undefined;
|
|
187
|
+
// Prepare a figure only for the final moving update,
|
|
188
|
+
// while its cursor and stored context share the read.
|
|
189
|
+
const context = page.kind === "page" &&
|
|
190
|
+
!page.hasMore &&
|
|
191
|
+
page.frames.some((frame) => frame.kind === "update" &&
|
|
192
|
+
frame.cursor !== state.after)
|
|
193
|
+
? prepareSessionContext(options, sessionId, latestCursor ?? null)
|
|
194
|
+
: Effect.succeed(undefined);
|
|
195
|
+
return { page, latestCursor, context };
|
|
196
|
+
}).pipe(Effect.flatMap(({ page, latestCursor, context, }) => {
|
|
138
197
|
if (page.kind === "reset")
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
frame.kind === "tool_output"
|
|
198
|
+
return reset();
|
|
199
|
+
if (!page.hasMore)
|
|
200
|
+
toolOutput = activity.toolOutput;
|
|
201
|
+
const progressFrames = page.hasMore
|
|
144
202
|
? []
|
|
145
|
-
:
|
|
146
|
-
|
|
203
|
+
: liveFrames(activity);
|
|
204
|
+
if (progressFrames === undefined)
|
|
205
|
+
return reset();
|
|
147
206
|
const next = {
|
|
148
207
|
after: latestCursor ?? state.after,
|
|
149
208
|
delivery: page.hasMore ? state.delivery : "live",
|
|
150
209
|
awaitSignal: !page.hasMore,
|
|
151
210
|
};
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
: page.frames.map((frame) => frame.kind === "update" &&
|
|
163
|
-
frame.cursor === latestCursor
|
|
164
|
-
? Object.freeze({ ...frame, context })
|
|
165
|
-
: frame),
|
|
211
|
+
return context.pipe(Effect.map((context) => [
|
|
212
|
+
[
|
|
213
|
+
...(context === undefined
|
|
214
|
+
? page.frames
|
|
215
|
+
: page.frames.map((frame) => frame.kind === "update" &&
|
|
216
|
+
frame.cursor === latestCursor
|
|
217
|
+
? Object.freeze({ ...frame, context })
|
|
218
|
+
: frame)),
|
|
219
|
+
...progressFrames,
|
|
220
|
+
],
|
|
166
221
|
Option.some(next),
|
|
167
222
|
]));
|
|
168
223
|
}));
|
|
@@ -190,7 +245,7 @@ function watchSession(options, runtime, observation, sessionId, input) {
|
|
|
190
245
|
registration.setQueued(snapshot.activity.queue.state === "queued");
|
|
191
246
|
return Object.freeze({
|
|
192
247
|
snapshot,
|
|
193
|
-
changes: subscriptionFrames(options, runtime, registration, decoded.sessionId, snapshot.cursor ?? undefined, "live", decoded.options.includeSandboxFailures === true).pipe(Stream.ensuring(Effect.sync(registration.close))),
|
|
248
|
+
changes: subscriptionFrames(options, runtime, registration, decoded.sessionId, snapshot.cursor ?? undefined, "live", decoded.options.includeSandboxFailures === true, snapshot.progress).pipe(Stream.ensuring(Effect.sync(registration.close))),
|
|
194
249
|
close: Effect.sync(registration.close),
|
|
195
250
|
});
|
|
196
251
|
}))).pipe(Effect.onError(() => Effect.sync(registration.close)));
|
|
@@ -288,7 +343,9 @@ function executeSessionCommand(options, runtime, sessionId, meta, input) {
|
|
|
288
343
|
.commit(sessionOwnerOperation(runtime, decodedSessionId, (decoded) => {
|
|
289
344
|
setSessionTitle(options.store, decoded, command.title);
|
|
290
345
|
}))
|
|
291
|
-
.pipe(Effect.
|
|
346
|
+
.pipe(Effect.tap(() => Effect.sync(() => {
|
|
347
|
+
publishAgentExecutionChange(options.store);
|
|
348
|
+
})), Effect.as(undefined));
|
|
292
349
|
}
|
|
293
350
|
}));
|
|
294
351
|
}
|
|
@@ -348,7 +405,7 @@ export function openCoordinator(options, runtime) {
|
|
|
348
405
|
})), Effect.asVoid))),
|
|
349
406
|
}),
|
|
350
407
|
extension: (extensionId) => Object.freeze({
|
|
351
|
-
call: (command, input) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "session", sessionId }, extensionId, command, input))),
|
|
408
|
+
call: (command, input, caller) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "session", sessionId }, extensionId, command, input, caller))),
|
|
352
409
|
}),
|
|
353
410
|
read: (input) => runtime.assertOpen.pipe(Effect.andThen(readSessionWithContext(options, runtime, sessionId, input))),
|
|
354
411
|
readSettlement: (commandId, timeoutMs = 0) => Effect.scoped(Effect.gen(function* () {
|
|
@@ -410,6 +467,11 @@ export function openCoordinator(options, runtime) {
|
|
|
410
467
|
}),
|
|
411
468
|
});
|
|
412
469
|
}
|
|
470
|
+
/** Whether the sender asked this mail to steer the recipient's running turn. */
|
|
471
|
+
function steersRunningTurn(mail) {
|
|
472
|
+
const meta = FungiMessageMetaSchema.safeParse(mail.meta);
|
|
473
|
+
return meta.success && meta.data.delegation?.steer === true;
|
|
474
|
+
}
|
|
413
475
|
/**
|
|
414
476
|
* Accept one addressed mail envelope; the door decodes it as a prompt. `reply`
|
|
415
477
|
* is the host's authentication of a delegated Agent's reply; only then does
|
|
@@ -449,6 +511,7 @@ export function acceptAddressedMail(runtime, mail, meta, reply, stageMail, sessi
|
|
|
449
511
|
const result = admit(meta === undefined ? request : { ...request, meta });
|
|
450
512
|
if (result.status !== "accepted")
|
|
451
513
|
throw new Error("Sessionless addressed mail was not admitted");
|
|
514
|
+
runtime.recordCreatedSessionDelegatorWithinTransaction(sessionId, addressed);
|
|
452
515
|
return {
|
|
453
516
|
status: "accepted",
|
|
454
517
|
commandId: result.commandId,
|
|
@@ -458,7 +521,7 @@ export function acceptAddressedMail(runtime, mail, meta, reply, stageMail, sessi
|
|
|
458
521
|
}
|
|
459
522
|
return admitUserWork(runtime, mail.to.sessionId, {
|
|
460
523
|
requestId: mail.key,
|
|
461
|
-
delivery: reply
|
|
524
|
+
delivery: reply !== undefined || steersRunningTurn(mail) ? "steer" : "followUp",
|
|
462
525
|
type: "prompt",
|
|
463
526
|
text: mail.content,
|
|
464
527
|
}, meta, mail, reply);
|
|
@@ -72,7 +72,7 @@ function sessionFacade(address, session) {
|
|
|
72
72
|
extension: (extensionId) => {
|
|
73
73
|
const extension = session.extension(extensionId);
|
|
74
74
|
return Object.freeze({
|
|
75
|
-
call: (command, input) => runShiitakePromise(extension.call(command, input)),
|
|
75
|
+
call: (command, input, caller) => runShiitakePromise(extension.call(command, input, caller)),
|
|
76
76
|
});
|
|
77
77
|
},
|
|
78
78
|
read: (input) => runShiitakePromise(session.read(input)),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HostMetaOptions } from "./host-meta.js";
|
|
2
2
|
import type { ExtensionRosterV0 } from "../extensions.js";
|
|
3
|
-
import type { Api, AssistantMessage, Model, MuleStreamFunction, RetryPolicy } from "@fungi.computer/mule/effect";
|
|
3
|
+
import type { Api, AssistantMessage, Model, MuleStreamFunction, ProviderRejection, RetryPolicy } from "@fungi.computer/mule/effect";
|
|
4
4
|
import type { CapabilitySource, ExecutionOptions, ModuleRegistration } from "@fungi.computer/mycelium";
|
|
5
5
|
import type { CommandSettlement, GrantsDelivery, GrantsDeliveryResult, ToolAccess } from "./contracts.js";
|
|
6
6
|
import type { Sandbox } from "@fungi.computer/mycelium/effect";
|
|
@@ -26,6 +26,8 @@ export type ModelAcquisitionEffect = Readonly<{
|
|
|
26
26
|
retryable: (message: AssistantMessage) => boolean;
|
|
27
27
|
/** Owner-authored reason for this door's failure message, never SDK prose. */
|
|
28
28
|
failureReason?: (message: AssistantMessage) => string | undefined;
|
|
29
|
+
/** Bounded provider facts for this door's exact failure, never SDK prose. */
|
|
30
|
+
rejection?: (message: AssistantMessage) => ProviderRejection | undefined;
|
|
29
31
|
/** Provider prose for this door's exact failed turn; never a diagnostic. */
|
|
30
32
|
providerError?: (message: AssistantMessage) => string | undefined;
|
|
31
33
|
/** Effect finalizer for acquired model resources. */
|
|
@@ -99,6 +101,11 @@ export type ShiitakeEffectOptions = Readonly<{
|
|
|
99
101
|
store: SqliteOwner;
|
|
100
102
|
/** Host storage-pressure check; absent never refuses for storage pressure. */
|
|
101
103
|
storagePressure?: StoragePressureCheck;
|
|
104
|
+
/** Host-owned synchronous new-work admission at the common door. */
|
|
105
|
+
admission?: Readonly<{
|
|
106
|
+
/** Check the host's current flag synchronously inside admission's transaction. */
|
|
107
|
+
assertOpen: () => void;
|
|
108
|
+
}>;
|
|
102
109
|
/** Compaction budgets; absent uses Pi's defaults for every model. */
|
|
103
110
|
compaction?: CompactionOptions;
|
|
104
111
|
/** One host lane in Shiitake's existing Watchdog, opened before recovery. */
|
|
@@ -201,7 +208,7 @@ export type ShiitakeEffectSession = Readonly<{
|
|
|
201
208
|
/** Effect-native Session-scope extension commands. */
|
|
202
209
|
export type ShiitakeEffectSessionExtension = Readonly<{
|
|
203
210
|
/** Run one command with its encoded input; succeeds with the encoded output. */
|
|
204
|
-
call: (command: string, input: Json) => Effect.Effect<Json, ShiitakeError>;
|
|
211
|
+
call: (command: string, input: Json, caller?: import("../extensions.js").ExtensionCommandCallerV0) => Effect.Effect<Json, ShiitakeError>;
|
|
205
212
|
}>;
|
|
206
213
|
/** Effect-native Agent-scope extension controls. */
|
|
207
214
|
export type ShiitakeEffectAgentExtensions = Readonly<{
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import * as Result from "effect/Result";
|
|
3
3
|
import * as Schema from "effect/Schema";
|
|
4
|
-
import type { AdmissionError, CommandHandlersV0, CommandsV0, ExtensionCommandError, ExtensionRead, ExtensionStateError, ExtensionTx, Json } from "../extensions.js";
|
|
4
|
+
import type { AdmissionError, CommandHandlersV0, CommandsV0, ExtensionCommandError, ExtensionCommandCallerV0, ExtensionRead, ExtensionStateError, ExtensionTx, Json } from "../extensions.js";
|
|
5
5
|
/** A parsed invocation retains its typed handler until the transaction runs. */
|
|
6
6
|
export type RegisteredExtensionCommandV0 = (frame: Readonly<{
|
|
7
7
|
sessionId: string | null;
|
|
8
8
|
input: Json;
|
|
9
|
+
caller?: ExtensionCommandCallerV0;
|
|
9
10
|
}>) => Result.Result<() => Effect.Effect<Json, ExtensionCommandError | ExtensionStateError | AdmissionError, ExtensionTx | ExtensionRead>, Schema.SchemaError>;
|
|
10
11
|
/** Capture each declared command's schemas and matching handler at activation. */
|
|
11
12
|
export declare function registerExtensionCommands<C extends CommandsV0>(specs: C, handlers: CommandHandlersV0<C>): ReadonlyMap<string, RegisteredExtensionCommandV0>;
|
|
@@ -13,7 +13,9 @@ function bindCommand(spec, handler) {
|
|
|
13
13
|
const decodeInput = Schema.decodeUnknownResult(spec.input, {
|
|
14
14
|
onExcessProperty: "error",
|
|
15
15
|
});
|
|
16
|
-
return (frame) => Result.flatMap(decodeSession(frame.sessionId), (sessionId) => Result.map(decodeInput(frame.input), (input) => () => invokeTransactionHook(() => handler(
|
|
16
|
+
return (frame) => Result.flatMap(decodeSession(frame.sessionId), (sessionId) => Result.map(decodeInput(frame.input), (input) => () => invokeTransactionHook(() => handler(frame.caller === undefined
|
|
17
|
+
? { sessionId, input }
|
|
18
|
+
: { sessionId, input, caller: frame.caller })).pipe(Effect.flatMap((output) => Schema.encodeEffect(spec.output)(output).pipe(Effect.flatMap(Schema.decodeUnknownEffect(JsonValueSchema)), Effect.orDie)))));
|
|
17
19
|
}
|
|
18
20
|
/** Capture each declared command's schemas and matching handler at activation. */
|
|
19
21
|
export function registerExtensionCommands(specs, handlers) {
|
|
@@ -9,6 +9,8 @@ import type { SqliteOwner } from "./contracts.js";
|
|
|
9
9
|
import type { AdmittedExtensionV0 } from "./extension-roster.js";
|
|
10
10
|
/** The settled consumer name core itself uses for mail and control repair. */
|
|
11
11
|
export declare const coreConsumer = "core";
|
|
12
|
+
/** Core reports to delegators after the extensions have seen a settlement. */
|
|
13
|
+
export declare const reportConsumer = "core-report";
|
|
12
14
|
/** Core's generic view of rostered v1 extensions. */
|
|
13
15
|
export type ExtensionRuntime = Readonly<{
|
|
14
16
|
/** Prepare additions without replacing any live member; apply only between turns. */
|
|
@@ -53,7 +55,7 @@ export type ExtensionRuntime = Readonly<{
|
|
|
53
55
|
sessionId: string;
|
|
54
56
|
} | {
|
|
55
57
|
scope: "agent";
|
|
56
|
-
}>, extensionId: string, command: string, input: Json) => Effect.Effect<Json, ShiitakeError>;
|
|
58
|
+
}>, extensionId: string, command: string, input: Json, caller?: import("../extensions.js").ExtensionCommandCallerV0) => Effect.Effect<Json, ShiitakeError>;
|
|
57
59
|
list: () => readonly ExtensionStatusV0[];
|
|
58
60
|
resume: (id: string) => Effect.Effect<ExtensionStatusV0, ShiitakeError>;
|
|
59
61
|
close: Effect.Effect<void, ShiitakeError>;
|
|
@@ -12,11 +12,13 @@ import { ShiitakeError } from "../errors.js";
|
|
|
12
12
|
import { AdmissionError, ExtensionCommandError, ExtensionRead, ExtensionStateError, ExtensionStop, ExtensionTx, } from "../extensions.js";
|
|
13
13
|
import { decodeClientSessionId } from "./client-boundary.js";
|
|
14
14
|
import { AsyncExtensionTransactionError, invokeExtensionHook, invokeSyncHandler, } from "./extension-hook.js";
|
|
15
|
-
import { setSessionAttention, clearSessionAttention, projectSessionAttention, readAttentionSessionPage, replaceAttentionGrants, hasStoredSession, readSessionLineage, hasPendingSessionSteer, hasSuccessfulToolResult, advanceCursor, declareSessionDocument, readSessionDocument, writeSessionDocument, deleteAllRecords, deleteRecord, deleteTimer, deleteExtensionTimers, readAllRecords, readCursor, readDueTimers, readRecord, readRecordPage, readStateLedger, readStatus, readTimer, readTimerWake, writeRecord, writeStateLedger, writeStatus, writeTimer, } from "./woodstock/index.js";
|
|
15
|
+
import { hasSessionAttention, setSessionAttention, clearSessionAttention, projectSessionAttention, readAttentionSessionPage, replaceAttentionGrants, hasStoredSession, readSessionLineage, hasPendingSessionSteer, readWorkSender, hasSuccessfulToolResult, advanceCursor, declareSessionDocument, readSessionDocument, writeSessionDocument, deleteAllRecords, deleteRecord, deleteTimer, deleteExtensionTimers, readAllRecords, readCursor, readDueTimers, readRecord, readRecordPage, readStateLedger, readStatus, readTimer, readTimerWake, writeRecord, writeStateLedger, writeStatus, writeTimer, } from "./woodstock/index.js";
|
|
16
16
|
import { JsonValueSchema } from "./json-value.js";
|
|
17
|
-
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
17
|
+
import { hasOwnerObservers, publishOwnerObservation, } from "./observation-kernel.js";
|
|
18
18
|
/** The settled consumer name core itself uses for mail and control repair. */
|
|
19
19
|
export const coreConsumer = "core";
|
|
20
|
+
/** Core reports to delegators after the extensions have seen a settlement. */
|
|
21
|
+
export const reportConsumer = "core-report";
|
|
20
22
|
const maximumKeyLength = 256;
|
|
21
23
|
const maximumRecordBytes = 1024 * 1024;
|
|
22
24
|
const maximumReasonLength = 512;
|
|
@@ -288,7 +290,7 @@ export function openExtensionRuntime(input) {
|
|
|
288
290
|
set: (sessionId, item) => {
|
|
289
291
|
const decoded = Schema.decodeUnknownResult(AttentionItemSchema, {
|
|
290
292
|
onExcessProperty: "error",
|
|
291
|
-
})(item);
|
|
293
|
+
})(item.waitingOn === undefined ? item : { ...item, inbox: false });
|
|
292
294
|
return decoded._tag === "Failure"
|
|
293
295
|
? Effect.fail(stateError("bound", "Attention item is invalid"))
|
|
294
296
|
: mutate(sessionId, () => {
|
|
@@ -297,6 +299,7 @@ export function openExtensionRuntime(input) {
|
|
|
297
299
|
setSessionAttention(owner, sessionId, id, decoded.success);
|
|
298
300
|
});
|
|
299
301
|
},
|
|
302
|
+
awaitsPerson: (sessionId) => requireRead(id).pipe(Effect.tap(() => grantGuard(member, "attention")), Effect.andThen(storeStep(() => hasSessionAttention(owner, sessionId)))),
|
|
300
303
|
clear: (sessionId, itemId) => boundedKey(itemId, "An attention id").pipe(Effect.andThen(mutate(sessionId, () => {
|
|
301
304
|
clearSessionAttention(owner, sessionId, id, itemId);
|
|
302
305
|
}))),
|
|
@@ -323,6 +326,7 @@ export function openExtensionRuntime(input) {
|
|
|
323
326
|
extensionId: id,
|
|
324
327
|
correlation: request.correlation ?? null,
|
|
325
328
|
},
|
|
329
|
+
continuesWorkId: request.continuesWorkId,
|
|
326
330
|
});
|
|
327
331
|
return result.status === "accepted"
|
|
328
332
|
? {
|
|
@@ -336,9 +340,11 @@ export function openExtensionRuntime(input) {
|
|
|
336
340
|
reason: error instanceof ShiitakeError
|
|
337
341
|
? error.cause === "conflict"
|
|
338
342
|
? "conflict"
|
|
339
|
-
: error.cause === "
|
|
340
|
-
? "
|
|
341
|
-
: "
|
|
343
|
+
: error.cause === "archived"
|
|
344
|
+
? "archived"
|
|
345
|
+
: error.cause === "closed"
|
|
346
|
+
? "closed"
|
|
347
|
+
: "invalid"
|
|
342
348
|
: "invalid",
|
|
343
349
|
message: error instanceof ShiitakeError
|
|
344
350
|
? error.message
|
|
@@ -567,6 +573,7 @@ export function openExtensionRuntime(input) {
|
|
|
567
573
|
send: sendFor(member),
|
|
568
574
|
timers: timersFor(member),
|
|
569
575
|
attention: attentionFor(member),
|
|
576
|
+
isObserved: (sessionId) => member.status === "running" && hasOwnerObservers(owner, sessionId),
|
|
570
577
|
sessionPosition: (sessionId) => requireRead(member.admitted.id).pipe(Effect.andThen(grantGuard(member, "state")), Effect.andThen(storeStep(() => {
|
|
571
578
|
const id = decodeClientSessionId(sessionId);
|
|
572
579
|
if (!hasStoredSession(owner, id))
|
|
@@ -575,6 +582,7 @@ export function openExtensionRuntime(input) {
|
|
|
575
582
|
?.newestSequence ?? 0);
|
|
576
583
|
}))),
|
|
577
584
|
hasPendingSteer: (run) => requireRead(member.admitted.id).pipe(Effect.andThen(grantGuard(member, "run_tools")), Effect.andThen(storeStep(() => hasPendingSessionSteer(owner, decodeClientSessionId(run.sessionId), run.workId)))),
|
|
585
|
+
workSender: (run) => requireRead(member.admitted.id).pipe(Effect.andThen(grantGuard(member, "run_tools")), Effect.andThen(storeStep(() => readWorkSender(owner, decodeClientSessionId(run.sessionId), run.workId) ?? null))),
|
|
578
586
|
hasSuccessfulToolResult: (run, toolCallId) => requireRead(member.admitted.id).pipe(Effect.andThen(grantGuard(member, "run_tools")), Effect.andThen(storeStep(() => hasSuccessfulToolResult(owner, decodeClientSessionId(run.sessionId), run.workId, toolCallId)))),
|
|
579
587
|
observe: (sessionId) => requireFrame(member.admitted.id).pipe(Effect.map((frame) => {
|
|
580
588
|
frame.observed.add(sessionId);
|
|
@@ -1020,7 +1028,7 @@ export function openExtensionRuntime(input) {
|
|
|
1020
1028
|
}
|
|
1021
1029
|
return Object.freeze(slots);
|
|
1022
1030
|
},
|
|
1023
|
-
call: (target, extensionId, command, raw) => Effect.gen(function* () {
|
|
1031
|
+
call: (target, extensionId, command, raw, caller) => Effect.gen(function* () {
|
|
1024
1032
|
const member = members.get(extensionId);
|
|
1025
1033
|
if (member?.admitted.grants.has("commands") !== true)
|
|
1026
1034
|
return yield* Effect.fail(new ShiitakeError("extensions", "unavailable", "Extension is unavailable"));
|
|
@@ -1049,7 +1057,9 @@ export function openExtensionRuntime(input) {
|
|
|
1049
1057
|
yield* recordStop(member, `command ${command} has no handler`, undefined, "own-transaction");
|
|
1050
1058
|
return yield* Effect.fail(new ShiitakeError("extensions", "unavailable", "Extension is stopped"));
|
|
1051
1059
|
}
|
|
1052
|
-
const prepared =
|
|
1060
|
+
const prepared = caller === undefined
|
|
1061
|
+
? handler({ sessionId, input: raw })
|
|
1062
|
+
: handler({ sessionId, input: raw, caller });
|
|
1053
1063
|
if (prepared._tag === "Failure")
|
|
1054
1064
|
return yield* Effect.fail(new ShiitakeError("contract", "invalid", "Extension command input is invalid"));
|
|
1055
1065
|
const body = prepared.success();
|
|
@@ -23,6 +23,8 @@ function asEffectAcquisition(acquisition) {
|
|
|
23
23
|
shared = { ...shared, failureReason: acquisition.failureReason };
|
|
24
24
|
if (acquisition.providerError !== undefined)
|
|
25
25
|
shared = { ...shared, providerError: acquisition.providerError };
|
|
26
|
+
if (acquisition.rejection !== undefined)
|
|
27
|
+
shared = { ...shared, rejection: acquisition.rejection };
|
|
26
28
|
if (acquisition.stream === undefined)
|
|
27
29
|
return acquisition.retryAfterMs === undefined
|
|
28
30
|
? Object.freeze(shared)
|
|
@@ -4,6 +4,8 @@ import type { SqliteOwner } from "./contracts.js";
|
|
|
4
4
|
/** Finite process-local signal; durable owner state remains authoritative. */
|
|
5
5
|
export type ObservationSignal = "closed" | "reset" | "durable" | "activity" | "progress";
|
|
6
6
|
export type ObservationReason = "durable" | "activity" | "progress" | "settlement";
|
|
7
|
+
/** Actual live-view demand, excluding exact-command settlement waiters. */
|
|
8
|
+
export declare function hasOwnerObservers(store: SqliteOwner, sessionId: string): boolean;
|
|
7
9
|
/** One scoped process-local observation registration. */
|
|
8
10
|
export type ObservationRegistration = Readonly<{
|
|
9
11
|
/** Inspect and consume one pending finite signal without waiting. */
|
|
@@ -4,6 +4,13 @@ import { reportCatchFailure } from "./failure.js";
|
|
|
4
4
|
const MAX_OBSERVATIONS = 64;
|
|
5
5
|
const MAX_PENDING_HINTS = 256;
|
|
6
6
|
const ownerPublishers = new WeakMap();
|
|
7
|
+
/** Actual live-view demand, excluding exact-command settlement waiters. */
|
|
8
|
+
export function hasOwnerObservers(store, sessionId) {
|
|
9
|
+
for (const publisher of ownerPublishers.get(store) ?? [])
|
|
10
|
+
if (publisher.observes(sessionId))
|
|
11
|
+
return true;
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
7
14
|
/** Combine coalesced reasons without allowing a progress hint to hide a commit. */
|
|
8
15
|
export function mergeObservationSignals(first, second) {
|
|
9
16
|
if (first === "closed" || second === "closed")
|
|
@@ -55,9 +62,9 @@ export function publishOwnerObservation(store, sessionId, reason = "durable") {
|
|
|
55
62
|
const publishers = ownerPublishers.get(store);
|
|
56
63
|
if (publishers === undefined)
|
|
57
64
|
return;
|
|
58
|
-
for (const
|
|
65
|
+
for (const publisher of publishers) {
|
|
59
66
|
try {
|
|
60
|
-
publish({ kind: "session", sessionId, reason });
|
|
67
|
+
publisher.publish({ kind: "session", sessionId, reason });
|
|
61
68
|
}
|
|
62
69
|
catch (cause) {
|
|
63
70
|
reportCatchFailure("owner-observation-publish", cause);
|
|
@@ -70,9 +77,9 @@ export function publishAgentExecutionChange(store) {
|
|
|
70
77
|
const publishers = ownerPublishers.get(store);
|
|
71
78
|
if (publishers === undefined)
|
|
72
79
|
return;
|
|
73
|
-
for (const
|
|
80
|
+
for (const publisher of publishers) {
|
|
74
81
|
try {
|
|
75
|
-
publish({ kind: "agent-execution" });
|
|
82
|
+
publisher.publish({ kind: "agent-execution" });
|
|
76
83
|
}
|
|
77
84
|
catch (cause) {
|
|
78
85
|
reportCatchFailure("owner-execution-change-publish", cause);
|
|
@@ -140,7 +147,11 @@ export function openObservationKernel(store) {
|
|
|
140
147
|
deliverObservation(listener, publication.reason === "settlement" ? "durable" : publication.reason);
|
|
141
148
|
}
|
|
142
149
|
};
|
|
143
|
-
|
|
150
|
+
const publisher = {
|
|
151
|
+
publish,
|
|
152
|
+
observes: (sessionId) => [...(sessions.get(sessionId) ?? [])].some((listener) => !listener.closed && !listener.settlementOnly),
|
|
153
|
+
};
|
|
154
|
+
publishers.add(publisher);
|
|
144
155
|
ownerPublishers.set(store, publishers);
|
|
145
156
|
return Object.freeze({
|
|
146
157
|
register: (sessionId, interest) => Effect.suspend(() => {
|
|
@@ -194,7 +205,7 @@ export function openObservationKernel(store) {
|
|
|
194
205
|
if (released)
|
|
195
206
|
return;
|
|
196
207
|
released = true;
|
|
197
|
-
publishers.delete(
|
|
208
|
+
publishers.delete(publisher);
|
|
198
209
|
if (publishers.size === 0)
|
|
199
210
|
ownerPublishers.delete(store);
|
|
200
211
|
for (const listener of active)
|
|
@@ -139,6 +139,8 @@ function liftOptions(options, runtime) {
|
|
|
139
139
|
Object.assign(base, { meta: options.meta });
|
|
140
140
|
if (options.storagePressure !== undefined)
|
|
141
141
|
Object.assign(base, { storagePressure: options.storagePressure });
|
|
142
|
+
if (options.admission !== undefined)
|
|
143
|
+
Object.assign(base, { admission: options.admission });
|
|
142
144
|
if (options.compaction !== undefined)
|
|
143
145
|
Object.assign(base, { compaction: options.compaction });
|
|
144
146
|
const toolSource = options.tools;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FileTool } from "@fungi.computer/knapsack";
|
|
2
|
+
import { type MuleError } from "@fungi.computer/mule/effect";
|
|
2
3
|
import type { ModuleRegistration } from "@fungi.computer/mycelium";
|
|
3
4
|
import * as Effect from "effect/Effect";
|
|
4
5
|
import { ShiitakeError } from "../errors.js";
|
|
@@ -17,6 +18,19 @@ export declare class ToolSetupError extends ShiitakeError {
|
|
|
17
18
|
constructor(phase: keyof typeof toolSetupMessages);
|
|
18
19
|
get diagnostic(): string;
|
|
19
20
|
}
|
|
21
|
+
/** Closed reasons a rejected run names, never a foreign error's own text. */
|
|
22
|
+
declare const runRejectedDiagnostics: {
|
|
23
|
+
readonly model: "model-failed";
|
|
24
|
+
readonly tool: "tool-failed";
|
|
25
|
+
readonly control: "control-failed";
|
|
26
|
+
readonly listener: "listener-failed";
|
|
27
|
+
};
|
|
28
|
+
/** A run Mule ended with a non-model error; it names a closed reason. */
|
|
29
|
+
export declare class RunRejectedError extends ShiitakeError {
|
|
30
|
+
private readonly reason;
|
|
31
|
+
constructor(reason: keyof typeof runRejectedDiagnostics, underlying: MuleError);
|
|
32
|
+
get diagnostic(): string;
|
|
33
|
+
}
|
|
20
34
|
/** Run one accepted command with Pi compaction explicitly composed around Mule. */
|
|
21
35
|
export declare function runAcceptedCommand(options: ShiitakeEffectOptions, prepared: PreparedSessionRun, executionContract: RunContract, extensionModules: readonly ModuleRegistration[], extensionFileTools: readonly FileTool[], compactionHandoff: CompactionHandoffV0 | undefined, promptSections?: () => Effect.Effect<readonly PromptSectionV0[], ShiitakeError>): Effect.Effect<{
|
|
22
36
|
run: import("@fungi.computer/mule/effect").MuleRunOutcome;
|