@fungi.computer/shiitake 0.3.0 → 0.4.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 (68) hide show
  1. package/README.md +18 -8
  2. package/dist/agent-control.d.ts +19 -15
  3. package/dist/agent-control.js +66 -126
  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 +145 -13
  11. package/dist/delegation.js +109 -37
  12. package/dist/effect.d.ts +1 -1
  13. package/dist/fleet.d.ts +30 -8
  14. package/dist/fleet.js +142 -38
  15. package/dist/http-routes.d.ts +91 -19
  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 +2 -0
  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.d.ts +5 -5
  23. package/dist/internal/compaction/compaction.js +4 -5
  24. package/dist/internal/compaction/settings.d.ts +1 -1
  25. package/dist/internal/compaction/utils.d.ts +1 -1
  26. package/dist/internal/contracts.d.ts +2 -0
  27. package/dist/internal/coordinator.d.ts +3 -1
  28. package/dist/internal/coordinator.js +98 -13
  29. package/dist/internal/effect-client.d.ts +3 -1
  30. package/dist/internal/effect-client.js +3 -2
  31. package/dist/internal/effect-contracts.d.ts +6 -2
  32. package/dist/internal/model-acquisition.js +2 -0
  33. package/dist/internal/model-preference.d.ts +2 -2
  34. package/dist/internal/observation-kernel.d.ts +8 -4
  35. package/dist/internal/observation-kernel.js +44 -9
  36. package/dist/internal/promise-facade.d.ts +1 -1
  37. package/dist/internal/promise-facade.js +1 -1
  38. package/dist/internal/run-scope.js +37 -8
  39. package/dist/internal/runtime-coordinator.d.ts +5 -0
  40. package/dist/internal/runtime-coordinator.js +8 -4
  41. package/dist/internal/runtime-core.d.ts +2 -1
  42. package/dist/internal/runtime-core.js +6 -1
  43. package/dist/internal/session-context.d.ts +1 -1
  44. package/dist/internal/session-context.js +84 -13
  45. package/dist/internal/woodstock/agent-reply.d.ts +7 -2
  46. package/dist/internal/woodstock/agent-reply.js +22 -13
  47. package/dist/internal/woodstock/fleet-store.d.ts +3 -2
  48. package/dist/internal/woodstock/fleet-store.js +43 -6
  49. package/dist/internal/woodstock/index.d.ts +2 -0
  50. package/dist/internal/woodstock/index.js +11 -5
  51. package/dist/internal/woodstock/run-recovery.js +4 -1
  52. package/dist/internal/woodstock/session-entry.d.ts +14 -4
  53. package/dist/internal/woodstock/session-entry.js +1 -0
  54. package/dist/internal/woodstock/session-store.d.ts +4 -0
  55. package/dist/internal/woodstock/session-store.js +26 -19
  56. package/dist/remote-client.js +5 -1
  57. package/dist/testing.d.ts +6 -3
  58. package/dist/testing.js +4 -2
  59. package/docs/api/agent-control.md +18 -12
  60. package/docs/api/client.md +78 -0
  61. package/docs/api/delegation.md +72 -55
  62. package/docs/api/effect.md +5 -0
  63. package/docs/api/fleet.md +23 -23
  64. package/docs/api/http.md +2 -1
  65. package/docs/api/index.md +13 -0
  66. package/docs/api/model-preference.md +10 -10
  67. package/docs/api/testing.md +3 -3
  68. package/package.json +9 -8
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
@@ -331,9 +341,23 @@ const delegationMetadata = z.object({
331
341
  const delegationRowSchema = z.lazy(() => z.strictObject({
332
342
  input: z.string(),
333
343
  mail: z.string(),
344
+ state: z.enum(["pending", "held", "accepted"]),
334
345
  receipt: z.string().nullable(),
335
346
  }));
347
+ const heldRecipientOffline = {
348
+ status: "held",
349
+ reason: "recipient_offline",
350
+ message: "waiting: recipient offline",
351
+ };
336
352
  const decodeMail = Schema.decodeUnknownSync(Schema.fromJsonString(BirdDogMailSchema));
353
+ function bindRecipientSession(mail, sessionId) {
354
+ if (mail.from.sessionId === undefined)
355
+ throw new DirectoryError(503, "partial_init");
356
+ return {
357
+ ...mail,
358
+ to: { ...mail.to, sessionId },
359
+ };
360
+ }
337
361
  /**
338
362
  * BirdDog submission and reply authentication. The durable row freezes
339
363
  * correspondence before transport; an ambiguous delivery reuses that exact mail.
@@ -384,21 +408,20 @@ export function createFleetCorrespondence(options) {
384
408
  });
385
409
  let row = read(task.requestId);
386
410
  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
411
  const from = { kind: "agent", ...parent };
398
412
  let mail = {
399
413
  key: task.requestId,
400
414
  from,
401
- to: { kind: "agent", agentId: task.agentId, sessionId },
415
+ to: task.sessionId === undefined
416
+ ? {
417
+ kind: "agent",
418
+ agentId: task.agentId,
419
+ }
420
+ : {
421
+ kind: "agent",
422
+ agentId: task.agentId,
423
+ sessionId: task.sessionId,
424
+ },
402
425
  replyTo: [from],
403
426
  content: task.text,
404
427
  createdAt: Date.now(),
@@ -411,11 +434,26 @@ export function createFleetCorrespondence(options) {
411
434
  throw new DirectoryError(503, "partial_init");
412
435
  if (row.input !== input)
413
436
  throw new DirectoryError(409, "request_conflict");
414
- if (row.receipt !== null)
437
+ if (row.state === "held")
438
+ return heldRecipientOffline;
439
+ if (row.state === "accepted") {
440
+ if (row.receipt === null)
441
+ throw new DirectoryError(503, "partial_init");
415
442
  return parseAccepted(row.receipt);
443
+ }
416
444
  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));
445
+ const receipt = parseDelegationReceipt(JSON.stringify(await options.deliver(decodeMail(row.mail))));
446
+ if (receipt.status === "held")
447
+ store.recordHeld(task.requestId);
448
+ else {
449
+ const sent = decodeMail(row.mail);
450
+ const sessionId = receipt.sessionId ?? sent.to.sessionId;
451
+ if (sessionId === undefined)
452
+ throw new DirectoryError(503, "partial_init");
453
+ const accepted = { ...receipt, sessionId };
454
+ store.recordReceipt(task.requestId, JSON.stringify(accepted), row.mail, JSON.stringify(bindRecipientSession(sent, sessionId)));
455
+ return accepted;
456
+ }
419
457
  return receipt;
420
458
  }
421
459
  /**
@@ -431,28 +469,45 @@ export function createFleetCorrespondence(options) {
431
469
  const suffix = ":reply:0";
432
470
  if (mail.replyTo.length !== 0 ||
433
471
  mail.reply === undefined ||
472
+ mail.reply.outcome === "accepted" ||
434
473
  !mail.key.endsWith(suffix))
435
474
  return undefined;
436
475
  const row = read(mail.key.slice(0, -suffix.length));
437
476
  if (row === undefined)
438
477
  return undefined;
439
478
  const sent = decodeMail(row.mail);
479
+ if (mail.reply.outcome !== "undeliverable" &&
480
+ mail.from.sessionId === undefined)
481
+ return undefined;
440
482
  if (mail.from.agentId !== sent.to.agentId ||
441
483
  mail.from.sessionId !== sent.to.sessionId ||
442
484
  mail.to.agentId !== sent.from.agentId ||
443
485
  mail.to.sessionId !== sent.from.sessionId)
444
486
  return undefined;
445
- if (row.receipt !== null &&
446
- parseAccepted(row.receipt).commandId !== mail.reply.commandId)
447
- return undefined;
487
+ if (mail.reply.outcome !== "undeliverable" &&
488
+ row.state === "accepted" &&
489
+ row.receipt !== null) {
490
+ const sentResult = parseAccepted(row.receipt);
491
+ if (sentResult.commandId !== mail.reply.commandId)
492
+ return undefined;
493
+ }
448
494
  const path = delegationMetadata.parse(sent.meta).delegation;
449
495
  if (path === undefined)
450
496
  return undefined;
451
- const reply = {
452
- agentId: mail.from.agentId,
453
- sessionId: mail.from.sessionId,
454
- ...mail.reply,
455
- };
497
+ let reply;
498
+ if (mail.reply.outcome === "undeliverable") {
499
+ const undeliverable = { agentId: mail.from.agentId, ...mail.reply };
500
+ reply =
501
+ mail.from.sessionId === undefined
502
+ ? undeliverable
503
+ : { ...undeliverable, sessionId: mail.from.sessionId };
504
+ }
505
+ else {
506
+ const sessionId = mail.from.sessionId;
507
+ if (sessionId === undefined)
508
+ return undefined;
509
+ reply = { agentId: mail.from.agentId, sessionId, ...mail.reply };
510
+ }
456
511
  return {
457
512
  ancestors: path.ancestors.slice(0, -1),
458
513
  maxDepth: path.maxDepth,
@@ -461,10 +516,48 @@ export function createFleetCorrespondence(options) {
461
516
  }
462
517
  /** Read only the existing acceptance receipt; do not reserve or deliver mail. */
463
518
  function receipt(requestId) {
464
- const encoded = read(requestId)?.receipt;
465
- return encoded === undefined || encoded === null
519
+ const row = read(requestId);
520
+ return row?.state !== "accepted" || row.receipt === null
466
521
  ? undefined
467
- : parseAccepted(encoded);
522
+ : parseAccepted(row.receipt);
523
+ }
524
+ /** Atomically bind the recipient Session carried by a late acceptance notice. */
525
+ function recordAcceptedNotice(mail) {
526
+ const suffix = ":accepted";
527
+ if (mail.replyTo.length !== 0 ||
528
+ mail.reply?.outcome !== "accepted" ||
529
+ mail.from.sessionId === undefined ||
530
+ !mail.key.endsWith(suffix))
531
+ return undefined;
532
+ const requestId = mail.key.slice(0, -suffix.length);
533
+ const row = read(requestId);
534
+ if (row === undefined)
535
+ return undefined;
536
+ const sent = decodeMail(row.mail);
537
+ const path = delegationMetadata.safeParse(sent.meta);
538
+ if (sent.from.sessionId === undefined ||
539
+ !path.success ||
540
+ path.data.delegation === undefined ||
541
+ mail.key !== `${sent.key}:accepted` ||
542
+ mail.from.agentId !== sent.to.agentId ||
543
+ (sent.to.sessionId !== undefined &&
544
+ sent.to.sessionId !== mail.from.sessionId) ||
545
+ mail.to.agentId !== sent.from.agentId ||
546
+ mail.to.sessionId !== sent.from.sessionId)
547
+ return undefined;
548
+ if (row.state === "accepted" && row.receipt !== null) {
549
+ const current = parseAccepted(row.receipt);
550
+ if (current.commandId !== mail.reply.commandId ||
551
+ current.sessionId !== mail.from.sessionId)
552
+ return undefined;
553
+ }
554
+ const accepted = {
555
+ status: "accepted",
556
+ commandId: mail.reply.commandId,
557
+ sessionId: mail.from.sessionId,
558
+ };
559
+ store.recordReceipt(requestId, JSON.stringify(accepted), row.mail, JSON.stringify(bindRecipientSession(sent, mail.from.sessionId)));
560
+ return accepted;
468
561
  }
469
562
  return {
470
563
  /** Submit frozen mail or reuse its recorded Session admission receipt. */
@@ -473,6 +566,8 @@ export function createFleetCorrespondence(options) {
473
566
  authenticateReply,
474
567
  /** Read an existing acceptance receipt without reserving or delivering mail. */
475
568
  receipt,
569
+ /** Consume a recipient's stable `<mailKey>:accepted` notice. */
570
+ recordAcceptedNotice,
476
571
  };
477
572
  }
478
573
  /** Local hosts may observe exact work directly; remote correspondence never pulls it. */
@@ -485,13 +580,18 @@ export function createFleetDelegation(options) {
485
580
  if (raw === undefined)
486
581
  throw new DirectoryError(503, "partial_init");
487
582
  const mail = decodeMail(delegationRowSchema.parse(raw).mail);
488
- const settlement = await options.readSettlement(mail.to.agentId, mail.to.sessionId, accepted.commandId);
583
+ if (accepted.status === "held")
584
+ return pending(1000);
585
+ const sessionId = mail.to.sessionId ?? accepted.sessionId;
586
+ if (sessionId === undefined)
587
+ throw new DirectoryError(503, "partial_init");
588
+ const settlement = await options.readSettlement(mail.to.agentId, sessionId, accepted.commandId);
489
589
  return settlement === undefined
490
590
  ? pending(1000)
491
591
  : {
492
592
  kind: "settled",
493
593
  agentId: mail.to.agentId,
494
- sessionId: mail.to.sessionId,
594
+ sessionId,
495
595
  ...settlement,
496
596
  };
497
597
  }
@@ -504,8 +604,12 @@ export function createFleetDelegation(options) {
504
604
  const row = delegationRowSchema.parse(raw);
505
605
  if (row.receipt === null)
506
606
  continue;
607
+ if (row.state !== "accepted")
608
+ continue;
507
609
  const { to } = decodeMail(row.mail);
508
610
  const { commandId } = parseAccepted(row.receipt);
611
+ if (to.sessionId === undefined)
612
+ throw new DirectoryError(503, "partial_init");
509
613
  const target = {
510
614
  agentId: to.agentId,
511
615
  sessionId: to.sessionId,
@@ -217,7 +217,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
217
217
  output: {
218
218
  readonly providerId: string;
219
219
  readonly modelId: string;
220
- readonly reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
220
+ readonly reasoning?: "high" | "low" | "max" | "medium" | "minimal" | "xhigh" | undefined;
221
221
  } | null;
222
222
  outputFormat: "json";
223
223
  status: 200;
@@ -230,7 +230,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
230
230
  json: {
231
231
  readonly providerId: string;
232
232
  readonly modelId: string;
233
- readonly reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
233
+ readonly reasoning?: "high" | "low" | "max" | "medium" | "minimal" | "xhigh" | undefined;
234
234
  } | null;
235
235
  } & {
236
236
  param: {
@@ -251,7 +251,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
251
251
  json: {
252
252
  readonly providerId: string;
253
253
  readonly modelId: string;
254
- readonly reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | undefined;
254
+ readonly reasoning?: "high" | "low" | "max" | "medium" | "minimal" | "xhigh" | undefined;
255
255
  } | null;
256
256
  } & {
257
257
  param: {
@@ -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: {
@@ -579,13 +650,13 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
579
650
  thinkingSignature?: string | undefined;
580
651
  redacted?: boolean | undefined;
581
652
  })[];
582
- api: import("@earendil-works/pi-ai").Api;
583
- provider: import("@earendil-works/pi-ai").ProviderId;
653
+ api: import("@fungi.computer/mule/effect").Api;
654
+ provider: import("@fungi.computer/mule/effect").ProviderId;
584
655
  model: string;
585
656
  responseModel?: string | undefined;
586
657
  responseId?: string | undefined;
587
658
  providerThinkingLevel?: string | undefined;
588
- thinkingLevel?: import("@earendil-works/pi-ai").ModelThinkingLevel | undefined;
659
+ thinkingLevel?: import("@fungi.computer/mule/effect").ModelThinkingLevel | undefined;
589
660
  diagnostics?: {
590
661
  type: string;
591
662
  timestamp: number;
@@ -615,7 +686,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
615
686
  total: number;
616
687
  };
617
688
  };
618
- stopReason: import("@earendil-works/pi-ai").StopReason;
689
+ stopReason: import("@fungi.computer/mule/effect").StopReason;
619
690
  deferred?: {
620
691
  provider: string;
621
692
  modelId: string;
@@ -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);
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, 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, 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";
@@ -22,7 +22,7 @@ export type ShiitakeHost = Readonly<{
22
22
  readSettlement: (sessionId: string, commandId: string) => Promise<CommandSettlement | undefined>;
23
23
  agent: ShiitakeOwnerAgent;
24
24
  /** Durably admit one addressed mail item through the host owner. */
25
- acceptMail: (mail: BirdDogMail, meta?: HostJson, reply?: VerifiedAgentReply) => Promise<Accepted>;
25
+ acceptMail: (mail: BirdDogMail, meta?: HostJson, reply?: VerifiedAgentReply, stageAcceptedNotice?: (mail: BirdDogMail) => void) => Promise<Accepted>;
26
26
  }>;
27
27
  /** Construct the host-only Promise facade over the canonical Effect program. */
28
28
  export declare function makeHost(options: ShiitakeOptions, mailRoom: BirdDogEffect): Promise<ShiitakeHost>;
@@ -32,6 +32,8 @@ 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 committed with a mail admission. */
36
+ onAccepted?: (sessionId: string, commandId: string) => void;
35
37
  }>;
36
38
  /** Outcome of one door admission. */
37
39
  export type AdmitResult = Readonly<{
@@ -35,6 +35,7 @@ export function openAdmissionDoor(input) {
35
35
  when: request.when,
36
36
  mail: request.mail,
37
37
  reply: decodeReply(request),
38
+ onAccepted: request.onAccepted,
38
39
  meta: request.meta === undefined
39
40
  ? undefined
40
41
  : checkHostMeta(input.hostMeta, request.meta),
@@ -69,6 +70,7 @@ export function openAdmissionDoor(input) {
69
70
  input.enqueue(work, request.mail);
70
71
  }
71
72
  : undefined, targetWorkId, request.reply);
73
+ request.onAccepted?.(sessionId, accepted.accepted.commandId);
72
74
  touched.set(sessionId, (touched.get(sessionId) ?? false) || accepted.delivery === "new");
73
75
  return Object.freeze({
74
76
  status: "accepted",
@@ -8,6 +8,16 @@ export declare const PromptImageSchema: Schema.Struct<{
8
8
  }>;
9
9
  /** Delivery policy for input accepted while a Session run is active. */
10
10
  export type PromptDelivery = "steer" | "followUp";
11
+ /** One bounded exact-command read, shared by the in-process and HTTP doors. */
12
+ export declare const decodeClientSettlementRequest: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Result.Result<{
13
+ readonly commandId: string;
14
+ readonly timeoutMs: number;
15
+ }, Schema.SchemaError>;
16
+ /** Query encoding of the same bounded exact-command read. */
17
+ export declare const decodeClientSettlementQuery: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Result.Result<{
18
+ readonly commandId: string;
19
+ readonly timeoutMs: number;
20
+ }, Schema.SchemaError>;
11
21
  declare const SessionCreationRequestSchema: Schema.Struct<{
12
22
  readonly requestId: Schema.String;
13
23
  }>;
@@ -27,6 +27,16 @@ export const PromptImageSchema = Schema.Struct({
27
27
  const PromptImagesSchema = Schema.Array(PromptImageSchema).check(Schema.isMinLength(1), Schema.isMaxLength(8));
28
28
  const PromptDeliverySchema = Schema.Literals(["steer", "followUp"]);
29
29
  const SessionIdSchema = boundedId;
30
+ /** One bounded exact-command read, shared by the in-process and HTTP doors. */
31
+ export const decodeClientSettlementRequest = Schema.decodeUnknownResult(Schema.Struct({
32
+ commandId: boundedId,
33
+ timeoutMs: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(30_000)),
34
+ }), { onExcessProperty: "error" });
35
+ /** Query encoding of the same bounded exact-command read. */
36
+ export const decodeClientSettlementQuery = Schema.decodeUnknownResult(Schema.Struct({
37
+ commandId: boundedId,
38
+ timeoutMs: Schema.NumberFromString.check(Schema.isInt(), Schema.isBetween({ minimum: 0, maximum: 30_000 })),
39
+ }), { onExcessProperty: "error" });
30
40
  const decodeSessionPageCursorValue = Schema.decodeUnknownResult(boundedText(512));
31
41
  const SessionCreationRequestSchema = Schema.Struct({ requestId: boundedId });
32
42
  const PromptCommandSchema = Schema.Struct({
@@ -5,7 +5,7 @@
5
5
  * Range selection follows Pi Durable spec §8.7 (`earendil-works/pi` at
6
6
  * 8efd314f) and token estimates are pi-ai's own.
7
7
  */
8
- import type { Api, Model, Usage } from "@earendil-works/pi-ai";
8
+ import type { Api, Model, Usage } from "@fungi.computer/mule/effect";
9
9
  import type { MuleStreamFunction } from "@fungi.computer/mule/effect";
10
10
  import * as Effect from "effect/Effect";
11
11
  import type { BorrowedCompactionViewV0 } from "../../extensions.js";
@@ -14,14 +14,14 @@ import { createFileOps, type CompactionSettings } from "./utils.js";
14
14
  export type CompactionSourceEntry = Readonly<{
15
15
  id: string;
16
16
  kind: "message";
17
- message: import("@earendil-works/pi-ai").Message;
17
+ message: import("@fungi.computer/mule/effect").Message;
18
18
  }> | Readonly<{
19
19
  id: string;
20
20
  kind: "compaction";
21
21
  /** Pi's entry head: the first entry the summary keeps. */
22
22
  head: string;
23
23
  /** Pi's entry model message: the summary the model reads. */
24
- model: import("@earendil-works/pi-ai").UserMessage;
24
+ model: import("@fungi.computer/mule/effect").UserMessage;
25
25
  usage: Usage;
26
26
  details?: Readonly<{
27
27
  readFiles: readonly string[];
@@ -30,8 +30,8 @@ export type CompactionSourceEntry = Readonly<{
30
30
  }>;
31
31
  export type CompactionPreparation = Readonly<{
32
32
  firstKeptEntryId: string;
33
- messagesToSummarize: readonly import("@earendil-works/pi-ai").Message[];
34
- turnPrefixMessages: readonly import("@earendil-works/pi-ai").Message[];
33
+ messagesToSummarize: readonly import("@fungi.computer/mule/effect").Message[];
34
+ turnPrefixMessages: readonly import("@fungi.computer/mule/effect").Message[];
35
35
  isSplitTurn: boolean;
36
36
  tokensBefore: number;
37
37
  previousSummary?: string;
@@ -1,13 +1,14 @@
1
+ import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
2
+ import { streamModel } from "@fungi.computer/mule/effect";
1
3
  // Leaf helpers, not Pi's root entry, which also evaluates its validators.
2
4
  import { estimateContextTokens, estimateMessageTokens, } from "@earendil-works/pi-ai/utils/estimate";
3
5
  import { retryAssistantCall } from "@earendil-works/pi-ai/utils/retry";
4
6
  import { contentText } from "@earendil-works/pi-ai/utils/text";
5
- import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
6
7
  import { uuidv7 } from "@earendil-works/pi-ai/utils/uuid";
7
8
  import * as Effect from "effect/Effect";
8
9
  import { ShiitakeError } from "../../errors.js";
9
10
  import { computeFileLists, createFileOps, extractFileOpsFromMessage, formatFileOperations, serializeConversation, SUMMARIZATION_PROMPT, SUMMARIZATION_SYSTEM_PROMPT, TURN_PREFIX_SUMMARIZATION_PROMPT, UPDATE_SUMMARIZATION_PROMPT, compactionSummaryText, summaryFailure, } from "./utils.js";
10
- /** A summary is a model call: its bounded provider message is shown. */
11
+ /** A summary uses the stream failure diagnostic, never failed-turn provider prose. */
11
12
  function failure(message) {
12
13
  return new ShiitakeError("model", "rejected", message);
13
14
  }
@@ -210,9 +211,7 @@ function completeSummarization(model, context, options, streamFn) {
210
211
  cacheRetention: "none",
211
212
  sessionId: options.sessionId ?? uuidv7(),
212
213
  };
213
- return retryAssistantCall(() => streamFn
214
- ? Promise.resolve(streamFn(model, normalizeContext(context), requestOptions)).then((stream) => stream.result())
215
- : import("@earendil-works/pi-ai/compat").then(({ completeSimple }) => completeSimple(model, context, requestOptions)), RETRY_POLICY, requestOptions.signal);
214
+ return retryAssistantCall(() => Promise.resolve((streamFn ?? streamModel)(model, normalizeContext(context), requestOptions)).then((stream) => stream.result()), RETRY_POLICY, requestOptions.signal);
216
215
  }
217
216
  function buildContext(promptText) {
218
217
  return {