@fungi.computer/shiitake 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +59 -21
  2. package/dist/agent-control.d.ts +94 -2
  3. package/dist/agent-control.js +147 -26
  4. package/dist/client-entry.d.ts +20 -0
  5. package/dist/client-entry.js +5 -0
  6. package/dist/client-visitor.d.ts +33 -0
  7. package/dist/client-visitor.js +231 -0
  8. package/dist/client-wire.d.ts +9 -0
  9. package/dist/client-wire.js +24 -3
  10. package/dist/client.d.ts +27 -6
  11. package/dist/client.js +23 -0
  12. package/dist/cloudflare.d.ts +20 -0
  13. package/dist/cloudflare.js +4 -1
  14. package/dist/delegation.d.ts +79 -53
  15. package/dist/delegation.js +26 -21
  16. package/dist/effect.d.ts +26 -1
  17. package/dist/effect.js +14 -1
  18. package/dist/errors.d.ts +2 -2
  19. package/dist/errors.js +2 -0
  20. package/dist/extensions.d.ts +22 -3
  21. package/dist/extensions.js +1 -1
  22. package/dist/fleet.d.ts +25 -2
  23. package/dist/fleet.js +108 -24
  24. package/dist/hosted.js +3 -0
  25. package/dist/http-routes.d.ts +23 -9
  26. package/dist/http.d.ts +1 -0
  27. package/dist/http.js +1 -0
  28. package/dist/index.d.ts +1 -1
  29. package/dist/internal/accepted-run-executor.js +2 -1
  30. package/dist/internal/admission-door.d.ts +6 -1
  31. package/dist/internal/admission-door.js +33 -3
  32. package/dist/internal/contracts.d.ts +19 -1
  33. package/dist/internal/coordinator.js +115 -52
  34. package/dist/internal/effect-client.js +1 -1
  35. package/dist/internal/effect-contracts.d.ts +9 -2
  36. package/dist/internal/extension-command-registry.d.ts +2 -1
  37. package/dist/internal/extension-command-registry.js +3 -1
  38. package/dist/internal/extension-runtime.d.ts +3 -1
  39. package/dist/internal/extension-runtime.js +18 -8
  40. package/dist/internal/model-acquisition.js +2 -0
  41. package/dist/internal/observation-kernel.d.ts +2 -0
  42. package/dist/internal/observation-kernel.js +17 -6
  43. package/dist/internal/promise-facade.js +2 -0
  44. package/dist/internal/run-scope.d.ts +14 -0
  45. package/dist/internal/run-scope.js +58 -15
  46. package/dist/internal/runtime-coordinator.d.ts +14 -0
  47. package/dist/internal/runtime-coordinator.js +60 -11
  48. package/dist/internal/runtime-core.d.ts +3 -0
  49. package/dist/internal/runtime-core.js +1 -0
  50. package/dist/internal/runtime-reconciliation.js +123 -25
  51. package/dist/internal/session-context.d.ts +5 -5
  52. package/dist/internal/session-context.js +80 -83
  53. package/dist/internal/session-status.d.ts +2 -2
  54. package/dist/internal/session-status.js +5 -3
  55. package/dist/internal/woodstock/agent-reply.d.ts +5 -0
  56. package/dist/internal/woodstock/agent-reply.js +3 -0
  57. package/dist/internal/woodstock/client-projection.js +13 -11
  58. package/dist/internal/woodstock/fleet-store.d.ts +3 -2
  59. package/dist/internal/woodstock/fleet-store.js +24 -4
  60. package/dist/internal/woodstock/index.d.ts +7 -3
  61. package/dist/internal/woodstock/index.js +23 -6
  62. package/dist/internal/woodstock/progress.d.ts +11 -1
  63. package/dist/internal/woodstock/progress.js +126 -25
  64. package/dist/internal/woodstock/session-delegator.d.ts +13 -0
  65. package/dist/internal/woodstock/session-delegator.js +44 -0
  66. package/dist/internal/woodstock/session-entry.d.ts +12 -2
  67. package/dist/internal/woodstock/session-store.d.ts +16 -2
  68. package/dist/internal/woodstock/session-store.js +349 -66
  69. package/dist/internal/woodstock/work-senders.d.ts +32 -0
  70. package/dist/internal/woodstock/work-senders.js +70 -0
  71. package/dist/node.js +11 -2
  72. package/dist/remote-client.js +7 -2
  73. package/dist/server-visitor-stream.d.ts +13 -0
  74. package/dist/server-visitor-stream.js +124 -0
  75. package/dist/session-attention.d.ts +30 -0
  76. package/dist/session-attention.js +14 -0
  77. package/docs/api/agent-control.md +212 -15
  78. package/docs/api/client-entry.md +3 -0
  79. package/docs/api/client.md +209 -5
  80. package/docs/api/cloudflare.md +23 -1
  81. package/docs/api/delegation.md +284 -77
  82. package/docs/api/effect.md +100 -12
  83. package/docs/api/extensions.md +73 -12
  84. package/docs/api/fleet.md +138 -25
  85. package/docs/api/http.md +28 -0
  86. package/docs/api/index.md +43 -17
  87. package/docs/api/testing.md +9 -7
  88. package/package.json +10 -6
