@fungi.computer/shiitake 0.5.0 → 0.6.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/dist/agent-control.d.ts +93 -1
- package/dist/agent-control.js +108 -6
- package/dist/client-entry.d.ts +20 -0
- package/dist/client-entry.js +5 -0
- package/dist/client-visitor.d.ts +31 -0
- package/dist/client-visitor.js +226 -0
- package/dist/client-wire.d.ts +5 -0
- package/dist/client-wire.js +6 -0
- package/dist/client.d.ts +4 -3
- package/dist/client.js +1 -0
- package/dist/delegation.d.ts +74 -53
- package/dist/delegation.js +4 -15
- package/dist/effect.d.ts +25 -0
- package/dist/effect.js +14 -1
- package/dist/extensions.d.ts +21 -2
- package/dist/extensions.js +1 -1
- package/dist/fleet.d.ts +15 -0
- package/dist/fleet.js +32 -6
- package/dist/hosted.js +3 -0
- package/dist/http-routes.d.ts +11 -0
- package/dist/internal/accepted-run-executor.js +2 -1
- package/dist/internal/admission-door.d.ts +5 -0
- package/dist/internal/admission-door.js +27 -1
- package/dist/internal/contracts.d.ts +14 -1
- package/dist/internal/coordinator.js +8 -2
- package/dist/internal/effect-client.js +1 -1
- package/dist/internal/effect-contracts.d.ts +4 -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 +13 -5
- 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/run-scope.d.ts +14 -0
- package/dist/internal/run-scope.js +32 -9
- package/dist/internal/runtime-coordinator.js +8 -1
- package/dist/internal/runtime-reconciliation.js +119 -24
- 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 +9 -1
- package/dist/internal/woodstock/index.d.ts +4 -0
- package/dist/internal/woodstock/index.js +7 -0
- package/dist/internal/woodstock/session-delegator.d.ts +12 -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 +2 -0
- package/dist/internal/woodstock/session-store.js +20 -0
- package/dist/internal/woodstock/work-senders.d.ts +32 -0
- package/dist/internal/woodstock/work-senders.js +70 -0
- package/dist/remote-client.js +7 -2
- package/dist/server-visitor-stream.d.ts +13 -0
- package/dist/server-visitor-stream.js +115 -0
- package/dist/session-attention.d.ts +30 -0
- package/dist/session-attention.js +14 -0
- package/docs/api/agent-control.md +209 -13
- package/docs/api/client-entry.md +3 -0
- package/docs/api/client.md +136 -1
- package/docs/api/cloudflare.md +7 -1
- package/docs/api/delegation.md +234 -61
- package/docs/api/effect.md +16 -6
- package/docs/api/extensions.md +65 -4
- package/docs/api/fleet.md +69 -16
- package/docs/api/index.md +6 -4
- package/package.json +10 -6
|
@@ -9,7 +9,7 @@ import { ExtensionId } from "../extension-contract.js";
|
|
|
9
9
|
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
10
10
|
import { returnedFailureReason } from "./run-failure-reason.js";
|
|
11
11
|
import { coreProtocolDigest } from "./extension-roster.js";
|
|
12
|
-
import { runAcceptedCommand, runAcceptedCompaction, ToolSetupError, } from "./run-scope.js";
|
|
12
|
+
import { runAcceptedCommand, runAcceptedCompaction, RunRejectedError, ToolSetupError, } from "./run-scope.js";
|
|
13
13
|
import { CompactionCommitError } from "./compaction/session-compaction.js";
|
|
14
14
|
import { BrokenSessionError, isCompactCommand, loadAcceptedRun, readExtensionAdmissionOrigin, recordWorkSettlement, } from "./woodstock/index.js";
|
|
15
15
|
/** Bounded process-loss recovery for every accepted Agent turn. */
|
|
@@ -25,6 +25,7 @@ function runtimeFailure(message) {
|
|
|
25
25
|
function failedDecision(error) {
|
|
26
26
|
const category = `${error.boundary}:${error.cause}`;
|
|
27
27
|
const diagnostic = error instanceof ToolSetupError ||
|
|
28
|
+
error instanceof RunRejectedError ||
|
|
28
29
|
error instanceof CompactionCommitError ||
|
|
29
30
|
error instanceof BrokenSessionError
|
|
30
31
|
? error.diagnostic
|
|
@@ -34,6 +34,11 @@ export type AdmitRequest = Readonly<{
|
|
|
34
34
|
reply?: VerifiedAgentReply;
|
|
35
35
|
/** Synchronous owner-local side effect committed with a mail admission. */
|
|
36
36
|
onAccepted?: (sessionId: string, commandId: string) => void;
|
|
37
|
+
/**
|
|
38
|
+
* New work that continues earlier work in this Session (for example, the
|
|
39
|
+
* follow-up that delivers an answer) keeps that work's sender.
|
|
40
|
+
*/
|
|
41
|
+
continuesWorkId?: string | undefined;
|
|
37
42
|
}>;
|
|
38
43
|
/** Outcome of one door admission. */
|
|
39
44
|
export type AdmitResult = Readonly<{
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { messageAuthor } from "@fungi.computer/agar/agents";
|
|
1
2
|
import * as Effect from "effect/Effect";
|
|
2
3
|
import * as Schema from "effect/Schema";
|
|
3
4
|
import { checkHostMeta } from "./host-meta.js";
|
|
4
5
|
import { ShiitakeError } from "../errors.js";
|
|
5
6
|
import { decodeClientAcceptedCommand, decodeClientSessionId, decodeClientSessionCreationRequestId, requireDecoded, } from "./client-boundary.js";
|
|
6
7
|
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
7
|
-
import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, VerifiedAgentReplySchema, } from "./woodstock/index.js";
|
|
8
|
+
import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, readWorkSender, recordWorkSender, recordSessionDelegator, VerifiedAgentReplySchema, } from "./woodstock/index.js";
|
|
8
9
|
const decodeOrigin = Schema.decodeUnknownResult(AdmissionOriginSchema, {
|
|
9
10
|
onExcessProperty: "error",
|
|
10
11
|
});
|
|
@@ -29,6 +30,24 @@ function decodeReply(request) {
|
|
|
29
30
|
throw new ShiitakeError("contract", "invalid", "Verified Agent reply does not match its mail");
|
|
30
31
|
return reply;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The Agent Session whose work this admission is: the sender of delegated
|
|
35
|
+
* mail that expects a reply, or the sender of the earlier work this one
|
|
36
|
+
* continues. Replies and every other admission have none.
|
|
37
|
+
*/
|
|
38
|
+
function workSender(owner, request) {
|
|
39
|
+
const mail = request.mail;
|
|
40
|
+
if (mail?.from.kind === "agent" && mail.replyTo.length > 0)
|
|
41
|
+
return {
|
|
42
|
+
agentId: mail.from.agentId,
|
|
43
|
+
sessionId: mail.from.sessionId,
|
|
44
|
+
// The host-authenticated author names the sender; mail.meta is the sender's own claim.
|
|
45
|
+
name: messageAuthor(request.meta)?.name ?? mail.from.agentId,
|
|
46
|
+
};
|
|
47
|
+
return request.continuesWorkId === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: readWorkSender(owner, request.sessionId, request.continuesWorkId);
|
|
50
|
+
}
|
|
32
51
|
/** Bind the door to one owner, run contract, Watchdog queue and host wake. */
|
|
33
52
|
export function openAdmissionDoor(input) {
|
|
34
53
|
/** The door's one decode, done before any owner operation when possible. */
|
|
@@ -41,6 +60,7 @@ export function openAdmissionDoor(input) {
|
|
|
41
60
|
mail: request.mail,
|
|
42
61
|
reply: decodeReply(request),
|
|
43
62
|
onAccepted: request.onAccepted,
|
|
63
|
+
continuesWorkId: request.continuesWorkId,
|
|
44
64
|
meta: request.meta === undefined
|
|
45
65
|
? undefined
|
|
46
66
|
: checkHostMeta(input.hostMeta, request.meta),
|
|
@@ -75,6 +95,12 @@ export function openAdmissionDoor(input) {
|
|
|
75
95
|
input.enqueue(work, request.mail);
|
|
76
96
|
}
|
|
77
97
|
: undefined, targetWorkId, request.reply);
|
|
98
|
+
if (accepted.delivery === "new" && targetWorkId === undefined) {
|
|
99
|
+
const sender = workSender(input.owner, request);
|
|
100
|
+
if (sender !== undefined)
|
|
101
|
+
recordWorkSender(input.owner, sessionId, accepted.accepted.commandId, sender);
|
|
102
|
+
}
|
|
103
|
+
recordSessionDelegator(input.owner, sessionId, request.mail);
|
|
78
104
|
request.onAccepted?.(sessionId, accepted.accepted.commandId);
|
|
79
105
|
touched.set(sessionId, (touched.get(sessionId) ?? false) || accepted.delivery === "new");
|
|
80
106
|
return Object.freeze({
|
|
@@ -3,7 +3,7 @@ import type { SessionTerminal, ShiitakeAgent } from "../client.js";
|
|
|
3
3
|
import type { GrantsDelivery, GrantsDeliveryResult } from "./woodstock/index.js";
|
|
4
4
|
import type { Json } from "../extensions.js";
|
|
5
5
|
import type { ExtensionRosterV0 } from "../extensions.js";
|
|
6
|
-
import type { Api, AssistantMessage, Model, MuleStreamFunction } from "@fungi.computer/mule/effect";
|
|
6
|
+
import type { Api, AssistantMessage, Model, MuleStreamFunction, ProviderRejection } from "@fungi.computer/mule/effect";
|
|
7
7
|
import type { AccessOptions, CapabilitySource, ExecutionOptions, ModuleRegistration, Sandbox } from "@fungi.computer/mycelium";
|
|
8
8
|
import type { ModelPreference } from "./model-preference.js";
|
|
9
9
|
import type { WatchdogSqliteValue, WatchdogSqliteRow, WatchdogSqliteOwner, WatchdogSqliteCursor } from "@fungi.computer/watchdog";
|
|
@@ -47,6 +47,8 @@ export type ModelAcquisition = Readonly<{
|
|
|
47
47
|
* Unknown messages must return undefined.
|
|
48
48
|
*/
|
|
49
49
|
failureReason?: (message: AssistantMessage) => string | undefined;
|
|
50
|
+
/** Bounded provider facts for this door's exact failure, never SDK prose. */
|
|
51
|
+
rejection?: (message: AssistantMessage) => ProviderRejection | undefined;
|
|
50
52
|
/** Provider prose for this door's exact failed turn; never a diagnostic. */
|
|
51
53
|
providerError?: (message: AssistantMessage) => string | undefined;
|
|
52
54
|
/** Release the acquired model resources. */
|
|
@@ -253,6 +255,8 @@ export type { HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, }
|
|
|
253
255
|
export type CommandSettlement = Readonly<{
|
|
254
256
|
/** Original Agent sender recorded at acceptance, never the terminal's canceller. */
|
|
255
257
|
acceptedSender: string | null;
|
|
258
|
+
/** Agent that first sent this Session work and receives its reports, if any. */
|
|
259
|
+
delegator: string | null;
|
|
256
260
|
/** Durable terminal and the command that ended the work's opaque metadata. */
|
|
257
261
|
terminal: SessionTerminal;
|
|
258
262
|
/** Last completed assistant text; cancelled work returns no answer. */
|
|
@@ -261,4 +265,13 @@ export type CommandSettlement = Readonly<{
|
|
|
261
265
|
usage: readonly Pick<AssistantMessage, "provider" | "model" | "usage">[];
|
|
262
266
|
/** Distinct tool-call intentions belonging to this exact work. */
|
|
263
267
|
toolCalls: number;
|
|
268
|
+
/**
|
|
269
|
+
* Open items of the Session that now wait on the Agent that sent this work.
|
|
270
|
+
* Child-authored data: summaries inform, only the sender's host checks decide.
|
|
271
|
+
*/
|
|
272
|
+
waitingOnYou: readonly Readonly<{
|
|
273
|
+
itemId: string;
|
|
274
|
+
kind: string;
|
|
275
|
+
summary: string;
|
|
276
|
+
}>[];
|
|
264
277
|
}>;
|
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
@@ -348,7 +349,7 @@ export function openCoordinator(options, runtime) {
|
|
|
348
349
|
})), Effect.asVoid))),
|
|
349
350
|
}),
|
|
350
351
|
extension: (extensionId) => Object.freeze({
|
|
351
|
-
call: (command, input) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "session", sessionId }, extensionId, command, input))),
|
|
352
|
+
call: (command, input, caller) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "session", sessionId }, extensionId, command, input, caller))),
|
|
352
353
|
}),
|
|
353
354
|
read: (input) => runtime.assertOpen.pipe(Effect.andThen(readSessionWithContext(options, runtime, sessionId, input))),
|
|
354
355
|
readSettlement: (commandId, timeoutMs = 0) => Effect.scoped(Effect.gen(function* () {
|
|
@@ -410,6 +411,11 @@ export function openCoordinator(options, runtime) {
|
|
|
410
411
|
}),
|
|
411
412
|
});
|
|
412
413
|
}
|
|
414
|
+
/** Whether the sender asked this mail to steer the recipient's running turn. */
|
|
415
|
+
function steersRunningTurn(mail) {
|
|
416
|
+
const meta = FungiMessageMetaSchema.safeParse(mail.meta);
|
|
417
|
+
return meta.success && meta.data.delegation?.steer === true;
|
|
418
|
+
}
|
|
413
419
|
/**
|
|
414
420
|
* Accept one addressed mail envelope; the door decodes it as a prompt. `reply`
|
|
415
421
|
* is the host's authentication of a delegated Agent's reply; only then does
|
|
@@ -458,7 +464,7 @@ export function acceptAddressedMail(runtime, mail, meta, reply, stageMail, sessi
|
|
|
458
464
|
}
|
|
459
465
|
return admitUserWork(runtime, mail.to.sessionId, {
|
|
460
466
|
requestId: mail.key,
|
|
461
|
-
delivery: reply
|
|
467
|
+
delivery: reply !== undefined || steersRunningTurn(mail) ? "steer" : "followUp",
|
|
462
468
|
type: "prompt",
|
|
463
469
|
text: mail.content,
|
|
464
470
|
}, 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. */
|
|
@@ -201,7 +203,7 @@ export type ShiitakeEffectSession = Readonly<{
|
|
|
201
203
|
/** Effect-native Session-scope extension commands. */
|
|
202
204
|
export type ShiitakeEffectSessionExtension = Readonly<{
|
|
203
205
|
/** Run one command with its encoded input; succeeds with the encoded output. */
|
|
204
|
-
call: (command: string, input: Json) => Effect.Effect<Json, ShiitakeError>;
|
|
206
|
+
call: (command: string, input: Json, caller?: import("../extensions.js").ExtensionCommandCallerV0) => Effect.Effect<Json, ShiitakeError>;
|
|
205
207
|
}>;
|
|
206
208
|
/** Effect-native Agent-scope extension controls. */
|
|
207
209
|
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
|
? {
|
|
@@ -567,6 +571,7 @@ export function openExtensionRuntime(input) {
|
|
|
567
571
|
send: sendFor(member),
|
|
568
572
|
timers: timersFor(member),
|
|
569
573
|
attention: attentionFor(member),
|
|
574
|
+
isObserved: (sessionId) => member.status === "running" && hasOwnerObservers(owner, sessionId),
|
|
570
575
|
sessionPosition: (sessionId) => requireRead(member.admitted.id).pipe(Effect.andThen(grantGuard(member, "state")), Effect.andThen(storeStep(() => {
|
|
571
576
|
const id = decodeClientSessionId(sessionId);
|
|
572
577
|
if (!hasStoredSession(owner, id))
|
|
@@ -575,6 +580,7 @@ export function openExtensionRuntime(input) {
|
|
|
575
580
|
?.newestSequence ?? 0);
|
|
576
581
|
}))),
|
|
577
582
|
hasPendingSteer: (run) => requireRead(member.admitted.id).pipe(Effect.andThen(grantGuard(member, "run_tools")), Effect.andThen(storeStep(() => hasPendingSessionSteer(owner, decodeClientSessionId(run.sessionId), run.workId)))),
|
|
583
|
+
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
584
|
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
585
|
observe: (sessionId) => requireFrame(member.admitted.id).pipe(Effect.map((frame) => {
|
|
580
586
|
frame.observed.add(sessionId);
|
|
@@ -1020,7 +1026,7 @@ export function openExtensionRuntime(input) {
|
|
|
1020
1026
|
}
|
|
1021
1027
|
return Object.freeze(slots);
|
|
1022
1028
|
},
|
|
1023
|
-
call: (target, extensionId, command, raw) => Effect.gen(function* () {
|
|
1029
|
+
call: (target, extensionId, command, raw, caller) => Effect.gen(function* () {
|
|
1024
1030
|
const member = members.get(extensionId);
|
|
1025
1031
|
if (member?.admitted.grants.has("commands") !== true)
|
|
1026
1032
|
return yield* Effect.fail(new ShiitakeError("extensions", "unavailable", "Extension is unavailable"));
|
|
@@ -1049,7 +1055,9 @@ export function openExtensionRuntime(input) {
|
|
|
1049
1055
|
yield* recordStop(member, `command ${command} has no handler`, undefined, "own-transaction");
|
|
1050
1056
|
return yield* Effect.fail(new ShiitakeError("extensions", "unavailable", "Extension is stopped"));
|
|
1051
1057
|
}
|
|
1052
|
-
const prepared =
|
|
1058
|
+
const prepared = caller === undefined
|
|
1059
|
+
? handler({ sessionId, input: raw })
|
|
1060
|
+
: handler({ sessionId, input: raw, caller });
|
|
1053
1061
|
if (prepared._tag === "Failure")
|
|
1054
1062
|
return yield* Effect.fail(new ShiitakeError("contract", "invalid", "Extension command input is invalid"));
|
|
1055
1063
|
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)
|
|
@@ -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;
|
|
@@ -10,9 +10,6 @@ import { openCompactionSettings } from "./compaction/settings.js";
|
|
|
10
10
|
import { acquireOwnedModel } from "./model-acquisition.js";
|
|
11
11
|
import { returnedFailureReason, unavailableRunReason, } from "./run-failure-reason.js";
|
|
12
12
|
import { isCompactCommand, openMuleRecorder, readSessionGrants, recordLiveRetryEvent, recordSandboxFailure, takeSessionControls, } from "./woodstock/index.js";
|
|
13
|
-
function runtimeFailure(message) {
|
|
14
|
-
return new ShiitakeError("runtime", "rejected", message);
|
|
15
|
-
}
|
|
16
13
|
const toolSetupMessages = {
|
|
17
14
|
missing: "Instruction capabilities have no tool runtime",
|
|
18
15
|
runtime: "Tool runtime failed",
|
|
@@ -29,6 +26,28 @@ export class ToolSetupError extends ShiitakeError {
|
|
|
29
26
|
return toolSetupMessages[this.phase];
|
|
30
27
|
}
|
|
31
28
|
}
|
|
29
|
+
/** Closed reasons a rejected run names, never a foreign error's own text. */
|
|
30
|
+
const runRejectedDiagnostics = {
|
|
31
|
+
model: "model-failed",
|
|
32
|
+
tool: "tool-failed",
|
|
33
|
+
control: "control-failed",
|
|
34
|
+
listener: "listener-failed",
|
|
35
|
+
};
|
|
36
|
+
/** A run Mule ended with a non-model error; it names a closed reason. */
|
|
37
|
+
export class RunRejectedError extends ShiitakeError {
|
|
38
|
+
reason;
|
|
39
|
+
constructor(reason, underlying) {
|
|
40
|
+
super("runtime", "rejected", "Mule rejected the durable command run", undefined, underlying);
|
|
41
|
+
this.reason = reason;
|
|
42
|
+
}
|
|
43
|
+
get diagnostic() {
|
|
44
|
+
return runRejectedDiagnostics[this.reason];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Name a non-model Mule error by its closed reason. */
|
|
48
|
+
function rejectedRun(error) {
|
|
49
|
+
return new RunRejectedError(error.phase, error);
|
|
50
|
+
}
|
|
32
51
|
/**
|
|
33
52
|
* Map Mule's retry transitions onto Woodstock's retained `live_retry` facts.
|
|
34
53
|
* A started attempt ends the backoff countdown before streaming resumes.
|
|
@@ -229,22 +248,26 @@ export function runAcceptedCommand(options, prepared, executionContract, extensi
|
|
|
229
248
|
acquired.failureReason?.(exit.value.message) ??
|
|
230
249
|
returnedFailureReason(exit.value);
|
|
231
250
|
failureReason = (failedModelAttempts > 1 &&
|
|
232
|
-
exit.value.modelLimitReason === undefined
|
|
251
|
+
exit.value.modelLimitReason === undefined &&
|
|
252
|
+
acquired.rejection?.(exit.value.message)?.code !==
|
|
253
|
+
"usage-limit-reached"
|
|
233
254
|
? `The model failed ${String(failedModelAttempts)} times on this conversation: ${cause}`
|
|
234
255
|
: cause).slice(0, 200);
|
|
235
256
|
return recorder(failedEnd, failureReason);
|
|
236
257
|
}
|
|
237
258
|
const error = Cause.findErrorOption(exit.cause);
|
|
238
|
-
return recorder(failedEnd, Option.
|
|
239
|
-
?
|
|
240
|
-
: unavailableRunReason({
|
|
259
|
+
return recorder(failedEnd, Option.isNone(error)
|
|
260
|
+
? unavailableRunReason({
|
|
241
261
|
runId: failedEnd.run.value,
|
|
242
262
|
provider: acquired.model.provider,
|
|
243
263
|
model: acquired.model.id,
|
|
244
|
-
})
|
|
264
|
+
})
|
|
265
|
+
: error.value.phase === "model"
|
|
266
|
+
? `model:rejected: ${error.value.message}`.slice(0, 200)
|
|
267
|
+
: `runtime:rejected: ${rejectedRun(error.value).diagnostic}`);
|
|
245
268
|
}), Effect.mapError((error) => error.phase === "model"
|
|
246
269
|
? new ShiitakeError("model", "rejected", error.message, undefined, error)
|
|
247
|
-
:
|
|
270
|
+
: rejectedRun(error)));
|
|
248
271
|
},
|
|
249
272
|
});
|
|
250
273
|
return { run, failureReason };
|
|
@@ -9,7 +9,7 @@ import { agentTurnRecovery, executeAcceptedRun, } from "./accepted-run-executor.
|
|
|
9
9
|
import { openAdmissionDoor } from "./admission-door.js";
|
|
10
10
|
import { openCompactionSettings } from "./compaction/settings.js";
|
|
11
11
|
import { admitExtensionRosterV0, } from "./extension-roster.js";
|
|
12
|
-
import { coreConsumer, openExtensionRuntime, } from "./extension-runtime.js";
|
|
12
|
+
import { coreConsumer, reportConsumer, openExtensionRuntime, } from "./extension-runtime.js";
|
|
13
13
|
import { reconcileRuntime } from "./runtime-reconciliation.js";
|
|
14
14
|
import { advanceCursor, createSessionWithinTransaction, deliverSessionGrants, prepareExtensionStore, projectSessionRun, openStorageAdmission, readCursor, } from "./woodstock/index.js";
|
|
15
15
|
function runtimeFailure(message) {
|
|
@@ -141,6 +141,12 @@ export function openRuntimeCoordinator(options, coreDigest, admitted, mailRoom)
|
|
|
141
141
|
options.store.transactionSync(() => {
|
|
142
142
|
advanceCursor(options.store, coreConsumer, 0);
|
|
143
143
|
});
|
|
144
|
+
// A store that predates reports starts them at the core cursor
|
|
145
|
+
// instead of reporting its history.
|
|
146
|
+
if (readCursor(options.store, reportConsumer) === undefined)
|
|
147
|
+
options.store.transactionSync(() => {
|
|
148
|
+
advanceCursor(options.store, reportConsumer, readCursor(options.store, coreConsumer) ?? 0);
|
|
149
|
+
});
|
|
144
150
|
},
|
|
145
151
|
catch: () => new ShiitakeError("store", "unavailable", "Extension store is unavailable"),
|
|
146
152
|
});
|
|
@@ -165,6 +171,7 @@ export function openRuntimeCoordinator(options, coreDigest, admitted, mailRoom)
|
|
|
165
171
|
const cursors = yield* Effect.try({
|
|
166
172
|
try: () => [
|
|
167
173
|
readCursor(options.store, coreConsumer) ?? 0,
|
|
174
|
+
readCursor(options.store, reportConsumer) ?? 0,
|
|
168
175
|
...extensions.settledConsumers().map(({ cursor }) => cursor),
|
|
169
176
|
],
|
|
170
177
|
catch: () => runtimeFailure("Settled cursor is unavailable"),
|