@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
package/dist/fleet.d.ts
CHANGED
|
@@ -82,8 +82,8 @@ export type AgentLifecycleFact = Readonly<{
|
|
|
82
82
|
}>;
|
|
83
83
|
/** Refusal of supplied authority; no host policy lookup is performed. */
|
|
84
84
|
export declare class AgentGrantError extends Error {
|
|
85
|
-
readonly reason: "agent" | "action" | "expired" | "delegation_loop" | "delegation_depth";
|
|
86
|
-
constructor(reason: "agent" | "action" | "expired" | "delegation_loop" | "delegation_depth");
|
|
85
|
+
readonly reason: "agent" | "action" | "expired" | "delegation_loop" | "delegation_depth" | "delegator_missing";
|
|
86
|
+
constructor(reason: "agent" | "action" | "expired" | "delegation_loop" | "delegation_depth" | "delegator_missing");
|
|
87
87
|
}
|
|
88
88
|
/** Check every operation against its explicit authority. */
|
|
89
89
|
export declare function assertAgentGrant(grant: AgentGrant, action: AgentAction, agentId?: string): void;
|
|
@@ -196,6 +196,7 @@ export declare const agentDelegationTaskSchema: z.ZodLazy<z.ZodObject<{
|
|
|
196
196
|
followUp: "followUp";
|
|
197
197
|
steer: "steer";
|
|
198
198
|
}>>;
|
|
199
|
+
goal: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<false>]>>;
|
|
199
200
|
text: z.ZodString;
|
|
200
201
|
}, z.core.$strict>>;
|
|
201
202
|
/** A caller reuses this task unchanged while polling Pending. */
|
|
@@ -271,6 +272,8 @@ export declare function delegationSendMeta(sender: Readonly<{
|
|
|
271
272
|
* while the verified reply retains the failed recipient's identity.
|
|
272
273
|
*/
|
|
273
274
|
export declare function delegationReplyMeta(delegation: AuthenticatedDelegationReply, replierName: string): DelegationReplyMeta;
|
|
275
|
+
/** An original one-way report can only name an ancestor of its Agent sender. */
|
|
276
|
+
export declare function isOneWayAncestorNotice(mail: BirdDogMail): boolean;
|
|
274
277
|
/**
|
|
275
278
|
* Delegated work an owner still waits on; `unreadable` says why its state
|
|
276
279
|
* could not be read, when it could not.
|
|
@@ -281,6 +284,11 @@ export type OutstandingDelegation = Readonly<{
|
|
|
281
284
|
commandId: string;
|
|
282
285
|
unreadable?: string;
|
|
283
286
|
}>;
|
|
287
|
+
/** Read confirmed sender receipts that have no locally observed settlement.
|
|
288
|
+
* Call inside the Agent owner's transaction that also changes archived state.
|
|
289
|
+
* Remote reads happen separately; an unobserved settlement remains busy.
|
|
290
|
+
*/
|
|
291
|
+
export declare function readOutstandingDelegationSync(storage: WatchdogSqliteOwner): OutstandingDelegation | undefined;
|
|
284
292
|
/**
|
|
285
293
|
* BirdDog submission and reply authentication. The durable row freezes
|
|
286
294
|
* correspondence before transport; an ambiguous delivery reuses that exact mail.
|
package/dist/fleet.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { commandSettlementSchema } from "./client-wire.js";
|
|
2
|
-
import { createFleetDirectoryStore, createFleetDelegationStore, } from "./internal/woodstock/index.js";
|
|
2
|
+
import { createFleetDirectoryStore, createFleetDelegationStore, readSessionDelegator, } from "./internal/woodstock/index.js";
|
|
3
3
|
import { pending } from "@fungi.computer/agar";
|
|
4
4
|
import { BirdDogMailSchema } from "@fungi.computer/bird-dog/effect";
|
|
5
5
|
import * as Schema from "effect/Schema";
|
|
@@ -280,17 +280,23 @@ export function settlementReadableBy(facts, agentId) {
|
|
|
280
280
|
(facts.acceptedSender === null && facts.delegator === agentId));
|
|
281
281
|
}
|
|
282
282
|
/** Replay identity and work, with the parent supplied exclusively by the grant. */
|
|
283
|
-
export const agentDelegationTaskSchema = z.lazy(() => z
|
|
283
|
+
export const agentDelegationTaskSchema = z.lazy(() => z
|
|
284
|
+
.strictObject({
|
|
284
285
|
requestId: identity,
|
|
285
286
|
agentId: identity,
|
|
286
287
|
sessionId: identity.optional(),
|
|
287
288
|
/** A live steer into the recipient's running turn; queued follow-up when omitted. */
|
|
288
289
|
delivery: z.enum(["followUp", "steer"]).optional(),
|
|
290
|
+
/** Only a new-Session follow-up carries goal intent. */
|
|
291
|
+
goal: z
|
|
292
|
+
.union([z.string().min(1).max(4_000), z.literal(false)])
|
|
293
|
+
.optional(),
|
|
289
294
|
text: z
|
|
290
295
|
.string()
|
|
291
296
|
.min(1)
|
|
292
297
|
.max(256 * 1024),
|
|
293
|
-
})
|
|
298
|
+
})
|
|
299
|
+
.refine(({ goal, sessionId, delivery }) => goal === undefined || (sessionId === undefined && delivery !== "steer"), { path: ["goal"], error: "goal requires a new-Session follow-up" }));
|
|
294
300
|
/**
|
|
295
301
|
* Provenance for one delegated send from the invoking Agent's Session. The
|
|
296
302
|
* model program's digest is frozen with the replay input, so a retried tool
|
|
@@ -355,6 +361,19 @@ export function delegationReplyMeta(delegation, replierName) {
|
|
|
355
361
|
const delegationMetadata = z.object({
|
|
356
362
|
delegation: FungiMessageMetaSchema.shape.delegation,
|
|
357
363
|
});
|
|
364
|
+
/** An original one-way report can only name an ancestor of its Agent sender. */
|
|
365
|
+
export function isOneWayAncestorNotice(mail) {
|
|
366
|
+
if (mail.reply !== undefined ||
|
|
367
|
+
mail.replyTo.length !== 0 ||
|
|
368
|
+
mail.from.kind !== "agent" ||
|
|
369
|
+
mail.to.sessionId === undefined)
|
|
370
|
+
return false;
|
|
371
|
+
const parsed = delegationMetadata.safeParse(mail.meta);
|
|
372
|
+
return (parsed.success &&
|
|
373
|
+
parsed.data.delegation !== undefined &&
|
|
374
|
+
parsed.data.delegation.ancestors.includes(mail.to.agentId) &&
|
|
375
|
+
parsed.data.delegation.ancestors.at(-1) === mail.from.agentId);
|
|
376
|
+
}
|
|
358
377
|
const delegationRowSchema = z.lazy(() => z.strictObject({
|
|
359
378
|
input: z.string(),
|
|
360
379
|
mail: z.string(),
|
|
@@ -375,6 +394,25 @@ function bindRecipientSession(mail, sessionId) {
|
|
|
375
394
|
to: { ...mail.to, sessionId },
|
|
376
395
|
};
|
|
377
396
|
}
|
|
397
|
+
/** Read confirmed sender receipts that have no locally observed settlement.
|
|
398
|
+
* Call inside the Agent owner's transaction that also changes archived state.
|
|
399
|
+
* Remote reads happen separately; an unobserved settlement remains busy.
|
|
400
|
+
*/
|
|
401
|
+
export function readOutstandingDelegationSync(storage) {
|
|
402
|
+
const raw = createFleetDelegationStore(storage).unsettled()[0];
|
|
403
|
+
if (raw === undefined)
|
|
404
|
+
return undefined;
|
|
405
|
+
const row = delegationRowSchema.parse(raw);
|
|
406
|
+
const mail = decodeMail(row.mail);
|
|
407
|
+
const sessionId = mail.to.sessionId;
|
|
408
|
+
if (sessionId === undefined)
|
|
409
|
+
throw new DirectoryError(503, "partial_init");
|
|
410
|
+
return {
|
|
411
|
+
agentId: mail.to.agentId,
|
|
412
|
+
sessionId,
|
|
413
|
+
commandId: parseAccepted(z.string().parse(row.receipt)).commandId,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
378
416
|
/**
|
|
379
417
|
* BirdDog submission and reply authentication. The durable row freezes
|
|
380
418
|
* correspondence before transport; an ambiguous delivery reuses that exact mail.
|
|
@@ -402,13 +440,18 @@ export function createFleetCorrespondence(options) {
|
|
|
402
440
|
: undefined;
|
|
403
441
|
const ancestors = inherited?.ancestors ?? [];
|
|
404
442
|
const maxDepth = Math.min(grant.maxDepth ?? 8, inherited?.maxDepth ?? 8);
|
|
405
|
-
//
|
|
443
|
+
// A report to an ancestor is one-way: the parent handles the work but
|
|
444
|
+
// must not send its settlement back as a new prompt to the worker.
|
|
406
445
|
if (parent.agentId === task.agentId)
|
|
407
446
|
throw new AgentGrantError("delegation_loop");
|
|
408
447
|
if (ancestors.length >= maxDepth)
|
|
409
448
|
throw new AgentGrantError("delegation_depth");
|
|
410
449
|
const path = { ancestors: [...ancestors, parent.agentId], maxDepth };
|
|
411
|
-
const delegation =
|
|
450
|
+
const delegation = {
|
|
451
|
+
...path,
|
|
452
|
+
...(task.delivery === "steer" && { steer: true }),
|
|
453
|
+
...(task.goal !== undefined && { goal: task.goal }),
|
|
454
|
+
};
|
|
412
455
|
const outgoingMeta = {
|
|
413
456
|
...(objectMeta.success
|
|
414
457
|
? objectMeta.data
|
|
@@ -427,10 +470,20 @@ export function createFleetCorrespondence(options) {
|
|
|
427
470
|
let row = read(task.requestId);
|
|
428
471
|
if (row === undefined) {
|
|
429
472
|
const from = { kind: "agent", ...parent };
|
|
473
|
+
const ancestor = ancestors.includes(task.agentId);
|
|
474
|
+
// A sessionless report to a parent goes to the creator's recorded
|
|
475
|
+
// Session. Never create a fresh Session merely to carry a notice.
|
|
476
|
+
const targetSessionId = task.sessionId ??
|
|
477
|
+
(ancestor
|
|
478
|
+
? readSessionDelegator(options.storage, parent.sessionId)?.replyTo.find(({ agentId }) => agentId === task.agentId)
|
|
479
|
+
?.sessionId
|
|
480
|
+
: undefined);
|
|
481
|
+
if (ancestor && targetSessionId === undefined)
|
|
482
|
+
throw new AgentGrantError("delegator_missing");
|
|
430
483
|
let mail = {
|
|
431
484
|
key: task.requestId,
|
|
432
485
|
from,
|
|
433
|
-
to:
|
|
486
|
+
to: targetSessionId === undefined
|
|
434
487
|
? {
|
|
435
488
|
kind: "agent",
|
|
436
489
|
agentId: task.agentId,
|
|
@@ -438,9 +491,9 @@ export function createFleetCorrespondence(options) {
|
|
|
438
491
|
: {
|
|
439
492
|
kind: "agent",
|
|
440
493
|
agentId: task.agentId,
|
|
441
|
-
sessionId:
|
|
494
|
+
sessionId: targetSessionId,
|
|
442
495
|
},
|
|
443
|
-
replyTo: [from],
|
|
496
|
+
replyTo: ancestor ? [] : [from],
|
|
444
497
|
content: task.text,
|
|
445
498
|
createdAt: Date.now(),
|
|
446
499
|
};
|
|
@@ -677,6 +730,8 @@ export function createFleetDelegation(options) {
|
|
|
677
730
|
if (sessionId === undefined)
|
|
678
731
|
throw new DirectoryError(503, "partial_init");
|
|
679
732
|
const settlement = await options.readSettlement(mail.to.agentId, sessionId, accepted.commandId);
|
|
733
|
+
if (settlement !== undefined)
|
|
734
|
+
store.recordSettled(task.requestId);
|
|
680
735
|
return settlement === undefined
|
|
681
736
|
? pending(1000)
|
|
682
737
|
: {
|
|
@@ -691,7 +746,8 @@ export function createFleetDelegation(options) {
|
|
|
691
746
|
* target, so the owner is still waiting for a reply; undefined when none.
|
|
692
747
|
*/
|
|
693
748
|
async function outstanding() {
|
|
694
|
-
|
|
749
|
+
let waiting;
|
|
750
|
+
for (const raw of store.unsettled()) {
|
|
695
751
|
const row = delegationRowSchema.parse(raw);
|
|
696
752
|
if (row.receipt === null)
|
|
697
753
|
continue;
|
|
@@ -706,21 +762,23 @@ export function createFleetDelegation(options) {
|
|
|
706
762
|
sessionId: to.sessionId,
|
|
707
763
|
commandId,
|
|
708
764
|
};
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
settlement
|
|
712
|
-
|
|
713
|
-
catch (cause) {
|
|
765
|
+
const result = await options
|
|
766
|
+
.readSettlement(to.agentId, to.sessionId, commandId)
|
|
767
|
+
.then((settlement) => ({ kind: "read", settlement }), (cause) => ({ kind: "unreadable", cause }));
|
|
768
|
+
if (result.kind === "unreadable") {
|
|
714
769
|
// Work whose state cannot be read is still owed; say why.
|
|
715
|
-
|
|
770
|
+
waiting ??= {
|
|
716
771
|
...target,
|
|
717
|
-
unreadable: cause instanceof Error ? cause.message : "unreadable",
|
|
772
|
+
unreadable: result.cause instanceof Error ? result.cause.message : "unreadable",
|
|
718
773
|
};
|
|
774
|
+
continue;
|
|
719
775
|
}
|
|
720
|
-
if (settlement === undefined)
|
|
721
|
-
|
|
776
|
+
if (result.settlement === undefined)
|
|
777
|
+
waiting ??= target;
|
|
778
|
+
else
|
|
779
|
+
store.recordSettled(decodeMail(row.mail).key);
|
|
722
780
|
}
|
|
723
|
-
return
|
|
781
|
+
return waiting;
|
|
724
782
|
}
|
|
725
783
|
return {
|
|
726
784
|
/** The delegated work this owner still waits on, if any. */
|
package/dist/http-routes.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
output: {
|
|
45
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
45
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
46
46
|
readonly _tag: "ShiitakeError";
|
|
47
47
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
48
48
|
readonly message: string;
|
|
@@ -109,7 +109,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
109
109
|
};
|
|
110
110
|
};
|
|
111
111
|
output: {
|
|
112
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
112
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
113
113
|
readonly _tag: "ShiitakeError";
|
|
114
114
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
115
115
|
readonly message: string;
|
|
@@ -150,7 +150,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
150
150
|
$get: {
|
|
151
151
|
input: {};
|
|
152
152
|
output: {
|
|
153
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
153
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
154
154
|
readonly _tag: "ShiitakeError";
|
|
155
155
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
156
156
|
readonly message: string;
|
|
@@ -238,7 +238,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
238
238
|
};
|
|
239
239
|
};
|
|
240
240
|
output: {
|
|
241
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
241
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
242
242
|
readonly _tag: "ShiitakeError";
|
|
243
243
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
244
244
|
readonly message: string;
|
|
@@ -276,7 +276,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
276
276
|
};
|
|
277
277
|
};
|
|
278
278
|
output: {
|
|
279
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
279
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
280
280
|
readonly _tag: "ShiitakeError";
|
|
281
281
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
282
282
|
readonly message: string;
|
|
@@ -352,7 +352,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
352
352
|
};
|
|
353
353
|
};
|
|
354
354
|
output: {
|
|
355
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
355
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
356
356
|
readonly _tag: "ShiitakeError";
|
|
357
357
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
358
358
|
readonly message: string;
|
|
@@ -481,7 +481,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
481
481
|
};
|
|
482
482
|
};
|
|
483
483
|
output: {
|
|
484
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
484
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
485
485
|
readonly _tag: "ShiitakeError";
|
|
486
486
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
487
487
|
readonly message: string;
|
|
@@ -557,7 +557,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
557
557
|
};
|
|
558
558
|
};
|
|
559
559
|
output: {
|
|
560
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
560
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
561
561
|
readonly _tag: "ShiitakeError";
|
|
562
562
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
563
563
|
readonly message: string;
|
|
@@ -821,6 +821,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
821
821
|
readonly kind: "session";
|
|
822
822
|
readonly sessionId: string;
|
|
823
823
|
readonly commandId: string;
|
|
824
|
+
readonly title: string;
|
|
824
825
|
} | {
|
|
825
826
|
readonly kind: "host";
|
|
826
827
|
readonly lane: string;
|
|
@@ -849,6 +850,8 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
849
850
|
readonly revision: number;
|
|
850
851
|
readonly text: string;
|
|
851
852
|
readonly thinking: string;
|
|
853
|
+
readonly textOffset: number;
|
|
854
|
+
readonly thinkingOffset: number;
|
|
852
855
|
readonly textTruncated: boolean;
|
|
853
856
|
readonly thinkingTruncated: boolean;
|
|
854
857
|
} | null;
|
|
@@ -926,7 +929,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
926
929
|
};
|
|
927
930
|
};
|
|
928
931
|
output: {
|
|
929
|
-
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
932
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
|
|
930
933
|
readonly _tag: "ShiitakeError";
|
|
931
934
|
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
932
935
|
readonly message: string;
|
package/dist/http.d.ts
CHANGED
package/dist/http.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { VerifiedAgentReply } from "./internal/woodstock/index.js";
|
|
|
5
5
|
import type { BirdDogEffect } from "@fungi.computer/bird-dog/effect";
|
|
6
6
|
import type { Accepted } from "./client.js";
|
|
7
7
|
import type { ShiitakeOptions, ShiitakeOwnerAgent } from "./internal/contracts.js";
|
|
8
|
-
export type { AbortCommand, AgentExtensionControls, BranchCommand, CompactCommand, CancelCommand, CancelResult, Accepted, DelegationReceipt, Held, ForkCommand, ForkedSession, OperatorCommand, TitleCommand, PromptCommand, PromptDelivery, SendOptions, ReadOptions, SessionCommand, SessionCommandHandler, SessionExtensionControls, SessionExtensionSlots, SessionFrame, SessionMessage, SessionProgress, SessionToolOutput, SessionContext, SessionSnapshot, SessionInboxState, SessionSummary, SessionSummaryPage, SessionSummaryRow, SessionActivity, AgentRunningWork, SessionTerminal, SessionCompactionMarker, SessionCompactionNotice, SessionCompactionTrigger, ShiitakeEventSourceConstructor, SessionWatch, ShiitakeAgent, ShiitakeSession, SubscribeOptions, TooLate, UnknownRequest, WatchOptions, } from "./client.js";
|
|
8
|
+
export type { AbortCommand, AgentExtensionControls, BranchCommand, CompactCommand, CancelCommand, CancelResult, Accepted, DelegationReceipt, Held, ForkCommand, ForkedSession, OperatorCommand, TitleCommand, PromptCommand, PromptDelivery, SendOptions, ReadOptions, SessionCommand, SessionCommandHandler, SessionExtensionControls, SessionExtensionSlots, SessionFrame, SessionMessage, SessionProgress, SessionProgressDelta, SessionToolOutput, SessionContext, SessionSnapshot, SessionInboxState, SessionSummary, SessionSummaryPage, SessionSummaryRow, SessionActivity, AgentRunningWork, SessionTerminal, SessionCompactionMarker, SessionCompactionNotice, SessionCompactionTrigger, ShiitakeEventSourceConstructor, SessionWatch, ShiitakeAgent, ShiitakeSession, SubscribeOptions, TooLate, UnknownRequest, WatchOptions, } from "./client.js";
|
|
9
9
|
export type { ColdSessionPage, ColdSessionPageRequest, ColdSessionRow, } from "./cold-sessions.js";
|
|
10
10
|
export { parseForkedSession, parseSessionFrame, parseSessionSnapshot, } from "./client.js";
|
|
11
11
|
export type { InstructionSnapshot, InstructionSource, ModelAcquisition, ModelDescription, ModelSource, RuntimeHost, RuntimePendingWork, SqliteCursor, SqliteOwner, SqliteValue, SqliteRow, StoragePressureCheck, CompactionBudget, CompactionOptions, GrantsDelivery, GrantsDeliveryResult, ShiitakeOptions, ShiitakeOwnerAgent, ToolAccess, HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, ToolSource, } from "./internal/contracts.js";
|
|
@@ -32,7 +32,7 @@ export type AdmitRequest = Readonly<{
|
|
|
32
32
|
* host passes it, after checking the reply against the mail it sent.
|
|
33
33
|
*/
|
|
34
34
|
reply?: VerifiedAgentReply;
|
|
35
|
-
/** Synchronous owner-local side effect
|
|
35
|
+
/** Synchronous owner-local side effect on first admission, in its transaction. */
|
|
36
36
|
onAccepted?: (sessionId: string, commandId: string) => void;
|
|
37
37
|
/**
|
|
38
38
|
* New work that continues earlier work in this Session (for example, the
|
|
@@ -5,7 +5,7 @@ import { checkHostMeta } from "./host-meta.js";
|
|
|
5
5
|
import { ShiitakeError } from "../errors.js";
|
|
6
6
|
import { decodeClientAcceptedCommand, decodeClientSessionId, decodeClientSessionCreationRequestId, requireDecoded, } from "./client-boundary.js";
|
|
7
7
|
import { publishOwnerObservation } from "./observation-kernel.js";
|
|
8
|
-
import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, readWorkSender, recordWorkSender,
|
|
8
|
+
import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, hasRecordedRunTerminal, readWorkSender, recordWorkSender, VerifiedAgentReplySchema, } from "./woodstock/index.js";
|
|
9
9
|
const decodeOrigin = Schema.decodeUnknownResult(AdmissionOriginSchema, {
|
|
10
10
|
onExcessProperty: "error",
|
|
11
11
|
});
|
|
@@ -87,7 +87,8 @@ export function openAdmissionDoor(input) {
|
|
|
87
87
|
const targetWorkId = command.type === "prompt" &&
|
|
88
88
|
command.delivery === "steer" &&
|
|
89
89
|
head.state === "running" &&
|
|
90
|
-
!head.cancellationRequested
|
|
90
|
+
!head.cancellationRequested &&
|
|
91
|
+
!hasRecordedRunTerminal(input.owner, sessionId, head.jobId)
|
|
91
92
|
? head.jobId
|
|
92
93
|
: undefined;
|
|
93
94
|
const accepted = acceptSessionCommandOwned(input.owner, sessionId, command, request.meta, origin, input.contract(), targetWorkId === undefined
|
|
@@ -100,8 +101,11 @@ export function openAdmissionDoor(input) {
|
|
|
100
101
|
if (sender !== undefined)
|
|
101
102
|
recordWorkSender(input.owner, sessionId, accepted.accepted.commandId, sender);
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
// The mail notice is staged in the same transaction as the first command.
|
|
105
|
+
// Replaying that admission must not stage a new notice with a different
|
|
106
|
+
// createdAt under the original Post key.
|
|
107
|
+
if (accepted.delivery === "new")
|
|
108
|
+
request.onAccepted?.(sessionId, accepted.accepted.commandId);
|
|
105
109
|
touched.set(sessionId, (touched.get(sessionId) ?? false) || accepted.delivery === "new");
|
|
106
110
|
return Object.freeze({
|
|
107
111
|
status: "accepted",
|
|
@@ -188,6 +188,11 @@ export type ShiitakeOptions = Readonly<{
|
|
|
188
188
|
store: SqliteOwner;
|
|
189
189
|
/** Host storage-pressure check; absent never refuses for storage pressure. */
|
|
190
190
|
storagePressure?: StoragePressureCheck;
|
|
191
|
+
/** Host-owned, synchronous admission for new Sessions and prompts only. */
|
|
192
|
+
admission?: Readonly<{
|
|
193
|
+
/** Check the host's current flag synchronously inside admission's transaction. */
|
|
194
|
+
assertOpen: () => void;
|
|
195
|
+
}>;
|
|
191
196
|
/** Compaction budgets; absent uses Pi's defaults for every model. */
|
|
192
197
|
compaction?: CompactionOptions;
|
|
193
198
|
/** Runtime host that schedules the opaque durable reconciler. */
|
|
@@ -7,11 +7,11 @@ import * as Option from "effect/Option";
|
|
|
7
7
|
import * as Stream from "effect/Stream";
|
|
8
8
|
import { ShiitakeError } from "../errors.js";
|
|
9
9
|
import { decodeClientReadOptions, decodeClientSettlementRequest, requireDecoded, decodeClientSessionCommand, decodeClientSessionId, decodeClientSubscribeOptions, } from "./client-boundary.js";
|
|
10
|
-
import { mergeObservationSignals, openObservationKernel, publishOwnerObservation, } from "./observation-kernel.js";
|
|
10
|
+
import { mergeObservationSignals, openObservationKernel, publishAgentExecutionChange, publishOwnerObservation, } from "./observation-kernel.js";
|
|
11
11
|
import { checkHostMeta } from "./host-meta.js";
|
|
12
|
-
import { readSessionActivity } from "./session-status.js";
|
|
13
|
-
import {
|
|
14
|
-
import { abortSession, cancelSession, branchSession, recordOperatorLine, forkSession, readSession, readCommandSettlement, readSessionModelPreference, readSessionReplayPage, readSessionInbox, readLatestPendingPrompt, createSession, deleteSession, deliverSessionGrants, readSessionGrants, setSessionModelPreference, setSessionTitle, readAdmissionIdentity, assertAdmissionIdentity, } from "./woodstock/index.js";
|
|
12
|
+
import { readSessionActivity, } from "./session-status.js";
|
|
13
|
+
import { prepareSessionContext } from "./session-context.js";
|
|
14
|
+
import { progressPosition, readProcessProgressDelta, abortSession, cancelSession, branchSession, recordOperatorLine, forkSession, readSession, readCommandSettlement, readSessionModelPreference, readSessionReplayPage, readSessionInbox, readLatestPendingPrompt, createSession, deleteSession, deliverSessionGrants, readSessionGrants, setSessionModelPreference, setSessionTitle, readAdmissionIdentity, assertAdmissionIdentity, } from "./woodstock/index.js";
|
|
15
15
|
/** Generic Agent-scope extension controls over the runtime's one call door. */
|
|
16
16
|
function agentExtensions(runtime) {
|
|
17
17
|
return Object.freeze({
|
|
@@ -20,12 +20,15 @@ function agentExtensions(runtime) {
|
|
|
20
20
|
call: (extensionId, command, input) => runtime.assertOpen.pipe(Effect.andThen(runtime.extensions.call({ scope: "agent" }, extensionId, command, input))),
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
|
-
function
|
|
23
|
+
function currentSessionWork(runtime, sessionId) {
|
|
24
24
|
return runtime
|
|
25
25
|
.readQueueHead(sessionId)
|
|
26
26
|
.pipe(Effect.flatMap((head) => (head.state === "queued"
|
|
27
27
|
? runtime.runningWork(head.jobId)
|
|
28
|
-
: Effect.succeed(null)).pipe(Effect.map((runningWork) =>
|
|
28
|
+
: Effect.succeed(null)).pipe(Effect.map((runningWork) => ({ head, runningWork })))));
|
|
29
|
+
}
|
|
30
|
+
function currentSessionActivity(options, runtime, sessionId) {
|
|
31
|
+
return currentSessionWork(runtime, sessionId).pipe(Effect.map(({ head, runningWork }) => readSessionActivity(options.store, sessionId, head, runningWork)));
|
|
29
32
|
}
|
|
30
33
|
/** Carry the Session's context use on a baseline, when it has one. */
|
|
31
34
|
function withContext(snapshot, context) {
|
|
@@ -35,7 +38,10 @@ function withContext(snapshot, context) {
|
|
|
35
38
|
}
|
|
36
39
|
/** Read the baseline and its context use for one Session. */
|
|
37
40
|
function readSessionWithContext(options, runtime, sessionId, input) {
|
|
38
|
-
return
|
|
41
|
+
return currentSessionWork(runtime, sessionId).pipe(Effect.flatMap(({ head, runningWork }) => readSession(options.store, sessionId, () => readSessionActivity(options.store, sessionId, head, runningWork), input, () => runtime.extensions.project(sessionId), (snapshot) => ({
|
|
42
|
+
snapshot,
|
|
43
|
+
context: prepareSessionContext(options, sessionId, snapshot.cursor, input?.before === undefined),
|
|
44
|
+
}))), Effect.flatMap(({ snapshot, context }) => context.pipe(Effect.map((value) => withContext(snapshot, value)))));
|
|
39
45
|
}
|
|
40
46
|
function sessionOwnerOperation(runtime, sessionId, operation) {
|
|
41
47
|
return runtime.assertOpen.pipe(Effect.andThen(Effect.try({
|
|
@@ -92,7 +98,46 @@ function continueFromReset(frame) {
|
|
|
92
98
|
}),
|
|
93
99
|
];
|
|
94
100
|
}
|
|
95
|
-
function subscriptionFrames(options, runtime, registration, sessionId, after, delivery, includeSandboxFailures = false) {
|
|
101
|
+
function subscriptionFrames(options, runtime, registration, sessionId, after, delivery, includeSandboxFailures = false, baselineProgress = null) {
|
|
102
|
+
let position = progressPosition(baselineProgress);
|
|
103
|
+
let toolOutput = [];
|
|
104
|
+
const reset = () => resetFrame(options, runtime, sessionId, includeSandboxFailures).pipe(Effect.map((frame) => {
|
|
105
|
+
position = progressPosition(frame.baseline.progress);
|
|
106
|
+
toolOutput = frame.baseline.toolOutput;
|
|
107
|
+
return continueFromReset(frame);
|
|
108
|
+
}));
|
|
109
|
+
const liveFrames = (activity) => {
|
|
110
|
+
const queue = activity.facts.queue;
|
|
111
|
+
const delta = queue.state !== "running"
|
|
112
|
+
? null
|
|
113
|
+
: readProcessProgressDelta(options.store, sessionId, queue.commandId, position);
|
|
114
|
+
if (delta === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
const frames = [];
|
|
117
|
+
if (delta !== null) {
|
|
118
|
+
frames.push({
|
|
119
|
+
kind: "progress",
|
|
120
|
+
progress: delta,
|
|
121
|
+
activity: activity.facts,
|
|
122
|
+
});
|
|
123
|
+
position = {
|
|
124
|
+
messageId: delta.messageId,
|
|
125
|
+
revision: delta.revision,
|
|
126
|
+
text: delta.text.offset + delta.text.append.length,
|
|
127
|
+
thinking: delta.thinking.offset + delta.thinking.append.length,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (toolOutput.length !== activity.toolOutput.length ||
|
|
131
|
+
toolOutput.some((slot, index) => slot !== activity.toolOutput[index])) {
|
|
132
|
+
toolOutput = activity.toolOutput;
|
|
133
|
+
frames.push({
|
|
134
|
+
kind: "tool_output",
|
|
135
|
+
toolOutput,
|
|
136
|
+
activity: activity.facts,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return frames;
|
|
140
|
+
};
|
|
96
141
|
const initial = {
|
|
97
142
|
after,
|
|
98
143
|
delivery,
|
|
@@ -106,64 +151,73 @@ function subscriptionFrames(options, runtime, registration, sessionId, after, de
|
|
|
106
151
|
if (signal === "closed")
|
|
107
152
|
return Effect.succeed([[], Option.none()]);
|
|
108
153
|
if (signal === "reset")
|
|
109
|
-
return
|
|
154
|
+
return reset();
|
|
110
155
|
return runtime.assertOpen.pipe(Effect.andThen(currentSessionActivity(options, runtime, sessionId)), Effect.flatMap((activity) => {
|
|
111
156
|
registration.setQueued(activity.facts.queue.state === "queued");
|
|
112
|
-
if (signal === "
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (signal === "progress" && state.delivery === "live") {
|
|
157
|
+
if ((signal === "progress" || signal === "activity") &&
|
|
158
|
+
state.delivery === "live") {
|
|
159
|
+
const frames = liveFrames(activity);
|
|
160
|
+
if (frames === undefined)
|
|
161
|
+
return reset();
|
|
118
162
|
return Effect.succeed([
|
|
119
163
|
[
|
|
120
|
-
...(
|
|
121
|
-
? [
|
|
122
|
-
: [
|
|
164
|
+
...(signal === "activity"
|
|
165
|
+
? [
|
|
123
166
|
{
|
|
124
|
-
kind: "
|
|
125
|
-
progress: activity.progress,
|
|
167
|
+
kind: "activity",
|
|
126
168
|
activity: activity.facts,
|
|
127
169
|
},
|
|
128
|
-
]
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
toolOutput: activity.toolOutput,
|
|
132
|
-
activity: activity.facts,
|
|
133
|
-
},
|
|
170
|
+
]
|
|
171
|
+
: []),
|
|
172
|
+
...frames,
|
|
134
173
|
],
|
|
135
174
|
Option.some({ ...state, awaitSignal: true }),
|
|
136
175
|
]);
|
|
137
176
|
}
|
|
138
|
-
return readSessionReplayPage(options.store, sessionId, state.after, state.delivery, activity, () => runtime.extensions.project(sessionId), includeSandboxFailures
|
|
177
|
+
return readSessionReplayPage(options.store, sessionId, state.after, state.delivery, activity, () => runtime.extensions.project(sessionId), includeSandboxFailures, (page) => {
|
|
178
|
+
const latestCursor = page.kind === "page"
|
|
179
|
+
? page.frames
|
|
180
|
+
.flatMap((frame) => frame.kind === "progress" ||
|
|
181
|
+
frame.kind === "activity" ||
|
|
182
|
+
frame.kind === "tool_output"
|
|
183
|
+
? []
|
|
184
|
+
: [frame.cursor])
|
|
185
|
+
.at(-1)
|
|
186
|
+
: undefined;
|
|
187
|
+
// Prepare a figure only for the final moving update,
|
|
188
|
+
// while its cursor and stored context share the read.
|
|
189
|
+
const context = page.kind === "page" &&
|
|
190
|
+
!page.hasMore &&
|
|
191
|
+
page.frames.some((frame) => frame.kind === "update" &&
|
|
192
|
+
frame.cursor !== state.after)
|
|
193
|
+
? prepareSessionContext(options, sessionId, latestCursor ?? null)
|
|
194
|
+
: Effect.succeed(undefined);
|
|
195
|
+
return { page, latestCursor, context };
|
|
196
|
+
}).pipe(Effect.flatMap(({ page, latestCursor, context, }) => {
|
|
139
197
|
if (page.kind === "reset")
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
frame.kind === "tool_output"
|
|
198
|
+
return reset();
|
|
199
|
+
if (!page.hasMore)
|
|
200
|
+
toolOutput = activity.toolOutput;
|
|
201
|
+
const progressFrames = page.hasMore
|
|
145
202
|
? []
|
|
146
|
-
:
|
|
147
|
-
|
|
203
|
+
: liveFrames(activity);
|
|
204
|
+
if (progressFrames === undefined)
|
|
205
|
+
return reset();
|
|
148
206
|
const next = {
|
|
149
207
|
after: latestCursor ?? state.after,
|
|
150
208
|
delivery: page.hasMore ? state.delivery : "live",
|
|
151
209
|
awaitSignal: !page.hasMore,
|
|
152
210
|
};
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
: page.frames.map((frame) => frame.kind === "update" &&
|
|
164
|
-
frame.cursor === latestCursor
|
|
165
|
-
? Object.freeze({ ...frame, context })
|
|
166
|
-
: frame),
|
|
211
|
+
return context.pipe(Effect.map((context) => [
|
|
212
|
+
[
|
|
213
|
+
...(context === undefined
|
|
214
|
+
? page.frames
|
|
215
|
+
: page.frames.map((frame) => frame.kind === "update" &&
|
|
216
|
+
frame.cursor === latestCursor
|
|
217
|
+
? Object.freeze({ ...frame, context })
|
|
218
|
+
: frame)),
|
|
219
|
+
...progressFrames,
|
|
220
|
+
],
|
|
167
221
|
Option.some(next),
|
|
168
222
|
]));
|
|
169
223
|
}));
|
|
@@ -191,7 +245,7 @@ function watchSession(options, runtime, observation, sessionId, input) {
|
|
|
191
245
|
registration.setQueued(snapshot.activity.queue.state === "queued");
|
|
192
246
|
return Object.freeze({
|
|
193
247
|
snapshot,
|
|
194
|
-
changes: subscriptionFrames(options, runtime, registration, decoded.sessionId, snapshot.cursor ?? undefined, "live", decoded.options.includeSandboxFailures === true).pipe(Stream.ensuring(Effect.sync(registration.close))),
|
|
248
|
+
changes: subscriptionFrames(options, runtime, registration, decoded.sessionId, snapshot.cursor ?? undefined, "live", decoded.options.includeSandboxFailures === true, snapshot.progress).pipe(Stream.ensuring(Effect.sync(registration.close))),
|
|
195
249
|
close: Effect.sync(registration.close),
|
|
196
250
|
});
|
|
197
251
|
}))).pipe(Effect.onError(() => Effect.sync(registration.close)));
|
|
@@ -289,7 +343,9 @@ function executeSessionCommand(options, runtime, sessionId, meta, input) {
|
|
|
289
343
|
.commit(sessionOwnerOperation(runtime, decodedSessionId, (decoded) => {
|
|
290
344
|
setSessionTitle(options.store, decoded, command.title);
|
|
291
345
|
}))
|
|
292
|
-
.pipe(Effect.
|
|
346
|
+
.pipe(Effect.tap(() => Effect.sync(() => {
|
|
347
|
+
publishAgentExecutionChange(options.store);
|
|
348
|
+
})), Effect.as(undefined));
|
|
293
349
|
}
|
|
294
350
|
}));
|
|
295
351
|
}
|
|
@@ -455,6 +511,7 @@ export function acceptAddressedMail(runtime, mail, meta, reply, stageMail, sessi
|
|
|
455
511
|
const result = admit(meta === undefined ? request : { ...request, meta });
|
|
456
512
|
if (result.status !== "accepted")
|
|
457
513
|
throw new Error("Sessionless addressed mail was not admitted");
|
|
514
|
+
runtime.recordCreatedSessionDelegatorWithinTransaction(sessionId, addressed);
|
|
458
515
|
return {
|
|
459
516
|
status: "accepted",
|
|
460
517
|
commandId: result.commandId,
|