package/dist/fleet.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { commandSettlementSchema } from "./client-wire.js";
2
- import { createFleetDirectoryStore, createFleetDelegationStore, } from "./internal/woodstock/index.js";
2
+ import { createFleetDirectoryStore, createFleetDelegationStore, readSessionDelegator, } from "./internal/woodstock/index.js";
3
3
  import { pending } from "@fungi.computer/agar";
4
4
  import { BirdDogMailSchema } from "@fungi.computer/bird-dog/effect";
5
5
  import * as Schema from "effect/Schema";
@@ -269,16 +269,34 @@ export function createAgentDirectory(sql) {
269
269
  erasureRosterComplete: store.erasureRosterComplete,
270
270
  };
271
271
  }
272
+ /**
273
+ * Whether an Agent may read one command's settlement: the Agent whose mail the
274
+ * command answered, or the Session's delegator of record for work no Agent's
275
+ * mail admitted (goal, loop and person turns, the work its reports carry).
276
+ * Another Agent's correspondence stays with that Agent.
277
+ */
278
+ export function settlementReadableBy(facts, agentId) {
279
+ return (facts.acceptedSender === agentId ||
280
+ (facts.acceptedSender === null && facts.delegator === agentId));
281
+ }
272
282
  /** Replay identity and work, with the parent supplied exclusively by the grant. */
