@fungi.computer/shiitake 0.1.0 → 0.3.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 +155 -34
- package/dist/agent-control.d.ts +78 -11
- package/dist/agent-control.js +273 -70
- package/dist/client-entry.d.ts +15 -8
- package/dist/client-entry.js +7 -5
- package/dist/client-wire.d.ts +24 -8
- package/dist/client-wire.js +53 -11
- package/dist/client.d.ts +56 -11
- package/dist/client.js +1 -1
- package/dist/cloudflare.d.ts +42 -23
- package/dist/cloudflare.js +76 -54
- package/dist/cold-sessions.d.ts +28 -0
- package/dist/cold-sessions.js +1 -0
- package/dist/delegation.d.ts +214 -0
- package/dist/delegation.js +319 -0
- package/dist/effect.d.ts +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -2
- package/dist/extension-contract.d.ts +10 -2
- package/dist/extensions.d.ts +6 -2
- package/dist/fleet.d.ts +52 -34
- package/dist/fleet.js +101 -63
- package/dist/hosted.js +1 -0
- package/dist/http-routes.d.ts +106 -22
- package/dist/http-routes.js +18 -1
- package/dist/index.d.ts +3 -2
- package/dist/internal/accepted-run-executor.d.ts +5 -0
- package/dist/internal/accepted-run-executor.js +10 -4
- package/dist/internal/admission-door.d.ts +3 -0
- package/dist/internal/admission-door.js +12 -2
- package/dist/internal/client-boundary.js +7 -1
- package/dist/internal/compaction/compaction-slot.d.ts +36 -0
- package/dist/internal/compaction/compaction-slot.js +41 -0
- package/dist/internal/compaction/compaction.d.ts +7 -4
- package/dist/internal/compaction/compaction.js +59 -30
- package/dist/internal/compaction/session-compaction.d.ts +8 -16
- package/dist/internal/compaction/session-compaction.js +358 -119
- package/dist/internal/compaction/settings.js +5 -0
- package/dist/internal/compaction/utils.d.ts +19 -16
- package/dist/internal/compaction/utils.js +30 -84
- package/dist/internal/contracts.d.ts +40 -13
- package/dist/internal/coordinator.js +28 -10
- package/dist/internal/effect-client.d.ts +4 -2
- package/dist/internal/effect-client.js +29 -0
- package/dist/internal/effect-contracts.d.ts +17 -4
- package/dist/internal/extension-runtime.d.ts +5 -0
- package/dist/internal/extension-runtime.js +184 -89
- package/dist/internal/host-meta.d.ts +7 -0
- package/dist/internal/model-acquisition.js +7 -1
- package/dist/internal/model-preference.d.ts +2 -0
- package/dist/internal/model-preference.js +2 -1
- package/dist/internal/promise-facade.js +4 -16
- package/dist/internal/run-failure-reason.d.ts +10 -0
- package/dist/internal/run-failure-reason.js +19 -0
- package/dist/internal/run-scope.d.ts +4 -1
- package/dist/internal/run-scope.js +38 -5
- package/dist/internal/runtime-coordinator.d.ts +2 -1
- package/dist/internal/runtime-coordinator.js +65 -4
- package/dist/internal/runtime-core.js +1 -0
- package/dist/internal/runtime-reconciliation.js +42 -6
- package/dist/internal/session-context.d.ts +1 -2
- package/dist/internal/session-context.js +8 -30
- package/dist/internal/session-status.d.ts +2 -1
- package/dist/internal/session-status.js +4 -1
- package/dist/internal/woodstock/client-projection.d.ts +6 -5
- package/dist/internal/woodstock/client-projection.js +79 -53
- package/dist/internal/woodstock/cold-sessions.d.ts +4 -0
- package/dist/internal/woodstock/cold-sessions.js +90 -0
- package/dist/internal/woodstock/extension-store.d.ts +2 -0
- package/dist/internal/woodstock/extension-store.js +4 -0
- package/dist/internal/woodstock/fleet-store.d.ts +3 -3
- package/dist/internal/woodstock/fleet-store.js +16 -11
- package/dist/internal/woodstock/index.d.ts +11 -3
- package/dist/internal/woodstock/index.js +33 -14
- package/dist/internal/woodstock/progress.d.ts +14 -1
- package/dist/internal/woodstock/progress.js +97 -19
- package/dist/internal/woodstock/run-recovery.js +6 -4
- package/dist/internal/woodstock/session-attention.d.ts +1 -1
- package/dist/internal/woodstock/session-attention.js +2 -2
- package/dist/internal/woodstock/session-entry.d.ts +109 -42
- package/dist/internal/woodstock/session-entry.js +63 -20
- package/dist/internal/woodstock/session-inbox.js +2 -0
- package/dist/internal/woodstock/session-store.d.ts +104 -24
- package/dist/internal/woodstock/session-store.js +937 -246
- package/dist/internal/woodstock/storage-admission.d.ts +8 -8
- package/dist/internal/woodstock/storage-admission.js +8 -34
- package/dist/internal/woodstock/storage-conformance.d.ts +15 -0
- package/dist/internal/woodstock/storage-conformance.js +489 -0
- package/dist/model-preference.d.ts +2 -2
- package/dist/model-preference.js +2 -2
- package/dist/node.js +1 -1
- package/dist/remote-client.js +8 -1
- package/dist/session-attention.d.ts +8 -1
- package/dist/session-attention.js +6 -1
- package/dist/testing.js +1 -0
- package/docs/api/README.md +18 -16
- package/docs/api/agent-control.md +172 -2
- package/docs/api/client-entry.md +26 -10
- package/docs/api/client.md +153 -37
- package/docs/api/cloudflare.md +76 -23
- package/docs/api/delegation.md +485 -0
- package/docs/api/effect.md +20 -2
- package/docs/api/extensions.md +21 -9
- package/docs/api/fleet.md +149 -122
- package/docs/api/http.md +4 -1
- package/docs/api/index.md +116 -20
- package/docs/api/internal/woodstock/storage-conformance.md +81 -0
- package/docs/api/model-preference.md +17 -0
- package/docs/api/testing.md +16 -4
- package/package.json +26 -14
- package/dist/internal/browser-sessions.d.ts +0 -31
- package/dist/internal/browser-sessions.js +0 -173
|
@@ -8,6 +8,7 @@ import { ShiitakeError } from "../errors.js";
|
|
|
8
8
|
import { agentTurnRecovery, executeAcceptedRun, } from "./accepted-run-executor.js";
|
|
9
9
|
import { openAdmissionDoor } from "./admission-door.js";
|
|
10
10
|
import { openCompactionSettings } from "./compaction/settings.js";
|
|
11
|
+
import { admitExtensionRosterV0, } from "./extension-roster.js";
|
|
11
12
|
import { coreConsumer, openExtensionRuntime, } from "./extension-runtime.js";
|
|
12
13
|
import { reconcileRuntime } from "./runtime-reconciliation.js";
|
|
13
14
|
import { advanceCursor, prepareExtensionStore, projectSessionRun, openStorageAdmission, readCursor, } from "./woodstock/index.js";
|
|
@@ -36,6 +37,9 @@ export function openRuntimeCoordinator(options, coreDigest, admitted, mailRoom)
|
|
|
36
37
|
}).pipe(Effect.uninterruptible);
|
|
37
38
|
const boundRuntime = {};
|
|
38
39
|
const extensionsRef = { current: undefined };
|
|
40
|
+
let turnOptions = options;
|
|
41
|
+
let pendingConfiguration;
|
|
42
|
+
let preparing = false;
|
|
39
43
|
const requestBoundWake = Effect.suspend(() => boundRuntime.requestWake ??
|
|
40
44
|
Effect.fail(runtimeFailure("Runtime host is unavailable")));
|
|
41
45
|
const hostExecutor = { current: undefined };
|
|
@@ -64,7 +68,17 @@ export function openRuntimeCoordinator(options, coreDigest, admitted, mailRoom)
|
|
|
64
68
|
}), () => job.lane === options.hostJobs?.lane
|
|
65
69
|
? (hostExecutor.current?.execute(job) ??
|
|
66
70
|
Effect.fail(new WatchdogError("execution_failed")))
|
|
67
|
-
:
|
|
71
|
+
: Effect.gen(function* () {
|
|
72
|
+
const pending = pendingConfiguration;
|
|
73
|
+
if (pending !== undefined) {
|
|
74
|
+
yield* pending.apply;
|
|
75
|
+
const { tools: oldTools, ...base } = options;
|
|
76
|
+
void oldTools;
|
|
77
|
+
turnOptions = { ...base, ...pending.configuration };
|
|
78
|
+
pendingConfiguration = undefined;
|
|
79
|
+
}
|
|
80
|
+
return yield* executeAcceptedRun(turnOptions, coreDigest, job, extensionsRef.current);
|
|
81
|
+
}).pipe(Effect.tapError((error) => Effect.logError("configuration_apply_failed", error)), Effect.mapError(() => new WatchdogError("execution_failed"))), () => Effect.sync(() => {
|
|
68
82
|
if (job.lane !== options.hostJobs?.lane)
|
|
69
83
|
activeSessions.delete(job.queue);
|
|
70
84
|
runningSlots.delete(job.lane === options.hostJobs?.lane ? "host" : "serial");
|
|
@@ -100,7 +114,7 @@ export function openRuntimeCoordinator(options, coreDigest, admitted, mailRoom)
|
|
|
100
114
|
: runtimeFailure("Compaction settings are unavailable"),
|
|
101
115
|
});
|
|
102
116
|
const storage = yield* Effect.try({
|
|
103
|
-
try: () => openStorageAdmission(options.
|
|
117
|
+
try: () => openStorageAdmission(options.storagePressure),
|
|
104
118
|
catch: (error) => error instanceof ShiitakeError
|
|
105
119
|
? error
|
|
106
120
|
: runtimeFailure("Storage budget is unavailable"),
|
|
@@ -160,9 +174,24 @@ export function openRuntimeCoordinator(options, coreDigest, admitted, mailRoom)
|
|
|
160
174
|
try: () => extensions.timerWake(now),
|
|
161
175
|
catch: () => runtimeFailure("Extension timers are unavailable"),
|
|
162
176
|
});
|
|
177
|
+
const mailWake = mailRoom === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: yield* mailRoom
|
|
180
|
+
.nextWakeAt()
|
|
181
|
+
.pipe(Effect.mapError(() => runtimeFailure("Reply retry wake is unavailable")));
|
|
182
|
+
// Carrier owns reply retry state. Keep it in the same durable answer
|
|
183
|
+
// as runtime work, so a host cannot erase its wake with an idle result.
|
|
184
|
+
const nextWakeAt = timers.nextWakeAt === null
|
|
185
|
+
? (mailWake ?? null)
|
|
186
|
+
: mailWake === undefined
|
|
187
|
+
? timers.nextWakeAt
|
|
188
|
+
: Math.min(timers.nextWakeAt, mailWake);
|
|
163
189
|
return {
|
|
164
|
-
pending: watchdogPending ||
|
|
165
|
-
|
|
190
|
+
pending: watchdogPending ||
|
|
191
|
+
settledLag ||
|
|
192
|
+
timers.due ||
|
|
193
|
+
(mailWake !== undefined && mailWake <= now),
|
|
194
|
+
nextWakeAt,
|
|
166
195
|
};
|
|
167
196
|
});
|
|
168
197
|
const bound = yield* options.runtime
|
|
@@ -179,6 +208,38 @@ export function openRuntimeCoordinator(options, coreDigest, admitted, mailRoom)
|
|
|
179
208
|
// Every host must get one post-bind tick to recover durable work after a cold reopen.
|
|
180
209
|
yield* bound.requestWake.pipe(Effect.onError(() => Effect.all([bound.close, extensions.close]).pipe(Effect.orDie)));
|
|
181
210
|
return Object.freeze({
|
|
211
|
+
prepareConfiguration: (configuration) => Effect.gen(function* () {
|
|
212
|
+
if (lifecycleState !== "open" ||
|
|
213
|
+
preparing ||
|
|
214
|
+
pendingConfiguration !== undefined)
|
|
215
|
+
return yield* Effect.fail(runtimeFailure("A configuration change is already pending, or the Agent is closing"));
|
|
216
|
+
preparing = true;
|
|
217
|
+
const staged = yield* admitExtensionRosterV0(configuration.extensions).pipe(Effect.flatMap((next) => extensions.stage(next)), Effect.onError(() => Effect.sync(() => {
|
|
218
|
+
preparing = false;
|
|
219
|
+
})));
|
|
220
|
+
let settled = false;
|
|
221
|
+
return {
|
|
222
|
+
commit: Effect.try({
|
|
223
|
+
try: () => {
|
|
224
|
+
if (settled || lifecycleState !== "open")
|
|
225
|
+
throw runtimeFailure("Configuration preparation is no longer available");
|
|
226
|
+
pendingConfiguration = { configuration, apply: staged.apply };
|
|
227
|
+
settled = true;
|
|
228
|
+
preparing = false;
|
|
229
|
+
},
|
|
230
|
+
catch: (cause) => cause instanceof ShiitakeError
|
|
231
|
+
? cause
|
|
232
|
+
: runtimeFailure("Configuration commit failed"),
|
|
233
|
+
}),
|
|
234
|
+
discard: Effect.suspend(() => {
|
|
235
|
+
if (settled)
|
|
236
|
+
return Effect.void;
|
|
237
|
+
settled = true;
|
|
238
|
+
preparing = false;
|
|
239
|
+
return staged.discard;
|
|
240
|
+
}),
|
|
241
|
+
};
|
|
242
|
+
}),
|
|
182
243
|
activeSessions: Effect.sync(() => [...activeSessions]),
|
|
183
244
|
runningWork: (jobId) => watchdog.read(jobId).pipe(Effect.map((result) => result.status === "found"
|
|
184
245
|
? (runningSlots.get(result.job.lane === options.hostJobs?.lane
|
|
@@ -7,7 +7,7 @@ import { ShiitakeError } from "../errors.js";
|
|
|
7
7
|
import { ExtensionId } from "../extension-contract.js";
|
|
8
8
|
import { coreConsumer } from "./extension-runtime.js";
|
|
9
9
|
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
10
|
-
import { advanceCursor, readCursor, readSettledRunFacts, readSettlementContent, readSettlementDiagnostic, readSettlementMeta, recordWorkSettlement, settleControls, } from "./woodstock/index.js";
|
|
10
|
+
import { advanceCursor, readCursor, readExtensionAdmissionOrigin, readSettledRunFacts, readSettlementContent, readSettlementDiagnostic, readSettlementMeta, 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);
|
|
@@ -66,6 +66,7 @@ function settleTargetedControls(store, watchdog, job) {
|
|
|
66
66
|
sessionId: payload.work.sessionId,
|
|
67
67
|
parentWorkId: job.jobId,
|
|
68
68
|
outcome,
|
|
69
|
+
diagnostic: job.failureReason,
|
|
69
70
|
enqueue: (work) => {
|
|
70
71
|
watchdog.transactional.enqueueAcceptedJob(agentJobFromInput({ work: { ...work, recovery: agentTurnRecovery } }));
|
|
71
72
|
},
|
|
@@ -79,10 +80,41 @@ function drainMail(mailRoom) {
|
|
|
79
80
|
.drainOutbox()
|
|
80
81
|
.pipe(Effect.mapError(() => runtimeFailure("Agent reply delivery is unavailable")));
|
|
81
82
|
}
|
|
82
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Whether the settled work belongs to a Session the lineage audit refused.
|
|
85
|
+
* Nothing more can be recorded in that Session: its Watchdog settlement, with
|
|
86
|
+
* the refusal as its failure reason, is the work's whole terminal.
|
|
87
|
+
*/
|
|
88
|
+
function settledInRefusedSession(store, job) {
|
|
89
|
+
return Effect.gen(function* () {
|
|
90
|
+
const payload = yield* Effect.try({
|
|
91
|
+
try: () => decodeAgentJob(job),
|
|
92
|
+
catch: () => runtimeFailure("Agent work payload is invalid"),
|
|
93
|
+
});
|
|
94
|
+
const refused = yield* Effect.try({
|
|
95
|
+
try: () => sessionLineageRefused(store, payload.work.sessionId),
|
|
96
|
+
catch: () => runtimeFailure("Session lineage state is unavailable"),
|
|
97
|
+
});
|
|
98
|
+
if (refused)
|
|
99
|
+
yield* Effect.logWarning("shiitake_settlement_session_refused").pipe(Effect.annotateLogs({ workId: job.jobId }));
|
|
100
|
+
return refused;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Join one core-processed settled record with its exact Woodstock facts. A
|
|
105
|
+
* run of a Session the lineage audit refused reaches extensions with only
|
|
106
|
+
* Watchdog's outcome and its admission origin: no terminal and no tool
|
|
107
|
+
* calls can be read from that history.
|
|
108
|
+
*/
|
|
83
109
|
function settledRun(store, record) {
|
|
84
110
|
const payload = decodeAgentJob(record.job);
|
|
85
|
-
const facts =
|
|
111
|
+
const facts = sessionLineageRefused(store, payload.work.sessionId)
|
|
112
|
+
? Object.freeze({
|
|
113
|
+
terminal: "missing",
|
|
114
|
+
toolCalls: 0,
|
|
115
|
+
origin: readExtensionAdmissionOrigin(store, payload.work.sessionId, payload.work.commandFactId, record.job.jobId),
|
|
116
|
+
})
|
|
117
|
+
: readSettledRunFacts(store, payload.work.sessionId, payload.work.commandFactId, record.job.jobId);
|
|
86
118
|
return Object.freeze({
|
|
87
119
|
sequence: record.sequence,
|
|
88
120
|
sessionId: payload.work.sessionId,
|
|
@@ -111,9 +143,13 @@ function settleCore(store, watchdog, mailRoom, records) {
|
|
|
111
143
|
// leave live watchers reporting the finished run as active. A replay
|
|
112
144
|
// after a crash repeats the same announcement.
|
|
113
145
|
publishOwnerObservation(store, record.job.queue);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
yield*
|
|
146
|
+
// A refused Session can neither record a repair nor give a reply
|
|
147
|
+
// its content: the failed Watchdog settlement is all there is.
|
|
148
|
+
if (!(yield* settledInRefusedSession(store, record.job))) {
|
|
149
|
+
yield* repairWorkSettlement(store, record.job);
|
|
150
|
+
yield* settleTargetedControls(store, watchdog, record.job);
|
|
151
|
+
yield* fanOutSettlement(store, mailRoom, record.job);
|
|
152
|
+
}
|
|
117
153
|
yield* Effect.try({
|
|
118
154
|
try: () => {
|
|
119
155
|
advanceCursor(store, coreConsumer, record.sequence);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import type { SessionContext } from "../client.js";
|
|
3
3
|
import type { ShiitakeEffectOptions } from "./effect-contracts.js";
|
|
4
|
-
import type { RuntimeCoordinator } from "./runtime-coordinator.js";
|
|
5
4
|
/**
|
|
6
5
|
* The Session's context use as compaction measures it, against the window of
|
|
7
6
|
* the model a run would select now. Undefined while the Session has no
|
|
8
7
|
* context or its context cannot be read; a window the host cannot describe is
|
|
9
8
|
* null, never guessed.
|
|
10
9
|
*/
|
|
11
|
-
export declare function readSessionContext(options: ShiitakeEffectOptions,
|
|
10
|
+
export declare function readSessionContext(options: ShiitakeEffectOptions, sessionId: string): Effect.Effect<SessionContext | undefined>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
|
-
import {
|
|
2
|
+
import { estimateContextTokens } from "@earendil-works/pi-ai/utils/estimate";
|
|
3
3
|
import { reportCatchFailure } from "./failure.js";
|
|
4
4
|
import { buildSessionContext, readSessionContextPath, readSessionModelPreference, } from "./woodstock/index.js";
|
|
5
5
|
/**
|
|
@@ -10,22 +10,7 @@ import { buildSessionContext, readSessionContextPath, readSessionModelPreference
|
|
|
10
10
|
function settledContextTokens(store, sessionId) {
|
|
11
11
|
const path = readSessionContextPath(store, { sessionId });
|
|
12
12
|
const messages = buildSessionContext(path);
|
|
13
|
-
return messages.length === 0
|
|
14
|
-
? null
|
|
15
|
-
: estimateThresholdContextTokens(path, messages);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Each runtime keeps the last described window per Session. A saved model
|
|
19
|
-
* preference changes the key, so `/model` is described again at once.
|
|
20
|
-
*/
|
|
21
|
-
const describedWindows = new WeakMap();
|
|
22
|
-
function windowsFor(runtime) {
|
|
23
|
-
const existing = describedWindows.get(runtime);
|
|
24
|
-
if (existing !== undefined)
|
|
25
|
-
return existing;
|
|
26
|
-
const created = new Map();
|
|
27
|
-
describedWindows.set(runtime, created);
|
|
28
|
-
return created;
|
|
13
|
+
return messages.length === 0 ? null : estimateContextTokens(messages).tokens;
|
|
29
14
|
}
|
|
30
15
|
/**
|
|
31
16
|
* The Session's context use as compaction measures it, against the window of
|
|
@@ -33,7 +18,7 @@ function windowsFor(runtime) {
|
|
|
33
18
|
* context or its context cannot be read; a window the host cannot describe is
|
|
34
19
|
* null, never guessed.
|
|
35
20
|
*/
|
|
36
|
-
export function readSessionContext(options,
|
|
21
|
+
export function readSessionContext(options, sessionId) {
|
|
37
22
|
return Effect.gen(function* () {
|
|
38
23
|
// A context too large to read safely has no footer figure.
|
|
39
24
|
const read = yield* Effect.result(Effect.try({
|
|
@@ -46,22 +31,15 @@ export function readSessionContext(options, runtime, sessionId) {
|
|
|
46
31
|
if (read._tag === "Failure" || read.success.tokens === null)
|
|
47
32
|
return undefined;
|
|
48
33
|
const { tokens, preference } = read.success;
|
|
49
|
-
const windows = windowsFor(runtime);
|
|
50
|
-
const key = JSON.stringify(preference);
|
|
51
|
-
const cached = windows.get(sessionId);
|
|
52
|
-
if (cached?.preference === key)
|
|
53
|
-
return Object.freeze({ tokens, window: cached.window });
|
|
54
34
|
const describe = options.model.describe;
|
|
55
35
|
if (describe === undefined)
|
|
56
36
|
return Object.freeze({ tokens, window: null });
|
|
57
|
-
|
|
37
|
+
// A null Session override follows a mutable Agent default. Resolve through
|
|
38
|
+
// the host's turn-model selector on every read, without acquiring a turn.
|
|
39
|
+
const window = yield* describe({ sessionId, preference }).pipe(Effect.map(({ contextWindow }) => contextWindow), Effect.catch((error) => Effect.sync(() => {
|
|
58
40
|
reportCatchFailure("session-context.describe", error);
|
|
59
|
-
return
|
|
41
|
+
return null;
|
|
60
42
|
})));
|
|
61
|
-
|
|
62
|
-
if (described === undefined)
|
|
63
|
-
return Object.freeze({ tokens, window: null });
|
|
64
|
-
windows.set(sessionId, { preference: key, window: described });
|
|
65
|
-
return Object.freeze({ tokens, window: described });
|
|
43
|
+
return Object.freeze({ tokens, window });
|
|
66
44
|
});
|
|
67
45
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { WatchdogQueueHead } from "@fungi.computer/watchdog/effect";
|
|
2
|
-
import type { AgentRunningWork, SessionActivity, SessionProgress } from "../client.js";
|
|
2
|
+
import type { AgentRunningWork, SessionActivity, SessionProgress, SessionToolOutput } from "../client.js";
|
|
3
3
|
import type { SqliteOwner } from "./contracts.js";
|
|
4
4
|
/** Public activity facts plus the process-local progress read with them. */
|
|
5
5
|
export type ObservedActivity = Readonly<{
|
|
6
6
|
facts: SessionActivity;
|
|
7
7
|
progress: SessionProgress | null;
|
|
8
|
+
toolOutput: readonly SessionToolOutput[];
|
|
8
9
|
}>;
|
|
9
10
|
/** Join Watchdog authority with Woodstock facts and process-local progress. */
|
|
10
11
|
export declare function readSessionActivity(owner: SqliteOwner, sessionId: string, head: WatchdogQueueHead, runningWork: AgentRunningWork | null): ObservedActivity;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readProcessProgressForWork, readProcessCompaction, readLatestLiveRetryForWork, readActiveToolForWork, readLastModelChoiceSequence, } from "./woodstock/index.js";
|
|
1
|
+
import { readProcessProgressForWork, readProcessToolOutputForWork, readProcessCompaction, readLatestLiveRetryForWork, readActiveToolForWork, readLastModelChoiceSequence, } from "./woodstock/index.js";
|
|
2
2
|
/** Join Watchdog authority with Woodstock facts and process-local progress. */
|
|
3
3
|
export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
4
4
|
const lastModelChoiceSequence = readLastModelChoiceSequence(owner, sessionId);
|
|
@@ -13,6 +13,7 @@ export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
|
13
13
|
compaction: null,
|
|
14
14
|
},
|
|
15
15
|
progress: null,
|
|
16
|
+
toolOutput: [],
|
|
16
17
|
};
|
|
17
18
|
if (head.state === "queued")
|
|
18
19
|
// The queue job id is the admitted command id: the one identity a person
|
|
@@ -27,6 +28,7 @@ export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
|
27
28
|
compaction: null,
|
|
28
29
|
},
|
|
29
30
|
progress: null,
|
|
31
|
+
toolOutput: [],
|
|
30
32
|
};
|
|
31
33
|
const latestRetry = readLatestLiveRetryForWork(owner, sessionId, head.jobId);
|
|
32
34
|
const retry = latestRetry?.event.type === "auto_retry_start"
|
|
@@ -48,5 +50,6 @@ export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
|
48
50
|
compaction: readProcessCompaction(owner, sessionId),
|
|
49
51
|
},
|
|
50
52
|
progress: readProcessProgressForWork(owner, sessionId, head.jobId),
|
|
53
|
+
toolOutput: readProcessToolOutputForWork(owner, sessionId, head.jobId),
|
|
51
54
|
};
|
|
52
55
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import type { SessionFrame, SessionExtensionSlots, SessionSnapshot } from "../../client.js";
|
|
1
|
+
import type { SessionFrame, SessionExtensionSlots, SessionPendingPrompts, SessionSnapshot } from "../../client.js";
|
|
2
2
|
import type { ClientReadOptions } from "../client-boundary.js";
|
|
3
3
|
import type { CommandSettlement, SqliteOwner } from "../contracts.js";
|
|
4
4
|
import type { ObservedActivity } from "../session-status.js";
|
|
5
|
-
import { type StoredSessionRow } from "./session-store.js";
|
|
5
|
+
import { type AdmissionIdentity, type StoredSessionRow } from "./session-store.js";
|
|
6
6
|
/**
|
|
7
|
-
* Newest
|
|
8
|
-
*
|
|
9
|
-
* fan-out decide how to present and bound it.
|
|
7
|
+
* Newest owner-produced terminal diagnostic for each work. Pi assistant errors
|
|
8
|
+
* and retry text can contain private provider bodies, not safe diagnostics.
|
|
10
9
|
*/
|
|
11
10
|
export declare function diagnosticsByWorkId(path: readonly StoredSessionRow[]): ReadonlyMap<string, string>;
|
|
11
|
+
/** Same payload projection as the FIFO page, selected independently of that page's bounds. */
|
|
12
|
+
export declare function readLatestPendingPrompt(owner: SqliteOwner, sessionId: string, identity?: AdmissionIdentity): SessionPendingPrompts["prompts"][number] | null;
|
|
12
13
|
/**
|
|
13
14
|
* Read one bounded public baseline from the selected Session lineage. Only
|
|
14
15
|
* the page's rows are decoded; `before` pages back through the whole saved
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { messageAuthor } from "@fungi.computer/agar/agents";
|
|
1
2
|
import { readSessionAttention } from "./session-attention.js";
|
|
2
3
|
import { ShiitakeError } from "../../errors.js";
|
|
3
4
|
import { decodeWoodstockCursor, encodeWoodstockCursor } from "./cursor.js";
|
|
4
5
|
import { operatorClientMessage, promptCommand } from "./session-entry.js";
|
|
5
|
-
import { lineageEntrySequence, lineageHasSequence, lineageRowLimit, readLineageWindow, readLineageWorkFacts, readSessionEntriesForWork, readSessionLineage, readPendingSessionCommands, readSessionCommandAdmission, } from "./session-store.js";
|
|
6
|
+
import { lineageEntrySequence, lineageHasSequence, lineageRowLimit, readLineageWindow, readLineageWorkFacts, readSessionEntriesForWork, readSessionLineage, readPendingSessionCommands, readSessionCommandAdmission, readNewestPendingSessionCommand, } from "./session-store.js";
|
|
6
7
|
const maximumPagePayloadBytes = 2_097_152;
|
|
7
8
|
const replayPageLimit = 200;
|
|
8
9
|
const maximumRowsPerPage = lineageRowLimit;
|
|
@@ -80,6 +81,7 @@ function projectMessage(row, work) {
|
|
|
80
81
|
id: row.id,
|
|
81
82
|
sequence: row.sequence,
|
|
82
83
|
commandId: prompt.workId,
|
|
84
|
+
requestId: prompt.requestId,
|
|
83
85
|
value: prompt.message,
|
|
84
86
|
};
|
|
85
87
|
let projected = message;
|
|
@@ -94,6 +96,7 @@ function projectMessage(row, work) {
|
|
|
94
96
|
id: row.id,
|
|
95
97
|
sequence: row.sequence,
|
|
96
98
|
commandId: null,
|
|
99
|
+
requestId: row.entry.requestId,
|
|
97
100
|
value: operatorClientMessage(row.entry),
|
|
98
101
|
};
|
|
99
102
|
let projected = message;
|
|
@@ -101,34 +104,30 @@ function projectMessage(row, work) {
|
|
|
101
104
|
projected = { ...projected, meta: row.entry.meta };
|
|
102
105
|
return Object.freeze(projected);
|
|
103
106
|
}
|
|
104
|
-
if (row.entry.kind === "message")
|
|
105
|
-
|
|
107
|
+
if (row.entry.kind === "message") {
|
|
108
|
+
const message = {
|
|
106
109
|
id: row.entry.message.value,
|
|
107
110
|
sequence: row.sequence,
|
|
108
111
|
commandId: work.commandIds.get(row.entry.workId) ?? null,
|
|
109
112
|
value: row.entry.value,
|
|
110
|
-
}
|
|
113
|
+
};
|
|
114
|
+
return Object.freeze(row.entry.data === undefined
|
|
115
|
+
? message
|
|
116
|
+
: { ...message, data: row.entry.data });
|
|
117
|
+
}
|
|
111
118
|
return undefined;
|
|
112
119
|
}
|
|
113
120
|
/**
|
|
114
|
-
* Newest
|
|
115
|
-
*
|
|
116
|
-
* fan-out decide how to present and bound it.
|
|
121
|
+
* Newest owner-produced terminal diagnostic for each work. Pi assistant errors
|
|
122
|
+
* and retry text can contain private provider bodies, not safe diagnostics.
|
|
117
123
|
*/
|
|
118
124
|
export function diagnosticsByWorkId(path) {
|
|
119
125
|
const diagnostics = new Map();
|
|
120
126
|
for (const { entry } of path) {
|
|
121
|
-
if (entry.kind === "work_terminal"
|
|
127
|
+
if ((entry.kind === "work_terminal" || entry.kind === "run_terminal") &&
|
|
128
|
+
entry.diagnostic !== undefined) {
|
|
122
129
|
diagnostics.set(entry.workId, entry.diagnostic);
|
|
123
130
|
}
|
|
124
|
-
else if (entry.kind === "message" && entry.value.role === "assistant") {
|
|
125
|
-
if (entry.value.errorMessage !== undefined)
|
|
126
|
-
diagnostics.set(entry.workId, entry.value.errorMessage);
|
|
127
|
-
}
|
|
128
|
-
else if (entry.kind === "live_retry" &&
|
|
129
|
-
entry.event.type === "auto_retry_end" &&
|
|
130
|
-
entry.event.finalError !== undefined)
|
|
131
|
-
diagnostics.set(entry.workId, entry.event.finalError);
|
|
132
131
|
}
|
|
133
132
|
return diagnostics;
|
|
134
133
|
}
|
|
@@ -156,6 +155,8 @@ function projectTerminal(row, { commandIds, diagnostics, cancellations }) {
|
|
|
156
155
|
* compaction kept (the same boundary `buildSessionContext` resumes from) so
|
|
157
156
|
* the client can render "the agent's memory starts below" accurately even
|
|
158
157
|
* when the kept entries are not the compaction row's immediate predecessors.
|
|
158
|
+
* A kept entry that is itself one of the validated page rows needs no lineage
|
|
159
|
+
* lookup; only one outside the page is checked against the lineage.
|
|
159
160
|
*/
|
|
160
161
|
function compactionMarkersIn(owner, lineage, rows) {
|
|
161
162
|
const markers = [];
|
|
@@ -163,16 +164,17 @@ function compactionMarkersIn(owner, lineage, rows) {
|
|
|
163
164
|
const entry = row.entry;
|
|
164
165
|
if (entry.kind !== "compaction")
|
|
165
166
|
continue;
|
|
166
|
-
const kept =
|
|
167
|
+
const kept = rows.find((candidate) => candidate.id === entry.head)?.sequence ??
|
|
168
|
+
lineageEntrySequence(owner, lineage, entry.head);
|
|
167
169
|
if (kept === undefined)
|
|
168
170
|
throw unavailable("Session compaction horizon is unavailable");
|
|
169
171
|
markers.push(Object.freeze({
|
|
170
172
|
id: row.id,
|
|
171
173
|
sequence: kept,
|
|
172
174
|
at: row.timestamp,
|
|
173
|
-
tokensBefore: entry.tokensBefore,
|
|
174
|
-
tokensAfter: entry.tokensAfter ?? null,
|
|
175
|
-
trigger: entry.
|
|
175
|
+
tokensBefore: entry.data.tokensBefore,
|
|
176
|
+
tokensAfter: entry.data.tokensAfter ?? null,
|
|
177
|
+
trigger: entry.data.reason ?? null,
|
|
176
178
|
}));
|
|
177
179
|
}
|
|
178
180
|
return Object.freeze(markers);
|
|
@@ -187,7 +189,9 @@ function compactionNoticesIn(rows) {
|
|
|
187
189
|
at: timestamp,
|
|
188
190
|
trigger: entry.trigger,
|
|
189
191
|
result: entry.result,
|
|
190
|
-
diagnostic: entry.
|
|
192
|
+
diagnostic: entry.result === "stale" || entry.result === "insufficient"
|
|
193
|
+
? null
|
|
194
|
+
: (entry.diagnostic ?? null),
|
|
191
195
|
}),
|
|
192
196
|
]
|
|
193
197
|
: []));
|
|
@@ -268,37 +272,7 @@ function readPendingPrompts(owner, sessionId) {
|
|
|
268
272
|
const prompts = [];
|
|
269
273
|
let bytes = encoder.encode(JSON.stringify({ prompts: [], hasMore: false, total })).byteLength;
|
|
270
274
|
for (const row of rows) {
|
|
271
|
-
const
|
|
272
|
-
if (entry === undefined)
|
|
273
|
-
throw unavailable("Pending Session prompt is unavailable");
|
|
274
|
-
const content = entry.message.content;
|
|
275
|
-
const prompt = isPromptParts(content)
|
|
276
|
-
? {
|
|
277
|
-
commandId: row.entry.workId,
|
|
278
|
-
text: content
|
|
279
|
-
.filter((part) => part.type === "text")
|
|
280
|
-
.map((part) => part.text)
|
|
281
|
-
.join(""),
|
|
282
|
-
images: content.filter((part) => part.type === "image"),
|
|
283
|
-
delivery: entry.delivery ?? "followUp",
|
|
284
|
-
}
|
|
285
|
-
: {
|
|
286
|
-
commandId: row.entry.workId,
|
|
287
|
-
text: content,
|
|
288
|
-
delivery: entry.delivery ?? "followUp",
|
|
289
|
-
};
|
|
290
|
-
const withMeta = row.entry.meta === undefined
|
|
291
|
-
? prompt
|
|
292
|
-
: { ...prompt, meta: row.entry.meta };
|
|
293
|
-
const attributed = row.origin.kind === "extension"
|
|
294
|
-
? {
|
|
295
|
-
...withMeta,
|
|
296
|
-
origin: {
|
|
297
|
-
extensionId: row.origin.extensionId,
|
|
298
|
-
correlation: row.origin.correlation,
|
|
299
|
-
},
|
|
300
|
-
}
|
|
301
|
-
: withMeta;
|
|
275
|
+
const attributed = projectPendingPrompt(row);
|
|
302
276
|
const size = encoder.encode(JSON.stringify(attributed)).byteLength +
|
|
303
277
|
(prompts.length === 0 ? 0 : 1);
|
|
304
278
|
if (prompts.length === maximumRowsPerPage ||
|
|
@@ -309,6 +283,45 @@ function readPendingPrompts(owner, sessionId) {
|
|
|
309
283
|
}
|
|
310
284
|
return { prompts, hasMore: false, total };
|
|
311
285
|
}
|
|
286
|
+
/** Same payload projection as the FIFO page, selected independently of that page's bounds. */
|
|
287
|
+
export function readLatestPendingPrompt(owner, sessionId, identity) {
|
|
288
|
+
const row = readNewestPendingSessionCommand(owner, sessionId, identity);
|
|
289
|
+
return row === undefined ? null : projectPendingPrompt(row);
|
|
290
|
+
}
|
|
291
|
+
function projectPendingPrompt(row) {
|
|
292
|
+
const entry = promptCommand(row.entry);
|
|
293
|
+
if (entry === undefined)
|
|
294
|
+
throw unavailable("Pending Session prompt is unavailable");
|
|
295
|
+
const content = entry.message.content;
|
|
296
|
+
const prompt = isPromptParts(content)
|
|
297
|
+
? {
|
|
298
|
+
commandId: row.entry.workId,
|
|
299
|
+
requestId: entry.requestId,
|
|
300
|
+
text: content
|
|
301
|
+
.filter((part) => part.type === "text")
|
|
302
|
+
.map((part) => part.text)
|
|
303
|
+
.join(""),
|
|
304
|
+
images: content.filter((part) => part.type === "image"),
|
|
305
|
+
delivery: entry.delivery ?? "followUp",
|
|
306
|
+
}
|
|
307
|
+
: {
|
|
308
|
+
commandId: row.entry.workId,
|
|
309
|
+
requestId: entry.requestId,
|
|
310
|
+
text: content,
|
|
311
|
+
delivery: entry.delivery ?? "followUp",
|
|
312
|
+
};
|
|
313
|
+
const withMeta = row.entry.meta === undefined ? prompt : { ...prompt, meta: row.entry.meta };
|
|
314
|
+
const attributed = row.origin.kind === "extension"
|
|
315
|
+
? {
|
|
316
|
+
...withMeta,
|
|
317
|
+
origin: {
|
|
318
|
+
extensionId: row.origin.extensionId,
|
|
319
|
+
correlation: row.origin.correlation,
|
|
320
|
+
},
|
|
321
|
+
}
|
|
322
|
+
: withMeta;
|
|
323
|
+
return attributed;
|
|
324
|
+
}
|
|
312
325
|
/**
|
|
313
326
|
* The exclusive upper sequence a baseline read pages back from: none for the
|
|
314
327
|
* latest page, or exactly the entry a `before` cursor names. Undefined means
|
|
@@ -345,6 +358,7 @@ export function readClientSnapshot(owner, sessionId, options, activity, projectE
|
|
|
345
358
|
pending,
|
|
346
359
|
activity: activity.facts,
|
|
347
360
|
progress: activity.progress,
|
|
361
|
+
toolOutput: activity.toolOutput,
|
|
348
362
|
extensions: projectExtensions(),
|
|
349
363
|
attention: readSessionAttention(owner, sessionId),
|
|
350
364
|
});
|
|
@@ -379,6 +393,7 @@ export function readClientSnapshot(owner, sessionId, options, activity, projectE
|
|
|
379
393
|
pending,
|
|
380
394
|
activity: activity.facts,
|
|
381
395
|
progress: options.before === undefined ? activity.progress : null,
|
|
396
|
+
toolOutput: options.before === undefined ? activity.toolOutput : [],
|
|
382
397
|
extensions: projectExtensions(),
|
|
383
398
|
attention: readSessionAttention(owner, sessionId),
|
|
384
399
|
};
|
|
@@ -433,7 +448,16 @@ function replayFrames(owner, lineage, start, delivery, activity, extensions, att
|
|
|
433
448
|
}));
|
|
434
449
|
return Object.freeze({
|
|
435
450
|
kind: "page",
|
|
436
|
-
frames: Object.freeze(
|
|
451
|
+
frames: Object.freeze(!page.hasMore
|
|
452
|
+
? [
|
|
453
|
+
...frames,
|
|
454
|
+
{
|
|
455
|
+
kind: "tool_output",
|
|
456
|
+
toolOutput: activity.toolOutput,
|
|
457
|
+
activity: activity.facts,
|
|
458
|
+
},
|
|
459
|
+
]
|
|
460
|
+
: frames),
|
|
437
461
|
hasMore: page.hasMore,
|
|
438
462
|
});
|
|
439
463
|
}
|
|
@@ -494,7 +518,9 @@ export function projectCommandSettlement(owner, sessionId, commandId) {
|
|
|
494
518
|
: []);
|
|
495
519
|
const last = answers.at(-1);
|
|
496
520
|
const meta = terminal.outcome === "cancelled" ? terminal.meta : admission.entry.meta;
|
|
521
|
+
const sender = messageAuthor(admission.entry.meta);
|
|
497
522
|
return {
|
|
523
|
+
acceptedSender: sender?.kind === "agent" ? sender.id : null,
|
|
498
524
|
terminal: meta === undefined ? terminal : { ...terminal, meta },
|
|
499
525
|
content: terminal.outcome === "cancelled"
|
|
500
526
|
? ""
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ColdSessionPage, ColdSessionPageRequest } from "../../cold-sessions.js";
|
|
2
|
+
import type { SqliteOwner } from "../contracts.js";
|
|
3
|
+
/** Read one bounded page of Sessions with stored bytes, coldest first. */
|
|
4
|
+
export declare function readColdSessionPageOwned(owner: SqliteOwner, request: ColdSessionPageRequest): ColdSessionPage;
|