@fungi.computer/shiitake 0.3.1 → 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 (63) 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 +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 +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.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 +3 -2
  44. package/dist/internal/woodstock/fleet-store.js +43 -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
@@ -3,7 +3,7 @@ import * as Schema from "effect/Schema";
3
3
  import { type BirdDogEffect } from "@fungi.computer/bird-dog/effect";
4
4
  import type { BirdDogMail } from "@fungi.computer/bird-dog";
5
5
  import { type PostRoute, type PostRuntime, type PostEnvelope, type PostDeliveryResult } from "@fungi.computer/carrier/post";
6
- import { type Accepted, type CancelResult } from "./client.js";
6
+ import { type Accepted, type DelegationReceipt, type CancelResult } from "./client.js";
7
7
  import type { CommandSettlement, ShiitakeHost, VerifiedAgentReply } from "./index.js";
8
8
  import type { AgentControlDirectory, AgentDelegationInput, AgentDelegationInvocation, AgentRecallInput } from "./agent-control.js";
9
9
  /** Shiitake host-pack identity; core consumes OysterAgents, never this engine. */
@@ -19,19 +19,23 @@ export declare class AgentDelegation {
19
19
  expiresAt: () => number;
20
20
  identity: () => DelegationIdentity | null | Promise<DelegationIdentity | null>;
21
21
  directory: () => AgentControlDirectory;
22
- deliver: (mail: BirdDogMail) => Promise<Accepted>;
22
+ deliver: (mail: BirdDogMail) => Promise<DelegationReceipt>;
23
23
  prepare: (mail: BirdDogMail) => Promise<void>;
24
+ /**
25
+ * Synchronously stage the accepted notice in this recipient's durable
26
+ * outbox, in the same transaction as acceptance.
27
+ */
28
+ stageAcceptedNotice: (mail: BirdDogMail, scopeId: string) => void;
24
29
  /** Admit mail; `reply` is present only for an authenticated reply. */
25
- accept: (mail: BirdDogMail, reply?: VerifiedAgentReply) => Promise<Accepted>;
30
+ accept: (mail: BirdDogMail, reply: VerifiedAgentReply | undefined, stageAcceptedNotice: (mail: BirdDogMail) => void) => Promise<Accepted>;
26
31
  }>);
27
32
  private fleetOwner;
28
33
  private fleet;
29
34
  private scope;
30
- private requestKey;
31
35
  /** Recipient-local disclosure through the public host, never another Agent's RPC. */
32
36
  settlement(sender: DelegationIdentity, sessionId: string, commandId: string, host: Pick<ShiitakeHost, "readSettlement">): Promise<CommandSettlement | undefined>;
33
37
  /** Delegate work to an in-scope Agent: steer its Session, or start fleet correspondence. */
34
- send(input: AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: AgentDelegationInvocation): Promise<Accepted>;
38
+ send(input: AgentDelegationInput, sourceSessionId: string, signal: AbortSignal, invocation?: AgentDelegationInvocation): Promise<DelegationReceipt>;
35
39
  /** Resolve this sender's exact request, then let the recipient guard pending work. */
36
40
  recall(input: AgentRecallInput, sourceSessionId: string, signal: AbortSignal): Promise<CancelResult>;
37
41
  /**
@@ -41,8 +45,11 @@ export declare class AgentDelegation {
41
45
  * settled or cancelled the work, and its meta carries the verified reply.
42
46
  */
43
47
  private authenticatedReply;
48
+ private parseAddressedMail;
44
49
  /** Admit one addressed Agent mail for this Agent, or refuse it. */
45
50
  receive(raw: PostEnvelope): Promise<PostDeliveryResult>;
51
+ private recordAcceptedNotice;
52
+ private acceptAdmitted;
46
53
  }
47
54
  /** A current correspondent no longer has disclosure authority. */