273
- export const agentDelegationTaskSchema = z.lazy(() => z.strictObject({
283
+ export const agentDelegationTaskSchema = z.lazy(() => z
284
+ .strictObject({
274
285
  requestId: identity,
275
286
  agentId: identity,
276
287
  sessionId: identity.optional(),
288
+ /** A live steer into the recipient's running turn; queued follow-up when omitted. */
289
+ delivery: z.enum(["followUp", "steer"]).optional(),
290
+ /** Only a new-Session follow-up carries goal intent. */
291
+ goal: z
292
+ .union([z.string().min(1).max(4_000), z.literal(false)])
293
+ .optional(),
277
294
  text: z
278
295
  .string()
279
296
  .min(1)
280
297
  .max(256 * 1024),
281
- }));
298
+ })
299
+ .refine(({ goal, sessionId, delivery }) => goal === undefined || (sessionId === undefined && delivery !== "steer"), { path: ["goal"], error: "goal requires a new-Session follow-up" }));
282
300
  /**
283
301
  * Provenance for one delegated send from the invoking Agent's Session. The
284
302
  * model program's digest is frozen with the replay input, so a retried tool
@@ -343,6 +361,19 @@ export function delegationReplyMeta(delegation, replierName) {
343
361
  const delegationMetadata = z.object({
344
362
  delegation: FungiMessageMetaSchema.shape.delegation,
345
363
  });
364
+ /** An original one-way report can only name an ancestor of its Agent sender. */
365
+ export function isOneWayAncestorNotice(mail) {
366
+ if (mail.reply !== undefined ||
367
+ mail.replyTo.length !== 0 ||
368
+ mail.from.kind !== "agent" ||
369
+ mail.to.sessionId === undefined)
370
+ return false;
371
+ const parsed = delegationMetadata.safeParse(mail.meta);
372
+ return (parsed.success &&
373
+ parsed.data.delegation !== undefined &&
374
+ parsed.data.delegation.ancestors.includes(mail.to.agentId) &&
375
+ parsed.data.delegation.ancestors.at(-1) === mail.from.agentId);
376
+ }
346
377
  const delegationRowSchema = z.lazy(() => z.strictObject({
347
378
  input: z.string(),
348
379
  mail: z.string(),
@@ -363,6 +394,25 @@ function bindRecipientSession(mail, sessionId) {
363
394
  to: { ...mail.to, sessionId },
364
395
  };
365
396
  }
397
+ /** Read confirmed sender receipts that have no locally observed settlement.
398
+ * Call inside the Agent owner's transaction that also changes archived state.
399
+ * Remote reads happen separately; an unobserved settlement remains busy.
400
+ */
401
+ export function readOutstandingDelegationSync(storage) {
402
+ const raw = createFleetDelegationStore(storage).unsettled()[0];
403
+ if (raw === undefined)
404
+ return undefined;
405
+ const row = delegationRowSchema.parse(raw);
406
+ const mail = decodeMail(row.mail);
407
+ const sessionId = mail.to.sessionId;
408
+ if (sessionId === undefined)
409
+ throw new DirectoryError(503, "partial_init");
410
+ return {
411
+ agentId: mail.to.agentId,
412
+ sessionId,
413
+ commandId: parseAccepted(z.string().parse(row.receipt)).commandId,
414
+ };
415
+ }
366
416
  /**
367
417
  * BirdDog submission and reply authentication. The durable row freezes
368
418
  * correspondence before transport; an ambiguous delivery reuses that exact mail.
@@ -390,12 +440,18 @@ export function createFleetCorrespondence(options) {
390
440
  : undefined;
391
441
  const ancestors = inherited?.ancestors ?? [];
392
442
  const maxDepth = Math.min(grant.maxDepth ?? 8, inherited?.maxDepth ?? 8);
393
- // Ancestry bounds work depth; it does not remove authorized peer targets.
443
+ // A report to an ancestor is one-way: the parent handles the work but
444
+ // must not send its settlement back as a new prompt to the worker.
394
445
  if (parent.agentId === task.agentId)
395
446
  throw new AgentGrantError("delegation_loop");
396
447
  if (ancestors.length >= maxDepth)
397
448
  throw new AgentGrantError("delegation_depth");
398
- const delegation = { ancestors: [...ancestors, parent.agentId], maxDepth };
449
+ const path = { ancestors: [...ancestors, parent.agentId], maxDepth };
450
+ const delegation = {
451
+ ...path,
452
+ ...(task.delivery === "steer" && { steer: true }),
453
+ ...(task.goal !== undefined && { goal: task.goal }),
454
+ };
399
455
  const outgoingMeta = {
400
456
  ...(objectMeta.success
401
457
  ? objectMeta.data
@@ -414,10 +470,20 @@ export function createFleetCorrespondence(options) {
414
470
  let row = read(task.requestId);
415
471
  if (row === undefined) {
416
472
  const from = { kind: "agent", ...parent };
473
+ const ancestor = ancestors.includes(task.agentId);
474
+ // A sessionless report to a parent goes to the creator's recorded
475
+ // Session. Never create a fresh Session merely to carry a notice.
476
+ const targetSessionId = task.sessionId ??
477
+ (ancestor
478
+ ? readSessionDelegator(options.storage, parent.sessionId)?.replyTo.find(({ agentId }) => agentId === task.agentId)
479
+ ?.sessionId
480
+ : undefined);
481
+ if (ancestor && targetSessionId === undefined)
482
+ throw new AgentGrantError("delegator_missing");
417
483
  let mail = {
418
484
  key: task.requestId,
419
485
  from,
420
- to: task.sessionId === undefined
486
+ to: targetSessionId === undefined
421
487
  ? {
422
488
  kind: "agent",
423
489
  agentId: task.agentId,
@@ -425,9 +491,9 @@ export function createFleetCorrespondence(options) {
425
491
  : {
426
492
  kind: "agent",
427
493
  agentId: task.agentId,
428
- sessionId: task.sessionId,
494
+ sessionId: targetSessionId,
429
495
  },
430
- replyTo: [from],
496
+ replyTo: ancestor ? [] : [from],
431
497
  content: task.text,
432
498
  createdAt: Date.now(),
433
499
  };
@@ -471,13 +537,23 @@ export function createFleetCorrespondence(options) {
471
537
  */
472
538
  function authenticateReply(mail) {
473
539
  // This owner sends to one correspondent, so BirdDog's reply index is zero.
474
- const suffix = ":reply:0";
540
+ // A report names the idle Session's delegator mail and the settled command.
475
541
  if (mail.replyTo.length !== 0 ||
476
542
  mail.reply === undefined ||
477
- mail.reply.outcome === "accepted" ||
478
- !mail.key.endsWith(suffix))
543
+ mail.reply.outcome === "accepted")
544
+ return undefined;
545
+ const reportAt = mail.key.lastIndexOf(":report:");
546
+ // A report's key names the command it settles.
547
+ const isReport = reportAt >= 0 &&
548
+ "commandId" in mail.reply &&
549
+ mail.key.slice(reportAt) === `:report:${mail.reply.commandId}:0`;
550
+ const mailKey = isReport
551
+ ? mail.key.slice(0, reportAt)
552
+ : mail.key.endsWith(":reply:0")
553
+ ? mail.key.slice(0, -":reply:0".length)
554
+ : undefined;
555
+ if (mailKey === undefined)
479
556
  return undefined;
480
- const mailKey = mail.key.slice(0, -suffix.length);
481
557
  const row = read(mailKey);
482
558
  if (row === undefined || row.state === "undeliverable")
483
559
  return undefined;
@@ -508,7 +584,8 @@ export function createFleetCorrespondence(options) {
508
584
  mail.reply.reason === "recipient_offline" &&
509
585
  !systemUndeliverable)
510
586
  return undefined;
511
- if (mail.reply.outcome !== "undeliverable" &&
587
+ if (!isReport &&
588
+ mail.reply.outcome !== "undeliverable" &&
512
589
  row.state === "accepted" &&
513
590
  row.receipt !== null) {
514
591
  const sentResult = parseAccepted(row.receipt);
@@ -627,7 +704,9 @@ export function createFleetCorrespondence(options) {
627
704
  start,
628
705
  /** Match a reply to frozen mail and restore the caller's delegation path. */
629
706
  authenticateReply,
707
+ /** Bind exhausted local delivery to its frozen sender correspondence. */
630
708
  recordLocalUndeliverable,
709
+ /** Prevent replay after a system undeliverable reply is admitted. */
631
710
  recordUndeliverableReply,
632
711
  /** Read an existing acceptance receipt without reserving or delivering mail. */
633
712
  receipt,
@@ -651,6 +730,8 @@ export function createFleetDelegation(options) {
651
730
  if (sessionId === undefined)
652
731
  throw new DirectoryError(503, "partial_init");
653
732
  const settlement = await options.readSettlement(mail.to.agentId, sessionId, accepted.commandId);
733
+ if (settlement !== undefined)
734
+ store.recordSettled(task.requestId);
654
735
  return settlement === undefined
655
736
  ? pending(1000)
656
737
  : {
@@ -665,7 +746,8 @@ export function createFleetDelegation(options) {
665
746
  * target, so the owner is still waiting for a reply; undefined when none.
666
747
  */
667
748
  async function outstanding() {
668
- for (const raw of store.sent()) {
749
+ let waiting;
750
+ for (const raw of store.unsettled()) {
669
751
  const row = delegationRowSchema.parse(raw);
670
752
  if (row.receipt === null)
671
753
  continue;
@@ -680,21 +762,23 @@ export function createFleetDelegation(options) {
680
762
  sessionId: to.sessionId,
681
763
  commandId,
682
764
  };
683
- let settlement;
684
- try {
685
- settlement = await options.readSettlement(to.agentId, to.sessionId, commandId);
686
- }
687
- catch (cause) {
765
+ const result = await options
766
+ .readSettlement(to.agentId, to.sessionId, commandId)
767
+ .then((settlement) => ({ kind: "read", settlement }), (cause) => ({ kind: "unreadable", cause }));
768
+ if (result.kind === "unreadable") {
688
769
  // Work whose state cannot be read is still owed; say why.
689
- return {
770
+ waiting ??= {
690
771
  ...target,
691
- unreadable: cause instanceof Error ? cause.message : "unreadable",
772
+ unreadable: result.cause instanceof Error ? result.cause.message : "unreadable",
692
773
  };
774
+ continue;
693
775
  }
694
- if (settlement === undefined)
695
- return target;
776
+ if (result.settlement === undefined)
777
+ waiting ??= target;
778
+ else
779
+ store.recordSettled(decodeMail(row.mail).key);
696
780
  }
697
- return undefined;
781
+ return waiting;
698
782
  }
699
783
  return {
700
784
  /** The delegated work this owner still waits on, if any. */
package/dist/hosted.js CHANGED
@@ -166,12 +166,15 @@ function hostedPort(manifest, revision, grants) {
166
166
  }),
167
167
  attention: Object.freeze({
168
168
  set: () => Effect.fail(unavailable("Attention")),
169
+ awaitsPerson: () => Effect.fail(unavailable("Attention")),
169
170
  clear: () => Effect.fail(unavailable("Attention")),
170
171
  }),
171
172
  hasPendingSteer: () => Effect.fail(unavailable("Steering")),
173
+ workSender: () => Effect.fail(unavailable("Run sender")),
172
174
  sessionPosition: () => Effect.fail(unavailable("Session timeline")),
173
175
  hasSuccessfulToolResult: () => Effect.fail(unavailable("A tool result")),
174
176
  observe: () => Effect.die(unavailable("Observation")),
177
+ isObserved: () => false,
175
178
  transact: () => Effect.fail(new ShiitakeError("extensions", "rejected", "A hosted extension has no Agent transaction")),
176
179
  }),
177
180
  });
@@ -42,7 +42,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
42
42
  };
43
43
  };
44
44
  output: {
45
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
45
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
46
46
  readonly _tag: "ShiitakeError";
47
47
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
48
48
  readonly message: string;
@@ -109,7 +109,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
109
109
  };
110
110
  };
111
111
  output: {
112
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
112
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
113
113
  readonly _tag: "ShiitakeError";
114
114
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
115
115
  readonly message: string;
@@ -150,7 +150,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
150
150
  $get: {
151
151
  input: {};
152
152
  output: {
153
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
153
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
154
154
  readonly _tag: "ShiitakeError";
155
155
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
156
156
  readonly message: string;
@@ -238,7 +238,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
238
238
  };
239
239
  };
240
240
  output: {
241
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
241
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
242
242
  readonly _tag: "ShiitakeError";
243
243
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
244
244
  readonly message: string;
@@ -276,7 +276,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
276
276
  };
277
277
  };
278
278
  output: {
279
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
279
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
280
280
  readonly _tag: "ShiitakeError";
281
281
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
282
282
  readonly message: string;
@@ -352,7 +352,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
352
352
  };
353
353
  };
354
354
  output: {
355
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
355
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
356
356
  readonly _tag: "ShiitakeError";
357
357
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
358
358
  readonly message: string;
@@ -481,7 +481,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
481
481
  };
482
482
  };
483
483
  output: {
484
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
484
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
485
485
  readonly _tag: "ShiitakeError";
486
486
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
487
487
  readonly message: string;
@@ -502,6 +502,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
502
502
  };
503
503
  output: {
504
504
  readonly acceptedSender: string | null;
505
+ readonly delegator: string | null;
505
506
  readonly terminal: {
506
507
  readonly commandId: string;
507
508
  readonly outcome: "completed" | "cancelled" | "failed" | "interrupted";
@@ -531,6 +532,11 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
531
532
  };
532
533
  }[];
533
534
  readonly toolCalls: number;
535
+ readonly waitingOnYou: readonly {
536
+ readonly itemId: string;
537
+ readonly kind: string;
538
+ readonly summary: string;
539
+ }[];
534
540
  } | null;
535
541
  outputFormat: "json";
536
542
  status: 200;
@@ -551,7 +557,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
551
557
  };
