@fungi.computer/shiitake 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -21
- package/dist/agent-control.d.ts +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 +18 -3
- package/dist/client.d.ts +24 -4
- 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 +10 -2
- package/dist/fleet.js +77 -19
- package/dist/http-routes.d.ts +12 -9
- 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/promise-facade.js +2 -0
- package/dist/internal/run-scope.js +27 -7
- package/dist/internal/runtime-coordinator.d.ts +14 -0
- package/dist/internal/runtime-coordinator.js +52 -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 +2 -2
- package/dist/internal/session-status.js +5 -3
- 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 +16 -6
- package/dist/internal/woodstock/progress.d.ts +11 -1
- package/dist/internal/woodstock/progress.js +126 -25
- package/dist/internal/woodstock/session-delegator.d.ts +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/docs/api/agent-control.md +3 -2
- package/docs/api/client.md +77 -8
- 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 +87 -27
- package/docs/api/http.md +28 -0
- package/docs/api/index.md +39 -15
- package/docs/api/testing.md +9 -7
- package/package.json +5 -5
|
@@ -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);
|
|
@@ -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<{
|
|
@@ -3,6 +3,92 @@ import * as Schema from "effect/Schema";
|
|
|
3
3
|
import * as Result from "effect/Result";
|
|
4
4
|
import { ShiitakeError } from "../../errors.js";
|
|
5
5
|
import { publishOwnerObservation } from "../observation-kernel.js";
|
|
6
|
+
const tailBytes = 65_536;
|
|
7
|
+
const chunkUnits = 2048;
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const emptyTail = () => ({ chunks: [], length: 0, bytes: 0 });
|
|
10
|
+
/** Bounded chunks: no concatenation or encoding of the reply-so-far. */
|
|
11
|
+
function appendTail(tail, append) {
|
|
12
|
+
for (let index = 0; index < append.length;) {
|
|
13
|
+
let end = Math.min(index + chunkUnits, append.length);
|
|
14
|
+
// Do not cut a Unicode scalar when bounding a provider's large delta.
|
|
15
|
+
const code = append.charCodeAt(end - 1);
|
|
16
|
+
if (end < append.length && code >= 0xd800 && code <= 0xdbff)
|
|
17
|
+
end--;
|
|
18
|
+
const text = append.slice(index, end);
|
|
19
|
+
const bytes = encoder.encode(text).byteLength;
|
|
20
|
+
const last = tail.chunks.at(-1);
|
|
21
|
+
if (last !== undefined && last.text.length + text.length <= chunkUnits) {
|
|
22
|
+
last.text += text;
|
|
23
|
+
last.bytes += bytes;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
tail.chunks.push({ offset: tail.length, text, bytes });
|
|
27
|
+
}
|
|
28
|
+
tail.length += text.length;
|
|
29
|
+
tail.bytes += bytes;
|
|
30
|
+
index = end;
|
|
31
|
+
while (tail.bytes > tailBytes && tail.chunks.length > 1) {
|
|
32
|
+
const dropped = tail.chunks.shift();
|
|
33
|
+
if (dropped !== undefined)
|
|
34
|
+
tail.bytes -= dropped.bytes;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function tailOffset(tail) {
|
|
39
|
+
return tail.chunks[0]?.offset ?? 0;
|
|
40
|
+
}
|
|
41
|
+
function tailAfter(tail, offset) {
|
|
42
|
+
return tail.chunks
|
|
43
|
+
.flatMap((chunk) => chunk.offset + chunk.text.length <= offset
|
|
44
|
+
? []
|
|
45
|
+
: [chunk.text.slice(Math.max(0, offset - chunk.offset))])
|
|
46
|
+
.join("");
|
|
47
|
+
}
|
|
48
|
+
function snapshot(value) {
|
|
49
|
+
return value === null
|
|
50
|
+
? null
|
|
51
|
+
: {
|
|
52
|
+
messageId: value.messageId,
|
|
53
|
+
revision: value.revision,
|
|
54
|
+
text: tailAfter(value.text, tailOffset(value.text)),
|
|
55
|
+
thinking: tailAfter(value.thinking, tailOffset(value.thinking)),
|
|
56
|
+
textOffset: tailOffset(value.text),
|
|
57
|
+
thinkingOffset: tailOffset(value.thinking),
|
|
58
|
+
textTruncated: tailOffset(value.text) > 0,
|
|
59
|
+
thinkingTruncated: tailOffset(value.thinking) > 0,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function progressPosition(value) {
|
|
63
|
+
return value === null
|
|
64
|
+
? null
|
|
65
|
+
: {
|
|
66
|
+
messageId: value.messageId,
|
|
67
|
+
revision: value.revision,
|
|
68
|
+
text: value.textOffset + value.text.length,
|
|
69
|
+
thinking: value.thinkingOffset + value.thinking.length,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Undefined means continuity was lost; null means no new model progress. */
|
|
73
|
+
export function readProcessProgressDelta(owner, sessionId, workId, after) {
|
|
74
|
+
const current = progressByOwner.get(owner)?.get(sessionId);
|
|
75
|
+
const value = current?.workId === workId ? current.value : null;
|
|
76
|
+
if (value === null)
|
|
77
|
+
return null;
|
|
78
|
+
const position = after?.messageId === value.messageId ? after : null;
|
|
79
|
+
if (position !== null && position.revision >= value.revision)
|
|
80
|
+
return null;
|
|
81
|
+
const text = position?.text ?? 0;
|
|
82
|
+
const thinking = position?.thinking ?? 0;
|
|
83
|
+
if (text < tailOffset(value.text) || thinking < tailOffset(value.thinking))
|
|
84
|
+
return undefined;
|
|
85
|
+
return {
|
|
86
|
+
messageId: value.messageId,
|
|
87
|
+
revision: value.revision,
|
|
88
|
+
text: { offset: text, append: tailAfter(value.text, text) },
|
|
89
|
+
thinking: { offset: thinking, append: tailAfter(value.thinking, thinking) },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
6
92
|
const progressByOwner = new WeakMap();
|
|
7
93
|
export function readProcessToolOutputForWork(owner, sessionId, workId) {
|
|
8
94
|
const current = progressByOwner.get(owner)?.get(sessionId);
|
|
@@ -96,23 +182,12 @@ function rejected(message) {
|
|
|
96
182
|
}
|
|
97
183
|
/** Read the current process-local progress value for one addressed Session. */
|
|
98
184
|
export function readProcessProgress(owner, sessionId) {
|
|
99
|
-
return progressFor(owner).get(sessionId)?.value ?? null;
|
|
185
|
+
return snapshot(progressFor(owner).get(sessionId)?.value ?? null);
|
|
100
186
|
}
|
|
101
187
|
/** Read progress only when it belongs to the current durable work head. */
|
|
102
188
|
export function readProcessProgressForWork(owner, sessionId, workId) {
|
|
103
189
|
const current = progressFor(owner).get(sessionId);
|
|
104
|
-
return current?.workId === workId ? current.value : null;
|
|
105
|
-
}
|
|
106
|
-
function textFor(event) {
|
|
107
|
-
let text = "";
|
|
108
|
-
let thinking = "";
|
|
109
|
-
for (const content of event.value.content) {
|
|
110
|
-
if (content.type === "text")
|
|
111
|
-
text += content.text;
|
|
112
|
-
if (content.type === "thinking")
|
|
113
|
-
thinking += content.thinking;
|
|
114
|
-
}
|
|
115
|
-
return { text, thinking };
|
|
190
|
+
return snapshot(current?.workId === workId ? current.value : null);
|
|
116
191
|
}
|
|
117
192
|
/**
|
|
118
193
|
* Keep live model progress in the current process only.
|
|
@@ -124,7 +199,23 @@ export function openProgress(owner, accepted, complete) {
|
|
|
124
199
|
return Effect.gen(function* () {
|
|
125
200
|
const current = yield* openProcessProgress(owner, accepted);
|
|
126
201
|
let started;
|
|
202
|
+
let publishedLength = 0;
|
|
203
|
+
let flushTimer;
|
|
204
|
+
const cancelFlush = () => {
|
|
205
|
+
if (flushTimer !== undefined)
|
|
206
|
+
clearTimeout(flushTimer);
|
|
207
|
+
flushTimer = undefined;
|
|
208
|
+
};
|
|
209
|
+
const publish = () => {
|
|
210
|
+
cancelFlush();
|
|
211
|
+
const value = current.value;
|
|
212
|
+
if (value === null)
|
|
213
|
+
return;
|
|
214
|
+
publishedLength = value.text.length + value.thinking.length;
|
|
215
|
+
publishOwnerObservation(owner, accepted.sessionId, "progress");
|
|
216
|
+
};
|
|
127
217
|
const clear = Effect.sync(() => {
|
|
218
|
+
cancelFlush();
|
|
128
219
|
const hadProgress = current.value !== null;
|
|
129
220
|
current.value = null;
|
|
130
221
|
if (hadProgress)
|
|
@@ -134,8 +225,10 @@ export function openProgress(owner, accepted, complete) {
|
|
|
134
225
|
yield* Effect.addFinalizer(() => clear);
|
|
135
226
|
return Object.freeze({
|
|
136
227
|
started: (event) => Effect.sync(() => {
|
|
228
|
+
cancelFlush();
|
|
137
229
|
started = event;
|
|
138
230
|
current.value = null;
|
|
231
|
+
publishedLength = 0;
|
|
139
232
|
}),
|
|
140
233
|
update: (event) => Effect.suspend(() => {
|
|
141
234
|
const messageStart = started;
|
|
@@ -145,20 +238,28 @@ export function openProgress(owner, accepted, complete) {
|
|
|
145
238
|
messageStart.turn.value !== event.turn.value ||
|
|
146
239
|
messageStart.message.value !== event.message.value)
|
|
147
240
|
return Effect.fail(rejected("Mule progress identity conflicts"));
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
previous.thinking === content.thinking)
|
|
241
|
+
const delta = event.event;
|
|
242
|
+
if ((delta.type !== "text_delta" && delta.type !== "thinking_delta") ||
|
|
243
|
+
delta.delta.length === 0)
|
|
152
244
|
return Effect.void;
|
|
153
|
-
current.value
|
|
245
|
+
const first = current.value === null;
|
|
246
|
+
const value = current.value ?? {
|
|
154
247
|
messageId: event.message.value,
|
|
155
|
-
revision:
|
|
156
|
-
text:
|
|
157
|
-
thinking:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
248
|
+
revision: 0,
|
|
249
|
+
text: emptyTail(),
|
|
250
|
+
thinking: emptyTail(),
|
|
251
|
+
};
|
|
252
|
+
appendTail(delta.type === "text_delta" ? value.text : value.thinking, delta.delta);
|
|
253
|
+
value.revision++;
|
|
254
|
+
current.value = value;
|
|
255
|
+
const length = value.text.length + value.thinking.length;
|
|
256
|
+
// Coalesce tiny tokens, but flush a paused provider within 100 ms.
|
|
257
|
+
// One scope-owned timer holds no reply text and never writes SQLite.
|
|
258
|
+
if (first || length - publishedLength >= 4096) {
|
|
259
|
+
publish();
|
|
260
|
+
}
|
|
261
|
+
else
|
|
262
|
+
flushTimer ??= setTimeout(publish, 100);
|
|
162
263
|
return Effect.void;
|
|
163
264
|
}),
|
|
164
265
|
terminal: (event) => clear.pipe(Effect.andThen(complete(event))),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BirdDogDelegator, BirdDogMail } from "@fungi.computer/bird-dog";
|
|
2
|
+
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
2
3
|
import type { SqliteOwner } from "../contracts.js";
|
|
3
4
|
/** Woodstock keeps each Session's delegator of record beside the Session. */
|
|
4
5
|
export declare function prepareSessionDelegators(owner: SqliteOwner): void;
|
|
@@ -9,4 +10,4 @@ export declare function prepareSessionDelegators(owner: SqliteOwner): void;
|
|
|
9
10
|
*/
|
|
10
11
|
export declare function recordSessionDelegator(owner: SqliteOwner, sessionId: string, mail: BirdDogMail | undefined): void;
|
|
11
12
|
/** The Session's delegator of record, if any Agent has sent it work. */
|
|
12
|
-
export declare function readSessionDelegator(owner:
|
|
13
|
+
export declare function readSessionDelegator(owner: WatchdogSqliteOwner, sessionId: string): BirdDogDelegator | undefined;
|
|
@@ -167,6 +167,7 @@ export type EnqueueAcceptedWork = (work: Readonly<{
|
|
|
167
167
|
jobId: string;
|
|
168
168
|
sessionId: string;
|
|
169
169
|
commandFactId: string;
|
|
170
|
+
delivery: "steer" | "followUp";
|
|
170
171
|
}>) => void;
|
|
171
172
|
export type RecordAcceptedCancellation = (input: Readonly<{
|
|
172
173
|
jobId: string;
|
|
@@ -192,6 +193,8 @@ export type ForkSessionAtInput = SelectSessionBranchInput;
|
|
|
192
193
|
export declare function hasStoredSession(owner: SqliteOwner, sessionId: string): boolean;
|
|
193
194
|
/** Read one retained Session entry by its globally unique row identity. */
|
|
194
195
|
export declare function readSessionEntryById(owner: SqliteOwner, id: string): StoredSessionRow | undefined;
|
|
196
|
+
/** A written run terminal closes the window for attaching another steer. */
|
|
197
|
+
export declare function hasRecordedRunTerminal(owner: SqliteOwner, sessionId: string, workId: string): boolean;
|
|
195
198
|
/** Host-owned metadata identity, interpreted by SQLite's JSON decoder only. */
|
|
196
199
|
export type AdmissionIdentity = Readonly<{
|
|
197
200
|
path: string;
|
|
@@ -332,9 +335,18 @@ export declare const lineageRowLimit = 200;
|
|
|
332
335
|
* deduplicating; the fork walk keeps every link for its whole-lineage check.
|
|
333
336
|
*/
|
|
334
337
|
declare const lineageWalks: {
|
|
335
|
-
readonly selected: "WITH RECURSIVE branch (id, parent_id, sequence, state) AS (\n SELECT id, parent_id, sequence, state\n FROM session_entries\n WHERE session_id = ? AND id = ?\n UNION ALL\n SELECT parent.id, parent.parent_id, parent.sequence, parent.state\n FROM
|
|
336
|
-
readonly fork: "WITH RECURSIVE branch (id, parent_id, sequence, state) AS (\n SELECT id, parent_id, sequence, state\n FROM session_entries\n WHERE session_id = ? AND id = ? AND state != 'streaming'\n UNION\n SELECT parent.id, parent.parent_id, parent.sequence, parent.state\n FROM
|
|
338
|
+
readonly selected: "WITH RECURSIVE branch (id, parent_id, sequence, state) AS (\n SELECT id, parent_id, sequence, state\n FROM session_entries\n WHERE session_id = ? AND id = ?\n UNION ALL\n SELECT parent.id, parent.parent_id, parent.sequence, parent.state\n FROM branch AS child\n CROSS JOIN session_entries AS parent\n WHERE parent.id = child.parent_id AND parent.session_id = ?\n AND child.sequence > ?\n AND +parent.sequence < child.sequence\n )";
|
|
339
|
+
readonly fork: "WITH RECURSIVE branch (id, parent_id, sequence, state) AS (\n SELECT id, parent_id, sequence, state\n FROM session_entries\n WHERE session_id = ? AND id = ? AND state != 'streaming'\n UNION\n SELECT parent.id, parent.parent_id, parent.sequence, parent.state\n FROM branch AS child\n CROSS JOIN session_entries AS parent\n WHERE parent.id = child.parent_id AND parent.session_id = ?\n AND child.sequence > ?\n AND parent.state != 'streaming'\n )";
|
|
337
340
|
};
|
|
341
|
+
/**
|
|
342
|
+
* One synchronous, transaction-consistent Session baseline. Activity, timeline,
|
|
343
|
+
* extension projections and context figures share its identity-only ancestry;
|
|
344
|
+
* asynchronous model description runs after this scope has ended. Nothing is
|
|
345
|
+
* retained between reads, and payload windows keep their row and byte bounds.
|
|
346
|
+
*/
|
|
347
|
+
export declare function withSessionRead<A>(owner: SqliteOwner, sessionId: string, read: () => A, before?: number): A;
|
|
348
|
+
/** Join the shared Session baseline, or keep a direct projection atomic. */
|
|
349
|
+
export declare function withSessionReadTransaction<A>(owner: SqliteOwner, sessionId: string, read: () => A): A;
|
|
338
350
|
/**
|
|
339
351
|
* One root-to-tip lineage. Its integrity is enforced where entries are
|
|
340
352
|
* written (`guardSessionLineage`), so locating it reads only its tip and its
|