48
55
  export declare class AgentDisclosureRefused extends Error {
@@ -63,7 +70,7 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
63
70
  readonly to: {
64
71
  readonly kind: "agent";
65
72
  readonly agentId: string;
66
- readonly sessionId: string;
73
+ readonly sessionId?: string;
67
74
  };
68
75
  readonly replyTo: readonly {
69
76
  readonly kind: "agent";
@@ -75,7 +82,37 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
75
82
  readonly commandId: string;
76
83
  readonly outcome: "completed" | "failed" | "cancelled" | "interrupted" | "outcome_unknown";
77
84
  readonly diagnostic?: string;
85
+ } | {
86
+ readonly outcome: "undeliverable";
87
+ readonly reason: "recipient_offline" | "recipient_left";
88
+ } | {
89
+ readonly outcome: "accepted";
90
+ readonly commandId: string;
91
+ };
92
+ } | {
93
+ readonly key: string;
94
+ readonly reply: {
95
+ readonly outcome: "undeliverable";
96
+ readonly reason: "recipient_offline" | "recipient_left";
78
97
  };
98
+ readonly content: string;
99
+ readonly createdAt: number;
100
+ readonly from: {
101
+ readonly kind: "agent";
102
+ readonly agentId: string;
103
+ readonly sessionId?: string;
104
+ };
105
+ readonly to: {
106
+ readonly kind: "agent";
107
+ readonly agentId: string;
108
+ readonly sessionId?: string;
109
+ };
110
+ readonly replyTo: readonly {
111
+ readonly kind: "agent";
112
+ readonly agentId: string;
113
+ readonly sessionId: string;
114
+ }[];
115
+ readonly meta?: Schema.Json;
79
116
  }, {
80
117
  readonly key: string;
81
118
  readonly content: string;
@@ -88,7 +125,7 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
88
125
  readonly to: {
89
126
  readonly kind: "agent";
90
127
  readonly agentId: string;
91
- readonly sessionId: string;
128
+ readonly sessionId?: string;
92
129
  };
93
130
  readonly replyTo: readonly {
94
131
  readonly kind: "agent";
@@ -100,7 +137,37 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
100
137
  readonly commandId: string;
101
138
  readonly outcome: "completed" | "failed" | "cancelled" | "interrupted" | "outcome_unknown";
102
139
  readonly diagnostic?: string;
140
+ } | {
141
+ readonly outcome: "undeliverable";
142
+ readonly reason: "recipient_offline" | "recipient_left";
143
+ } | {
144
+ readonly outcome: "accepted";
145
+ readonly commandId: string;
146
+ };
147
+ } | {
148
+ readonly key: string;
149
+ readonly reply: {
150
+ readonly outcome: "undeliverable";
151
+ readonly reason: "recipient_offline" | "recipient_left";
152
+ };
153
+ readonly content: string;
154
+ readonly createdAt: number;
155
+ readonly from: {
156
+ readonly kind: "agent";
157
+ readonly agentId: string;
158
+ readonly sessionId?: string;
159
+ };
160
+ readonly to: {
161
+ readonly kind: "agent";
162
+ readonly agentId: string;
163
+ readonly sessionId?: string;
103
164
  };
165
+ readonly replyTo: readonly {
166
+ readonly kind: "agent";
167
+ readonly agentId: string;
168
+ readonly sessionId: string;
169
+ }[];
170
+ readonly meta?: Schema.Json;
104
171
  }, import("@fungi.computer/seam/machine-link-protocol").AgentMailAddress, {
105
172
  readonly key: string;
106
173
  readonly content: string;
@@ -113,7 +180,7 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
113
180
  readonly to: {
114
181
  readonly kind: "agent";
115
182
  readonly agentId: string;
116
- readonly sessionId: string;
183
+ readonly sessionId?: string;
117
184
  };
118
185
  readonly replyTo: readonly {
119
186
  readonly kind: "agent";
@@ -125,7 +192,37 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
125
192
  readonly commandId: string;
126
193
  readonly outcome: "completed" | "failed" | "cancelled" | "interrupted" | "outcome_unknown";
127
194
  readonly diagnostic?: string;
195
+ } | {
196
+ readonly outcome: "undeliverable";
197
+ readonly reason: "recipient_offline" | "recipient_left";
198
+ } | {
199
+ readonly outcome: "accepted";
200
+ readonly commandId: string;
201
+ };
202
+ } | {
203
+ readonly key: string;
204
+ readonly reply: {
205
+ readonly outcome: "undeliverable";
206
+ readonly reason: "recipient_offline" | "recipient_left";
207
+ };
208
+ readonly content: string;
209
+ readonly createdAt: number;
210
+ readonly from: {
211
+ readonly kind: "agent";
212
+ readonly agentId: string;
213
+ readonly sessionId?: string;
214
+ };
215
+ readonly to: {
216
+ readonly kind: "agent";
217
+ readonly agentId: string;
218
+ readonly sessionId?: string;
128
219
  };
220
+ readonly replyTo: readonly {
221
+ readonly kind: "agent";
222
+ readonly agentId: string;
223
+ readonly sessionId: string;
224
+ }[];
225
+ readonly meta?: Schema.Json;
129
226
  }, {
130
227
  readonly key: string;
131
228
  readonly content: string;
@@ -138,7 +235,7 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
138
235
  readonly to: {
139
236
  readonly kind: "agent";
140
237
  readonly agentId: string;
141
- readonly sessionId: string;
238
+ readonly sessionId?: string;
142
239
  };
143
240
  readonly replyTo: readonly {
144
241
  readonly kind: "agent";
@@ -150,9 +247,40 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
150
247
  readonly commandId: string;
151
248
  readonly outcome: "completed" | "failed" | "cancelled" | "interrupted" | "outcome_unknown";
152
249
  readonly diagnostic?: string;
250
+ } | {
251
+ readonly outcome: "undeliverable";
252
+ readonly reason: "recipient_offline" | "recipient_left";
253
+ } | {
254
+ readonly outcome: "accepted";
255
+ readonly commandId: string;
256
+ };
257
+ } | {
258
+ readonly key: string;
259
+ readonly reply: {
260
+ readonly outcome: "undeliverable";
261
+ readonly reason: "recipient_offline" | "recipient_left";
262
+ };
263
+ readonly content: string;
264
+ readonly createdAt: number;
265
+ readonly from: {
266
+ readonly kind: "agent";
267
+ readonly agentId: string;
268
+ readonly sessionId?: string;
269
+ };
270
+ readonly to: {
271
+ readonly kind: "agent";
272
+ readonly agentId: string;
273
+ readonly sessionId?: string;
153
274
  };
275
+ readonly replyTo: readonly {
276
+ readonly kind: "agent";
277
+ readonly agentId: string;
278
+ readonly sessionId: string;
279
+ }[];
280
+ readonly meta?: Schema.Json;
154
281
  }> & Omit<Readonly<{
155
282
  addressKind: string;
283
+ deadlineMs?: number | undefined;
156
284
  address: Schema.Codec<unknown, unknown>;
157
285
  event: Schema.Codec<unknown, unknown>;
158
286
  encodeEnvelope: (envelope: PostEnvelope) => PostEnvelope;
@@ -181,10 +309,14 @@ export declare class AgentMail {
181
309
  wake: () => Promise<void>;
182
310
  }>);