552
558
  };
553
559
  output: {
554
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
560
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
555
561
  readonly _tag: "ShiitakeError";
556
562
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
557
563
  readonly message: string;
@@ -815,6 +821,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
815
821
  readonly kind: "session";
816
822
  readonly sessionId: string;
817
823
  readonly commandId: string;
824
+ readonly title: string;
818
825
  } | {
819
826
  readonly kind: "host";
820
827
  readonly lane: string;
@@ -843,6 +850,8 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
843
850
  readonly revision: number;
844
851
  readonly text: string;
845
852
  readonly thinking: string;
853
+ readonly textOffset: number;
854
+ readonly thinkingOffset: number;
846
855
  readonly textTruncated: boolean;
847
856
  readonly thinkingTruncated: boolean;
848
857
  } | null;
@@ -873,6 +882,11 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
873
882
  readonly extensionId: string;
874
883
  readonly expiresAt?: number | undefined;
875
884
  readonly inbox?: false | undefined;
885
+ readonly waitingOn?: {
886
+ readonly agentId: string;
887
+ readonly sessionId: string;
888
+ readonly name: string;
889
+ } | undefined;
876
890
  }[];
877
891
  readonly context?: {
878
892
  readonly tokens: number;
@@ -915,7 +929,7 @@ export declare function createShiitakeHttpRoutes(resolveSession: ShiitakeSession
915
929
  };
916
930
  };
