@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,162 @@
|
|
|
1
|
+
import { agentJobFromInput, decodeAgentJob, } from "@fungi.computer/bird-dog/effect";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Exit from "effect/Exit";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import { agentTurnRecovery } from "./accepted-run-executor.js";
|
|
6
|
+
import { ShiitakeError } from "../errors.js";
|
|
7
|
+
import { ExtensionId } from "../extension-contract.js";
|
|
8
|
+
import { coreConsumer } from "./extension-runtime.js";
|
|
9
|
+
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
10
|
+
import { advanceCursor, readCursor, readSettledRunFacts, readSettlementContent, readSettlementDiagnostic, readSettlementMeta, recordWorkSettlement, settleControls, } from "./woodstock/index.js";
|
|
11
|
+
const pageSize = 64;
|
|
12
|
+
function runtimeFailure(message) {
|
|
13
|
+
return new ShiitakeError("runtime", "rejected", message);
|
|
14
|
+
}
|
|
15
|
+
function fanOutSettlement(store, mailRoom, job) {
|
|
16
|
+
if (mailRoom === undefined)
|
|
17
|
+
return Effect.void;
|
|
18
|
+
return Effect.gen(function* () {
|
|
19
|
+
const payload = yield* Effect.try({
|
|
20
|
+
try: () => decodeAgentJob(job),
|
|
21
|
+
catch: () => runtimeFailure("Agent work payload is invalid"),
|
|
22
|
+
});
|
|
23
|
+
const content = job.outcome === "cancelled"
|
|
24
|
+
? ""
|
|
25
|
+
: yield* readSettlementContent(store, payload.work);
|
|
26
|
+
const meta = yield* readSettlementMeta(store, payload.work, job.outcome);
|
|
27
|
+
const diagnostic = job.outcome === "completed" || job.outcome === "cancelled"
|
|
28
|
+
? undefined
|
|
29
|
+
: yield* readSettlementDiagnostic(store, payload.work);
|
|
30
|
+
const settled = diagnostic === undefined
|
|
31
|
+
? { job, content }
|
|
32
|
+
: { job, content, diagnostic };
|
|
33
|
+
yield* mailRoom
|
|
34
|
+
.fanOut(meta === undefined ? settled : { ...settled, meta })
|
|
35
|
+
.pipe(Effect.mapError(() => runtimeFailure("Agent reply fan-out is unavailable")));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function repairWorkSettlement(store, job) {
|
|
39
|
+
const outcome = job.outcome;
|
|
40
|
+
if (outcome !== "interrupted" &&
|
|
41
|
+
outcome !== "cancelled" &&
|
|
42
|
+
outcome !== "failed")
|
|
43
|
+
return Effect.void;
|
|
44
|
+
return Effect.gen(function* () {
|
|
45
|
+
const payload = yield* Effect.try({
|
|
46
|
+
try: () => decodeAgentJob(job),
|
|
47
|
+
catch: () => runtimeFailure("Agent work payload is invalid"),
|
|
48
|
+
});
|
|
49
|
+
yield* recordWorkSettlement(store, {
|
|
50
|
+
sessionId: payload.work.sessionId,
|
|
51
|
+
commandFactId: payload.work.commandFactId,
|
|
52
|
+
workId: job.jobId,
|
|
53
|
+
outcome,
|
|
54
|
+
diagnostic: job.failureReason,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function settleTargetedControls(store, watchdog, job) {
|
|
59
|
+
return Effect.gen(function* () {
|
|
60
|
+
const payload = yield* Effect.try({
|
|
61
|
+
try: () => decodeAgentJob(job),
|
|
62
|
+
catch: () => runtimeFailure("Agent work payload is invalid"),
|
|
63
|
+
});
|
|
64
|
+
const outcome = job.outcome === "outcome_unknown" ? "interrupted" : job.outcome;
|
|
65
|
+
yield* settleControls(store, {
|
|
66
|
+
sessionId: payload.work.sessionId,
|
|
67
|
+
parentWorkId: job.jobId,
|
|
68
|
+
outcome,
|
|
69
|
+
enqueue: (work) => {
|
|
70
|
+
watchdog.transactional.enqueueAcceptedJob(agentJobFromInput({ work: { ...work, recovery: agentTurnRecovery } }));
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function drainMail(mailRoom) {
|
|
76
|
+
if (mailRoom === undefined)
|
|
77
|
+
return Effect.void;
|
|
78
|
+
return mailRoom
|
|
79
|
+
.drainOutbox()
|
|
80
|
+
.pipe(Effect.mapError(() => runtimeFailure("Agent reply delivery is unavailable")));
|
|
81
|
+
}
|
|
82
|
+
/** Join one core-processed settled record with its exact Woodstock facts. */
|
|
83
|
+
function settledRun(store, record) {
|
|
84
|
+
const payload = decodeAgentJob(record.job);
|
|
85
|
+
const facts = readSettledRunFacts(store, payload.work.sessionId, payload.work.commandFactId, record.job.jobId);
|
|
86
|
+
return Object.freeze({
|
|
87
|
+
sequence: record.sequence,
|
|
88
|
+
sessionId: payload.work.sessionId,
|
|
89
|
+
workId: record.job.jobId,
|
|
90
|
+
outcome: record.job.outcome,
|
|
91
|
+
terminal: facts.terminal,
|
|
92
|
+
toolCalls: facts.toolCalls,
|
|
93
|
+
origin: facts.origin === null
|
|
94
|
+
? null
|
|
95
|
+
: Object.freeze({
|
|
96
|
+
extensionId: Schema.decodeUnknownSync(ExtensionId)(facts.origin.extensionId),
|
|
97
|
+
correlation: facts.origin.correlation,
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The `core` settled consumer: publish the idle owner observation, repair
|
|
103
|
+
* terminals, settle targeted controls, fan out mail, then advance the durable core cursor, one record
|
|
104
|
+
* at a time. Every step is idempotent, so a crash before the cursor advance
|
|
105
|
+
* repeats work without duplicating a reply.
|
|
106
|
+
*/
|
|
107
|
+
function settleCore(store, watchdog, mailRoom, records) {
|
|
108
|
+
return Effect.forEach(records, (record) => Effect.gen(function* () {
|
|
109
|
+
// Watchdog has durably settled the work: its queue head is idle now.
|
|
110
|
+
// Announce that before any Session repair, so a repair failure cannot
|
|
111
|
+
// leave live watchers reporting the finished run as active. A replay
|
|
112
|
+
// after a crash repeats the same announcement.
|
|
113
|
+
publishOwnerObservation(store, record.job.queue);
|
|
114
|
+
yield* repairWorkSettlement(store, record.job);
|
|
115
|
+
yield* settleTargetedControls(store, watchdog, record.job);
|
|
116
|
+
yield* fanOutSettlement(store, mailRoom, record.job);
|
|
117
|
+
yield* Effect.try({
|
|
118
|
+
try: () => {
|
|
119
|
+
advanceCursor(store, coreConsumer, record.sequence);
|
|
120
|
+
},
|
|
121
|
+
catch: () => runtimeFailure("Settled cursor is unavailable"),
|
|
122
|
+
});
|
|
123
|
+
}), { discard: true });
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* One reconcile tick. Each step is its own Exit boundary: a failed extension
|
|
127
|
+
* stops only itself, and mail always drains. Only a core failure fails the
|
|
128
|
+
* tick, so the host backs off only for core.
|
|
129
|
+
*/
|
|
130
|
+
export function reconcileRuntime(input) {
|
|
131
|
+
return Effect.gen(function* () {
|
|
132
|
+
const result = yield* input.watchdog.tick();
|
|
133
|
+
if (result.status === "busy")
|
|
134
|
+
return yield* Effect.fail(runtimeFailure("Runtime reconciliation is already active"));
|
|
135
|
+
const coreCursor = yield* Effect.try({
|
|
136
|
+
try: () => readCursor(input.store, coreConsumer) ?? 0,
|
|
137
|
+
catch: () => runtimeFailure("Settled cursor is unavailable"),
|
|
138
|
+
});
|
|
139
|
+
const consumers = input.extensions.settledConsumers();
|
|
140
|
+
const after = Math.min(coreCursor, ...consumers.map(({ cursor }) => cursor));
|
|
141
|
+
const page = yield* input.watchdog.readSettled(pageSize, after);
|
|
142
|
+
const core = yield* Effect.exit(settleCore(input.store, input.watchdog, input.mailRoom, page.filter(({ sequence }) => sequence > coreCursor)));
|
|
143
|
+
const runs = yield* Effect.try({
|
|
144
|
+
try: () => {
|
|
145
|
+
const processed = readCursor(input.store, coreConsumer) ?? 0;
|
|
146
|
+
return page
|
|
147
|
+
.filter(({ sequence }) => sequence <= processed)
|
|
148
|
+
.map((record) => settledRun(input.store, record));
|
|
149
|
+
},
|
|
150
|
+
catch: () => runtimeFailure("Settled run facts are unavailable"),
|
|
151
|
+
});
|
|
152
|
+
yield* input.extensions.deliverSettled(runs, readCursor(input.store, coreConsumer) ?? 0);
|
|
153
|
+
const moreTimers = yield* input.extensions.fireDueTimers(Date.now());
|
|
154
|
+
if (page.length === pageSize || moreTimers)
|
|
155
|
+
yield* input.requestWake;
|
|
156
|
+
yield* drainMail(input.mailRoom);
|
|
157
|
+
if (Exit.isFailure(core))
|
|
158
|
+
return yield* Effect.failCause(core.cause);
|
|
159
|
+
}).pipe(Effect.mapError((failure) => failure instanceof ShiitakeError
|
|
160
|
+
? failure
|
|
161
|
+
: runtimeFailure("Runtime reconciliation failed")));
|
|
162
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type { SessionContext } from "../client.js";
|
|
3
|
+
import type { ShiitakeEffectOptions } from "./effect-contracts.js";
|
|
4
|
+
import type { RuntimeCoordinator } from "./runtime-coordinator.js";
|
|
5
|
+
/**
|
|
6
|
+
* The Session's context use as compaction measures it, against the window of
|
|
7
|
+
* the model a run would select now. Undefined while the Session has no
|
|
8
|
+
* context or its context cannot be read; a window the host cannot describe is
|
|
9
|
+
* null, never guessed.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readSessionContext(options: ShiitakeEffectOptions, runtime: RuntimeCoordinator, sessionId: string): Effect.Effect<SessionContext | undefined>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { estimateThresholdContextTokens } from "./compaction/session-compaction.js";
|
|
3
|
+
import { reportCatchFailure } from "./failure.js";
|
|
4
|
+
import { buildSessionContext, readSessionContextPath, readSessionModelPreference, } from "./woodstock/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* The context size compaction measures against the model's window, for the
|
|
7
|
+
* Session's settled context: the next run starts from it. Null before the
|
|
8
|
+
* Session has any context.
|
|
9
|
+
*/
|
|
10
|
+
function settledContextTokens(store, sessionId) {
|
|
11
|
+
const path = readSessionContextPath(store, { sessionId });
|
|
12
|
+
const messages = buildSessionContext(path);
|
|
13
|
+
return messages.length === 0
|
|
14
|
+
? null
|
|
15
|
+
: estimateThresholdContextTokens(path, messages);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Each runtime keeps the last described window per Session. A saved model
|
|
19
|
+
* preference changes the key, so `/model` is described again at once.
|
|
20
|
+
*/
|
|
21
|
+
const describedWindows = new WeakMap();
|
|
22
|
+
function windowsFor(runtime) {
|
|
23
|
+
const existing = describedWindows.get(runtime);
|
|
24
|
+
if (existing !== undefined)
|
|
25
|
+
return existing;
|
|
26
|
+
const created = new Map();
|
|
27
|
+
describedWindows.set(runtime, created);
|
|
28
|
+
return created;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The Session's context use as compaction measures it, against the window of
|
|
32
|
+
* the model a run would select now. Undefined while the Session has no
|
|
33
|
+
* context or its context cannot be read; a window the host cannot describe is
|
|
34
|
+
* null, never guessed.
|
|
35
|
+
*/
|
|
36
|
+
export function readSessionContext(options, runtime, sessionId) {
|
|
37
|
+
return Effect.gen(function* () {
|
|
38
|
+
// A context too large to read safely has no footer figure.
|
|
39
|
+
const read = yield* Effect.result(Effect.try({
|
|
40
|
+
try: () => ({
|
|
41
|
+
tokens: settledContextTokens(options.store, sessionId),
|
|
42
|
+
preference: readSessionModelPreference(options.store, sessionId),
|
|
43
|
+
}),
|
|
44
|
+
catch: (error) => error,
|
|
45
|
+
}));
|
|
46
|
+
if (read._tag === "Failure" || read.success.tokens === null)
|
|
47
|
+
return undefined;
|
|
48
|
+
const { tokens, preference } = read.success;
|
|
49
|
+
const windows = windowsFor(runtime);
|
|
50
|
+
const key = JSON.stringify(preference);
|
|
51
|
+
const cached = windows.get(sessionId);
|
|
52
|
+
if (cached?.preference === key)
|
|
53
|
+
return Object.freeze({ tokens, window: cached.window });
|
|
54
|
+
const describe = options.model.describe;
|
|
55
|
+
if (describe === undefined)
|
|
56
|
+
return Object.freeze({ tokens, window: null });
|
|
57
|
+
const described = yield* describe({ sessionId, preference }).pipe(Effect.map(({ contextWindow }) => contextWindow), Effect.catch((error) => Effect.sync(() => {
|
|
58
|
+
reportCatchFailure("session-context.describe", error);
|
|
59
|
+
return undefined;
|
|
60
|
+
})));
|
|
61
|
+
// A failed description is retried on the next read, never cached.
|
|
62
|
+
if (described === undefined)
|
|
63
|
+
return Object.freeze({ tokens, window: null });
|
|
64
|
+
windows.set(sessionId, { preference: key, window: described });
|
|
65
|
+
return Object.freeze({ tokens, window: described });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WatchdogQueueHead } from "@fungi.computer/watchdog/effect";
|
|
2
|
+
import type { AgentRunningWork, SessionActivity, SessionProgress } from "../client.js";
|
|
3
|
+
import type { SqliteOwner } from "./contracts.js";
|
|
4
|
+
/** Public activity facts plus the process-local progress read with them. */
|
|
5
|
+
export type ObservedActivity = Readonly<{
|
|
6
|
+
facts: SessionActivity;
|
|
7
|
+
progress: SessionProgress | null;
|
|
8
|
+
}>;
|
|
9
|
+
/** Join Watchdog authority with Woodstock facts and process-local progress. */
|
|
10
|
+
export declare function readSessionActivity(owner: SqliteOwner, sessionId: string, head: WatchdogQueueHead, runningWork: AgentRunningWork | null): ObservedActivity;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readProcessProgressForWork, readProcessCompaction, readLatestLiveRetryForWork, readActiveToolForWork, readLastModelChoiceSequence, } from "./woodstock/index.js";
|
|
2
|
+
/** Join Watchdog authority with Woodstock facts and process-local progress. */
|
|
3
|
+
export function readSessionActivity(owner, sessionId, head, runningWork) {
|
|
4
|
+
const lastModelChoiceSequence = readLastModelChoiceSequence(owner, sessionId);
|
|
5
|
+
if (head.state === "idle")
|
|
6
|
+
return {
|
|
7
|
+
facts: {
|
|
8
|
+
queue: { state: "idle" },
|
|
9
|
+
cancellationRequested: false,
|
|
10
|
+
retry: null,
|
|
11
|
+
activeTool: null,
|
|
12
|
+
lastModelChoiceSequence,
|
|
13
|
+
compaction: null,
|
|
14
|
+
},
|
|
15
|
+
progress: null,
|
|
16
|
+
};
|
|
17
|
+
if (head.state === "queued")
|
|
18
|
+
// The queue job id is the admitted command id: the one identity a person
|
|
19
|
+
// may cancel while this Session's work waits for the Agent.
|
|
20
|
+
return {
|
|
21
|
+
facts: {
|
|
22
|
+
queue: { state: "queued", commandId: head.jobId, runningWork },
|
|
23
|
+
cancellationRequested: head.cancellationRequested,
|
|
24
|
+
retry: null,
|
|
25
|
+
activeTool: null,
|
|
26
|
+
lastModelChoiceSequence,
|
|
27
|
+
compaction: null,
|
|
28
|
+
},
|
|
29
|
+
progress: null,
|
|
30
|
+
};
|
|
31
|
+
const latestRetry = readLatestLiveRetryForWork(owner, sessionId, head.jobId);
|
|
32
|
+
const retry = latestRetry?.event.type === "auto_retry_start"
|
|
33
|
+
? {
|
|
34
|
+
attempt: latestRetry.event.attempt,
|
|
35
|
+
maxAttempts: latestRetry.event.maxAttempts,
|
|
36
|
+
delayMs: latestRetry.event.delayMs,
|
|
37
|
+
errorMessage: latestRetry.event.errorMessage,
|
|
38
|
+
scheduledAt: latestRetry.timestamp,
|
|
39
|
+
}
|
|
40
|
+
: null;
|
|
41
|
+
return {
|
|
42
|
+
facts: {
|
|
43
|
+
queue: { state: "running", commandId: head.jobId },
|
|
44
|
+
cancellationRequested: head.cancellationRequested,
|
|
45
|
+
retry,
|
|
46
|
+
activeTool: readActiveToolForWork(owner, sessionId, head.jobId) ?? null,
|
|
47
|
+
lastModelChoiceSequence,
|
|
48
|
+
compaction: readProcessCompaction(owner, sessionId),
|
|
49
|
+
},
|
|
50
|
+
progress: readProcessProgressForWork(owner, sessionId, head.jobId),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { UserMessage } from "@fungi.computer/mule/effect";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
/**
|
|
4
|
+
* A delegated Agent's reply after the admitting host authenticated it against
|
|
5
|
+
* the mail it sent: the replying Agent and Session plus BirdDog's settlement
|
|
6
|
+
* facts. Only `acceptMail` stores it, and only the host passes it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const VerifiedAgentReplySchema: Schema.Struct<{
|
|
9
|
+
readonly commandId: Schema.String;
|
|
10
|
+
readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
|
|
11
|
+
readonly diagnostic: Schema.optionalKey<Schema.String>;
|
|
12
|
+
readonly agentId: Schema.String;
|
|
13
|
+
readonly sessionId: Schema.String;
|
|
14
|
+
}>;
|
|
15
|
+
/** A delegated Agent's reply after the admitting host authenticated it. */
|
|
16
|
+
export type VerifiedAgentReply = typeof VerifiedAgentReplySchema.Type;
|
|
17
|
+
/** The tag that marks a host-verified Agent reply in model context. */
|
|
18
|
+
export declare const AGENT_REPLY_TAG = "fungi_agent_reply";
|
|
19
|
+
/**
|
|
20
|
+
* What a model sees of one stored user-role line. Every typed, mailed or
|
|
21
|
+
* operator text is neutralized, so only Shiitake can open the verified-reply
|
|
22
|
+
* tag; a line the host admitted with a verified reply is wrapped in it. The
|
|
23
|
+
* provider role stays `user`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function userContextMessage(message: UserMessage, reply?: VerifiedAgentReply): UserMessage;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { BirdDogReplySchema } from "@fungi.computer/bird-dog/effect";
|
|
2
|
+
import * as Schema from "effect/Schema";
|
|
3
|
+
const Identifier = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256));
|
|
4
|
+
/**
|
|
5
|
+
* A delegated Agent's reply after the admitting host authenticated it against
|
|
6
|
+
* the mail it sent: the replying Agent and Session plus BirdDog's settlement
|
|
7
|
+
* facts. Only `acceptMail` stores it, and only the host passes it.
|
|
8
|
+
*/
|
|
9
|
+
export const VerifiedAgentReplySchema = Schema.Struct({
|
|
10
|
+
agentId: Identifier,
|
|
11
|
+
sessionId: Identifier,
|
|
12
|
+
...BirdDogReplySchema.fields,
|
|
13
|
+
});
|
|
14
|
+
/** The tag that marks a host-verified Agent reply in model context. */
|
|
15
|
+
export const AGENT_REPLY_TAG = "fungi_agent_reply";
|
|
16
|
+
// `<` before the tag name, allowing the spacing and case a model would still read as the tag.
|
|
17
|
+
const tagOpening = new RegExp(`<(?=\\s*/?\\s*${AGENT_REPLY_TAG})`, "giu");
|
|
18
|
+
/** Make untrusted text unable to open or close the verified-reply tag. */
|
|
19
|
+
function neutralize(text) {
|
|
20
|
+
return text.replace(tagOpening, "<");
|
|
21
|
+
}
|
|
22
|
+
function attribute(value) {
|
|
23
|
+
return value
|
|
24
|
+
.replaceAll("&", "&")
|
|
25
|
+
.replaceAll('"', """)
|
|
26
|
+
.replaceAll("<", "<")
|
|
27
|
+
.replaceAll(">", ">");
|
|
28
|
+
}
|
|
29
|
+
function opening(reply) {
|
|
30
|
+
const attributes = [
|
|
31
|
+
["agent_id", reply.agentId],
|
|
32
|
+
["session_id", reply.sessionId],
|
|
33
|
+
["command_id", reply.commandId],
|
|
34
|
+
["outcome", reply.outcome],
|
|
35
|
+
];
|
|
36
|
+
if (reply.diagnostic !== undefined)
|
|
37
|
+
attributes.push(["diagnostic", reply.diagnostic]);
|
|
38
|
+
const rendered = attributes
|
|
39
|
+
.map(([name, value]) => `${name}="${attribute(value)}"`)
|
|
40
|
+
.join(" ");
|
|
41
|
+
return `<${AGENT_REPLY_TAG} ${rendered}>`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* What a model sees of one stored user-role line. Every typed, mailed or
|
|
45
|
+
* operator text is neutralized, so only Shiitake can open the verified-reply
|
|
46
|
+
* tag; a line the host admitted with a verified reply is wrapped in it. The
|
|
47
|
+
* provider role stays `user`.
|
|
48
|
+
*/
|
|
49
|
+
export function userContextMessage(message, reply) {
|
|
50
|
+
const content = Array.isArray(message.content)
|
|
51
|
+
? message.content.map((part) => part.type === "text" ? { ...part, text: neutralize(part.text) } : part)
|
|
52
|
+
: neutralize(message.content);
|
|
53
|
+
if (reply === undefined)
|
|
54
|
+
return { ...message, content };
|
|
55
|
+
const open = opening(reply);
|
|
56
|
+
const close = `</${AGENT_REPLY_TAG}>`;
|
|
57
|
+
return {
|
|
58
|
+
...message,
|
|
59
|
+
content: Array.isArray(content)
|
|
60
|
+
? [
|
|
61
|
+
{ type: "text", text: open },
|
|
62
|
+
...content,
|
|
63
|
+
{ type: "text", text: close },
|
|
64
|
+
]
|
|
65
|
+
: `${open}\n${content}\n${close}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SessionFrame, SessionExtensionSlots, SessionSnapshot } from "../../client.js";
|
|
2
|
+
import type { ClientReadOptions } from "../client-boundary.js";
|
|
3
|
+
import type { CommandSettlement, SqliteOwner } from "../contracts.js";
|
|
4
|
+
import type { ObservedActivity } from "../session-status.js";
|
|
5
|
+
import { type StoredSessionRow } from "./session-store.js";
|
|
6
|
+
/**
|
|
7
|
+
* Newest failure message each work recorded: a work-terminal diagnostic, an
|
|
8
|
+
* assistant error or a final retry error. Raw text; clients and the reply
|
|
9
|
+
* fan-out decide how to present and bound it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function diagnosticsByWorkId(path: readonly StoredSessionRow[]): ReadonlyMap<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Read one bounded public baseline from the selected Session lineage. Only
|
|
14
|
+
* the page's rows are decoded; `before` pages back through the whole saved
|
|
15
|
+
* history, including what compaction summarized away.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readClientSnapshot(owner: SqliteOwner, sessionId: string, options: ClientReadOptions, activity: ObservedActivity, projectExtensions?: () => SessionExtensionSlots): SessionSnapshot;
|
|
18
|
+
/** One bounded durable replay decision for the process-local observation stream. */
|
|
19
|
+
export type ClientReplayPage = Readonly<{
|
|
20
|
+
kind: "page";
|
|
21
|
+
frames: readonly SessionFrame[];
|
|
22
|
+
hasMore: boolean;
|
|
23
|
+
}> | Readonly<{
|
|
24
|
+
kind: "reset";
|
|
25
|
+
}>;
|
|
26
|
+
/** Read strict-after history plus the current waiting-admission view. */
|
|
27
|
+
export declare function readClientReplayPage(owner: SqliteOwner, sessionId: string, after: string | undefined, delivery: "replay" | "live", activity: ObservedActivity, projectExtensions?: () => SessionExtensionSlots, includeSandboxFailures?: boolean): ClientReplayPage;
|
|
28
|
+
/** Project one exact command's durable facts without selecting a transcript lineage. */
|
|
29
|
+
export declare function projectCommandSettlement(owner: SqliteOwner, sessionId: string, commandId: string): CommandSettlement | undefined;
|