183
311
  private envelope;
184
- /** Submit one mail and return the recipient's Accepted receipt. */
185
- send(mail: BirdDogMail): Promise<Accepted>;
312
+ /** Submit one mail and return recipient acceptance or relay-held custody. */
313
+ send(mail: BirdDogMail): Promise<DelegationReceipt>;
314
+ /** Stage the acceptance reply through this Post owner without leaving the transaction. */
315
+ stageAcceptedNotice(mail: BirdDogMail, scopeId: string): void;
186
316
  /** BirdDog's mail room: disclosed settlement replies fan out over Post. */
187
- mailRoom(): BirdDogEffect;
317
+ mailRoom(): BirdDogEffect & {
318
+ stageAcceptedNotice: (mail: BirdDogMail, scopeId: string) => void;
319
+ };
188
320
  }
189
321
  /**
190
322
  * The Agent wire decoders a host lends Oyster's machine-link client, so the
@@ -197,7 +329,7 @@ export declare const machineAgentCodecs: {
197
329
  }> | null;
198
330
  /** One serialized settlement from a hosted or machine-homed Agent. */
199
331
  readonly settlement: (encoded: string) => CommandSettlement;
200
- /** Throws unless an acknowledgment carries a well-formed Accepted receipt. */
332
+ /** Throws unless an acknowledgment carries a valid acceptance or Held result. */
201
333
  readonly accepted: (receipt: Schema.Json) => void;
202
334
  };
