@fungi.computer/shiitake 0.3.1 → 0.5.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 +19 -18
- package/dist/agent-control.d.ts +24 -19
- package/dist/agent-control.js +69 -128
- package/dist/client-wire.d.ts +12 -3
- package/dist/client-wire.js +33 -8
- package/dist/client.d.ts +34 -3
- package/dist/client.js +1 -1
- package/dist/cloudflare.d.ts +3 -1
- package/dist/cloudflare.js +2 -2
- package/dist/delegation.d.ts +151 -17
- package/dist/delegation.js +174 -41
- package/dist/effect.d.ts +1 -1
- package/dist/fleet.d.ts +37 -10
- package/dist/fleet.js +217 -48
- package/dist/http-routes.d.ts +84 -12
- package/dist/http-routes.js +15 -1
- package/dist/index.d.ts +2 -2
- package/dist/internal/admission-door.d.ts +2 -0
- package/dist/internal/admission-door.js +9 -2
- package/dist/internal/client-boundary.d.ts +10 -0
- package/dist/internal/client-boundary.js +10 -0
- package/dist/internal/compaction/compaction.js +1 -1
- package/dist/internal/contracts.d.ts +2 -0
- package/dist/internal/coordinator.d.ts +3 -1
- package/dist/internal/coordinator.js +98 -13
- package/dist/internal/effect-client.d.ts +3 -1
- package/dist/internal/effect-client.js +3 -2
- package/dist/internal/effect-contracts.d.ts +6 -2
- package/dist/internal/model-acquisition.js +2 -0
- package/dist/internal/observation-kernel.d.ts +8 -4
- package/dist/internal/observation-kernel.js +44 -9
- package/dist/internal/promise-facade.d.ts +1 -1
- package/dist/internal/promise-facade.js +1 -1
- package/dist/internal/run-scope.js +37 -8
- package/dist/internal/runtime-coordinator.d.ts +5 -0
- package/dist/internal/runtime-coordinator.js +8 -4
- package/dist/internal/runtime-core.d.ts +2 -1
- package/dist/internal/runtime-core.js +6 -1
- package/dist/internal/session-context.d.ts +1 -1
- package/dist/internal/session-context.js +84 -13
- package/dist/internal/woodstock/agent-reply.d.ts +7 -2
- package/dist/internal/woodstock/agent-reply.js +22 -13
- package/dist/internal/woodstock/fleet-store.d.ts +4 -2
- package/dist/internal/woodstock/fleet-store.js +70 -6
- package/dist/internal/woodstock/index.d.ts +2 -0
- package/dist/internal/woodstock/index.js +11 -5
- package/dist/internal/woodstock/run-recovery.js +4 -1
- package/dist/internal/woodstock/session-entry.d.ts +13 -3
- package/dist/internal/woodstock/session-entry.js +1 -0
- package/dist/internal/woodstock/session-store.d.ts +4 -0
- package/dist/internal/woodstock/session-store.js +26 -19
- package/dist/remote-client.js +5 -1
- package/dist/testing.d.ts +5 -2
- package/dist/testing.js +4 -2
- package/docs/api/agent-control.md +18 -12
- package/docs/api/client.md +78 -0
- package/docs/api/delegation.md +72 -55
- package/docs/api/effect.md +5 -0
- package/docs/api/fleet.md +23 -23
- package/docs/api/http.md +2 -1
- package/docs/api/index.md +13 -0
- package/docs/api/testing.md +3 -3
- package/package.json +9 -8
package/dist/fleet.d.ts
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
|
+
export { commandSettlementSchema } from "./client-wire.js";
|
|
1
2
|
import { type VerifiedAgentReply } from "./internal/woodstock/index.js";
|
|
2
3
|
import type { CommandSettlement } from "./internal/contracts.js";
|
|
3
4
|
import { type Pending } from "@fungi.computer/agar";
|
|
4
5
|
import type { BirdDogMail } from "@fungi.computer/bird-dog";
|
|
5
|
-
import
|
|
6
|
-
import { type Accepted, type ShiitakeAgent } from "./client.js";
|
|
6
|
+
import { type Accepted, type DelegationReceipt, type ShiitakeAgent } from "./client.js";
|
|
7
7
|
import type { Json } from "./extensions.js";
|
|
8
8
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
9
9
|
import { type CairnId } from "@fungi.computer/cairn/id";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { type AuthorMeta } from "@fungi.computer/agar/agents";
|
|
12
|
+
/** Inputs shared by Local and hosted delegation request identity. */
|
|
13
|
+
export type AgentDelegationRequestKeyInput = Readonly<{
|
|
14
|
+
owner: Readonly<{
|
|
15
|
+
kind: "local";
|
|
16
|
+
agentId: string;
|
|
17
|
+
}> | Readonly<{
|
|
18
|
+
kind: "hosted";
|
|
19
|
+
scopeId: string;
|
|
20
|
+
agentId: string;
|
|
21
|
+
}>;
|
|
22
|
+
sourceSessionId: string;
|
|
23
|
+
targetAgentId: string;
|
|
24
|
+
requestId: string;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* One idempotency identity for a caller-owned delegation request.
|
|
28
|
+
* The target Session is deliberately excluded: a sessionless send learns it
|
|
29
|
+
* only after acceptance, when recall must still resolve the original receipt.
|
|
30
|
+
*/
|
|
31
|
+
export declare function agentDelegationRequestKey(input: AgentDelegationRequestKeyInput): Promise<string>;
|
|
12
32
|
/** Operations explicitly admitted by the host. */
|
|
13
33
|
export declare const agentActionSchema: z.ZodEnum<{
|
|
14
34
|
send: "send";
|
|
@@ -69,7 +89,7 @@ export declare class AgentGrantError extends Error {
|
|
|
69
89
|
export declare function assertAgentGrant(grant: AgentGrant, action: AgentAction, agentId?: string): void;
|
|
70
90
|
/** Stable Agent identities, retained across host compositions. */
|
|
71
91
|
export declare const agentIds: Readonly<{
|
|
72
|
-
readonly agent: import("@fungi.computer/cairn
|
|
92
|
+
readonly agent: import("@fungi.computer/cairn").IdCodec<"agent", "agent", 1>;
|
|
73
93
|
}>;
|
|
74
94
|
/** An ordinary Agent directory identity. */
|
|
75
95
|
export type AgentId = CairnId<"agent", "agent">;
|
|
@@ -169,8 +189,6 @@ export declare const agentDelegationTaskSchema: z.ZodLazy<z.ZodObject<{
|
|
|
169
189
|
}, z.core.$strict>>;
|
|
170
190
|
/** A caller reuses this task unchanged while polling Pending. */
|
|
171
191
|
export type AgentDelegationTask = z.infer<typeof agentDelegationTaskSchema>;
|
|
172
|
-
/** Exact host facts encoded over internal transports, without transcript content. */
|
|
173
|
-
export declare const commandSettlementSchema: Schema.Codec<CommandSettlement>;
|
|
174
192
|
/** Exact settled Session facts, including Pi usage and tool-call records. */
|
|
175
193
|
export type AgentDelegationOutcome = Readonly<{
|
|
176
194
|
kind: "settled";
|
|
@@ -181,7 +199,7 @@ export type AgentDelegationOutcome = Readonly<{
|
|
|
181
199
|
export type FleetDelegationOptions = Readonly<{
|
|
182
200
|
storage: WatchdogSqliteOwner;
|
|
183
201
|
/** Admit the frozen addressed mail through the destination owner. */
|
|
184
|
-
deliver: (mail: BirdDogMail) => Promise<
|
|
202
|
+
deliver: (mail: BirdDogMail) => Promise<DelegationReceipt>;
|
|
185
203
|
/** Read exact work facts at the target owner, independent of transcript selection. */
|
|
186
204
|
readSettlement: (agentId: string, sessionId: string, commandId: string) => Promise<CommandSettlement | undefined>;
|
|
187
205
|
/** Acquire a Session client only when creating the delegated conversation. */
|
|
@@ -237,8 +255,9 @@ export declare function delegationSendMeta(sender: Readonly<{
|
|
|
237
255
|
name: string;
|
|
238
256
|
}>, invocationMeta?: Json): DelegationSendMeta;
|
|
239
257
|
/**
|
|
240
|
-
* The host metadata for one authenticated reply line
|
|
241
|
-
*
|
|
258
|
+
* The host metadata for one authenticated reply line. An ordinary reply is
|
|
259
|
+
* authored by its replying Agent; a delivery failure is a Hub/system fact,
|
|
260
|
+
* while the verified reply retains the failed recipient's identity.
|
|
242
261
|
*/
|
|
243
262
|
export declare function delegationReplyMeta(delegation: AuthenticatedDelegationReply, replierName: string): DelegationReplyMeta;
|
|
244
263
|
/**
|
|
@@ -258,22 +277,30 @@ export type OutstandingDelegation = Readonly<{
|
|
|
258
277
|
*/
|
|
259
278
|
export declare function createFleetCorrespondence(options: Omit<FleetDelegationOptions, "readSettlement">): {
|
|
260
279
|
/** Submit frozen mail or reuse its recorded Session admission receipt. */
|
|
261
|
-
start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<
|
|
280
|
+
start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<DelegationReceipt>;
|
|
262
281
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
263
282
|
authenticateReply: (mail: BirdDogMail) => AuthenticatedDelegationReply | undefined;
|
|
283
|
+
recordLocalUndeliverable: (original: BirdDogMail, reply: BirdDogMail) => boolean;
|
|
284
|
+
recordUndeliverableReply: (mailKey: string) => boolean;
|
|
264
285
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
265
286
|
receipt: (requestId: string) => Accepted | undefined;
|
|
287
|
+
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
|
288
|
+
recordAcceptedNotice: (mail: BirdDogMail) => Accepted | undefined;
|
|
266
289
|
};
|
|
267
290
|
/** Local hosts may observe exact work directly; remote correspondence never pulls it. */
|
|
268
291
|
export declare function createFleetDelegation(options: FleetDelegationOptions): {
|
|
269
292
|
/** Submit or poll the exact turn, yielding Agar Pending until settled. */
|
|
270
293
|
delegate: (grant: AgentGrant, task: AgentDelegationTask) => Promise<Pending | AgentDelegationOutcome>;
|
|
271
294
|
/** Submit frozen mail or reuse its recorded Session admission receipt. */
|
|
272
|
-
start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<
|
|
295
|
+
start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<DelegationReceipt>;
|
|
273
296
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
274
297
|
authenticateReply: (mail: BirdDogMail) => AuthenticatedDelegationReply | undefined;
|
|
298
|
+
recordLocalUndeliverable: (original: BirdDogMail, reply: BirdDogMail) => boolean;
|
|
299
|
+
recordUndeliverableReply: (mailKey: string) => boolean;
|
|
275
300
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
276
301
|
receipt: (requestId: string) => Accepted | undefined;
|
|
302
|
+
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
|
303
|
+
recordAcceptedNotice: (mail: BirdDogMail) => Accepted | undefined;
|
|
277
304
|
/** The delegated work this owner still waits on, if any. */
|
|
278
305
|
outstanding: () => Promise<OutstandingDelegation | undefined>;
|
|
279
306
|
};
|
package/dist/fleet.js
CHANGED
|
@@ -1,13 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
export { commandSettlementSchema } from "./client-wire.js";
|
|
2
|
+
import { createFleetDirectoryStore, createFleetDelegationStore, } 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";
|
|
6
|
-
import { parseAccepted } from "./client.js";
|
|
6
|
+
import { parseAccepted, parseDelegationReceipt, } from "./client.js";
|
|
7
7
|
import { checkHostMeta } from "./internal/host-meta.js";
|
|
8
8
|
import { defineIdFamily } from "@fungi.computer/cairn/id";
|
|
9
|
+
import { deterministicReplayKey } from "@fungi.computer/cairn";
|
|
9
10
|
import { z } from "zod";
|
|
10
11
|
import { authorMeta, FungiMessageMetaSchema, } from "@fungi.computer/agar/agents";
|
|
12
|
+
/**
|
|
13
|
+
* One idempotency identity for a caller-owned delegation request.
|
|
14
|
+
* The target Session is deliberately excluded: a sessionless send learns it
|
|
15
|
+
* only after acceptance, when recall must still resolve the original receipt.
|
|
16
|
+
*/
|
|
17
|
+
export async function agentDelegationRequestKey(input) {
|
|
18
|
+
const ownerIdentity = input.owner.kind === "hosted"
|
|
19
|
+
? [input.owner.scopeId, input.owner.agentId]
|
|
20
|
+
: [input.owner.agentId];
|
|
21
|
+
return deterministicReplayKey("agent-delegation-request", [
|
|
22
|
+
input.owner.kind,
|
|
23
|
+
...ownerIdentity,
|
|
24
|
+
input.sourceSessionId,
|
|
25
|
+
input.targetAgentId,
|
|
26
|
+
input.requestId,
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
11
29
|
const identity = z.string().min(1).max(256);
|
|
12
30
|
/** Operations explicitly admitted by the host. */
|
|
13
31
|
export const agentActionSchema = z.enum([
|
|
@@ -261,14 +279,6 @@ export const agentDelegationTaskSchema = z.lazy(() => z.strictObject({
|
|
|
261
279
|
.min(1)
|
|
262
280
|
.max(256 * 1024),
|
|
263
281
|
}));
|
|
264
|
-
/** Exact host facts encoded over internal transports, without transcript content. */
|
|
265
|
-
export const commandSettlementSchema = Schema.Struct({
|
|
266
|
-
acceptedSender: Schema.NullOr(Schema.String),
|
|
267
|
-
terminal: SessionTerminalSchema,
|
|
268
|
-
content: Schema.String,
|
|
269
|
-
usage: Schema.Array(Schema.Struct({ provider: Schema.String, model: Schema.String, usage })),
|
|
270
|
-
toolCalls: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
271
|
-
});
|
|
272
282
|
/**
|
|
273
283
|
* Provenance for one delegated send from the invoking Agent's Session. The
|
|
274
284
|
* model program's digest is frozen with the replay input, so a retried tool
|
|
@@ -308,16 +318,21 @@ export function delegationSendMeta(sender, invocationMeta) {
|
|
|
308
318
|
};
|
|
309
319
|
}
|
|
310
320
|
/**
|
|
311
|
-
* The host metadata for one authenticated reply line
|
|
312
|
-
*
|
|
321
|
+
* The host metadata for one authenticated reply line. An ordinary reply is
|
|
322
|
+
* authored by its replying Agent; a delivery failure is a Hub/system fact,
|
|
323
|
+
* while the verified reply retains the failed recipient's identity.
|
|
313
324
|
*/
|
|
314
325
|
export function delegationReplyMeta(delegation, replierName) {
|
|
315
|
-
|
|
316
|
-
|
|
326
|
+
const author = delegation.reply.outcome === "undeliverable" &&
|
|
327
|
+
delegation.reply.reason === "recipient_offline"
|
|
328
|
+
? authorMeta({ id: "hub/system", name: "Hub/system" })
|
|
329
|
+
: authorMeta({
|
|
317
330
|
id: delegation.reply.agentId,
|
|
318
331
|
name: replierName,
|
|
319
332
|
kind: "agent",
|
|
320
|
-
})
|
|
333
|
+
});
|
|
334
|
+
return {
|
|
335
|
+
...author,
|
|
321
336
|
delegation: {
|
|
322
337
|
ancestors: [...delegation.ancestors],
|
|
323
338
|
maxDepth: delegation.maxDepth,
|
|
@@ -331,9 +346,23 @@ const delegationMetadata = z.object({
|
|
|
331
346
|
const delegationRowSchema = z.lazy(() => z.strictObject({
|
|
332
347
|
input: z.string(),
|
|
333
348
|
mail: z.string(),
|
|
349
|
+
state: z.enum(["pending", "held", "accepted", "undeliverable"]),
|
|
334
350
|
receipt: z.string().nullable(),
|
|
335
351
|
}));
|
|
352
|
+
const heldRecipientOffline = {
|
|
353
|
+
status: "held",
|
|
354
|
+
reason: "recipient_offline",
|
|
355
|
+
message: "waiting: recipient offline",
|
|
356
|
+
};
|
|
336
357
|
const decodeMail = Schema.decodeUnknownSync(Schema.fromJsonString(BirdDogMailSchema));
|
|
358
|
+
function bindRecipientSession(mail, sessionId) {
|
|
359
|
+
if (mail.from.kind !== "agent")
|
|
360
|
+
throw new DirectoryError(503, "partial_init");
|
|
361
|
+
return {
|
|
362
|
+
...mail,
|
|
363
|
+
to: { ...mail.to, sessionId },
|
|
364
|
+
};
|
|
365
|
+
}
|
|
337
366
|
/**
|
|
338
367
|
* BirdDog submission and reply authentication. The durable row freezes
|
|
339
368
|
* correspondence before transport; an ambiguous delivery reuses that exact mail.
|
|
@@ -384,21 +413,20 @@ export function createFleetCorrespondence(options) {
|
|
|
384
413
|
});
|
|
385
414
|
let row = read(task.requestId);
|
|
386
415
|
if (row === undefined) {
|
|
387
|
-
let sessionId = task.sessionId;
|
|
388
|
-
if (sessionId === undefined) {
|
|
389
|
-
const opened = await options.open(task.agentId);
|
|
390
|
-
try {
|
|
391
|
-
sessionId = await opened.agent.createSession(task.requestId);
|
|
392
|
-
}
|
|
393
|
-
finally {
|
|
394
|
-
await opened.release();
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
416
|
const from = { kind: "agent", ...parent };
|
|
398
417
|
let mail = {
|
|
399
418
|
key: task.requestId,
|
|
400
419
|
from,
|
|
401
|
-
to:
|
|
420
|
+
to: task.sessionId === undefined
|
|
421
|
+
? {
|
|
422
|
+
kind: "agent",
|
|
423
|
+
agentId: task.agentId,
|
|
424
|
+
}
|
|
425
|
+
: {
|
|
426
|
+
kind: "agent",
|
|
427
|
+
agentId: task.agentId,
|
|
428
|
+
sessionId: task.sessionId,
|
|
429
|
+
},
|
|
402
430
|
replyTo: [from],
|
|
403
431
|
content: task.text,
|
|
404
432
|
createdAt: Date.now(),
|
|
@@ -411,11 +439,26 @@ export function createFleetCorrespondence(options) {
|
|
|
411
439
|
throw new DirectoryError(503, "partial_init");
|
|
412
440
|
if (row.input !== input)
|
|
413
441
|
throw new DirectoryError(409, "request_conflict");
|
|
414
|
-
if (row.
|
|
442
|
+
if (row.state === "held" || row.state === "undeliverable")
|
|
443
|
+
return heldRecipientOffline;
|
|
444
|
+
if (row.state === "accepted") {
|
|
445
|
+
if (row.receipt === null)
|
|
446
|
+
throw new DirectoryError(503, "partial_init");
|
|
415
447
|
return parseAccepted(row.receipt);
|
|
448
|
+
}
|
|
416
449
|
assertAgentGrant(grant, "delegate", task.agentId);
|
|
417
|
-
const receipt =
|
|
418
|
-
|
|
450
|
+
const receipt = parseDelegationReceipt(JSON.stringify(await options.deliver(decodeMail(row.mail))));
|
|
451
|
+
if (receipt.status === "held")
|
|
452
|
+
store.recordHeld(task.requestId);
|
|
453
|
+
else {
|
|
454
|
+
const sent = decodeMail(row.mail);
|
|
455
|
+
const sessionId = receipt.sessionId ?? sent.to.sessionId;
|
|
456
|
+
if (sessionId === undefined)
|
|
457
|
+
throw new DirectoryError(503, "partial_init");
|
|
458
|
+
const accepted = { ...receipt, sessionId };
|
|
459
|
+
store.recordReceipt(task.requestId, JSON.stringify(accepted), row.mail, JSON.stringify(bindRecipientSession(sent, sessionId)));
|
|
460
|
+
return accepted;
|
|
461
|
+
}
|
|
419
462
|
return receipt;
|
|
420
463
|
}
|
|
421
464
|
/**
|
|
@@ -431,48 +474,165 @@ export function createFleetCorrespondence(options) {
|
|
|
431
474
|
const suffix = ":reply:0";
|
|
432
475
|
if (mail.replyTo.length !== 0 ||
|
|
433
476
|
mail.reply === undefined ||
|
|
477
|
+
mail.reply.outcome === "accepted" ||
|
|
434
478
|
!mail.key.endsWith(suffix))
|
|
435
479
|
return undefined;
|
|
436
|
-
const
|
|
437
|
-
|
|
480
|
+
const mailKey = mail.key.slice(0, -suffix.length);
|
|
481
|
+
const row = read(mailKey);
|
|
482
|
+
if (row === undefined || row.state === "undeliverable")
|
|
438
483
|
return undefined;
|
|
439
484
|
const sent = decodeMail(row.mail);
|
|
440
|
-
if (
|
|
441
|
-
mail.from.sessionId !== sent.to.sessionId ||
|
|
442
|
-
mail.to.agentId !== sent.from.agentId ||
|
|
443
|
-
mail.to.sessionId !== sent.from.sessionId)
|
|
485
|
+
if (sent.key !== mailKey)
|
|
444
486
|
return undefined;
|
|
445
|
-
|
|
446
|
-
|
|
487
|
+
const systemUndeliverable = mail.reply.outcome === "undeliverable" &&
|
|
488
|
+
mail.reply.reason === "recipient_offline" &&
|
|
489
|
+
mail.from.kind === "system";
|
|
490
|
+
if (systemUndeliverable) {
|
|
491
|
+
if (row.state !== "held" ||
|
|
492
|
+
sent.from.kind !== "agent" ||
|
|
493
|
+
mail.key !== `${sent.key}:reply:0` ||
|
|
494
|
+
mail.to.agentId !== sent.from.agentId ||
|
|
495
|
+
mail.to.sessionId !== sent.from.sessionId)
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
if (sent.from.kind !== "agent" ||
|
|
500
|
+
mail.from.kind !== "agent" ||
|
|
501
|
+
mail.from.agentId !== sent.to.agentId ||
|
|
502
|
+
mail.from.sessionId !== sent.to.sessionId ||
|
|
503
|
+
mail.to.agentId !== sent.from.agentId ||
|
|
504
|
+
mail.to.sessionId !== sent.from.sessionId)
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
if (mail.reply.outcome === "undeliverable" &&
|
|
508
|
+
mail.reply.reason === "recipient_offline" &&
|
|
509
|
+
!systemUndeliverable)
|
|
447
510
|
return undefined;
|
|
511
|
+
if (mail.reply.outcome !== "undeliverable" &&
|
|
512
|
+
row.state === "accepted" &&
|
|
513
|
+
row.receipt !== null) {
|
|
514
|
+
const sentResult = parseAccepted(row.receipt);
|
|
515
|
+
if (sentResult.commandId !== mail.reply.commandId)
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
448
518
|
const path = delegationMetadata.parse(sent.meta).delegation;
|
|
449
519
|
if (path === undefined)
|
|
450
520
|
return undefined;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
521
|
+
let reply;
|
|
522
|
+
if (mail.reply.outcome === "undeliverable") {
|
|
523
|
+
const agentId = systemUndeliverable
|
|
524
|
+
? sent.to.agentId
|
|
525
|
+
: mail.from.kind === "agent"
|
|
526
|
+
? mail.from.agentId
|
|
527
|
+
: undefined;
|
|
528
|
+
if (agentId === undefined)
|
|
529
|
+
return undefined;
|
|
530
|
+
const undeliverable = { agentId, ...mail.reply };
|
|
531
|
+
const sessionId = systemUndeliverable
|
|
532
|
+
? sent.to.sessionId
|
|
533
|
+
: mail.from.kind === "agent"
|
|
534
|
+
? mail.from.sessionId
|
|
535
|
+
: undefined;
|
|
536
|
+
reply =
|
|
537
|
+
sessionId === undefined
|
|
538
|
+
? undeliverable
|
|
539
|
+
: { ...undeliverable, sessionId };
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
if (mail.from.kind !== "agent")
|
|
543
|
+
return undefined;
|
|
544
|
+
const sessionId = mail.from.sessionId;
|
|
545
|
+
reply = { agentId: mail.from.agentId, sessionId, ...mail.reply };
|
|
546
|
+
}
|
|
456
547
|
return {
|
|
457
548
|
ancestors: path.ancestors.slice(0, -1),
|
|
458
549
|
maxDepth: path.maxDepth,
|
|
459
550
|
reply,
|
|
460
551
|
};
|
|
461
552
|
}
|
|
553
|
+
/** Bind an exhausted local Carrier row to its frozen sender correspondence. */
|
|
554
|
+
function recordLocalUndeliverable(original, reply) {
|
|
555
|
+
const row = read(original.key);
|
|
556
|
+
if (row === undefined ||
|
|
557
|
+
(row.state !== "pending" && row.state !== "held") ||
|
|
558
|
+
original.from.kind !== "agent" ||
|
|
559
|
+
original.reply !== undefined ||
|
|
560
|
+
JSON.stringify(decodeMail(row.mail)) !== JSON.stringify(original) ||
|
|
561
|
+
reply.key !== `${original.key}:reply:0` ||
|
|
562
|
+
reply.from.kind !== "system" ||
|
|
563
|
+
reply.reply?.outcome !== "undeliverable" ||
|
|
564
|
+
reply.reply.reason !== "recipient_offline" ||
|
|
565
|
+
reply.to.agentId !== original.from.agentId ||
|
|
566
|
+
reply.to.sessionId !== original.from.sessionId ||
|
|
567
|
+
reply.replyTo.length !== 0)
|
|
568
|
+
return false;
|
|
569
|
+
store.recordHeld(original.key);
|
|
570
|
+
return read(original.key)?.state === "held";
|
|
571
|
+
}
|
|
572
|
+
/** Make a successfully admitted system disposition replay-ineligible. */
|
|
573
|
+
function recordUndeliverableReply(mailKey) {
|
|
574
|
+
return store.recordUndeliverable(mailKey);
|
|
575
|
+
}
|
|
462
576
|
/** Read only the existing acceptance receipt; do not reserve or deliver mail. */
|
|
463
577
|
function receipt(requestId) {
|
|
464
|
-
const
|
|
465
|
-
return
|
|
578
|
+
const row = read(requestId);
|
|
579
|
+
return row?.state !== "accepted" || row.receipt === null
|
|
466
580
|
? undefined
|
|
467
|
-
: parseAccepted(
|
|
581
|
+
: parseAccepted(row.receipt);
|
|
582
|
+
}
|
|
583
|
+
/** Atomically bind the recipient Session carried by a late acceptance notice. */
|
|
584
|
+
function recordAcceptedNotice(mail) {
|
|
585
|
+
const suffix = ":accepted";
|
|
586
|
+
if (mail.from.kind !== "agent")
|
|
587
|
+
return undefined;
|
|
588
|
+
const sender = mail.from;
|
|
589
|
+
if (mail.replyTo.length !== 0 ||
|
|
590
|
+
mail.reply?.outcome !== "accepted" ||
|
|
591
|
+
!mail.key.endsWith(suffix))
|
|
592
|
+
return undefined;
|
|
593
|
+
const requestId = mail.key.slice(0, -suffix.length);
|
|
594
|
+
const row = read(requestId);
|
|
595
|
+
if (row === undefined)
|
|
596
|
+
return undefined;
|
|
597
|
+
const sent = decodeMail(row.mail);
|
|
598
|
+
if (sent.from.kind !== "agent")
|
|
599
|
+
return undefined;
|
|
600
|
+
const sentSender = sent.from;
|
|
601
|
+
const path = delegationMetadata.safeParse(sent.meta);
|
|
602
|
+
if (!path.success ||
|
|
603
|
+
path.data.delegation === undefined ||
|
|
604
|
+
mail.key !== `${sent.key}:accepted` ||
|
|
605
|
+
sender.agentId !== sent.to.agentId ||
|
|
606
|
+
(sent.to.sessionId !== undefined &&
|
|
607
|
+
sent.to.sessionId !== sender.sessionId) ||
|
|
608
|
+
mail.to.agentId !== sentSender.agentId ||
|
|
609
|
+
mail.to.sessionId !== sentSender.sessionId)
|
|
610
|
+
return undefined;
|
|
611
|
+
if (row.state === "accepted" && row.receipt !== null) {
|
|
612
|
+
const current = parseAccepted(row.receipt);
|
|
613
|
+
if (current.commandId !== mail.reply.commandId ||
|
|
614
|
+
current.sessionId !== sender.sessionId)
|
|
615
|
+
return undefined;
|
|
616
|
+
}
|
|
617
|
+
const accepted = {
|
|
618
|
+
status: "accepted",
|
|
619
|
+
commandId: mail.reply.commandId,
|
|
620
|
+
sessionId: sender.sessionId,
|
|
621
|
+
};
|
|
622
|
+
store.recordReceipt(requestId, JSON.stringify(accepted), row.mail, JSON.stringify(bindRecipientSession(sent, sender.sessionId)));
|
|
623
|
+
return accepted;
|
|
468
624
|
}
|
|
469
625
|
return {
|
|
470
626
|
/** Submit frozen mail or reuse its recorded Session admission receipt. */
|
|
471
627
|
start,
|
|
472
628
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
473
629
|
authenticateReply,
|
|
630
|
+
recordLocalUndeliverable,
|
|
631
|
+
recordUndeliverableReply,
|
|
474
632
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
475
633
|
receipt,
|
|
634
|
+
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
|
635
|
+
recordAcceptedNotice,
|
|
476
636
|
};
|
|
477
637
|
}
|
|
478
638
|
/** Local hosts may observe exact work directly; remote correspondence never pulls it. */
|
|
@@ -485,13 +645,18 @@ export function createFleetDelegation(options) {
|
|
|
485
645
|
if (raw === undefined)
|
|
486
646
|
throw new DirectoryError(503, "partial_init");
|
|
487
647
|
const mail = decodeMail(delegationRowSchema.parse(raw).mail);
|
|
488
|
-
|
|
648
|
+
if (accepted.status === "held")
|
|
649
|
+
return pending(1000);
|
|
650
|
+
const sessionId = mail.to.sessionId ?? accepted.sessionId;
|
|
651
|
+
if (sessionId === undefined)
|
|
652
|
+
throw new DirectoryError(503, "partial_init");
|
|
653
|
+
const settlement = await options.readSettlement(mail.to.agentId, sessionId, accepted.commandId);
|
|
489
654
|
return settlement === undefined
|
|
490
655
|
? pending(1000)
|
|
491
656
|
: {
|
|
492
657
|
kind: "settled",
|
|
493
658
|
agentId: mail.to.agentId,
|
|
494
|
-
sessionId
|
|
659
|
+
sessionId,
|
|
495
660
|
...settlement,
|
|
496
661
|
};
|
|
497
662
|
}
|
|
@@ -504,8 +669,12 @@ export function createFleetDelegation(options) {
|
|
|
504
669
|
const row = delegationRowSchema.parse(raw);
|
|
505
670
|
if (row.receipt === null)
|
|
506
671
|
continue;
|
|
672
|
+
if (row.state !== "accepted")
|
|
673
|
+
continue;
|
|
507
674
|
const { to } = decodeMail(row.mail);
|
|
508
675
|
const { commandId } = parseAccepted(row.receipt);
|
|
676
|
+
if (to.sessionId === undefined)
|
|
677
|
+
throw new DirectoryError(503, "partial_init");
|
|
509
678
|
const target = {
|
|
510
679
|
agentId: to.agentId,
|
|
511
680
|
sessionId: to.sessionId,
|
package/dist/http-routes.d.ts
CHANGED
|
@@ -297,6 +297,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
297
297
|
output: {
|
|
298
298
|
readonly status: "accepted";
|
|
299
299
|
readonly commandId: string;
|
|
300
|
+
readonly sessionId?: string | undefined;
|
|
300
301
|
} | {
|
|
301
302
|
readonly status: "unknown";
|
|
302
303
|
};
|
|
@@ -453,6 +454,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
453
454
|
output: {
|
|
454
455
|
readonly status: "accepted";
|
|
455
456
|
readonly commandId: string;
|
|
457
|
+
readonly sessionId?: string | undefined;
|
|
456
458
|
} | {
|
|
457
459
|
readonly status: "unknown";
|
|
458
460
|
} | {
|
|
@@ -465,6 +467,75 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
465
467
|
status: 200;
|
|
466
468
|
};
|
|
467
469
|
};
|
|
470
|
+
} & {
|
|
471
|
+
"/sessions/:sessionId/settlement": {
|
|
472
|
+
$get: {
|
|
473
|
+
input: {
|
|
474
|
+
query: {
|
|
475
|
+
readonly commandId: string | string[];
|
|
476
|
+
readonly timeoutMs: string | string[];
|
|
477
|
+
};
|
|
478
|
+
} & {
|
|
479
|
+
param: {
|
|
480
|
+
sessionId: string;
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
output: {
|
|
484
|
+
readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
|
|
485
|
+
readonly _tag: "ShiitakeError";
|
|
486
|
+
readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
|
|
487
|
+
readonly message: string;
|
|
488
|
+
readonly requestId?: string | undefined;
|
|
489
|
+
};
|
|
490
|
+
outputFormat: "json";
|
|
491
|
+
status: 400;
|
|
492
|
+
} | {
|
|
493
|
+
input: {
|
|
494
|
+
query: {
|
|
495
|
+
readonly commandId: string | string[];
|
|
496
|
+
readonly timeoutMs: string | string[];
|
|
497
|
+
};
|
|
498
|
+
} & {
|
|
499
|
+
param: {
|
|
500
|
+
sessionId: string;
|
|
501
|
+
};
|
|
502
|
+
};
|
|
503
|
+
output: {
|
|
504
|
+
readonly acceptedSender: string | null;
|
|
505
|
+
readonly terminal: {
|
|
506
|
+
readonly commandId: string;
|
|
507
|
+
readonly outcome: "completed" | "cancelled" | "failed" | "interrupted";
|
|
508
|
+
readonly sequence: number;
|
|
509
|
+
readonly diagnostic: string | null;
|
|
510
|
+
readonly meta?: any;
|
|
511
|
+
};
|
|
512
|
+
readonly content: string;
|
|
513
|
+
readonly usage: readonly {
|
|
514
|
+
model: string;
|
|
515
|
+
provider: import("@fungi.computer/mule/effect").ProviderId;
|
|
516
|
+
usage: {
|
|
517
|
+
input: number;
|
|
518
|
+
output: number;
|
|
519
|
+
cacheRead: number;
|
|
520
|
+
cacheWrite: number;
|
|
521
|
+
cacheWrite1h?: number | undefined;
|
|
522
|
+
reasoning?: number | undefined;
|
|
523
|
+
totalTokens: number;
|
|
524
|
+
cost: {
|
|
525
|
+
input: number;
|
|
526
|
+
output: number;
|
|
527
|
+
cacheRead: number;
|
|
528
|
+
cacheWrite: number;
|
|
529
|
+
total: number;
|
|
530
|
+
};
|
|
531
|
+
};
|
|
532
|
+
}[];
|
|
533
|
+
readonly toolCalls: number;
|
|
534
|
+
} | null;
|
|
535
|
+
outputFormat: "json";
|
|
536
|
+
status: 200;
|
|
537
|
+
};
|
|
538
|
+
};
|
|
468
539
|
} & {
|
|
469
540
|
"/sessions/:sessionId": {
|
|
470
541
|
$get: {
|
|
@@ -626,6 +697,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
626
697
|
data?: any;
|
|
627
698
|
} | undefined;
|
|
628
699
|
errorMessage?: string | undefined;
|
|
700
|
+
providerError?: string | undefined;
|
|
629
701
|
rawStopReason?: string | undefined;
|
|
630
702
|
endTurn?: boolean | undefined;
|
|
631
703
|
timestamp: number;
|
|
@@ -633,6 +705,17 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
633
705
|
} | {
|
|
634
706
|
toolCallId: string;
|
|
635
707
|
role: "toolResult";
|
|
708
|
+
timestamp: number;
|
|
709
|
+
content: ({
|
|
710
|
+
type: "text";
|
|
711
|
+
text: string;
|
|
712
|
+
textSignature?: string | undefined;
|
|
713
|
+
} | {
|
|
714
|
+
type: "image";
|
|
715
|
+
data: string;
|
|
716
|
+
mimeType: string;
|
|
717
|
+
})[];
|
|
718
|
+
toolName: string;
|
|
636
719
|
usage?: {
|
|
637
720
|
input: number;
|
|
638
721
|
output: number;
|
|
@@ -649,18 +732,6 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
649
732
|
total: number;
|
|
650
733
|
};
|
|
651
734
|
} | undefined;
|
|
652
|
-
content: ({
|
|
653
|
-
type: "text";
|
|
654
|
-
text: string;
|
|
655
|
-
textSignature?: string | undefined;
|
|
656
|
-
} | {
|
|
657
|
-
type: "image";
|
|
658
|
-
data: string;
|
|
659
|
-
mimeType: string;
|
|
660
|
-
})[];
|
|
661
|
-
timestamp: number;
|
|
662
|
-
durationMs?: number | undefined;
|
|
663
|
-
toolName: string;
|
|
664
735
|
nestedCalls?: {
|
|
665
736
|
calls: {
|
|
666
737
|
id: string;
|
|
@@ -676,6 +747,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
|
|
|
676
747
|
complete: boolean;
|
|
677
748
|
} | undefined;
|
|
678
749
|
isError: boolean;
|
|
750
|
+
durationMs?: number | undefined;
|
|
679
751
|
};
|
|
680
752
|
}[];
|
|
681
753
|
readonly terminals: readonly {
|
package/dist/http-routes.js
CHANGED
|
@@ -6,7 +6,7 @@ import { validator } from "hono/validator";
|
|
|
6
6
|
import * as Schema from "effect/Schema";
|
|
7
7
|
import { ShiitakeError, toShiitakeErrorWire } from "./errors.js";
|
|
8
8
|
import { JsonValueSchema } from "./internal/json-value.js";
|
|
9
|
-
import { decodeClientIdentity, decodeClientEventsQuery, decodeClientReadQuery, decodeClientSessionCommandRequest, decodeClientSessionCreationRequest, decodeClientPageCursor, decodeClientSessionId, } from "./internal/client-boundary.js";
|
|
9
|
+
import { decodeClientIdentity, decodeClientEventsQuery, decodeClientReadQuery, decodeClientSettlementQuery, decodeClientSessionCommandRequest, decodeClientSessionCreationRequest, decodeClientPageCursor, decodeClientSessionId, } from "./internal/client-boundary.js";
|
|
10
10
|
const decodeJsonValue = Schema.decodeUnknownResult(Schema.fromJsonString(JsonValueSchema));
|
|
11
11
|
/** One encoded extension output; its contract, not Hono, types the value. */
|
|
12
12
|
function jsonResponse(value) {
|
|
@@ -218,6 +218,20 @@ export function createShiitakeHttpRoutes(resolveSession, listSessions, createSes
|
|
|
218
218
|
return context.body(null, 204);
|
|
219
219
|
}
|
|
220
220
|
return context.json(await session.command(command), 200);
|
|
221
|
+
})
|
|
222
|
+
.get("/sessions/:sessionId/settlement", validator("query", (value, context) => {
|
|
223
|
+
const decoded = decodeClientSettlementQuery(value);
|
|
224
|
+
if (decoded._tag === "Failure")
|
|
225
|
+
return context.json(toShiitakeErrorWire(invalidRequest("Settlement request is invalid")), 400);
|
|
226
|
+
return decoded.success;
|
|
227
|
+
}), async (context) => {
|
|
228
|
+
const session = await resolveSession(decodeClientSessionId(context.req.param("sessionId")), context.get("shiitakeMeta"));
|
|
229
|
+
const { commandId, timeoutMs } = context.req.valid("query");
|
|
230
|
+
const settled = await session.readSettlement(commandId, {
|
|
231
|
+
timeoutMs,
|
|
232
|
+
signal: context.req.raw.signal,
|
|
233
|
+
});
|
|
234
|
+
return context.json(settled ?? null, 200);
|
|
221
235
|
})
|
|
222
236
|
.get("/sessions/:sessionId", validator("query", (value, context) => {
|
|
223
237
|
const decoded = decodeClientReadQuery(value);
|