@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.
Files changed (63) hide show
  1. package/README.md +19 -18
  2. package/dist/agent-control.d.ts +24 -19
  3. package/dist/agent-control.js +69 -128
  4. package/dist/client-wire.d.ts +12 -3
  5. package/dist/client-wire.js +33 -8
  6. package/dist/client.d.ts +34 -3
  7. package/dist/client.js +1 -1
  8. package/dist/cloudflare.d.ts +3 -1
  9. package/dist/cloudflare.js +2 -2
  10. package/dist/delegation.d.ts +151 -17
  11. package/dist/delegation.js +174 -41
  12. package/dist/effect.d.ts +1 -1
  13. package/dist/fleet.d.ts +37 -10
  14. package/dist/fleet.js +217 -48
  15. package/dist/http-routes.d.ts +84 -12
  16. package/dist/http-routes.js +15 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/internal/admission-door.d.ts +2 -0
  19. package/dist/internal/admission-door.js +9 -2
  20. package/dist/internal/client-boundary.d.ts +10 -0
  21. package/dist/internal/client-boundary.js +10 -0
  22. package/dist/internal/compaction/compaction.js +1 -1
  23. package/dist/internal/contracts.d.ts +2 -0
  24. package/dist/internal/coordinator.d.ts +3 -1
  25. package/dist/internal/coordinator.js +98 -13
  26. package/dist/internal/effect-client.d.ts +3 -1
  27. package/dist/internal/effect-client.js +3 -2
  28. package/dist/internal/effect-contracts.d.ts +6 -2
  29. package/dist/internal/model-acquisition.js +2 -0
  30. package/dist/internal/observation-kernel.d.ts +8 -4
  31. package/dist/internal/observation-kernel.js +44 -9
  32. package/dist/internal/promise-facade.d.ts +1 -1
  33. package/dist/internal/promise-facade.js +1 -1
  34. package/dist/internal/run-scope.js +37 -8
  35. package/dist/internal/runtime-coordinator.d.ts +5 -0
  36. package/dist/internal/runtime-coordinator.js +8 -4
  37. package/dist/internal/runtime-core.d.ts +2 -1
  38. package/dist/internal/runtime-core.js +6 -1
  39. package/dist/internal/session-context.d.ts +1 -1
  40. package/dist/internal/session-context.js +84 -13
  41. package/dist/internal/woodstock/agent-reply.d.ts +7 -2
  42. package/dist/internal/woodstock/agent-reply.js +22 -13
  43. package/dist/internal/woodstock/fleet-store.d.ts +4 -2
  44. package/dist/internal/woodstock/fleet-store.js +70 -6
  45. package/dist/internal/woodstock/index.d.ts +2 -0
  46. package/dist/internal/woodstock/index.js +11 -5
  47. package/dist/internal/woodstock/run-recovery.js +4 -1
  48. package/dist/internal/woodstock/session-entry.d.ts +13 -3
  49. package/dist/internal/woodstock/session-entry.js +1 -0
  50. package/dist/internal/woodstock/session-store.d.ts +4 -0
  51. package/dist/internal/woodstock/session-store.js +26 -19
  52. package/dist/remote-client.js +5 -1
  53. package/dist/testing.d.ts +5 -2
  54. package/dist/testing.js +4 -2
  55. package/docs/api/agent-control.md +18 -12
  56. package/docs/api/client.md +78 -0
  57. package/docs/api/delegation.md +72 -55
  58. package/docs/api/effect.md +5 -0
  59. package/docs/api/fleet.md +23 -23
  60. package/docs/api/http.md +2 -1
  61. package/docs/api/index.md +13 -0
  62. package/docs/api/testing.md +3 -3
  63. 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 * as Schema from "effect/Schema";
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/id").IdCodec<"agent", "agent", 1>;
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<Accepted>;
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: the replying Agent is
241
- * its author, and the verified reply rides beside the caller's own path.
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<Accepted>;
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<Accepted>;
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
- import { SessionTerminalSchema } from "./client-wire.js";
2
- import { usage, createFleetDirectoryStore, createFleetDelegationStore, } from "./internal/woodstock/index.js";
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: the replying Agent is
312
- * its author, and the verified reply rides beside the caller's own path.
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
- return {
316
- ...authorMeta({
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: { kind: "agent", agentId: task.agentId, sessionId },
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.receipt !== null)
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 = parseAccepted(JSON.stringify(await options.deliver(decodeMail(row.mail))));
418
- store.recordReceipt(task.requestId, JSON.stringify(receipt));
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 row = read(mail.key.slice(0, -suffix.length));
437
- if (row === undefined)
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 (mail.from.agentId !== sent.to.agentId ||
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
- if (row.receipt !== null &&
446
- parseAccepted(row.receipt).commandId !== mail.reply.commandId)
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
- const reply = {
452
- agentId: mail.from.agentId,
453
- sessionId: mail.from.sessionId,
454
- ...mail.reply,
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 encoded = read(requestId)?.receipt;
465
- return encoded === undefined || encoded === null
578
+ const row = read(requestId);
579
+ return row?.state !== "accepted" || row.receipt === null
466
580
  ? undefined
467
- : parseAccepted(encoded);
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
- const settlement = await options.readSettlement(mail.to.agentId, mail.to.sessionId, accepted.commandId);
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: mail.to.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,
@@ -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 {
@@ -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);