203
335
  /**
@@ -3,15 +3,14 @@
3
3
  * addressed Agent mail over Carrier Post, and the Agent grammar a host hands
4
4
  * to Oyster's machine link as ports. Oyster core never imports this engine.
5
5
  */
6
- import { commandSettlementSchema, createAgentDirectory, createFleetCorrespondence, delegationProvenance, delegationReplyMeta, delegationSendMeta, } from "./fleet.js";
7
- import { deterministicReplayKey } from "@fungi.computer/cairn";
6
+ import { commandSettlementSchema, createAgentDirectory, createFleetCorrespondence, agentDelegationRequestKey, delegationProvenance, delegationReplyMeta, delegationSendMeta, } from "./fleet.js";
8
7
  import * as Schema from "effect/Schema";
9
8
  import { agentMailAddressSchema } from "@fungi.computer/seam/machine-link-protocol";
10
9
  import { BirdDogMailSchema, BirdDogError, settlementReplies, } from "@fungi.computer/bird-dog/effect";
11
10
  import * as Effect from "effect/Effect";
12
11
  import { messageAuthor } from "@fungi.computer/agar/agents";
13
12
  import { PostEnvelopeSchema, definePostRoute, } from "@fungi.computer/carrier/post";
14
- import { parseAccepted } from "./client.js";
13
+ import { parseDelegationReceipt, } from "./client.js";
15
14
  import { decodeClientSessionCreationRequest } from "./internal/client-boundary.js";
16
15
  /** Host-owned identity and admission around BirdDog's ordinary envelope. */
