@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
|
@@ -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,17 @@ 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;
|
|
190
|
+
return snapshot(current?.workId === workId ? current.value : null);
|
|
105
191
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (content.type === "text")
|
|
111
|
-
text += content.text;
|
|
112
|
-
if (content.type === "thinking")
|
|
113
|
-
thinking += content.thinking;
|
|
114
|
-
}
|
|
115
|
-
return { text, thinking };
|
|
192
|
+
/** Read a phase fact without flattening another Session's private text tails. */
|
|
193
|
+
export function hasProcessTextForWork(owner, sessionId, workId) {
|
|
194
|
+
const current = progressByOwner.get(owner)?.get(sessionId);
|
|
195
|
+
return current?.workId === workId && (current.value?.text.length ?? 0) > 0;
|
|
116
196
|
}
|
|
117
197
|
/**
|
|
118
198
|
* Keep live model progress in the current process only.
|
|
@@ -124,7 +204,23 @@ export function openProgress(owner, accepted, complete) {
|
|
|
124
204
|
return Effect.gen(function* () {
|
|
125
205
|
const current = yield* openProcessProgress(owner, accepted);
|
|
126
206
|
let started;
|
|
207
|
+
let publishedLength = 0;
|
|
208
|
+
let flushTimer;
|
|
209
|
+
const cancelFlush = () => {
|
|
210
|
+
if (flushTimer !== undefined)
|
|
211
|
+
clearTimeout(flushTimer);
|
|
212
|
+
flushTimer = undefined;
|
|
213
|
+
};
|
|
214
|
+
const publish = () => {
|
|
215
|
+
cancelFlush();
|
|
216
|
+
const value = current.value;
|
|
217
|
+
if (value === null)
|
|
218
|
+
return;
|
|
219
|
+
publishedLength = value.text.length + value.thinking.length;
|
|
220
|
+
publishOwnerObservation(owner, accepted.sessionId, "progress");
|
|
221
|
+
};
|
|
127
222
|
const clear = Effect.sync(() => {
|
|
223
|
+
cancelFlush();
|
|
128
224
|
const hadProgress = current.value !== null;
|
|
129
225
|
current.value = null;
|
|
130
226
|
if (hadProgress)
|
|
@@ -134,8 +230,10 @@ export function openProgress(owner, accepted, complete) {
|
|
|
134
230
|
yield* Effect.addFinalizer(() => clear);
|
|
135
231
|
return Object.freeze({
|
|
136
232
|
started: (event) => Effect.sync(() => {
|
|
233
|
+
cancelFlush();
|
|
137
234
|
started = event;
|
|
138
235
|
current.value = null;
|
|
236
|
+
publishedLength = 0;
|
|
139
237
|
}),
|
|
140
238
|
update: (event) => Effect.suspend(() => {
|
|
141
239
|
const messageStart = started;
|
|
@@ -145,20 +243,28 @@ export function openProgress(owner, accepted, complete) {
|
|
|
145
243
|
messageStart.turn.value !== event.turn.value ||
|
|
146
244
|
messageStart.message.value !== event.message.value)
|
|
147
245
|
return Effect.fail(rejected("Mule progress identity conflicts"));
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
previous.thinking === content.thinking)
|
|
246
|
+
const delta = event.event;
|
|
247
|
+
if ((delta.type !== "text_delta" && delta.type !== "thinking_delta") ||
|
|
248
|
+
delta.delta.length === 0)
|
|
152
249
|
return Effect.void;
|
|
153
|
-
current.value
|
|
250
|
+
const first = current.value === null;
|
|
251
|
+
const value = current.value ?? {
|
|
154
252
|
messageId: event.message.value,
|
|
155
|
-
revision:
|
|
156
|
-
text:
|
|
157
|
-
thinking:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
253
|
+
revision: 0,
|
|
254
|
+
text: emptyTail(),
|
|
255
|
+
thinking: emptyTail(),
|
|
256
|
+
};
|
|
257
|
+
appendTail(delta.type === "text_delta" ? value.text : value.thinking, delta.delta);
|
|
258
|
+
value.revision++;
|
|
259
|
+
current.value = value;
|
|
260
|
+
const length = value.text.length + value.thinking.length;
|
|
261
|
+
// Coalesce tiny tokens, but flush a paused provider within 100 ms.
|
|
262
|
+
// One scope-owned timer holds no reply text and never writes SQLite.
|
|
263
|
+
if (first || length - publishedLength >= 4096) {
|
|
264
|
+
publish();
|
|
265
|
+
}
|
|
266
|
+
else
|
|
267
|
+
flushTimer ??= setTimeout(publish, 100);
|
|
162
268
|
return Effect.void;
|
|
163
269
|
}),
|
|
164
270
|
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
|