@fungi.computer/shiitake 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -21
- package/dist/agent-control.d.ts +1 -1
- package/dist/agent-control.js +40 -21
- package/dist/client-visitor.d.ts +2 -0
- package/dist/client-visitor.js +6 -1
- package/dist/client-wire.d.ts +4 -0
- package/dist/client-wire.js +26 -15
- package/dist/client.d.ts +23 -11
- package/dist/client.js +22 -0
- package/dist/cloudflare.d.ts +20 -0
- package/dist/cloudflare.js +4 -1
- package/dist/delegation.d.ts +5 -0
- package/dist/delegation.js +24 -8
- package/dist/effect.d.ts +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +2 -0
- package/dist/extensions.d.ts +1 -1
- package/dist/fleet.d.ts +14 -2
- package/dist/fleet.js +81 -19
- package/dist/http-routes.d.ts +12 -16
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/internal/admission-door.d.ts +1 -1
- package/dist/internal/admission-door.js +8 -4
- package/dist/internal/contracts.d.ts +5 -0
- package/dist/internal/coordinator.js +107 -50
- package/dist/internal/effect-contracts.d.ts +5 -0
- package/dist/internal/extension-runtime.js +5 -3
- package/dist/internal/observation-kernel.js +12 -3
- package/dist/internal/promise-facade.js +2 -0
- package/dist/internal/run-scope.d.ts +1 -0
- package/dist/internal/run-scope.js +29 -7
- package/dist/internal/runtime-coordinator.d.ts +14 -0
- package/dist/internal/runtime-coordinator.js +57 -10
- package/dist/internal/runtime-core.d.ts +3 -0
- package/dist/internal/runtime-core.js +1 -0
- package/dist/internal/runtime-reconciliation.js +4 -1
- package/dist/internal/session-context.d.ts +5 -5
- package/dist/internal/session-context.js +80 -83
- package/dist/internal/session-status.d.ts +4 -2
- package/dist/internal/session-status.js +22 -4
- package/dist/internal/woodstock/client-projection.js +5 -11
- package/dist/internal/woodstock/fleet-store.d.ts +3 -2
- package/dist/internal/woodstock/fleet-store.js +24 -4
- package/dist/internal/woodstock/index.d.ts +3 -3
- package/dist/internal/woodstock/index.js +18 -7
- package/dist/internal/woodstock/progress.d.ts +13 -1
- package/dist/internal/woodstock/progress.js +130 -24
- package/dist/internal/woodstock/session-delegator.d.ts +2 -1
- package/dist/internal/woodstock/session-store.d.ts +14 -2
- package/dist/internal/woodstock/session-store.js +329 -66
- package/dist/node.js +11 -2
- package/dist/server-visitor-stream.js +33 -24
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +2 -0
- package/docs/api/agent-control.md +3 -2
- package/docs/api/client.md +79 -19
- package/docs/api/cloudflare.md +16 -0
- package/docs/api/delegation.md +84 -50
- package/docs/api/effect.md +85 -7
- package/docs/api/extensions.md +8 -8
- package/docs/api/fleet.md +118 -29
- package/docs/api/http.md +28 -0
- package/docs/api/index.md +39 -15
- package/docs/api/testing.md +20 -7
- package/package.json +5 -5
|
@@ -14,103 +14,100 @@ function settledContextTokens(store, sessionId) {
|
|
|
14
14
|
return messages.length === 0 ? null : estimateContextTokens(messages).tokens;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* null, never guessed.
|
|
17
|
+
* Read the stored figure synchronously with the Session baseline. Only model
|
|
18
|
+
* description remains in the returned Effect, outside the SQLite transaction
|
|
19
|
+
* and the request's ancestry scope. Undefined without readable context; a model
|
|
20
|
+
* window the host cannot describe is null, never guessed.
|
|
21
21
|
*/
|
|
22
|
-
export function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return yield* describe({ sessionId, preference }).pipe(Effect.map(({ contextWindow }) => {
|
|
46
|
-
if (previousValue.window === contextWindow)
|
|
47
|
-
return previousValue;
|
|
48
|
-
const value = Object.freeze({
|
|
49
|
-
tokens: previousValue.tokens,
|
|
50
|
-
window: contextWindow,
|
|
51
|
-
});
|
|
52
|
-
if (cache && ownerCache.get(sessionId)?.cursor === cursor)
|
|
53
|
-
ownerCache.set(sessionId, {
|
|
54
|
-
cursor,
|
|
55
|
-
preference: preferenceKey,
|
|
56
|
-
value,
|
|
57
|
-
});
|
|
58
|
-
return value;
|
|
59
|
-
}), Effect.catch((error) => Effect.sync(() => {
|
|
60
|
-
reportCatchFailure("session-context.describe", error);
|
|
22
|
+
export function prepareSessionContext(options, sessionId, cursor, cache = true) {
|
|
23
|
+
let preference;
|
|
24
|
+
try {
|
|
25
|
+
preference = readSessionModelPreference(options.store, sessionId);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
reportCatchFailure("session-context.preference", error);
|
|
29
|
+
return Effect.succeed(undefined);
|
|
30
|
+
}
|
|
31
|
+
const preferenceKey = JSON.stringify(preference);
|
|
32
|
+
const ownerCache = contextByOwner.get(options) ?? new Map();
|
|
33
|
+
contextByOwner.set(options, ownerCache);
|
|
34
|
+
const previous = cache ? ownerCache.get(sessionId) : undefined;
|
|
35
|
+
const describe = options.model.describe;
|
|
36
|
+
if (previous?.cursor === cursor && previous.preference === preferenceKey) {
|
|
37
|
+
if (previous.value === undefined || describe === undefined)
|
|
38
|
+
return Effect.succeed(previous.value);
|
|
39
|
+
const previousValue = previous.value;
|
|
40
|
+
// A null Session override follows a mutable Agent default. Check the
|
|
41
|
+
// selected window without decoding history again; a changed window is
|
|
42
|
+
// a distinct effective preference for this cursor.
|
|
43
|
+
return describe({ sessionId, preference }).pipe(Effect.map(({ contextWindow }) => {
|
|
44
|
+
if (previousValue.window === contextWindow)
|
|
61
45
|
return previousValue;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// the whole window. A cold-cache connection also reads the whole kept
|
|
68
|
-
// window.
|
|
69
|
-
const contextRead = yield* Effect.result(Effect.try({
|
|
70
|
-
try: () => settledContextTokens(options.store, sessionId),
|
|
71
|
-
catch: (error) => error,
|
|
72
|
-
}));
|
|
73
|
-
if (contextRead._tag === "Failure") {
|
|
74
|
-
reportCatchFailure("session-context.figure", contextRead.failure);
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
|
-
const tokens = contextRead.success;
|
|
78
|
-
if (tokens === null) {
|
|
79
|
-
if (cache)
|
|
46
|
+
const value = Object.freeze({
|
|
47
|
+
tokens: previousValue.tokens,
|
|
48
|
+
window: contextWindow,
|
|
49
|
+
});
|
|
50
|
+
if (cache && ownerCache.get(sessionId)?.cursor === cursor)
|
|
80
51
|
ownerCache.set(sessionId, {
|
|
81
52
|
cursor,
|
|
82
53
|
preference: preferenceKey,
|
|
83
|
-
value
|
|
54
|
+
value,
|
|
84
55
|
});
|
|
85
|
-
return undefined;
|
|
86
|
-
}
|
|
87
|
-
if (describe === undefined) {
|
|
88
|
-
const value = Object.freeze({ tokens, window: null });
|
|
89
|
-
if (cache)
|
|
90
|
-
ownerCache.set(sessionId, { cursor, preference: preferenceKey, value });
|
|
91
56
|
return value;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
57
|
+
}), Effect.catch((error) => Effect.sync(() => {
|
|
58
|
+
reportCatchFailure("session-context.describe", error);
|
|
59
|
+
return previousValue;
|
|
60
|
+
})));
|
|
61
|
+
}
|
|
62
|
+
// At most one cached whole-window read per Session, cursor, and
|
|
63
|
+
// preference: a late describe may publish only while this cursor still
|
|
64
|
+
// owns the cache entry. Reads before the tip bypass the cache and each read
|
|
65
|
+
// the whole window. A cold-cache connection also reads the whole kept
|
|
66
|
+
// window.
|
|
67
|
+
let tokens;
|
|
68
|
+
try {
|
|
69
|
+
tokens = settledContextTokens(options.store, sessionId);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
reportCatchFailure("session-context.figure", error);
|
|
73
|
+
return Effect.succeed(undefined);
|
|
74
|
+
}
|
|
75
|
+
if (tokens === null) {
|
|
96
76
|
if (cache)
|
|
97
77
|
ownerCache.set(sessionId, {
|
|
98
78
|
cursor,
|
|
99
79
|
preference: preferenceKey,
|
|
100
|
-
value:
|
|
80
|
+
value: undefined,
|
|
101
81
|
});
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
82
|
+
return Effect.succeed(undefined);
|
|
83
|
+
}
|
|
84
|
+
if (describe === undefined) {
|
|
85
|
+
const value = Object.freeze({ tokens, window: null });
|
|
86
|
+
if (cache)
|
|
87
|
+
ownerCache.set(sessionId, { cursor, preference: preferenceKey, value });
|
|
88
|
+
return Effect.succeed(value);
|
|
89
|
+
}
|
|
90
|
+
// Publish the decoded figure before yielding to the host's model
|
|
91
|
+
// description. Concurrent watchers then reuse these tokens rather than
|
|
92
|
+
// decoding the same Session window while `describe` is in flight.
|
|
93
|
+
if (cache)
|
|
94
|
+
ownerCache.set(sessionId, {
|
|
95
|
+
cursor,
|
|
96
|
+
preference: preferenceKey,
|
|
97
|
+
value: Object.freeze({ tokens, window: null }),
|
|
98
|
+
});
|
|
99
|
+
// A null Session override follows a mutable Agent default. Resolve through
|
|
100
|
+
// the host's turn-model selector on every read, without acquiring a turn.
|
|
101
|
+
return describe({
|
|
102
|
+
sessionId,
|
|
103
|
+
preference,
|
|
104
|
+
}).pipe(Effect.map(({ contextWindow }) => contextWindow), Effect.catch((error) => Effect.sync(() => {
|
|
105
|
+
reportCatchFailure("session-context.describe", error);
|
|
106
|
+
return null;
|
|
107
|
+
})), Effect.map((window) => {
|
|
111
108
|
const value = Object.freeze({ tokens, window });
|
|
112
109
|
if (cache && ownerCache.get(sessionId)?.cursor === cursor)
|
|
113
110
|
ownerCache.set(sessionId, { cursor, preference: preferenceKey, value });
|
|
114
111
|
return value;
|
|
115
|
-
});
|
|
112
|
+
}));
|
|
116
113
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { WatchdogQueueHead } from "@fungi.computer/watchdog/effect";
|
|
2
2
|
import type { AgentRunningWork, SessionActivity, SessionProgress, SessionToolOutput } from "../client.js";
|
|
3
3
|
import type { SqliteOwner } from "./contracts.js";
|
|
4
|
-
/**
|
|
4
|
+
/** Activity facts plus an explicit, on-demand bounded progress read. */
|
|
5
5
|
export type ObservedActivity = Readonly<{
|
|
6
6
|
facts: SessionActivity;
|
|
7
|
-
|
|
7
|
+
readProgress: () => SessionProgress | null;
|
|
8
8
|
toolOutput: readonly SessionToolOutput[];
|
|
9
9
|
}>;
|
|
10
|
+
/** Project only the occupying Session's activity, without disclosing its data. */
|
|
11
|
+
export declare function readSessionPhase(owner: SqliteOwner, sessionId: string, head: WatchdogQueueHead): AgentRunningWork["phase"];
|
|
10
12
|
/** Join Watchdog authority with Woodstock facts and process-local progress. */
|
|
11
13
|
export declare function readSessionActivity(owner: SqliteOwner, sessionId: string, head: WatchdogQueueHead, runningWork: AgentRunningWork | null): ObservedActivity;
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
import { readProcessProgressForWork, readProcessToolOutputForWork, readProcessCompaction, readLatestLiveRetryForWork, readActiveToolForWork, readLastModelChoiceSequence, } from "./woodstock/index.js";
|
|
1
|
+
import { readProcessProgressForWork, readProcessToolOutputForWork, readProcessCompaction, readLatestLiveRetryForWork, readActiveToolForWork, readLastModelChoiceSequence, readSessionAttention, hasProcessTextForWork, } from "./woodstock/index.js";
|
|
2
|
+
/** Project only the occupying Session's activity, without disclosing its data. */
|
|
3
|
+
export function readSessionPhase(owner, sessionId, head) {
|
|
4
|
+
const { facts } = readSessionActivity(owner, sessionId, head, null);
|
|
5
|
+
if (facts.cancellationRequested)
|
|
6
|
+
return "cancelling";
|
|
7
|
+
if (readSessionAttention(owner, sessionId).some((item) => item.inbox !== false && item.waitingOn === undefined))
|
|
8
|
+
return "waiting_on_you";
|
|
9
|
+
if (facts.compaction !== null || facts.retry !== null)
|
|
10
|
+
return "thinking";
|
|
11
|
+
if (facts.activeTool !== null)
|
|
12
|
+
return "running_tool";
|
|
13
|
+
if (head.state === "running" &&
|
|
14
|
+
hasProcessTextForWork(owner, sessionId, head.jobId))
|
|
15
|
+
return "streaming";
|
|
16
|
+
return "thinking";
|
|
17
|
+
}
|
|
2
18
|
/** Join Watchdog authority with Woodstock facts and process-local progress. */
|
|
3
19
|
export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
4
20
|
const lastModelChoiceSequence = readLastModelChoiceSequence(owner, sessionId);
|
|
@@ -12,7 +28,7 @@ export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
|
12
28
|
lastModelChoiceSequence,
|
|
13
29
|
compaction: null,
|
|
14
30
|
},
|
|
15
|
-
|
|
31
|
+
readProgress: () => null,
|
|
16
32
|
toolOutput: [],
|
|
17
33
|
};
|
|
18
34
|
if (head.state === "queued")
|
|
@@ -27,7 +43,7 @@ export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
|
27
43
|
lastModelChoiceSequence,
|
|
28
44
|
compaction: null,
|
|
29
45
|
},
|
|
30
|
-
|
|
46
|
+
readProgress: () => null,
|
|
31
47
|
toolOutput: [],
|
|
32
48
|
};
|
|
33
49
|
const latestRetry = readLatestLiveRetryForWork(owner, sessionId, head.jobId);
|
|
@@ -49,7 +65,9 @@ export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
|
49
65
|
lastModelChoiceSequence,
|
|
50
66
|
compaction: readProcessCompaction(owner, sessionId),
|
|
51
67
|
},
|
|
52
|
-
|
|
68
|
+
// Only baselines need a flattened bounded tail. Live subscribers read
|
|
69
|
+
// appends directly and must not materialize it on every token.
|
|
70
|
+
readProgress: () => readProcessProgressForWork(owner, sessionId, head.jobId),
|
|
53
71
|
toolOutput: readProcessToolOutputForWork(owner, sessionId, head.jobId),
|
|
54
72
|
};
|
|
55
73
|
}
|
|
@@ -5,7 +5,7 @@ import { readSessionDelegator } from "./session-delegator.js";
|
|
|
5
5
|
import { ShiitakeError } from "../../errors.js";
|
|
6
6
|
import { decodeWoodstockCursor, encodeWoodstockCursor } from "./cursor.js";
|
|
7
7
|
import { operatorClientMessage, promptCommand } from "./session-entry.js";
|
|
8
|
-
import { lineageEntrySequence, lineageHasSequence, lineageRowLimit, readLineageWindow, readLineageWorkFacts, hasSessionWorkTerminal, readSessionEntriesForWork, readSessionLineage, readPendingSessionCommands, readSessionCommandAdmission, readNewestPendingSessionCommand, } from "./session-store.js";
|
|
8
|
+
import { lineageEntrySequence, lineageHasSequence, lineageRowLimit, readLineageWindow, readLineageWorkFacts, hasSessionWorkTerminal, readSessionEntriesForWork, readSessionLineage, readPendingSessionCommands, readSessionCommandAdmission, readNewestPendingSessionCommand, withSessionReadTransaction, } from "./session-store.js";
|
|
9
9
|
const maximumPagePayloadBytes = 2_097_152;
|
|
10
10
|
const replayPageLimit = 200;
|
|
11
11
|
const maximumRowsPerPage = lineageRowLimit;
|
|
@@ -344,7 +344,7 @@ function resolveHistoryAnchor(owner, lineage, before) {
|
|
|
344
344
|
* history, including what compaction summarized away.
|
|
345
345
|
*/
|
|
346
346
|
export function readClientSnapshot(owner, sessionId, options, activity, projectExtensions = () => ({})) {
|
|
347
|
-
return owner
|
|
347
|
+
return withSessionReadTransaction(owner, sessionId, () => {
|
|
348
348
|
const pending = readPendingPrompts(owner, sessionId);
|
|
349
349
|
const lineage = readSessionLineage(owner, { sessionId });
|
|
350
350
|
if (lineage === undefined)
|
|
@@ -359,7 +359,7 @@ export function readClientSnapshot(owner, sessionId, options, activity, projectE
|
|
|
359
359
|
compactionNotices: Object.freeze([]),
|
|
360
360
|
pending,
|
|
361
361
|
activity: activity.facts,
|
|
362
|
-
progress: activity.
|
|
362
|
+
progress: activity.readProgress(),
|
|
363
363
|
toolOutput: activity.toolOutput,
|
|
364
364
|
extensions: projectExtensions(),
|
|
365
365
|
attention: readSessionAttention(owner, sessionId),
|
|
@@ -394,7 +394,7 @@ export function readClientSnapshot(owner, sessionId, options, activity, projectE
|
|
|
394
394
|
compactionNotices: compactionNoticesIn(consumedRows),
|
|
395
395
|
pending,
|
|
396
396
|
activity: activity.facts,
|
|
397
|
-
progress: options.before === undefined ? activity.
|
|
397
|
+
progress: options.before === undefined ? activity.readProgress() : null,
|
|
398
398
|
toolOutput: options.before === undefined ? activity.toolOutput : [],
|
|
399
399
|
extensions: projectExtensions(),
|
|
400
400
|
attention: readSessionAttention(owner, sessionId),
|
|
@@ -442,12 +442,6 @@ function replayFrames(owner, lineage, start, delivery, activity, extensions, att
|
|
|
442
442
|
? { ...frame, sandboxFailures: page.sandboxFailures }
|
|
443
443
|
: frame),
|
|
444
444
|
];
|
|
445
|
-
if (delivery === "live" && !page.hasMore && activity.progress !== null)
|
|
446
|
-
frames.push(Object.freeze({
|
|
447
|
-
kind: "progress",
|
|
448
|
-
progress: activity.progress,
|
|
449
|
-
activity: activity.facts,
|
|
450
|
-
}));
|
|
451
445
|
return Object.freeze({
|
|
452
446
|
kind: "page",
|
|
453
447
|
frames: Object.freeze(!page.hasMore
|
|
@@ -468,7 +462,7 @@ export function readClientReplayPage(owner, sessionId, after, delivery, activity
|
|
|
468
462
|
const position = after === undefined ? undefined : decodeWoodstockCursor(after);
|
|
469
463
|
if (after !== undefined && position === undefined)
|
|
470
464
|
return resetReplayPage;
|
|
471
|
-
return owner
|
|
465
|
+
return withSessionReadTransaction(owner, sessionId, () => {
|
|
472
466
|
const pending = readPendingPrompts(owner, sessionId);
|
|
473
467
|
const lineage = readSessionLineage(owner, { sessionId });
|
|
474
468
|
if (lineage === undefined) {
|
|
@@ -27,10 +27,11 @@ export declare function createFleetDelegationStore(storage: WatchdogSqliteOwner)
|
|
|
27
27
|
read: (requestId: string) => {
|
|
28
28
|
readonly [x: string]: string | number | ArrayBuffer | null;
|
|
29
29
|
} | undefined;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/** Accepted sends not yet observed settled, for owner-local archive admission. */
|
|
31
|
+
unsettled: () => readonly {
|
|
32
32
|
readonly [x: string]: string | number | ArrayBuffer | null;
|
|
33
33
|
}[];
|
|
34
|
+
recordSettled(requestId: string): void;
|
|
34
35
|
reserve(requestId: string, input: string, mail: string): void;
|
|
35
36
|
recordReceipt(requestId: string, receipt: string, expectedMail: string, acceptedMail: string): void;
|
|
36
37
|
recordHeld(requestId: string): void;
|
|
@@ -90,6 +90,12 @@ export function createFleetDelegationStore(storage) {
|
|
|
90
90
|
storage.sql
|
|
91
91
|
.exec(`CREATE TABLE IF NOT EXISTS shiitake_fleet_undeliverable_delegations (
|
|
92
92
|
request_id TEXT PRIMARY KEY
|
|
93
|
+
) STRICT`);
|
|
94
|
+
// A remote settlement confirmed by this owner. Without this local fact an
|
|
95
|
+
// accepted sender receipt remains indistinguishable from outstanding work.
|
|
96
|
+
storage.sql
|
|
97
|
+
.exec(`CREATE TABLE IF NOT EXISTS shiitake_fleet_settled_delegations (
|
|
98
|
+
request_id TEXT PRIMARY KEY
|
|
93
99
|
) STRICT`);
|
|
94
100
|
return {
|
|
95
101
|
read: (requestId) => storage.sql
|
|
@@ -108,13 +114,27 @@ export function createFleetDelegationStore(storage) {
|
|
|
108
114
|
ON terminal.request_id = delegation.request_id
|
|
109
115
|
WHERE delegation.request_id = ?`, requestId)
|
|
110
116
|
.toArray()[0],
|
|
111
|
-
/**
|
|
112
|
-
|
|
117
|
+
/** Accepted sends not yet observed settled, for owner-local archive admission. */
|
|
118
|
+
unsettled: () => storage.sql
|
|
113
119
|
.exec(`SELECT input, mail, 'accepted' AS state, receipt
|
|
114
|
-
FROM shiitake_fleet_delegations
|
|
120
|
+
FROM shiitake_fleet_delegations AS delegation
|
|
115
121
|
WHERE receipt IS NOT NULL
|
|
116
|
-
|
|
122
|
+
AND NOT EXISTS (
|
|
123
|
+
SELECT 1 FROM shiitake_fleet_settled_delegations AS settled
|
|
124
|
+
WHERE settled.request_id = delegation.request_id
|
|
125
|
+
)
|
|
126
|
+
ORDER BY delegation.rowid DESC`)
|
|
117
127
|
.toArray(),
|
|
128
|
+
recordSettled(requestId) {
|
|
129
|
+
storage.transactionSync(() => {
|
|
130
|
+
const accepted = storage.sql
|
|
131
|
+
.exec("SELECT 1 FROM shiitake_fleet_delegations WHERE request_id = ? AND receipt IS NOT NULL", requestId)
|
|
132
|
+
.toArray();
|
|
133
|
+
if (accepted.length !== 1)
|
|
134
|
+
throw new Error("Cannot record settlement without acceptance");
|
|
135
|
+
storage.sql.exec("INSERT OR IGNORE INTO shiitake_fleet_settled_delegations(request_id) VALUES(?)", requestId);
|
|
136
|
+
});
|
|
137
|
+
},
|
|
118
138
|
reserve(requestId, input, mail) {
|
|
119
139
|
storage.transactionSync(() => {
|
|
120
140
|
storage.sql.exec("INSERT OR IGNORE INTO shiitake_fleet_delegations(request_id, input, mail) VALUES (?, ?, ?)", requestId, input, mail);
|
|
@@ -119,7 +119,7 @@ export declare function readSettlementDiagnostic(owner: SqliteOwner, input: Read
|
|
|
119
119
|
commandFactId: string;
|
|
120
120
|
}>): Effect.Effect<string | undefined, ShiitakeError>;
|
|
121
121
|
/** Read one bounded public Session snapshot. */
|
|
122
|
-
export declare function readSession(owner: SqliteOwner, sessionId: string, activity: ObservedActivity, input
|
|
122
|
+
export declare function readSession<A>(owner: SqliteOwner, sessionId: string, activity: () => ObservedActivity, input: ReadOptions | undefined, projectExtensions: () => SessionExtensionSlots, complete: (snapshot: SessionSnapshot) => A): Effect.Effect<A, ShiitakeError>;
|
|
123
123
|
/** Read one bounded folder-resume summary through Woodstock's Effect boundary. */
|
|
124
124
|
export declare function readSessionSummary(owner: SqliteOwner, sessionId: string): Effect.Effect<import("./session-store.js").StoredSessionSummary | undefined, ShiitakeError>;
|
|
125
125
|
/**
|
|
@@ -142,8 +142,8 @@ export declare function readColdSessionPage(owner: SqliteOwner, request: ColdSes
|
|
|
142
142
|
export declare function readSessionInbox(owner: SqliteOwner, after: string | undefined): Effect.Effect<import("../../client.js").SessionSummaryPage, ShiitakeError>;
|
|
143
143
|
/** Read a Session's open attention items from the store alone. */
|
|
144
144
|
export declare function readStoredSessionAttention(owner: SqliteOwner, sessionId: string): Effect.Effect<readonly import("../../client.js").SessionAttention[], ShiitakeError>;
|
|
145
|
-
/** Read one strict-after page
|
|
146
|
-
export declare function readSessionReplayPage(owner: SqliteOwner, sessionId: string, after: string | undefined, delivery: "replay" | "live", activity: ObservedActivity, projectExtensions
|
|
145
|
+
/** Read and complete one strict-after page in the same Session transaction. */
|
|
146
|
+
export declare function readSessionReplayPage<A>(owner: SqliteOwner, sessionId: string, after: string | undefined, delivery: "replay" | "live", activity: ObservedActivity, projectExtensions: () => SessionExtensionSlots, includeSandboxFailures: boolean, complete: (page: ClientReplayPage) => A): Effect.Effect<A, ShiitakeError>;
|
|
147
147
|
/** Persist an operator-only sandbox diagnostic beside its accepted Session work. */
|
|
148
148
|
export declare function recordSandboxFailure(owner: SqliteOwner, accepted: Pick<AcceptedCommand, "sessionId" | "commandId">, event: SandboxFailureEvent): Effect.Effect<void, ShiitakeError>;
|
|
149
149
|
/** Extension that admitted one accepted command; null for other producers. */
|
|
@@ -5,9 +5,10 @@ import { publishOwnerObservation } from "../observation-kernel.js";
|
|
|
5
5
|
import { readColdSessionPageOwned } from "./cold-sessions.js";
|
|
6
6
|
import { diagnosticsByWorkId, projectCommandSettlement, readClientReplayPage, readClientSnapshot, } from "./client-projection.js";
|
|
7
7
|
import { sessionIds } from "./identities.js";
|
|
8
|
+
import { decodeWoodstockCursor } from "./cursor.js";
|
|
8
9
|
import { openProgress, openToolProgress } from "./progress.js";
|
|
9
10
|
import { encodeStoredSessionEntry, } from "./session-entry.js";
|
|
10
|
-
import { appendSessionEntry, appendCompactionTransitionOwned, appendCompactionNoticeOwned, placeCompactionTransitionOwned, acceptSessionAbort, acceptSessionCancellation, completeSessionLeaf, prepareSessionRun, prepareSessionStore, readSessionEntriesForWork, readSessionCommandAdmission, settleSessionControls, takePendingSessionControls, readSessionEntryById, selectSessionBranch, forkSessionAt, readStoredSessionSummary as readSessionSummaryOwned, readStoredSessionFactPage, createStoredSession, createStoredSessionWithinTransaction, deleteStoredSession, appendOperatorEntry, } from "./session-store.js";
|
|
11
|
+
import { appendSessionEntry, appendCompactionTransitionOwned, appendCompactionNoticeOwned, placeCompactionTransitionOwned, acceptSessionAbort, acceptSessionCancellation, completeSessionLeaf, prepareSessionRun, prepareSessionStore, readSessionEntriesForWork, readSessionCommandAdmission, settleSessionControls, takePendingSessionControls, readSessionEntryById, selectSessionBranch, forkSessionAt, readStoredSessionSummary as readSessionSummaryOwned, readStoredSessionFactPage, createStoredSession, createStoredSessionWithinTransaction, deleteStoredSession, appendOperatorEntry, withSessionRead, } from "./session-store.js";
|
|
11
12
|
import { readSessionInboxPage, recordSessionInboxDeletion, } from "./session-inbox.js";
|
|
12
13
|
import { readSessionAttention } from "./session-attention.js";
|
|
13
14
|
export * from "./extension-store.js";
|
|
@@ -191,8 +192,14 @@ export function readSettlementDiagnostic(owner, input) {
|
|
|
191
192
|
});
|
|
192
193
|
}
|
|
193
194
|
/** Read one bounded public Session snapshot. */
|
|
194
|
-
export function readSession(owner, sessionId, activity, input, projectExtensions
|
|
195
|
-
return storeEffect(() =>
|
|
195
|
+
export function readSession(owner, sessionId, activity, input, projectExtensions, complete) {
|
|
196
|
+
return storeEffect(() => {
|
|
197
|
+
const id = decodeClientSessionId(sessionId);
|
|
198
|
+
const options = decodeClientReadOptions(input);
|
|
199
|
+
return withSessionRead(owner, id, () => complete(readClientSnapshot(owner, id, options, activity(), projectExtensions)), options.before === undefined
|
|
200
|
+
? undefined
|
|
201
|
+
: decodeWoodstockCursor(options.before)?.sequence);
|
|
202
|
+
});
|
|
196
203
|
}
|
|
197
204
|
/** Read one bounded folder-resume summary through Woodstock's Effect boundary. */
|
|
198
205
|
export function readSessionSummary(owner, sessionId) {
|
|
@@ -235,9 +242,12 @@ export function readSessionInbox(owner, after) {
|
|
|
235
242
|
export function readStoredSessionAttention(owner, sessionId) {
|
|
236
243
|
return storeEffect(() => readSessionAttention(owner, sessionId));
|
|
237
244
|
}
|
|
238
|
-
/** Read one strict-after page
|
|
239
|
-
export function readSessionReplayPage(owner, sessionId, after, delivery, activity, projectExtensions
|
|
240
|
-
return storeEffect(() =>
|
|
245
|
+
/** Read and complete one strict-after page in the same Session transaction. */
|
|
246
|
+
export function readSessionReplayPage(owner, sessionId, after, delivery, activity, projectExtensions, includeSandboxFailures, complete) {
|
|
247
|
+
return storeEffect(() => {
|
|
248
|
+
const id = decodeClientSessionId(sessionId);
|
|
249
|
+
return withSessionRead(owner, id, () => complete(readClientReplayPage(owner, id, after, delivery, activity, projectExtensions, includeSandboxFailures)));
|
|
250
|
+
});
|
|
241
251
|
}
|
|
242
252
|
function sameEntry(left, right) {
|
|
243
253
|
return encodeStoredSessionEntry(left) === encodeStoredSessionEntry(right);
|
|
@@ -523,10 +533,11 @@ export function recordLiveRetryEvent(owner, accepted, event, turn) {
|
|
|
523
533
|
sameIdentity(current.event.run, event.run));
|
|
524
534
|
}).pipe(Effect.mapError(listenerFailure));
|
|
525
535
|
}
|
|
526
|
-
const listenerFailure = () => ({
|
|
536
|
+
const listenerFailure = (cause) => ({
|
|
527
537
|
_tag: "MuleError",
|
|
528
538
|
phase: "listener",
|
|
529
539
|
message: "Woodstock could not record the Mule event",
|
|
540
|
+
cause,
|
|
530
541
|
});
|
|
531
542
|
/**
|
|
532
543
|
* Open the one scoped listener that projects Mule's exact event stream into
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { MuleEvent } from "@fungi.computer/mule";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import type * as Scope from "effect/Scope";
|
|
4
|
-
import type { SessionProgress, SessionToolOutput } from "../../client.js";
|
|
4
|
+
import type { SessionProgress, SessionProgressDelta, SessionToolOutput } from "../../client.js";
|
|
5
5
|
import { ShiitakeError } from "../../errors.js";
|
|
6
6
|
import type { SqliteOwner } from "../contracts.js";
|
|
7
7
|
import type { AcceptedCommand } from "./session-store.js";
|
|
8
|
+
/** Per-watch position; a gap in the retained tail requires a fresh baseline. */
|
|
9
|
+
export type ProgressPosition = Readonly<{
|
|
10
|
+
messageId: string;
|
|
11
|
+
revision: number;
|
|
12
|
+
text: number;
|
|
13
|
+
thinking: number;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function progressPosition(value: SessionProgress | null): ProgressPosition | null;
|
|
16
|
+
/** Undefined means continuity was lost; null means no new model progress. */
|
|
17
|
+
export declare function readProcessProgressDelta(owner: SqliteOwner, sessionId: string, workId: string, after: ProgressPosition | null): SessionProgressDelta | null | undefined;
|
|
8
18
|
export declare function readProcessToolOutputForWork(owner: SqliteOwner, sessionId: string, workId: string): readonly SessionToolOutput[];
|
|
9
19
|
/** Run-scoped, replaceable tool snapshots: never stored in Woodstock. */
|
|
10
20
|
export declare function openToolProgress(owner: SqliteOwner, accepted: AcceptedCommand): Effect.Effect<{
|
|
@@ -22,6 +32,8 @@ export declare function openToolProgress(owner: SqliteOwner, accepted: AcceptedC
|
|
|
22
32
|
export declare function readProcessProgress(owner: SqliteOwner, sessionId: string): SessionProgress | null;
|
|
23
33
|
/** Read progress only when it belongs to the current durable work head. */
|
|
24
34
|
export declare function readProcessProgressForWork(owner: SqliteOwner, sessionId: string, workId: string): SessionProgress | null;
|
|
35
|
+
/** Read a phase fact without flattening another Session's private text tails. */
|
|
36
|
+
export declare function hasProcessTextForWork(owner: SqliteOwner, sessionId: string, workId: string): boolean;
|
|
25
37
|
export type ProgressMessageStart = Extract<MuleEvent, {
|
|
26
38
|
type: "message_start";
|
|
27
39
|
}>;
|