17
16
  export class AgentDelegation {
@@ -37,16 +36,6 @@ export class AgentDelegation {
37
36
  throw new Error("Agent is outside this scope");
38
37
  return { ...identity, agents, source };
39
38
  }
40
- requestKey(scope, input, sourceSessionId) {
41
- return deterministicReplayKey("oyster-agent-delegation", [
42
- scope.scopeId,
43
- scope.agentId,
44
- sourceSessionId,
45
- input.agentId,
46
- input.sessionId,
47
- input.requestId,
48
- ]);
49
- }
50
39
  /** Recipient-local disclosure through the public host, never another Agent's RPC. */
51
40
  async settlement(sender, sessionId, commandId, host) {
52
41
  const scope = await this.scope(new AbortController().signal);
@@ -65,7 +54,16 @@ export class AgentDelegation {
65
54
  const scope = await this.scope(signal);
66
55
  if (!scope.agents.some(({ agentId }) => agentId === input.agentId))
67
56
  throw new Error("Agent is outside this scope");
68
- const requestId = await this.requestKey(scope, input, sourceSessionId);
57
+ const requestId = await agentDelegationRequestKey({
58
+ owner: {
59
+ kind: "hosted",
60
+ scopeId: scope.scopeId,
61
+ agentId: scope.agentId,
62
+ },
63
+ sourceSessionId,
64
+ targetAgentId: input.agentId,
65
+ requestId: input.requestId,
66
+ });
69
67
  signal.throwIfAborted();
70
68
  if (input.delivery === "steer") {
71
69
  const opened = await this.deps.directory().open(input.agentId, signal);
@@ -104,12 +102,25 @@ export class AgentDelegation {
104
102
  const scope = await this.scope(signal);
105
103
  if (!scope.agents.some(({ agentId }) => agentId === input.agentId))
106
104
  throw new Error("Agent is outside this scope");
107
- const requestId = await this.requestKey(scope, input, sourceSessionId);
105
+ const requestId = await agentDelegationRequestKey({
106
+ owner: {
107
+ kind: "hosted",
108
+ scopeId: scope.scopeId,
109
+ agentId: scope.agentId,
110
+ },
111
+ sourceSessionId,
112
+ targetAgentId: input.agentId,
113
+ requestId: input.requestId,
114
+ });
108
115
  signal.throwIfAborted();
109
116
  const opened = await this.deps.directory().open(input.agentId, signal);
110
117
  try {
111
- const session = opened.agent.session(input.sessionId);
112
- const admitted = this.fleet().receipt(requestId) ?? (await session.withdraw(requestId));
118
+ const receipt = this.fleet().receipt(requestId);
119
+ if (receipt?.sessionId !== undefined &&
120
+ receipt.sessionId !== input.sessionId)
121
+ return { status: "unknown" };
122
+ const session = opened.agent.session(receipt?.sessionId ?? input.sessionId);
123
+ const admitted = receipt ?? (await session.withdraw(requestId));
113
124
  return admitted.status === "accepted"
114
125
  ? await session.cancel({
115
126
  commandId: admitted.commandId,
@@ -133,46 +144,81 @@ export class AgentDelegation {
133
144
  return undefined;
134
145
  const scope = await this.scope(new AbortController().signal);
135
146
  const replier = scope.agents.find(({ agentId }) => agentId === delegation.reply.agentId);
136
- if (replier === undefined)
147
+ const undeliverable = delegation.reply.outcome === "undeliverable";
148
+ if (replier === undefined && !undeliverable)
137
149
  return undefined;
138
- const meta = delegationReplyMeta(delegation, replier.name);
150
+ const meta = delegationReplyMeta(delegation, replier?.name ?? delegation.reply.agentId);
139
151
  return { mail: { ...mail, meta }, reply: delegation.reply };
140
152
  }
141
- /** Admit one addressed Agent mail for this Agent, or refuse it. */
142
- async receive(raw) {
153
+ parseAddressedMail(raw) {
143
154
  const envelope = Schema.decodeUnknownOption(PostEnvelopeSchema)(raw);
144
155
  if (envelope._tag === "None" || envelope.value.kind !== "agentMail")
145
- return "refused";
156
+ return undefined;
146
157
  const address = Schema.decodeUnknownOption(agentMailAddressSchema)(envelope.value.address);
147
- const parsed = Schema.decodeUnknownOption(BirdDogMailSchema)(envelope.value.event);
148
- if (address._tag === "None" || parsed._tag === "None")
158
+ const mail = Schema.decodeUnknownOption(BirdDogMailSchema)(envelope.value.event);
159
+ if (address._tag === "None" || mail._tag === "None")
160
+ return undefined;
161
+ return {
162
+ envelope: envelope.value,
163
+ address: address.value,
164
+ mail: mail.value,
165
+ };
166
+ }
167
+ /** Admit one addressed Agent mail for this Agent, or refuse it. */
168
+ async receive(raw) {
169
+ const addressed = this.parseAddressedMail(raw);
170
+ if (addressed === undefined)
149
171
  return "refused";
150
- const mail = parsed.value;
172
+ const { envelope, address, mail } = addressed;
173
+ let authenticated;
151
174
  const valid = async () => {
152
175
  const scope = await this.scope(new AbortController().signal);
153
- return (address.value.scopeId === scope.scopeId &&
154
- address.value.agentId === scope.agentId &&
176
+ const frozenUndeliverable = authenticated?.reply.outcome === "undeliverable" &&
177
+ authenticated.reply.agentId === mail.from.agentId;
178
+ return (address.scopeId === scope.scopeId &&
179
+ address.agentId === scope.agentId &&
155
180
  mail.to.agentId === scope.agentId &&
156
- envelope.value.key === mail.key &&
157
- [mail.from, ...mail.replyTo].every((ref) => scope.agents.some(({ agentId }) => agentId === ref.agentId)) &&
181
+ envelope.key === mail.key &&
182
+ (frozenUndeliverable ||
183
+ scope.agents.some(({ agentId }) => agentId === mail.from.agentId)) &&
184
+ mail.replyTo.every(({ agentId }) => scope.agents.some((agent) => agent.agentId === agentId)) &&
158
185
  (mail.replyTo.length === 0 ||
159
186
  (mail.replyTo.length === 1 &&
160
187
  mail.replyTo[0]?.agentId === mail.from.agentId &&
161
188
  mail.replyTo[0].sessionId === mail.from.sessionId)));
162
189
  };
190
+ if (mail.replyTo.length === 0)
191
+ authenticated = await this.authenticatedReply(mail);
163
192
  if (!(await valid()))
164
193
  return "refused";
165
- const admitted = mail.replyTo.length > 0
166
- ? { mail, reply: undefined }
167
- : await this.authenticatedReply(mail);
194
+ if (mail.reply?.outcome === "accepted")
195
+ return this.recordAcceptedNotice(mail, valid);
196
+ const admitted = mail.replyTo.length > 0 ? { mail, reply: undefined } : authenticated;
168
197
  if (admitted === undefined)
169
198
  return "refused";
199
+ return this.acceptAdmitted(admitted, address.scopeId, valid);
200
+ }
201
+ async recordAcceptedNotice(mail, valid) {
202
+ if (mail.to.sessionId === undefined)
203
+ return "refused";
204
+ await this.deps.prepare(mail);
205
+ if (!(await valid()))
206
+ return "refused";
207
+ const receipt = this.fleet().recordAcceptedNotice(mail);
208
+ return receipt === undefined ? "refused" : { status: "accepted", receipt };
209
+ }
210
+ async acceptAdmitted(admitted, scopeId, valid) {
170
211
  await this.deps.prepare(admitted.mail);
171
212
  if (!(await valid()))
172
213
  return "refused";
214
+ const stageAcceptedNotice = (notice) => {
215
+ this.deps.stageAcceptedNotice(notice, scopeId);
216
+ };
217
+ const receipt = await this.deps.accept(admitted.mail, admitted.reply, stageAcceptedNotice);
218
+ const sessionId = admitted.mail.to.sessionId ?? receipt.sessionId;
173
219
  return {
174
220
  status: "accepted",
175
- receipt: await this.deps.accept(admitted.mail, admitted.reply),
221
+ receipt: sessionId === undefined ? receipt : { ...receipt, sessionId },
176
222
  };
177
223
  }
178
224
  }
@@ -194,6 +240,13 @@ export function agentMailRoute(namespace, replySettlement) {
194
240
  deliver: async (envelope) => {
195
241
  const mail = Schema.decodeUnknownSync(BirdDogMailSchema)(envelope.event);
196
242
  if (mail.reply !== undefined &&
243
+ mail.reply.outcome !== "accepted" &&
244
+ mail.reply.outcome !== "undeliverable" &&
245
+ mail.to.sessionId === undefined)
246
+ return "refused";
247
+ if (mail.reply !== undefined &&
248
+ mail.reply.outcome !== "undeliverable" &&
249
+ mail.reply.outcome !== "accepted" &&
197
250
  (await replySettlement(mail)) === undefined)
198
251
  return "refused";
199
252
  return namespace.getByName(name).deliver(envelope);
@@ -219,18 +272,27 @@ export class AgentMail {
219
272
  event: mail,
220
273
  };
221
274
  }
222
- /** Submit one mail and return the recipient's Accepted receipt. */
275
+ /** Submit one mail and return recipient acceptance or relay-held custody. */
223
276
  async send(mail) {
224
277
  this.deps.post().send(await this.envelope(mail));
225
278
  // Borrow the existing Post lane once, not a new waiter, loop or supervisor.
226
279
  await this.deps.drain();
227
280
  const row = this.deps.post().read(mail.key);
228
281
  if (row?.state === "delivered" && row.receipt !== undefined)
229
- return parseAccepted(JSON.stringify(row.receipt));
282
+ return parseDelegationReceipt(JSON.stringify(row.receipt));
230
283
  if (row?.state === "dead")
231
284
  throw new Error(`Agent delivery ${row.reason}`);
232
285
  throw new Error("Agent did not return a delegated work receipt");
233
286
  }
287
+ /** Stage the acceptance reply through this Post owner without leaving the transaction. */
288
+ stageAcceptedNotice(mail, scopeId) {
289
+ this.deps.post().send({
290
+ key: mail.key,
291
+ kind: "agentMail",
292
+ address: { scopeId, agentId: mail.to.agentId },
293
+ event: mail,
294
+ });
295
+ }
234
296
  /** BirdDog's mail room: disclosed settlement replies fan out over Post. */
235
297
  mailRoom() {
236
298
  return {
@@ -268,6 +330,16 @@ export class AgentMail {
268
330
  },
269
331
  catch: (cause) => new BirdDogError("Agent reply fan-out is unavailable", { cause }),
270
332
  }),
333
+ postMail: (mail) => Effect.tryPromise({
334
+ try: async () => {
335
+ this.deps.post().send(await this.envelope(mail));
336
+ await this.deps.wake();
337
+ },
338
+ catch: (cause) => new BirdDogError("Agent mail staging is unavailable", { cause }),
339
+ }),
340
+ stageAcceptedNotice: (mail, scopeId) => {
341
+ this.stageAcceptedNotice(mail, scopeId);
342
+ },
271
343
  drainOutbox: () => Effect.void,
272
344
  nextWakeAt: () => Effect.try({
273
345
  try: () => this.deps.post().nextWake() ?? undefined,
@@ -290,9 +362,9 @@ export const machineAgentCodecs = {
290
362
  settlement(encoded) {
291
363
  return Schema.decodeUnknownSync(Schema.fromJsonString(commandSettlementSchema))(encoded);
292
364
  },
293
- /** Throws unless an acknowledgment carries a well-formed Accepted receipt. */
365
+ /** Throws unless an acknowledgment carries a valid acceptance or Held result. */
294
366
  accepted(receipt) {
295
- parseAccepted(JSON.stringify(receipt));
367
+ parseDelegationReceipt(JSON.stringify(receipt));
296
368
  },
297
369
  };
298
370
  /**
package/dist/effect.d.ts CHANGED
@@ -22,7 +22,7 @@ export type ShiitakeEffectHost = Readonly<{
22
22
  readSettlement: (sessionId: string, commandId: string) => Effect.Effect<CommandSettlement | undefined, ShiitakeError>;
23
23
  agent: ShiitakeEffectOwner;
24
24
  /** Durably admit one addressed mail item through the host owner. */
25
- acceptMail: (mail: BirdDogMail, meta?: HostJson, reply?: VerifiedAgentReply) => Effect.Effect<Accepted, ShiitakeError>;
25
+ acceptMail: (mail: BirdDogMail, meta?: HostJson, reply?: VerifiedAgentReply, stageAcceptedNotice?: (mail: BirdDogMail) => void) => Effect.Effect<Accepted, ShiitakeError>;
26
26
  }>;
27
27
  /** Open the Effect-native host composition with addressed mail. */
28
28
  export declare function openHost(options: ShiitakeEffectOptions, mailRoom: BirdDogEffect): Effect.Effect<ShiitakeEffectHost, ShiitakeError>;
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. */
@@ -258,22 +276,26 @@ export type OutstandingDelegation = Readonly<{
258
276
  */
259
277
  export declare function createFleetCorrespondence(options: Omit<FleetDelegationOptions, "readSettlement">): {
260
278
  /** Submit frozen mail or reuse its recorded Session admission receipt. */
261
- start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<Accepted>;
279
+ start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<DelegationReceipt>;
262
280
  /** Match a reply to frozen mail and restore the caller's delegation path. */
263
281
  authenticateReply: (mail: BirdDogMail) => AuthenticatedDelegationReply | undefined;
264
282
  /** Read an existing acceptance receipt without reserving or delivering mail. */
265
283
  receipt: (requestId: string) => Accepted | undefined;
284
+ /** Consume a recipient's stable `<mailKey>:accepted` notice. */
285
+ recordAcceptedNotice: (mail: BirdDogMail) => Accepted | undefined;
266
286
  };
267
287
  /** Local hosts may observe exact work directly; remote correspondence never pulls it. */
268
288
  export declare function createFleetDelegation(options: FleetDelegationOptions): {
269
289
  /** Submit or poll the exact turn, yielding Agar Pending until settled. */
270
290
  delegate: (grant: AgentGrant, task: AgentDelegationTask) => Promise<Pending | AgentDelegationOutcome>;
271
291
  /** Submit frozen mail or reuse its recorded Session admission receipt. */
272
- start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<Accepted>;
292
+ start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<DelegationReceipt>;
273
293
  /** Match a reply to frozen mail and restore the caller's delegation path. */
274
294
  authenticateReply: (mail: BirdDogMail) => AuthenticatedDelegationReply | undefined;
275
295
  /** Read an existing acceptance receipt without reserving or delivering mail. */
276
296
  receipt: (requestId: string) => Accepted | undefined;
297
+ /** Consume a recipient's stable `<mailKey>:accepted` notice. */
298
+ recordAcceptedNotice: (mail: BirdDogMail) => Accepted | undefined;
277
299
  /** The delegated work this owner still waits on, if any. */
278
300
  outstanding: () => Promise<OutstandingDelegation | undefined>;
279
301
  };