@fungi.computer/shiitake 0.7.0 → 0.9.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 +28 -0
- package/dist/client-wire.js +10 -13
- package/dist/client.d.ts +6 -10
- package/dist/client.js +1 -0
- package/dist/delegation.d.ts +4 -4
- package/dist/fleet.d.ts +5 -1
- package/dist/fleet.js +5 -1
- package/dist/http-routes.d.ts +4 -11
- package/dist/http-routes.js +2 -1
- package/dist/internal/coordinator.d.ts +2 -1
- package/dist/internal/coordinator.js +10 -218
- package/dist/internal/observation-kernel.d.ts +21 -0
- package/dist/internal/observation-kernel.js +140 -4
- package/dist/internal/run-scope.d.ts +1 -0
- package/dist/internal/run-scope.js +5 -3
- package/dist/internal/runtime-coordinator.js +12 -7
- package/dist/internal/runtime-core.js +2 -1
- package/dist/internal/runtime-reconciliation.js +5 -2
- package/dist/internal/session-observation.d.ts +37 -0
- package/dist/internal/session-observation.js +440 -0
- package/dist/internal/session-status.d.ts +2 -0
- package/dist/internal/session-status.js +17 -1
- package/dist/internal/woodstock/client-projection.d.ts +3 -1
- package/dist/internal/woodstock/client-projection.js +12 -2
- package/dist/internal/woodstock/index.d.ts +7 -1
- package/dist/internal/woodstock/index.js +47 -4
- package/dist/internal/woodstock/progress.d.ts +2 -0
- package/dist/internal/woodstock/progress.js +5 -0
- package/dist/internal/woodstock/session-entry.d.ts +2 -2
- package/dist/remote-client.js +5 -1
- package/dist/server-visitor-stream.js +11 -1
- package/dist/session-liveness.d.ts +4 -0
- package/dist/session-liveness.js +4 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +2 -0
- package/docs/api/agent-control.md +2 -2
- package/docs/api/client.md +28 -14
- package/docs/api/delegation.md +2 -2
- package/docs/api/fleet.md +33 -4
- package/docs/api/testing.md +11 -0
- package/package.json +5 -5
|
@@ -1,8 +1,128 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Pull from "effect/Pull";
|
|
3
|
+
import * as Queue from "effect/Queue";
|
|
2
4
|
import { ShiitakeError } from "../errors.js";
|
|
3
5
|
import { reportCatchFailure } from "./failure.js";
|
|
4
6
|
const MAX_OBSERVATIONS = 64;
|
|
5
7
|
const MAX_PENDING_HINTS = 256;
|
|
8
|
+
const encodedFrames = new WeakMap();
|
|
9
|
+
const frameEncoder = new TextEncoder();
|
|
10
|
+
/** The feed shares one frame's JSON and byte accounting across its watchers. */
|
|
11
|
+
export function encodeSessionFrame(frame) {
|
|
12
|
+
const cached = encodedFrames.get(frame);
|
|
13
|
+
if (cached !== undefined)
|
|
14
|
+
return cached;
|
|
15
|
+
const data = JSON.stringify(frame);
|
|
16
|
+
const encoded = Object.freeze({
|
|
17
|
+
data,
|
|
18
|
+
bytes: frameEncoder.encode(data).byteLength,
|
|
19
|
+
});
|
|
20
|
+
encodedFrames.set(frame, encoded);
|
|
21
|
+
return encoded;
|
|
22
|
+
}
|
|
23
|
+
function withoutSandboxFailures(frame) {
|
|
24
|
+
if (frame.kind === "update" && frame.sandboxFailures !== undefined) {
|
|
25
|
+
const value = { ...frame };
|
|
26
|
+
delete value.sandboxFailures;
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
if (frame.kind === "reset" && frame.baseline.sandboxFailures !== undefined) {
|
|
30
|
+
const baseline = { ...frame.baseline };
|
|
31
|
+
delete baseline.sandboxFailures;
|
|
32
|
+
return { ...frame, baseline };
|
|
33
|
+
}
|
|
34
|
+
return frame;
|
|
35
|
+
}
|
|
36
|
+
/** Bounded dropping fan-out; Woodstock, never this queue, owns replay. */
|
|
37
|
+
export function openSessionFrameFeed(onIdle) {
|
|
38
|
+
const listeners = new Set();
|
|
39
|
+
let ended = false;
|
|
40
|
+
let failure;
|
|
41
|
+
const clear = (listener) => {
|
|
42
|
+
Effect.runSync(Queue.clear(listener.frames));
|
|
43
|
+
listener.bytes = 0;
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
register(includeSandboxFailures = false) {
|
|
47
|
+
const listener = {
|
|
48
|
+
frames: Effect.runSync(Queue.dropping(256)),
|
|
49
|
+
bytes: 0,
|
|
50
|
+
lagged: false,
|
|
51
|
+
closed: false,
|
|
52
|
+
observed: -1,
|
|
53
|
+
includeSandboxFailures,
|
|
54
|
+
};
|
|
55
|
+
listeners.add(listener);
|
|
56
|
+
const take = Effect.suspend(() => {
|
|
57
|
+
if (listener.closed)
|
|
58
|
+
return Effect.succeed("closed");
|
|
59
|
+
if (failure !== undefined)
|
|
60
|
+
return Effect.fail(failure);
|
|
61
|
+
if (listener.lagged)
|
|
62
|
+
return Effect.succeed("reset");
|
|
63
|
+
if (ended)
|
|
64
|
+
return Effect.succeed("closed");
|
|
65
|
+
return Queue.take(listener.frames).pipe(Pull.catchDone(() => Effect.succeed("closed")), Effect.flatMap((frame) => {
|
|
66
|
+
if (failure !== undefined)
|
|
67
|
+
return Effect.fail(failure);
|
|
68
|
+
if (frame !== "reset" && frame !== "closed")
|
|
69
|
+
listener.bytes -= encodeSessionFrame(frame).bytes;
|
|
70
|
+
return Effect.succeed(frame);
|
|
71
|
+
}));
|
|
72
|
+
});
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
take,
|
|
75
|
+
checkpoint: (revision = -1) => {
|
|
76
|
+
clear(listener);
|
|
77
|
+
listener.lagged = false;
|
|
78
|
+
listener.observed = revision;
|
|
79
|
+
},
|
|
80
|
+
close: () => {
|
|
81
|
+
if (listener.closed)
|
|
82
|
+
return;
|
|
83
|
+
listener.closed = true;
|
|
84
|
+
clear(listener);
|
|
85
|
+
listeners.delete(listener);
|
|
86
|
+
Queue.endUnsafe(listener.frames);
|
|
87
|
+
if (listeners.size === 0)
|
|
88
|
+
onIdle();
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
publish(frame, revision = Infinity) {
|
|
93
|
+
if (ended)
|
|
94
|
+
return;
|
|
95
|
+
const publicFrame = withoutSandboxFailures(frame);
|
|
96
|
+
for (const listener of listeners) {
|
|
97
|
+
if (listener.closed || listener.lagged || revision <= listener.observed)
|
|
98
|
+
continue;
|
|
99
|
+
const value = listener.includeSandboxFailures ? frame : publicFrame;
|
|
100
|
+
const { bytes } = encodeSessionFrame(value);
|
|
101
|
+
if (listener.bytes + bytes > 1_048_576 ||
|
|
102
|
+
!Queue.offerUnsafe(listener.frames, value)) {
|
|
103
|
+
clear(listener);
|
|
104
|
+
// Keep exactly one reset obligation; no incoming backlog can grow it.
|
|
105
|
+
listener.lagged = true;
|
|
106
|
+
Queue.offerUnsafe(listener.frames, "reset");
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
listener.bytes += bytes;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
end(error) {
|
|
114
|
+
if (ended)
|
|
115
|
+
return;
|
|
116
|
+
ended = true;
|
|
117
|
+
failure = error;
|
|
118
|
+
for (const listener of listeners) {
|
|
119
|
+
clear(listener);
|
|
120
|
+
listener.lagged = false;
|
|
121
|
+
Queue.endUnsafe(listener.frames);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
6
126
|
const ownerPublishers = new WeakMap();
|
|
7
127
|
/** Actual live-view demand, excluding exact-command settlement waiters. */
|
|
8
128
|
export function hasOwnerObservers(store, sessionId) {
|
|
@@ -39,7 +159,10 @@ function queueObservation(listener, reason) {
|
|
|
39
159
|
: "progress";
|
|
40
160
|
}
|
|
41
161
|
function deliverObservation(listener, reason) {
|
|
42
|
-
if (listener.closed
|
|
162
|
+
if (listener.closed)
|
|
163
|
+
return;
|
|
164
|
+
listener.revision += 1;
|
|
165
|
+
if (listener.reset)
|
|
43
166
|
return;
|
|
44
167
|
const wake = listener.wake;
|
|
45
168
|
if (wake === undefined) {
|
|
@@ -57,6 +180,14 @@ function deliverObservation(listener, reason) {
|
|
|
57
180
|
listener.reset = true;
|
|
58
181
|
}
|
|
59
182
|
}
|
|
183
|
+
/** Queued views reconcile occupancy without replaying another Session's facts. */
|
|
184
|
+
function notifyQueuedViews(listeners, sourceSessionId) {
|
|
185
|
+
for (const listener of listeners)
|
|
186
|
+
if (listener.queued &&
|
|
187
|
+
!listener.settlementOnly &&
|
|
188
|
+
listener.sessionId !== sourceSessionId)
|
|
189
|
+
deliverObservation(listener, "activity");
|
|
190
|
+
}
|
|
60
191
|
/** Notify observers of one Session's durable or process-local change. */
|
|
61
192
|
export function publishOwnerObservation(store, sessionId, reason = "durable") {
|
|
62
193
|
const publishers = ownerPublishers.get(store);
|
|
@@ -131,11 +262,12 @@ export function openObservationKernel(store) {
|
|
|
131
262
|
};
|
|
132
263
|
const publish = (publication) => {
|
|
133
264
|
if (publication.kind === "agent-execution") {
|
|
134
|
-
|
|
135
|
-
if (listener.queued && !listener.settlementOnly)
|
|
136
|
-
deliverObservation(listener, "activity");
|
|
265
|
+
notifyQueuedViews(active, null);
|
|
137
266
|
return;
|
|
138
267
|
}
|
|
268
|
+
// Reuse bounded activity hints for the occupant's changing phase.
|
|
269
|
+
if (publication.sessionId !== null && publication.reason !== "settlement")
|
|
270
|
+
notifyQueuedViews(active, publication.sessionId);
|
|
139
271
|
const listeners = publication.sessionId === null
|
|
140
272
|
? active
|
|
141
273
|
: sessions.get(publication.sessionId);
|
|
@@ -160,6 +292,7 @@ export function openObservationKernel(store) {
|
|
|
160
292
|
if (active.size >= MAX_OBSERVATIONS)
|
|
161
293
|
return Effect.fail(new ShiitakeError("runtime", "rejected", "Observation capacity is exhausted"));
|
|
162
294
|
const listener = {
|
|
295
|
+
revision: 0,
|
|
163
296
|
sessionId,
|
|
164
297
|
settlementOnly: interest === "settlement",
|
|
165
298
|
closed: false,
|
|
@@ -190,6 +323,9 @@ export function openObservationKernel(store) {
|
|
|
190
323
|
});
|
|
191
324
|
});
|
|
192
325
|
return Effect.succeed(Object.freeze({
|
|
326
|
+
get revision() {
|
|
327
|
+
return listener.revision;
|
|
328
|
+
},
|
|
193
329
|
pollSignal,
|
|
194
330
|
awaitSignal,
|
|
195
331
|
close: () => {
|
|
@@ -7,6 +7,7 @@ import type { ShiitakeEffectOptions } from "./effect-contracts.js";
|
|
|
7
7
|
import type { CompactionHandoffV0, PromptSectionV0 } from "../extensions.js";
|
|
8
8
|
import { type ManualCompactionOutcome } from "./compaction/session-compaction.js";
|
|
9
9
|
import { type PreparedSessionRun, type RunContract } from "./woodstock/index.js";
|
|
10
|
+
export declare const runFinalizerGraceMs = 1000;
|
|
10
11
|
declare const toolSetupMessages: {
|
|
11
12
|
readonly missing: "Instruction capabilities have no tool runtime";
|
|
12
13
|
readonly runtime: "Tool runtime failed";
|
|
@@ -12,9 +12,11 @@ import { openCompactionSettings } from "./compaction/settings.js";
|
|
|
12
12
|
import { acquireOwnedModel } from "./model-acquisition.js";
|
|
13
13
|
import { returnedFailureReason, unavailableRunReason, } from "./run-failure-reason.js";
|
|
14
14
|
import { isCompactCommand, openMuleRecorder, readSessionGrants, recordLiveRetryEvent, recordSandboxFailure, takeSessionControls, } from "./woodstock/index.js";
|
|
15
|
-
// A run writes its terminal before scope release.
|
|
16
|
-
//
|
|
17
|
-
|
|
15
|
+
// A run writes its terminal before scope release. Allow one second per foreign
|
|
16
|
+
// release before detaching it: the held-model regression exercises the full
|
|
17
|
+
// grace and proves Watchdog can settle and claim the next priority-1 steer.
|
|
18
|
+
// Keep the test's eventual queue-idle budget tied to this product bound.
|
|
19
|
+
export const runFinalizerGraceMs = 1_000;
|
|
18
20
|
function boundedRunFinalizer(name, workId, release) {
|
|
19
21
|
return Effect.gen(function* () {
|
|
20
22
|
// A detached release may finish later; interrupting a finalizer itself
|
|
@@ -3,6 +3,7 @@ import { WatchdogError } from "@fungi.computer/watchdog";
|
|
|
3
3
|
import { makeRuntime as makeWatchdogRuntime, } from "@fungi.computer/watchdog/effect";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
5
|
import * as Latch from "effect/Latch";
|
|
6
|
+
import { readSessionPhase } from "./session-status.js";
|
|
6
7
|
import { readOutstandingDelegationSync } from "../fleet.js";
|
|
7
8
|
import { publishAgentExecutionChange } from "./observation-kernel.js";
|
|
8
9
|
import { ShiitakeError } from "../errors.js";
|
|
@@ -12,7 +13,7 @@ import { openCompactionSettings } from "./compaction/settings.js";
|
|
|
12
13
|
import { admitExtensionRosterV0, } from "./extension-roster.js";
|
|
13
14
|
import { coreConsumer, reportConsumer, openExtensionRuntime, } from "./extension-runtime.js";
|
|
14
15
|
import { reconcileRuntime } from "./runtime-reconciliation.js";
|
|
15
|
-
import { advanceCursor, createSessionWithinTransaction, deliverSessionGrants, prepareExtensionStore, recordSessionDelegator, projectSessionRun, openStorageAdmission, readCursor, readSessionInboxPage,
|
|
16
|
+
import { advanceCursor, createSessionWithinTransaction, deliverSessionGrants, prepareExtensionStore, recordSessionDelegator, projectSessionRun, openStorageAdmission, readCursor, readSessionInboxPage, } from "./woodstock/index.js";
|
|
16
17
|
function runtimeFailure(message) {
|
|
17
18
|
return new ShiitakeError("runtime", "rejected", message);
|
|
18
19
|
}
|
|
@@ -285,12 +286,16 @@ export function openRuntimeCoordinator(options, coreDigest, admitted, mailRoom)
|
|
|
285
286
|
? "host"
|
|
286
287
|
: "serial")
|
|
287
288
|
: undefined;
|
|
288
|
-
if (work === undefined
|
|
289
|
-
return Effect.succeed(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
289
|
+
if (work === undefined)
|
|
290
|
+
return Effect.succeed(null);
|
|
291
|
+
if (work.kind === "host")
|
|
292
|
+
return Effect.succeed({ phase: "thinking" });
|
|
293
|
+
return Effect.try({
|
|
294
|
+
try: () => ({
|
|
295
|
+
phase: readSessionPhase(options.store, work.sessionId, watchdog.transactional.readQueueHead(work.sessionId)),
|
|
296
|
+
}),
|
|
297
|
+
catch: (cause) => new ShiitakeError("runtime", "rejected", "Running phase is unavailable", undefined, cause),
|
|
298
|
+
});
|
|
294
299
|
}), Effect.mapError((cause) => new ShiitakeError("runtime", "rejected", "Running work is unavailable", undefined, cause))),
|
|
295
300
|
extensions,
|
|
296
301
|
commit: bound.commit,
|
|
@@ -12,7 +12,8 @@ export function openRuntimeCore(options, mailRoom) {
|
|
|
12
12
|
const coreDigest = yield* coreProtocolDigest([]);
|
|
13
13
|
yield* prepareWoodstockSchema(options.store);
|
|
14
14
|
const runtime = yield* openRuntimeCoordinator(options, coreDigest, admitted, mailRoom);
|
|
15
|
-
const
|
|
15
|
+
const ownerContext = yield* Effect.context();
|
|
16
|
+
const coordinator = openCoordinator(options, runtime, ownerContext);
|
|
16
17
|
const agent = Object.freeze({
|
|
17
18
|
session: coordinator.session,
|
|
18
19
|
createSession: coordinator.createSession,
|
|
@@ -7,7 +7,7 @@ import { ShiitakeError } from "../errors.js";
|
|
|
7
7
|
import { ExtensionId } from "../extension-contract.js";
|
|
8
8
|
import { coreConsumer, reportConsumer, } from "./extension-runtime.js";
|
|
9
9
|
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
10
|
-
import { advanceCursor, readCursor, readExtensionAdmissionOrigin, readSettledRunFacts, readSettlementContent, readSettlementDiagnostic, readSessionDelegator, readSettlementMeta, readWaitingOn, readWaitingOnSender, recordWorkSettlement, sessionLineageRefused, settleControls, } from "./woodstock/index.js";
|
|
10
|
+
import { advanceCursor, readCursor, readExtensionAdmissionOrigin, readSettledRunFacts, readSettlementContent, readSettlementStretchContent, readSettlementDiagnostic, readSessionDelegator, readSettlementMeta, readWaitingOn, readWaitingOnSender, recordWorkSettlement, sessionLineageRefused, settleControls, } from "./woodstock/index.js";
|
|
11
11
|
const pageSize = 64;
|
|
12
12
|
function runtimeFailure(message) {
|
|
13
13
|
return new ShiitakeError("runtime", "rejected", message);
|
|
@@ -100,7 +100,10 @@ function reportToDelegator(store, watchdog, mailRoom, job) {
|
|
|
100
100
|
catch: () => runtimeFailure("Session attention is unavailable"),
|
|
101
101
|
});
|
|
102
102
|
const settled = yield* settlementFacts(store, job, waiting);
|
|
103
|
-
|
|
103
|
+
const content = job.outcome === "cancelled"
|
|
104
|
+
? ""
|
|
105
|
+
: yield* readSettlementStretchContent(store, payload.work);
|
|
106
|
+
yield* fanOut(mailRoom, { ...settled, content, delegator });
|
|
104
107
|
});
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type * as Context from "effect/Context";
|
|
3
|
+
import * as Stream from "effect/Stream";
|
|
4
|
+
import type { SessionContext, SessionFrame, SessionSnapshot, SubscribeOptions, WatchOptions } from "../client.js";
|
|
5
|
+
import { ShiitakeError } from "../errors.js";
|
|
6
|
+
import type { ShiitakeEffectOptions } from "./effect-contracts.js";
|
|
7
|
+
import { type ObservationKernel } from "./observation-kernel.js";
|
|
8
|
+
import type { RuntimeCoordinator } from "./runtime-coordinator.js";
|
|
9
|
+
import { readSession } from "./woodstock/index.js";
|
|
10
|
+
/** Shared live observation; Woodstock remains the bounded baseline and replay owner. */
|
|
11
|
+
export declare function openSessionObservation(options: ShiitakeEffectOptions, runtime: RuntimeCoordinator, observation: ObservationKernel, ownerContext: Context.Context<never>): Readonly<{
|
|
12
|
+
read: (sessionId: string, input: Parameters<typeof readSession>[3]) => Effect.Effect<Readonly<{
|
|
13
|
+
sessionId: string;
|
|
14
|
+
cursor: string | null;
|
|
15
|
+
hasMore: boolean;
|
|
16
|
+
oldestCursor?: string | null;
|
|
17
|
+
messages: readonly import("../client.js").SessionMessage[];
|
|
18
|
+
terminals: readonly import("../client.js").SessionTerminal[];
|
|
19
|
+
compactions?: readonly import("../client.js").SessionCompactionMarker[];
|
|
20
|
+
compactionNotices?: readonly import("../client.js").SessionCompactionNotice[];
|
|
21
|
+
sandboxFailures?: readonly import("@fungi.computer/mycelium").SandboxFailureEvent[];
|
|
22
|
+
pending: import("../client.js").SessionPendingPrompts;
|
|
23
|
+
activity: import("../client.js").SessionActivity;
|
|
24
|
+
progress: import("../client.js").SessionProgress | null;
|
|
25
|
+
toolOutput: readonly import("../client.js").SessionToolOutput[];
|
|
26
|
+
extensions: import("../client.js").SessionExtensionSlots;
|
|
27
|
+
attention: readonly import("../session-attention.js").SessionAttention[];
|
|
28
|
+
context?: SessionContext;
|
|
29
|
+
}>, ShiitakeError, never>;
|
|
30
|
+
subscribe: (sessionId: string, input: SubscribeOptions) => Stream.Stream<SessionFrame, ShiitakeError, never>;
|
|
31
|
+
watch: (sessionId: string, input: WatchOptions | undefined) => Effect.Effect<Readonly<{
|
|
32
|
+
snapshot: SessionSnapshot;
|
|
33
|
+
changes: Stream.Stream<SessionFrame, ShiitakeError>;
|
|
34
|
+
close: Effect.Effect<void, ShiitakeError>;
|
|
35
|
+
}>, ShiitakeError, never>;
|
|
36
|
+
close: Effect.Effect<void, never, never>;
|
|
37
|
+
}>;
|