917
931
  output: {
918
- readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure";
932
+ readonly cause: "closed" | "conflict" | "incompatible" | "invalid" | "rejected" | "unavailable" | "state_reset_required" | "storage_pressure" | "archived";
919
933
  readonly _tag: "ShiitakeError";
920
934
  readonly boundary: "contract" | "extensions" | "instructions" | "model" | "runtime" | "store" | "tools";
921
935
  readonly message: string;
package/dist/http.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { createShiitakeHttpRoutes, type ShiitakeHttpEnv, type ShiitakeSessionResolver, } from "./http-routes.js";
2
+ export { toShiitakeErrorWire } from "./errors.js";
package/dist/http.js CHANGED
@@ -1 +1,2 @@
1
1
  export { createShiitakeHttpRoutes, } from "./http-routes.js";
2
+ export { toShiitakeErrorWire } from "./errors.js";
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import type { VerifiedAgentReply } from "./internal/woodstock/index.js";
5
5
  import type { BirdDogEffect } from "@fungi.computer/bird-dog/effect";
6
6
  import type { Accepted } from "./client.js";
7
7
  import type { ShiitakeOptions, ShiitakeOwnerAgent } from "./internal/contracts.js";
8
- export type { AbortCommand, AgentExtensionControls, BranchCommand, CompactCommand, CancelCommand, CancelResult, Accepted, DelegationReceipt, Held, ForkCommand, ForkedSession, OperatorCommand, TitleCommand, PromptCommand, PromptDelivery, SendOptions, ReadOptions, SessionCommand, SessionCommandHandler, SessionExtensionControls, SessionExtensionSlots, SessionFrame, SessionMessage, SessionProgress, SessionToolOutput, SessionContext, SessionSnapshot, SessionInboxState, SessionSummary, SessionSummaryPage, SessionSummaryRow, SessionActivity, AgentRunningWork, SessionTerminal, SessionCompactionMarker, SessionCompactionNotice, SessionCompactionTrigger, ShiitakeEventSourceConstructor, SessionWatch, ShiitakeAgent, ShiitakeSession, SubscribeOptions, TooLate, UnknownRequest, WatchOptions, } from "./client.js";
8
+ export type { AbortCommand, AgentExtensionControls, BranchCommand, CompactCommand, CancelCommand, CancelResult, Accepted, DelegationReceipt, Held, ForkCommand, ForkedSession, OperatorCommand, TitleCommand, PromptCommand, PromptDelivery, SendOptions, ReadOptions, SessionCommand, SessionCommandHandler, SessionExtensionControls, SessionExtensionSlots, SessionFrame, SessionMessage, SessionProgress, SessionProgressDelta, SessionToolOutput, SessionContext, SessionSnapshot, SessionInboxState, SessionSummary, SessionSummaryPage, SessionSummaryRow, SessionActivity, AgentRunningWork, SessionTerminal, SessionCompactionMarker, SessionCompactionNotice, SessionCompactionTrigger, ShiitakeEventSourceConstructor, SessionWatch, ShiitakeAgent, ShiitakeSession, SubscribeOptions, TooLate, UnknownRequest, WatchOptions, } from "./client.js";
9
9
  export type { ColdSessionPage, ColdSessionPageRequest, ColdSessionRow, } from "./cold-sessions.js";
10
10
  export { parseForkedSession, parseSessionFrame, parseSessionSnapshot, } from "./client.js";
11
11
  export type { InstructionSnapshot, InstructionSource, ModelAcquisition, ModelDescription, ModelSource, RuntimeHost, RuntimePendingWork, SqliteCursor, SqliteOwner, SqliteValue, SqliteRow, StoragePressureCheck, CompactionBudget, CompactionOptions, GrantsDelivery, GrantsDeliveryResult, ShiitakeOptions, ShiitakeOwnerAgent, ToolAccess, HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, ToolSource, } from "./internal/contracts.js";
@@ -9,7 +9,7 @@ import { ExtensionId } from "../extension-contract.js";
9
9
  import { publishOwnerObservation } from "./observation-kernel.js";
10
10
  import { returnedFailureReason } from "./run-failure-reason.js";
11
11
  import { coreProtocolDigest } from "./extension-roster.js";
12
- import { runAcceptedCommand, runAcceptedCompaction, ToolSetupError, } from "./run-scope.js";
12
+ import { runAcceptedCommand, runAcceptedCompaction, RunRejectedError, ToolSetupError, } from "./run-scope.js";
13
13
  import { CompactionCommitError } from "./compaction/session-compaction.js";
14
14
  import { BrokenSessionError, isCompactCommand, loadAcceptedRun, readExtensionAdmissionOrigin, recordWorkSettlement, } from "./woodstock/index.js";
15
15
  /** Bounded process-loss recovery for every accepted Agent turn. */
@@ -25,6 +25,7 @@ function runtimeFailure(message) {
25
25
  function failedDecision(error) {
26
26
  const category = `${error.boundary}:${error.cause}`;
27
27
  const diagnostic = error instanceof ToolSetupError ||
28
+ error instanceof RunRejectedError ||
28
29
  error instanceof CompactionCommitError ||
29
30
  error instanceof BrokenSessionError
30
31
  ? error.diagnostic
@@ -32,8 +32,13 @@ 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. */
35
+ /** Synchronous owner-local side effect on first admission, in its transaction. */
36
36
  onAccepted?: (sessionId: string, commandId: string) => void;
37
+ /**
38
+ * New work that continues earlier work in this Session (for example, the
39
+ * follow-up that delivers an answer) keeps that work's sender.
40
+ */
41
+ continuesWorkId?: string | undefined;
37
42
  }>;
38
43
  /** Outcome of one door admission. */
39
44
  export type AdmitResult = Readonly<{
@@ -1,10 +1,11 @@
1
+ import { messageAuthor } from "@fungi.computer/agar/agents";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Schema from "effect/Schema";
3
4
  import { checkHostMeta } from "./host-meta.js";
4
5
  import { ShiitakeError } from "../errors.js";
5
6
  import { decodeClientAcceptedCommand, decodeClientSessionId, decodeClientSessionCreationRequestId, requireDecoded, } from "./client-boundary.js";
6
7
  import { publishOwnerObservation } from "./observation-kernel.js";
7
- import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, VerifiedAgentReplySchema, } from "./woodstock/index.js";
8
+ import { AdmissionOriginSchema, acceptSessionCommandOwned, readSessionCommandAdmissionByRequest, withdrawSessionRequestOwned, assertSessionRequestNotWithdrawn, hasStoredSession, hasRecordedRunTerminal, readWorkSender, recordWorkSender, VerifiedAgentReplySchema, } from "./woodstock/index.js";
8
9
  const decodeOrigin = Schema.decodeUnknownResult(AdmissionOriginSchema, {
9
10
  onExcessProperty: "error",
10
11
  });
@@ -29,6 +30,24 @@ function decodeReply(request) {
29
30
  throw new ShiitakeError("contract", "invalid", "Verified Agent reply does not match its mail");
30
31
  return reply;
31
32
  }
33
+ /**
34
+ * The Agent Session whose work this admission is: the sender of delegated
35
+ * mail that expects a reply, or the sender of the earlier work this one
36
+ * continues. Replies and every other admission have none.
37
+ */
38
+ function workSender(owner, request) {
39
+ const mail = request.mail;
40
+ if (mail?.from.kind === "agent" && mail.replyTo.length > 0)
41
+ return {
42
+ agentId: mail.from.agentId,
43
+ sessionId: mail.from.sessionId,
44
+ // The host-authenticated author names the sender; mail.meta is the sender's own claim.
45
+ name: messageAuthor(request.meta)?.name ?? mail.from.agentId,
46
+ };
47
+ return request.continuesWorkId === undefined
48
+ ? undefined
49
+ : readWorkSender(owner, request.sessionId, request.continuesWorkId);
50
+ }
32
51
  /** Bind the door to one owner, run contract, Watchdog queue and host wake. */
33
52
  export function openAdmissionDoor(input) {
34
53
  /** The door's one decode, done before any owner operation when possible. */
@@ -41,6 +60,7 @@ export function openAdmissionDoor(input) {
41
60
  mail: request.mail,
42
61
  reply: decodeReply(request),
43
62
  onAccepted: request.onAccepted,
63
+ continuesWorkId: request.continuesWorkId,
44
64
  meta: request.meta === undefined
45
65
  ? undefined
46
66
  : checkHostMeta(input.hostMeta, request.meta),
@@ -67,7 +87,8 @@ export function openAdmissionDoor(input) {
67
87
  const targetWorkId = command.type === "prompt" &&
68
88
  command.delivery === "steer" &&
69
89
  head.state === "running" &&
70
- !head.cancellationRequested
90
+ !head.cancellationRequested &&
91
+ !hasRecordedRunTerminal(input.owner, sessionId, head.jobId)
71
92
  ? head.jobId
72
93
  : undefined;
73
94
  const accepted = acceptSessionCommandOwned(input.owner, sessionId, command, request.meta, origin, input.contract(), targetWorkId === undefined
@@ -75,7 +96,16 @@ export function openAdmissionDoor(input) {
75
96
  input.enqueue(work, request.mail);
76
97
  }
77
98
  : undefined, targetWorkId, request.reply);
78
- request.onAccepted?.(sessionId, accepted.accepted.commandId);
99
+ if (accepted.delivery === "new" && targetWorkId === undefined) {
100
+ const sender = workSender(input.owner, request);
101
+ if (sender !== undefined)
102
+ recordWorkSender(input.owner, sessionId, accepted.accepted.commandId, sender);
103
+ }
104
+ // The mail notice is staged in the same transaction as the first command.
105
+ // Replaying that admission must not stage a new notice with a different
106
+ // createdAt under the original Post key.
107
+ if (accepted.delivery === "new")
108
+ request.onAccepted?.(sessionId, accepted.accepted.commandId);
79
109
  touched.set(sessionId, (touched.get(sessionId) ?? false) || accepted.delivery === "new");
80
110
  return Object.freeze({
81
111
  status: "accepted",
@@ -3,7 +3,7 @@ import type { SessionTerminal, ShiitakeAgent } from "../client.js";
3
3
  import type { GrantsDelivery, GrantsDeliveryResult } from "./woodstock/index.js";
4
4
  import type { Json } from "../extensions.js";
5
5
  import type { ExtensionRosterV0 } from "../extensions.js";
6
- import type { Api, AssistantMessage, Model, MuleStreamFunction } from "@fungi.computer/mule/effect";
6
+ import type { Api, AssistantMessage, Model, MuleStreamFunction, ProviderRejection } from "@fungi.computer/mule/effect";
7
7
  import type { AccessOptions, CapabilitySource, ExecutionOptions, ModuleRegistration, Sandbox } from "@fungi.computer/mycelium";
8
8
  import type { ModelPreference } from "./model-preference.js";
9
9
  import type { WatchdogSqliteValue, WatchdogSqliteRow, WatchdogSqliteOwner, WatchdogSqliteCursor } from "@fungi.computer/watchdog";
@@ -47,6 +47,8 @@ export type ModelAcquisition = Readonly<{
47
47
  * Unknown messages must return undefined.
48
48
  */
49
49
  failureReason?: (message: AssistantMessage) => string | undefined;
50
+ /** Bounded provider facts for this door's exact failure, never SDK prose. */
51
+ rejection?: (message: AssistantMessage) => ProviderRejection | undefined;
50
52
  /** Provider prose for this door's exact failed turn; never a diagnostic. */
51
53
  providerError?: (message: AssistantMessage) => string | undefined;
52
54
  /** Release the acquired model resources. */
@@ -186,6 +188,11 @@ export type ShiitakeOptions = Readonly<{
186
188
  store: SqliteOwner;
187
189
  /** Host storage-pressure check; absent never refuses for storage pressure. */
188
190
  storagePressure?: StoragePressureCheck;
191
+ /** Host-owned, synchronous admission for new Sessions and prompts only. */
192
+ admission?: Readonly<{
193
+ /** Check the host's current flag synchronously inside admission's transaction. */
194
+ assertOpen: () => void;
195
+ }>;
189
196
  /** Compaction budgets; absent uses Pi's defaults for every model. */
190
197
  compaction?: CompactionOptions;
191
198
  /** Runtime host that schedules the opaque durable reconciler. */
@@ -253,6 +260,8 @@ export type { HostMetaOptions, StandardHostMetaResult, StandardHostMetaSchema, }
253
260
  export type CommandSettlement = Readonly<{
254
261
  /** Original Agent sender recorded at acceptance, never the terminal's canceller. */
255
262
  acceptedSender: string | null;
263
+ /** Agent that first sent this Session work and receives its reports, if any. */
264
+ delegator: string | null;
256
265
  /** Durable terminal and the command that ended the work's opaque metadata. */
257
266
  terminal: SessionTerminal;
258
267
  /** Last completed assistant text; cancelled work returns no answer. */
@@ -261,4 +270,13 @@ export type CommandSettlement = Readonly<{
261
270
  usage: readonly Pick<AssistantMessage, "provider" | "model" | "usage">[];
262
271
  /** Distinct tool-call intentions belonging to this exact work. */
263
272
  toolCalls: number;
273
+ /**
274
+ * Open items of the Session that now wait on the Agent that sent this work.
275
+ * Child-authored data: summaries inform, only the sender's host checks decide.
276
+ */
277
+ waitingOnYou: readonly Readonly<{
278
+ itemId: string;
279
+ kind: string;
280
+ summary: string;
281
+ }>[];
264
282
  }>;