@fungi.computer/shiitake 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +454 -2
- package/dist/agar-extension.d.ts +9 -0
- package/dist/agar-extension.js +38 -0
- package/dist/agent-control.d.ts +77 -0
- package/dist/agent-control.js +434 -0
- package/dist/client-entry.d.ts +211 -0
- package/dist/client-entry.js +101 -0
- package/dist/client-wire.d.ts +199 -0
- package/dist/client-wire.js +271 -0
- package/dist/client.d.ts +439 -0
- package/dist/client.js +4 -0
- package/dist/cloudflare.d.ts +220 -0
- package/dist/cloudflare.js +531 -0
- package/dist/effect.d.ts +42 -0
- package/dist/effect.js +20 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.js +81 -0
- package/dist/extension-client.d.ts +38 -0
- package/dist/extension-client.js +56 -0
- package/dist/extension-contract.d.ts +73 -0
- package/dist/extension-contract.js +11 -0
- package/dist/extensions.d.ts +589 -0
- package/dist/extensions.js +263 -0
- package/dist/fleet.d.ts +313 -0
- package/dist/fleet.js +540 -0
- package/dist/hosted.d.ts +133 -0
- package/dist/hosted.js +265 -0
- package/dist/http-routes.d.ts +772 -0
- package/dist/http-routes.js +242 -0
- package/dist/http.d.ts +1 -0
- package/dist/http.js +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +13 -0
- package/dist/internal/accepted-run-executor.d.ts +23 -0
- package/dist/internal/accepted-run-executor.js +129 -0
- package/dist/internal/admission-door.d.ts +75 -0
- package/dist/internal/admission-door.js +105 -0
- package/dist/internal/agent-close.d.ts +4 -0
- package/dist/internal/agent-close.js +42 -0
- package/dist/internal/browser-sessions.d.ts +31 -0
- package/dist/internal/browser-sessions.js +173 -0
- package/dist/internal/client-boundary.d.ts +262 -0
- package/dist/internal/client-boundary.js +202 -0
- package/dist/internal/compaction/compaction.d.ts +54 -0
- package/dist/internal/compaction/compaction.js +291 -0
- package/dist/internal/compaction/session-compaction.d.ts +69 -0
- package/dist/internal/compaction/session-compaction.js +330 -0
- package/dist/internal/compaction/settings.d.ts +11 -0
- package/dist/internal/compaction/settings.js +40 -0
- package/dist/internal/compaction/utils.d.ts +45 -0
- package/dist/internal/compaction/utils.js +319 -0
- package/dist/internal/contracts.d.ts +235 -0
- package/dist/internal/contracts.js +1 -0
- package/dist/internal/coordinator.d.ts +18 -0
- package/dist/internal/coordinator.js +362 -0
- package/dist/internal/document-codec.d.ts +11 -0
- package/dist/internal/document-codec.js +39 -0
- package/dist/internal/effect-client.d.ts +13 -0
- package/dist/internal/effect-client.js +102 -0
- package/dist/internal/effect-contracts.d.ts +236 -0
- package/dist/internal/effect-contracts.js +2 -0
- package/dist/internal/extension-command-registry.d.ts +11 -0
- package/dist/internal/extension-command-registry.js +29 -0
- package/dist/internal/extension-command-tools.d.ts +17 -0
- package/dist/internal/extension-command-tools.js +50 -0
- package/dist/internal/extension-context.d.ts +25 -0
- package/dist/internal/extension-context.js +35 -0
- package/dist/internal/extension-definitions.d.ts +5 -0
- package/dist/internal/extension-definitions.js +6 -0
- package/dist/internal/extension-hook.d.ts +25 -0
- package/dist/internal/extension-hook.js +46 -0
- package/dist/internal/extension-roster.d.ts +23 -0
- package/dist/internal/extension-roster.js +228 -0
- package/dist/internal/extension-runtime.d.ts +63 -0
- package/dist/internal/extension-runtime.js +1004 -0
- package/dist/internal/failure.d.ts +2 -0
- package/dist/internal/failure.js +9 -0
- package/dist/internal/host-meta.d.ts +41 -0
- package/dist/internal/host-meta.js +39 -0
- package/dist/internal/json-value.d.ts +4 -0
- package/dist/internal/json-value.js +3 -0
- package/dist/internal/model-acquisition.d.ts +21 -0
- package/dist/internal/model-acquisition.js +98 -0
- package/dist/internal/model-preference.d.ts +21 -0
- package/dist/internal/model-preference.js +20 -0
- package/dist/internal/observation-kernel.d.ts +28 -0
- package/dist/internal/observation-kernel.js +169 -0
- package/dist/internal/promise-facade.d.ts +18 -0
- package/dist/internal/promise-facade.js +172 -0
- package/dist/internal/run-scope.d.ts +27 -0
- package/dist/internal/run-scope.js +214 -0
- package/dist/internal/runtime-coordinator.d.ts +34 -0
- package/dist/internal/runtime-coordinator.js +233 -0
- package/dist/internal/runtime-core.d.ts +19 -0
- package/dist/internal/runtime-core.js +39 -0
- package/dist/internal/runtime-reconciliation.d.ts +18 -0
- package/dist/internal/runtime-reconciliation.js +162 -0
- package/dist/internal/session-context.d.ts +11 -0
- package/dist/internal/session-context.js +67 -0
- package/dist/internal/session-status.d.ts +10 -0
- package/dist/internal/session-status.js +52 -0
- package/dist/internal/woodstock/agent-reply.d.ts +25 -0
- package/dist/internal/woodstock/agent-reply.js +67 -0
- package/dist/internal/woodstock/client-projection.d.ts +29 -0
- package/dist/internal/woodstock/client-projection.js +512 -0
- package/dist/internal/woodstock/compaction-activity.d.ts +17 -0
- package/dist/internal/woodstock/compaction-activity.js +32 -0
- package/dist/internal/woodstock/cursor.d.ts +9 -0
- package/dist/internal/woodstock/cursor.js +44 -0
- package/dist/internal/woodstock/extension-store.d.ts +72 -0
- package/dist/internal/woodstock/extension-store.js +262 -0
- package/dist/internal/woodstock/fleet-store.d.ts +36 -0
- package/dist/internal/woodstock/fleet-store.js +100 -0
- package/dist/internal/woodstock/identities.d.ts +6 -0
- package/dist/internal/woodstock/identities.js +11 -0
- package/dist/internal/woodstock/index.d.ts +170 -0
- package/dist/internal/woodstock/index.js +546 -0
- package/dist/internal/woodstock/progress.d.ts +32 -0
- package/dist/internal/woodstock/progress.js +89 -0
- package/dist/internal/woodstock/run-recovery.d.ts +8 -0
- package/dist/internal/woodstock/run-recovery.js +95 -0
- package/dist/internal/woodstock/session-attention.d.ts +16 -0
- package/dist/internal/woodstock/session-attention.js +88 -0
- package/dist/internal/woodstock/session-documents.d.ts +18 -0
- package/dist/internal/woodstock/session-documents.js +51 -0
- package/dist/internal/woodstock/session-entry.d.ts +478 -0
- package/dist/internal/woodstock/session-entry.js +484 -0
- package/dist/internal/woodstock/session-grants.d.ts +24 -0
- package/dist/internal/woodstock/session-grants.js +84 -0
- package/dist/internal/woodstock/session-inbox.d.ts +56 -0
- package/dist/internal/woodstock/session-inbox.js +307 -0
- package/dist/internal/woodstock/session-store.d.ts +432 -0
- package/dist/internal/woodstock/session-store.js +2006 -0
- package/dist/internal/woodstock/storage-admission.d.ts +14 -0
- package/dist/internal/woodstock/storage-admission.js +44 -0
- package/dist/model-preference.d.ts +20 -0
- package/dist/model-preference.js +30 -0
- package/dist/node.d.ts +64 -0
- package/dist/node.js +256 -0
- package/dist/remote-client.d.ts +11 -0
- package/dist/remote-client.js +298 -0
- package/dist/session-attention.d.ts +35 -0
- package/dist/session-attention.js +22 -0
- package/dist/session-documents.d.ts +28 -0
- package/dist/session-documents.js +1 -0
- package/dist/testing.d.ts +54 -0
- package/dist/testing.js +215 -0
- package/docs/api/README.md +26 -0
- package/docs/api/agar-extension.md +40 -0
- package/docs/api/agent-control.md +151 -0
- package/docs/api/client-entry.md +344 -0
- package/docs/api/client.md +1112 -0
- package/docs/api/cloudflare.md +500 -0
- package/docs/api/effect.md +884 -0
- package/docs/api/extensions.md +2003 -0
- package/docs/api/fleet.md +734 -0
- package/docs/api/hosted.md +382 -0
- package/docs/api/http.md +111 -0
- package/docs/api/index.md +1116 -0
- package/docs/api/model-preference.md +151 -0
- package/docs/api/node.md +142 -0
- package/docs/api/testing.md +250 -0
- package/package.json +130 -3
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { ShiitakeError } from "../../errors.js";
|
|
3
|
+
import { decodeClientReadOptions, decodeClientSessionCreationRequestId, decodeClientSessionId, } from "../client-boundary.js";
|
|
4
|
+
import { publishOwnerObservation } from "../observation-kernel.js";
|
|
5
|
+
import { diagnosticsByWorkId, projectCommandSettlement, readClientReplayPage, readClientSnapshot, } from "./client-projection.js";
|
|
6
|
+
import { sessionIds } from "./identities.js";
|
|
7
|
+
import { openProgress } from "./progress.js";
|
|
8
|
+
import { encodeStoredSessionEntry, } from "./session-entry.js";
|
|
9
|
+
import { appendSessionEntry, appendCompactionTransitionOwned, appendCompactionNoticeOwned, acceptSessionAbort, acceptSessionCancellation, completeSessionLeaf, prepareSessionRun, prepareSessionStore, readSessionEntriesForWork, readSessionCommandAdmission, settleSessionControls, takePendingSessionControls, readSessionEntryById, selectSessionBranch, forkSessionAt, readStoredSessionSummary as readSessionSummaryOwned, readStoredSessionFactPage, createStoredSession, deleteStoredSession, appendOperatorEntry, } from "./session-store.js";
|
|
10
|
+
import { readSessionInboxPage, recordSessionInboxDeletion, } from "./session-inbox.js";
|
|
11
|
+
export * from "./extension-store.js";
|
|
12
|
+
export { createFleetDirectoryStore, createFleetDelegationStore, } from "./fleet-store.js";
|
|
13
|
+
export * from "./session-attention.js";
|
|
14
|
+
export { deliverSessionGrants, prepareSessionGrants, readSessionGrants, } from "./session-grants.js";
|
|
15
|
+
export * from "./progress.js";
|
|
16
|
+
export * from "./compaction-activity.js";
|
|
17
|
+
export * from "./agent-reply.js";
|
|
18
|
+
export * from "./session-entry.js";
|
|
19
|
+
export * from "./session-store.js";
|
|
20
|
+
export * from "./session-inbox.js";
|
|
21
|
+
export { contextMessageIds } from "./run-recovery.js";
|
|
22
|
+
export * from "./storage-admission.js";
|
|
23
|
+
/** Record one compaction notice and publish it to observers. */
|
|
24
|
+
export function appendCompactionNotice(owner, input) {
|
|
25
|
+
const row = appendCompactionNoticeOwned(owner, input);
|
|
26
|
+
publishOwnerObservation(owner, input.sessionId);
|
|
27
|
+
return row;
|
|
28
|
+
}
|
|
29
|
+
/** Append compaction through Woodstock's ordinary owner-observation boundary. */
|
|
30
|
+
export function appendCompactionTransition(owner, input) {
|
|
31
|
+
const row = appendCompactionTransitionOwned(owner, input);
|
|
32
|
+
publishOwnerObservation(owner, input.sessionId);
|
|
33
|
+
return row;
|
|
34
|
+
}
|
|
35
|
+
function rejected(message) {
|
|
36
|
+
return new ShiitakeError("store", "rejected", message);
|
|
37
|
+
}
|
|
38
|
+
function conflict(message) {
|
|
39
|
+
return new ShiitakeError("store", "conflict", message);
|
|
40
|
+
}
|
|
41
|
+
function storeEffect(operation) {
|
|
42
|
+
return Effect.try({
|
|
43
|
+
try: operation,
|
|
44
|
+
catch: (error) => error instanceof ShiitakeError
|
|
45
|
+
? error
|
|
46
|
+
: new ShiitakeError("store", "rejected", "Native Session store operation failed", undefined, error),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** Prepare Woodstock's retained Session and private admission schema. */
|
|
50
|
+
export function prepareWoodstockSchema(owner) {
|
|
51
|
+
return storeEffect(() => {
|
|
52
|
+
prepareSessionStore(owner);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** Atomically cancel one exact owner-issued command. */
|
|
56
|
+
export function cancelSession(owner, sessionId, commandId, onlyIfPending, readQueueHead, record, meta) {
|
|
57
|
+
return storeEffect(() => {
|
|
58
|
+
const cancellation = acceptSessionCancellation(owner, sessionId, commandId, readQueueHead, record, onlyIfPending, meta);
|
|
59
|
+
if (cancellation?.delivery === "new")
|
|
60
|
+
publishOwnerObservation(owner, sessionId);
|
|
61
|
+
return cancellation;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Deliver one pending control through an active Mule safe point. */
|
|
65
|
+
export function takeSessionControls(owner, input) {
|
|
66
|
+
return storeEffect(() => {
|
|
67
|
+
const messages = takePendingSessionControls(owner, input.sessionId, input.delivery, input.workId);
|
|
68
|
+
if (messages.length > 0)
|
|
69
|
+
publishOwnerObservation(owner, input.sessionId);
|
|
70
|
+
return messages;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/** Record terminals for controls delivered through one settled parent run. */
|
|
74
|
+
export function settleControls(owner, input) {
|
|
75
|
+
return storeEffect(() => {
|
|
76
|
+
settleSessionControls(owner, input);
|
|
77
|
+
publishOwnerObservation(owner, input.sessionId);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** Resolve Session-scoped abort to private work and record it atomically. */
|
|
81
|
+
export function abortSession(owner, sessionId, readQueueHead, record, meta) {
|
|
82
|
+
return storeEffect(() => {
|
|
83
|
+
const cancellation = acceptSessionAbort(owner, sessionId, readQueueHead, record, meta);
|
|
84
|
+
publishOwnerObservation(owner, sessionId);
|
|
85
|
+
return cancellation?.accepted;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** Select one complete entry as the active Session lineage leaf. */
|
|
89
|
+
export function branchSession(owner, input) {
|
|
90
|
+
return storeEffect(() => {
|
|
91
|
+
selectSessionBranch(owner, {
|
|
92
|
+
sessionId: input.sessionId,
|
|
93
|
+
entryId: input.entryId,
|
|
94
|
+
assertIdleSession: input.assertIdleSession,
|
|
95
|
+
});
|
|
96
|
+
publishOwnerObservation(owner, input.sessionId);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** Record one operator or party line on an idle Session; false on replay. */
|
|
100
|
+
export function recordOperatorLine(owner, input) {
|
|
101
|
+
return storeEffect(() => {
|
|
102
|
+
if (appendOperatorEntry(owner, input))
|
|
103
|
+
publishOwnerObservation(owner, input.sessionId);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Fork one complete entry lineage into a new cold-readable Session. */
|
|
107
|
+
export function forkSession(owner, input) {
|
|
108
|
+
return storeEffect(() => {
|
|
109
|
+
const forkedSessionId = forkSessionAt(owner, {
|
|
110
|
+
sessionId: input.sessionId,
|
|
111
|
+
entryId: input.entryId,
|
|
112
|
+
assertIdleSession: input.assertIdleSession,
|
|
113
|
+
});
|
|
114
|
+
publishOwnerObservation(owner, forkedSessionId);
|
|
115
|
+
return Object.freeze({ sessionId: forkedSessionId });
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** Load the exact retained lineage ending at one accepted command. */
|
|
119
|
+
export function loadAcceptedRun(owner, input) {
|
|
120
|
+
return storeEffect(() => {
|
|
121
|
+
const prepared = prepareSessionRun(owner, {
|
|
122
|
+
sessionId: decodeClientSessionId(input.sessionId),
|
|
123
|
+
commandFactId: input.commandFactId,
|
|
124
|
+
});
|
|
125
|
+
if (prepared.entryDelivery === "new")
|
|
126
|
+
publishOwnerObservation(owner, input.sessionId);
|
|
127
|
+
return prepared;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function assistantText(message) {
|
|
131
|
+
return message.content
|
|
132
|
+
.flatMap((part) => (part.type === "text" ? [part.text] : []))
|
|
133
|
+
.join("")
|
|
134
|
+
.trim();
|
|
135
|
+
}
|
|
136
|
+
/** Read the last exact completed assistant text produced by accepted work. */
|
|
137
|
+
export function readSettlementContent(owner, input) {
|
|
138
|
+
return storeEffect(() => {
|
|
139
|
+
const admission = readSessionCommandAdmission(owner, input.sessionId, input.commandFactId);
|
|
140
|
+
if (admission === undefined)
|
|
141
|
+
throw rejected("Accepted Session command is unavailable");
|
|
142
|
+
const entries = readSessionEntriesForWork(owner, input.sessionId, admission.entry.workId);
|
|
143
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
144
|
+
const entry = entries[index]?.entry;
|
|
145
|
+
if (entry?.kind === "message" && entry.value.role === "assistant")
|
|
146
|
+
return assistantText(entry.value);
|
|
147
|
+
}
|
|
148
|
+
return "";
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The host metadata that settles with one accepted command: the
|
|
153
|
+
* cancellation's when a host-attributed cancel ended it, otherwise the
|
|
154
|
+
* command's own. Absent when the host attached none.
|
|
155
|
+
*/
|
|
156
|
+
export function readSettlementMeta(owner, input, outcome) {
|
|
157
|
+
return storeEffect(() => {
|
|
158
|
+
const admission = readSessionCommandAdmission(owner, input.sessionId, input.commandFactId);
|
|
159
|
+
if (admission === undefined)
|
|
160
|
+
throw rejected("Accepted Session command is unavailable");
|
|
161
|
+
if (outcome !== "cancelled")
|
|
162
|
+
return admission.entry.meta;
|
|
163
|
+
const cancellation = readSessionEntriesForWork(owner, input.sessionId, admission.entry.workId).find(({ entry }) => entry.kind === "cancellation");
|
|
164
|
+
return cancellation?.entry.kind === "cancellation"
|
|
165
|
+
? cancellation.entry.meta
|
|
166
|
+
: undefined;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The Session's recorded terminal diagnostic for one accepted command, the
|
|
171
|
+
* same text `agents.wait` and clients see; absent when none was recorded.
|
|
172
|
+
*/
|
|
173
|
+
export function readSettlementDiagnostic(owner, input) {
|
|
174
|
+
return storeEffect(() => {
|
|
175
|
+
const admission = readSessionCommandAdmission(owner, input.sessionId, input.commandFactId);
|
|
176
|
+
if (admission === undefined)
|
|
177
|
+
throw rejected("Accepted Session command is unavailable");
|
|
178
|
+
return diagnosticsByWorkId(readSessionEntriesForWork(owner, input.sessionId, admission.entry.workId)).get(admission.entry.workId);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/** Read one bounded public Session snapshot. */
|
|
182
|
+
export function readSession(owner, sessionId, activity, input, projectExtensions = () => ({})) {
|
|
183
|
+
return storeEffect(() => readClientSnapshot(owner, decodeClientSessionId(sessionId), decodeClientReadOptions(input), activity, projectExtensions));
|
|
184
|
+
}
|
|
185
|
+
/** Read one bounded folder-resume summary through Woodstock's Effect boundary. */
|
|
186
|
+
export function readSessionSummary(owner, sessionId) {
|
|
187
|
+
return storeEffect(() => readSessionSummaryOwned(owner, decodeClientSessionId(sessionId)));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Persist or replay the Session reserved for a creation request ID.
|
|
191
|
+
* `admitNew` may refuse a new Session; a replayed request is never refused.
|
|
192
|
+
*/
|
|
193
|
+
export function createSession(owner, requestId, admitNew) {
|
|
194
|
+
return storeEffect(() => createStoredSession(owner, decodeClientSessionCreationRequestId({ requestId }), admitNew));
|
|
195
|
+
}
|
|
196
|
+
/** Permanently delete one idle Session; false when it does not exist. */
|
|
197
|
+
export function deleteSession(owner, input) {
|
|
198
|
+
return storeEffect(() => deleteStoredSession(owner, {
|
|
199
|
+
sessionId: decodeClientSessionId(input.sessionId),
|
|
200
|
+
assertIdleSession: input.assertIdleSession,
|
|
201
|
+
recordInboxDeletion: (sessionId) => {
|
|
202
|
+
recordSessionInboxDeletion(owner, sessionId);
|
|
203
|
+
},
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
/** Read a page of stored conversation facts; status needs the Session owner. */
|
|
207
|
+
export function readSessionFactPage(owner, after) {
|
|
208
|
+
return storeEffect(() => readStoredSessionFactPage(owner, after === undefined ? undefined : decodeClientSessionId(after)));
|
|
209
|
+
}
|
|
210
|
+
/** Read one bounded inbox page in tier, then newest-activity order. */
|
|
211
|
+
export function readSessionInbox(owner, after) {
|
|
212
|
+
return storeEffect(() => readSessionInboxPage(owner, after));
|
|
213
|
+
}
|
|
214
|
+
/** Read one strict-after page from Woodstock's selected lineage. */
|
|
215
|
+
export function readSessionReplayPage(owner, sessionId, after, delivery, activity, projectExtensions = () => ({}), includeSandboxFailures = false) {
|
|
216
|
+
return storeEffect(() => readClientReplayPage(owner, decodeClientSessionId(sessionId), after, delivery, activity, projectExtensions, includeSandboxFailures));
|
|
217
|
+
}
|
|
218
|
+
function sameEntry(left, right) {
|
|
219
|
+
return encodeStoredSessionEntry(left) === encodeStoredSessionEntry(right);
|
|
220
|
+
}
|
|
221
|
+
function sameIdentity(left, right) {
|
|
222
|
+
return (left.kind === right.kind &&
|
|
223
|
+
left.value === right.value &&
|
|
224
|
+
left.parent?.kind === right.parent?.kind &&
|
|
225
|
+
left.parent?.value === right.parent?.value);
|
|
226
|
+
}
|
|
227
|
+
function sameContract(left, right) {
|
|
228
|
+
return (left.core === right.core &&
|
|
229
|
+
left.extensions.length === right.extensions.length &&
|
|
230
|
+
left.extensions.every(([id, fingerprint], index) => {
|
|
231
|
+
const selected = right.extensions[index];
|
|
232
|
+
return selected?.[0] === id && selected[1] === fingerprint;
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
function appendRecordedEntry(owner, accepted, entry, findExisting, beforeAppend) {
|
|
236
|
+
const delivery = owner.transactionSync(() => {
|
|
237
|
+
beforeAppend?.();
|
|
238
|
+
const existing = readSessionEntriesForWork(owner, accepted.sessionId, accepted.commandId).find(findExisting);
|
|
239
|
+
if (existing !== undefined) {
|
|
240
|
+
if (entry.kind === "work_terminal" &&
|
|
241
|
+
existing.entry.kind === "run_terminal")
|
|
242
|
+
return "replay";
|
|
243
|
+
if (sameEntry(existing.entry, entry))
|
|
244
|
+
return "replay";
|
|
245
|
+
throw conflict("Mule event identity conflicts");
|
|
246
|
+
}
|
|
247
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
248
|
+
const factId = sessionIds.fact.generate();
|
|
249
|
+
if (readSessionEntryById(owner, factId) !== undefined ||
|
|
250
|
+
readSessionCommandAdmission(owner, accepted.sessionId, factId) !==
|
|
251
|
+
undefined)
|
|
252
|
+
continue;
|
|
253
|
+
appendSessionEntry(owner, {
|
|
254
|
+
sessionId: accepted.sessionId,
|
|
255
|
+
id: factId,
|
|
256
|
+
entry,
|
|
257
|
+
});
|
|
258
|
+
return "new";
|
|
259
|
+
}
|
|
260
|
+
throw rejected("Session fact identity allocation failed");
|
|
261
|
+
});
|
|
262
|
+
if (delivery === "new")
|
|
263
|
+
publishOwnerObservation(owner, accepted.sessionId);
|
|
264
|
+
}
|
|
265
|
+
/** Persist an operator-only sandbox diagnostic beside its accepted Session work. */
|
|
266
|
+
export function recordSandboxFailure(owner, accepted, event) {
|
|
267
|
+
return storeEffect(() => {
|
|
268
|
+
appendRecordedEntry(owner, accepted, {
|
|
269
|
+
kind: "sandbox_failure",
|
|
270
|
+
workId: accepted.commandId,
|
|
271
|
+
event,
|
|
272
|
+
}, ({ entry }) => entry.kind === "sandbox_failure" && entry.event.ref === event.ref);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
/** Read the admitting extension of one accepted command. */
|
|
276
|
+
export function readExtensionAdmissionOrigin(owner, sessionId, commandFactId, workId) {
|
|
277
|
+
const admission = readSessionCommandAdmission(owner, sessionId, commandFactId);
|
|
278
|
+
if (admission?.entry.workId !== workId)
|
|
279
|
+
throw rejected("Accepted Session work is unavailable");
|
|
280
|
+
return admission.origin.kind === "extension"
|
|
281
|
+
? Object.freeze({
|
|
282
|
+
extensionId: admission.origin.extensionId,
|
|
283
|
+
correlation: admission.origin.correlation,
|
|
284
|
+
})
|
|
285
|
+
: null;
|
|
286
|
+
}
|
|
287
|
+
/** Read the terminal and admission origin of one settled accepted command. */
|
|
288
|
+
export function readSettledRunFacts(owner, sessionId, commandFactId, workId) {
|
|
289
|
+
const origin = readExtensionAdmissionOrigin(owner, sessionId, commandFactId, workId);
|
|
290
|
+
const entries = readSessionEntriesForWork(owner, sessionId, workId).map(({ entry }) => entry);
|
|
291
|
+
const toolCalls = new Set(entries.flatMap((entry) => entry.kind === "tool_intent" ? [entry.call.id] : [])).size;
|
|
292
|
+
const terminal = entries
|
|
293
|
+
.filter((entry) => entry.kind === "run_terminal" || entry.kind === "work_terminal")
|
|
294
|
+
.at(-1);
|
|
295
|
+
return Object.freeze({
|
|
296
|
+
terminal: terminal?.kind === "run_terminal" || terminal?.kind === "work_terminal"
|
|
297
|
+
? terminal.outcome
|
|
298
|
+
: "missing",
|
|
299
|
+
origin,
|
|
300
|
+
toolCalls,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/** Record unsuccessful Watchdog settlement when Mule has no recorded terminal. */
|
|
304
|
+
export function recordWorkSettlement(owner, input) {
|
|
305
|
+
return storeEffect(() => {
|
|
306
|
+
const admission = readSessionCommandAdmission(owner, input.sessionId, input.commandFactId);
|
|
307
|
+
if (admission?.entry.workId !== input.workId)
|
|
308
|
+
throw rejected("Settled Session work is unavailable");
|
|
309
|
+
const entry = {
|
|
310
|
+
workId: input.workId,
|
|
311
|
+
kind: "work_terminal",
|
|
312
|
+
outcome: input.outcome,
|
|
313
|
+
};
|
|
314
|
+
appendRecordedEntry(owner, { sessionId: input.sessionId, commandId: input.workId }, input.diagnostic === undefined
|
|
315
|
+
? entry
|
|
316
|
+
: { ...entry, diagnostic: input.diagnostic }, ({ entry: current }) => current.kind === "work_terminal" ||
|
|
317
|
+
(current.kind === "run_terminal" && current.outcome === input.outcome), () => {
|
|
318
|
+
owner.sql.exec("DELETE FROM session_entries WHERE session_id = ? AND work_id = ? AND state = 'streaming' AND type = 'message_start'", input.sessionId, input.workId);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
function recordLifecycle(owner, accepted, contract, event) {
|
|
323
|
+
return storeEffect(() => {
|
|
324
|
+
const entry = event.type === "run_start"
|
|
325
|
+
? {
|
|
326
|
+
kind: "run_started",
|
|
327
|
+
workId: accepted.commandId,
|
|
328
|
+
run: event.run,
|
|
329
|
+
contract,
|
|
330
|
+
}
|
|
331
|
+
: event.type === "turn_start"
|
|
332
|
+
? {
|
|
333
|
+
kind: "turn_started",
|
|
334
|
+
workId: accepted.commandId,
|
|
335
|
+
run: event.run,
|
|
336
|
+
turn: event.turn,
|
|
337
|
+
}
|
|
338
|
+
: {
|
|
339
|
+
kind: "run_terminal",
|
|
340
|
+
workId: accepted.commandId,
|
|
341
|
+
run: event.run,
|
|
342
|
+
terminal: event.terminal,
|
|
343
|
+
outcome: event.outcome,
|
|
344
|
+
};
|
|
345
|
+
appendRecordedEntry(owner, accepted, entry, ({ entry: current }) => {
|
|
346
|
+
if (current.kind !== entry.kind)
|
|
347
|
+
return false;
|
|
348
|
+
if (current.kind === "run_started" && entry.kind === "run_started")
|
|
349
|
+
return (sameIdentity(current.run, entry.run) &&
|
|
350
|
+
sameContract(current.contract, contract));
|
|
351
|
+
if (current.kind === "turn_started" && entry.kind === "turn_started")
|
|
352
|
+
return sameIdentity(current.turn, entry.turn);
|
|
353
|
+
if (current.kind === "run_terminal" && entry.kind === "run_terminal")
|
|
354
|
+
return sameIdentity(current.terminal, entry.terminal);
|
|
355
|
+
return false;
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function recordMessageStart(owner, accepted, event) {
|
|
360
|
+
return storeEffect(() => {
|
|
361
|
+
const entry = {
|
|
362
|
+
kind: "message_start",
|
|
363
|
+
workId: accepted.commandId,
|
|
364
|
+
run: event.run,
|
|
365
|
+
turn: event.turn,
|
|
366
|
+
message: event.message,
|
|
367
|
+
role: event.value.role,
|
|
368
|
+
};
|
|
369
|
+
const delivery = owner.transactionSync(() => {
|
|
370
|
+
const existing = readSessionEntriesForWork(owner, accepted.sessionId, accepted.commandId).find(({ entry: current }) => (current.kind === "message_start" || current.kind === "message") &&
|
|
371
|
+
sameIdentity(current.message, event.message));
|
|
372
|
+
if (existing !== undefined) {
|
|
373
|
+
if (existing.entry.kind === "message") {
|
|
374
|
+
if (existing.entry.workId !== entry.workId ||
|
|
375
|
+
!sameIdentity(existing.entry.run, entry.run) ||
|
|
376
|
+
!sameIdentity(existing.entry.turn, entry.turn) ||
|
|
377
|
+
existing.entry.value.role !== entry.role)
|
|
378
|
+
throw conflict("Mule message-start identity conflicts");
|
|
379
|
+
return "replay";
|
|
380
|
+
}
|
|
381
|
+
if (!sameEntry(existing.entry, entry))
|
|
382
|
+
throw conflict("Mule message-start identity conflicts");
|
|
383
|
+
return "replay";
|
|
384
|
+
}
|
|
385
|
+
appendSessionEntry(owner, {
|
|
386
|
+
sessionId: accepted.sessionId,
|
|
387
|
+
id: event.message.value,
|
|
388
|
+
entry,
|
|
389
|
+
state: "streaming",
|
|
390
|
+
});
|
|
391
|
+
return "new";
|
|
392
|
+
});
|
|
393
|
+
if (delivery === "new")
|
|
394
|
+
publishOwnerObservation(owner, accepted.sessionId);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
/** Persist exactly the system prompt before a provider attempt is sent. */
|
|
398
|
+
function recordModelPrompt(owner, accepted, contract, event) {
|
|
399
|
+
return storeEffect(() => {
|
|
400
|
+
const entry = {
|
|
401
|
+
kind: "model_prompt",
|
|
402
|
+
workId: accepted.commandId,
|
|
403
|
+
run: event.run,
|
|
404
|
+
turn: event.turn,
|
|
405
|
+
attempt: event.attempt,
|
|
406
|
+
contract,
|
|
407
|
+
systemPrompt: event.systemPrompt,
|
|
408
|
+
};
|
|
409
|
+
appendRecordedEntry(owner, accepted, entry, ({ entry: current }) => current.kind === "model_prompt" &&
|
|
410
|
+
sameIdentity(current.turn, event.turn) &&
|
|
411
|
+
current.attempt === event.attempt &&
|
|
412
|
+
sameContract(current.contract, contract));
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function recordMessageEnd(owner, accepted, event) {
|
|
416
|
+
return storeEffect(() => {
|
|
417
|
+
const entry = {
|
|
418
|
+
kind: "message",
|
|
419
|
+
workId: accepted.commandId,
|
|
420
|
+
run: event.run,
|
|
421
|
+
turn: event.turn,
|
|
422
|
+
message: event.message,
|
|
423
|
+
value: event.value,
|
|
424
|
+
};
|
|
425
|
+
const delivery = owner.transactionSync(() => {
|
|
426
|
+
const existing = readSessionEntriesForWork(owner, accepted.sessionId, accepted.commandId).find(({ entry: current }) => (current.kind === "message_start" || current.kind === "message") &&
|
|
427
|
+
sameIdentity(current.message, event.message));
|
|
428
|
+
if (existing?.entry.kind === "message") {
|
|
429
|
+
if (!sameEntry(existing.entry, entry))
|
|
430
|
+
throw conflict("Mule completed-message identity conflicts");
|
|
431
|
+
return "replay";
|
|
432
|
+
}
|
|
433
|
+
completeSessionLeaf(owner, {
|
|
434
|
+
sessionId: accepted.sessionId,
|
|
435
|
+
id: event.message.value,
|
|
436
|
+
entry,
|
|
437
|
+
});
|
|
438
|
+
return "new";
|
|
439
|
+
});
|
|
440
|
+
if (delivery === "new")
|
|
441
|
+
publishOwnerObservation(owner, accepted.sessionId);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function recordToolIntent(owner, accepted, contract, event) {
|
|
445
|
+
return storeEffect(() => {
|
|
446
|
+
const entry = {
|
|
447
|
+
kind: "tool_intent",
|
|
448
|
+
replay: event.replay,
|
|
449
|
+
contract,
|
|
450
|
+
workId: accepted.commandId,
|
|
451
|
+
run: event.run,
|
|
452
|
+
turn: event.turn,
|
|
453
|
+
toolCall: event.toolCall,
|
|
454
|
+
call: event.call,
|
|
455
|
+
};
|
|
456
|
+
appendRecordedEntry(owner, accepted, entry, ({ entry: current }) => current.kind === "tool_intent" &&
|
|
457
|
+
sameIdentity(current.toolCall, event.toolCall) &&
|
|
458
|
+
sameContract(current.contract, contract));
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
function recordToolOutcome(owner, accepted, event) {
|
|
462
|
+
return storeEffect(() => {
|
|
463
|
+
const entry = {
|
|
464
|
+
kind: "tool_outcome",
|
|
465
|
+
workId: accepted.commandId,
|
|
466
|
+
run: event.run,
|
|
467
|
+
turn: event.turn,
|
|
468
|
+
toolCall: event.toolCall,
|
|
469
|
+
call: event.call,
|
|
470
|
+
result: event.result,
|
|
471
|
+
};
|
|
472
|
+
appendRecordedEntry(owner, accepted, entry, ({ entry: current }) => current.kind === "tool_outcome" &&
|
|
473
|
+
sameIdentity(current.toolCall, event.toolCall));
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
/** Record one retained live Pi retry observation on the Session lineage. */
|
|
477
|
+
export function recordLiveRetryEvent(owner, accepted, event, turn) {
|
|
478
|
+
return storeEffect(() => {
|
|
479
|
+
// Pi numbers retries per assistant turn, not per run. The retained turn
|
|
480
|
+
// boundary scopes replay identity without changing older event payloads.
|
|
481
|
+
const boundary = readSessionEntriesForWork(owner, accepted.sessionId, accepted.commandId).find(({ entry }) => entry.kind === "turn_started" && sameIdentity(entry.turn, turn));
|
|
482
|
+
if (boundary === undefined)
|
|
483
|
+
throw conflict("Retry has no recorded turn");
|
|
484
|
+
const entry = {
|
|
485
|
+
kind: "live_retry",
|
|
486
|
+
workId: accepted.commandId,
|
|
487
|
+
event,
|
|
488
|
+
};
|
|
489
|
+
appendRecordedEntry(owner, accepted, entry, ({ entry: current, sequence }) => sequence > boundary.sequence &&
|
|
490
|
+
current.kind === "live_retry" &&
|
|
491
|
+
current.event.type === event.type &&
|
|
492
|
+
current.event.attempt === event.attempt &&
|
|
493
|
+
sameIdentity(current.event.run, event.run));
|
|
494
|
+
}).pipe(Effect.mapError(listenerFailure));
|
|
495
|
+
}
|
|
496
|
+
const listenerFailure = () => ({
|
|
497
|
+
_tag: "MuleError",
|
|
498
|
+
phase: "listener",
|
|
499
|
+
message: "Woodstock could not record the Mule event",
|
|
500
|
+
});
|
|
501
|
+
/**
|
|
502
|
+
* Open the one scoped listener that projects Mule's exact event stream into
|
|
503
|
+
* retained Session facts and bounded process-local progress.
|
|
504
|
+
*/
|
|
505
|
+
export function openMuleRecorder(owner, accepted, contract) {
|
|
506
|
+
return Effect.gen(function* () {
|
|
507
|
+
const progress = yield* openProgress(owner, accepted, (event) => recordMessageEnd(owner, accepted, event));
|
|
508
|
+
const record = (event) => {
|
|
509
|
+
switch (event.type) {
|
|
510
|
+
case "model_prompt":
|
|
511
|
+
return recordModelPrompt(owner, accepted, contract, event);
|
|
512
|
+
case "run_start":
|
|
513
|
+
case "turn_start":
|
|
514
|
+
case "run_end":
|
|
515
|
+
return recordLifecycle(owner, accepted, contract, event);
|
|
516
|
+
case "message_start":
|
|
517
|
+
if (event.value.role === "user")
|
|
518
|
+
return Effect.void;
|
|
519
|
+
return recordMessageStart(owner, accepted, event).pipe(Effect.andThen(event.value.role === "assistant"
|
|
520
|
+
? progress.started(event)
|
|
521
|
+
: Effect.void));
|
|
522
|
+
case "message_update":
|
|
523
|
+
return progress.update(event);
|
|
524
|
+
case "message_end":
|
|
525
|
+
if (event.value.role === "user")
|
|
526
|
+
return Effect.void;
|
|
527
|
+
return event.value.role === "assistant"
|
|
528
|
+
? progress.terminal(event)
|
|
529
|
+
: recordMessageEnd(owner, accepted, event);
|
|
530
|
+
case "tool_execution_start":
|
|
531
|
+
return recordToolIntent(owner, accepted, contract, event);
|
|
532
|
+
case "tool_execution_end":
|
|
533
|
+
return recordToolOutcome(owner, accepted, event);
|
|
534
|
+
case "tool_execution_update":
|
|
535
|
+
case "turn_end":
|
|
536
|
+
return Effect.void;
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
return (event) => record(event).pipe(Effect.mapError(listenerFailure));
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
export { declareSessionDocument } from "./session-documents.js";
|
|
543
|
+
/** Read exact settled work facts through Woodstock's native command/work indexes. */
|
|
544
|
+
export function readCommandSettlement(owner, sessionId, commandId) {
|
|
545
|
+
return storeEffect(() => projectCommandSettlement(owner, decodeClientSessionId(sessionId), decodeClientSessionId(commandId)));
|
|
546
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MuleEvent } from "@fungi.computer/mule";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import type * as Scope from "effect/Scope";
|
|
4
|
+
import type { SessionProgress } from "../../client.js";
|
|
5
|
+
import { ShiitakeError } from "../../errors.js";
|
|
6
|
+
import type { SqliteOwner } from "../contracts.js";
|
|
7
|
+
import type { AcceptedCommand } from "./session-store.js";
|
|
8
|
+
/** Read the current process-local progress value for one addressed Session. */
|
|
9
|
+
export declare function readProcessProgress(owner: SqliteOwner, sessionId: string): SessionProgress | null;
|
|
10
|
+
/** Read progress only when it belongs to the current durable work head. */
|
|
11
|
+
export declare function readProcessProgressForWork(owner: SqliteOwner, sessionId: string, workId: string): SessionProgress | null;
|
|
12
|
+
export type ProgressMessageStart = Extract<MuleEvent, {
|
|
13
|
+
type: "message_start";
|
|
14
|
+
}>;
|
|
15
|
+
export type ProgressMessageUpdate = Extract<MuleEvent, {
|
|
16
|
+
type: "message_update";
|
|
17
|
+
}>;
|
|
18
|
+
export type ProgressMessageEnd = Extract<MuleEvent, {
|
|
19
|
+
type: "message_end";
|
|
20
|
+
}>;
|
|
21
|
+
export type ProgressController = Readonly<{
|
|
22
|
+
started: (event: ProgressMessageStart) => Effect.Effect<void, ShiitakeError>;
|
|
23
|
+
update: (event: ProgressMessageUpdate) => Effect.Effect<void, ShiitakeError>;
|
|
24
|
+
terminal: (event: ProgressMessageEnd) => Effect.Effect<void, ShiitakeError>;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Keep live model progress in the current process only.
|
|
28
|
+
*
|
|
29
|
+
* Woodstock durably owns one empty assistant placeholder and the exact Pi
|
|
30
|
+
* message at completion. Updates deliberately perform no SQLite writes.
|
|
31
|
+
*/
|
|
32
|
+
export declare function openProgress(owner: SqliteOwner, accepted: AcceptedCommand, complete: (event: ProgressMessageEnd) => Effect.Effect<void, ShiitakeError>): Effect.Effect<ProgressController, never, Scope.Scope>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { ShiitakeError } from "../../errors.js";
|
|
3
|
+
import { publishOwnerObservation } from "../observation-kernel.js";
|
|
4
|
+
const progressByOwner = new WeakMap();
|
|
5
|
+
function progressFor(owner) {
|
|
6
|
+
const existing = progressByOwner.get(owner);
|
|
7
|
+
if (existing !== undefined)
|
|
8
|
+
return existing;
|
|
9
|
+
const created = new Map();
|
|
10
|
+
progressByOwner.set(owner, created);
|
|
11
|
+
return created;
|
|
12
|
+
}
|
|
13
|
+
function rejected(message) {
|
|
14
|
+
return new ShiitakeError("runtime", "rejected", message);
|
|
15
|
+
}
|
|
16
|
+
/** Read the current process-local progress value for one addressed Session. */
|
|
17
|
+
export function readProcessProgress(owner, sessionId) {
|
|
18
|
+
return progressFor(owner).get(sessionId)?.value ?? null;
|
|
19
|
+
}
|
|
20
|
+
/** Read progress only when it belongs to the current durable work head. */
|
|
21
|
+
export function readProcessProgressForWork(owner, sessionId, workId) {
|
|
22
|
+
const current = progressFor(owner).get(sessionId);
|
|
23
|
+
return current?.workId === workId ? current.value : null;
|
|
24
|
+
}
|
|
25
|
+
function textFor(event) {
|
|
26
|
+
let text = "";
|
|
27
|
+
let thinking = "";
|
|
28
|
+
for (const content of event.value.content) {
|
|
29
|
+
if (content.type === "text")
|
|
30
|
+
text += content.text;
|
|
31
|
+
if (content.type === "thinking")
|
|
32
|
+
thinking += content.thinking;
|
|
33
|
+
}
|
|
34
|
+
return { text, thinking };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Keep live model progress in the current process only.
|
|
38
|
+
*
|
|
39
|
+
* Woodstock durably owns one empty assistant placeholder and the exact Pi
|
|
40
|
+
* message at completion. Updates deliberately perform no SQLite writes.
|
|
41
|
+
*/
|
|
42
|
+
export function openProgress(owner, accepted, complete) {
|
|
43
|
+
return Effect.gen(function* () {
|
|
44
|
+
let started;
|
|
45
|
+
const clear = Effect.sync(() => {
|
|
46
|
+
if (progressFor(owner).delete(accepted.sessionId))
|
|
47
|
+
publishOwnerObservation(owner, accepted.sessionId, "progress");
|
|
48
|
+
started = undefined;
|
|
49
|
+
});
|
|
50
|
+
yield* Effect.addFinalizer(() => clear);
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
started: (event) => Effect.sync(() => {
|
|
53
|
+
started = event;
|
|
54
|
+
progressFor(owner).delete(accepted.sessionId);
|
|
55
|
+
}),
|
|
56
|
+
update: (event) => Effect.suspend(() => {
|
|
57
|
+
const messageStart = started;
|
|
58
|
+
if (messageStart === undefined)
|
|
59
|
+
return Effect.fail(rejected("Mule progress arrived before its message start"));
|
|
60
|
+
if (messageStart.run.value !== event.run.value ||
|
|
61
|
+
messageStart.turn.value !== event.turn.value ||
|
|
62
|
+
messageStart.message.value !== event.message.value)
|
|
63
|
+
return Effect.fail(rejected("Mule progress identity conflicts"));
|
|
64
|
+
const previous = progressFor(owner).get(accepted.sessionId);
|
|
65
|
+
const content = textFor(event);
|
|
66
|
+
if (previous?.workId === accepted.commandId &&
|
|
67
|
+
previous.value.text === content.text &&
|
|
68
|
+
previous.value.thinking === content.thinking)
|
|
69
|
+
return Effect.void;
|
|
70
|
+
progressFor(owner).set(accepted.sessionId, Object.freeze({
|
|
71
|
+
workId: accepted.commandId,
|
|
72
|
+
value: Object.freeze({
|
|
73
|
+
messageId: event.message.value,
|
|
74
|
+
revision: (previous?.workId === accepted.commandId
|
|
75
|
+
? previous.value.revision
|
|
76
|
+
: 0) + 1,
|
|
77
|
+
text: content.text,
|
|
78
|
+
thinking: content.thinking,
|
|
79
|
+
textTruncated: false,
|
|
80
|
+
thinkingTruncated: false,
|
|
81
|
+
}),
|
|
82
|
+
}));
|
|
83
|
+
publishOwnerObservation(owner, accepted.sessionId, "progress");
|
|
84
|
+
return Effect.void;
|
|
85
|
+
}),
|
|
86
|
+
terminal: (event) => clear.pipe(Effect.andThen(complete(event))),
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MuleResume } from "@fungi.computer/mule/effect";
|
|
2
|
+
import type { StoredSessionRow } from "./session-store.js";
|
|
3
|
+
/** Derive continuation from facts, without introducing a second checkpoint store. */
|
|
4
|
+
export declare function recoverAssistantTurn(rows: readonly StoredSessionRow[]): MuleResume | undefined;
|
|
5
|
+
/** One visibility rule for model context and compaction, including recovered runs. */
|
|
6
|
+
export declare function contextMessageIds(path: readonly StoredSessionRow[], activeWorkId?: string): ReadonlySet<string>;
|
|
7
|
+
/** Returned assistant failures still pass through compaction; abnormal exits do not. */
|
|
8
|
+
export declare function abnormalTerminal(rows: readonly StoredSessionRow[]): "failed" | "cancelled" | undefined;
|