@fungi.computer/shiitake 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -10
- package/dist/agent-control.d.ts +5 -4
- package/dist/agent-control.js +3 -2
- package/dist/delegation.d.ts +34 -32
- package/dist/delegation.js +69 -8
- package/dist/fleet.d.ts +7 -2
- package/dist/fleet.js +96 -31
- package/dist/internal/admission-door.js +7 -2
- package/dist/internal/woodstock/fleet-store.d.ts +1 -0
- package/dist/internal/woodstock/fleet-store.js +28 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -20,16 +20,7 @@ To add your own behaviour, start the extension guide at
|
|
|
20
20
|
## Community-funded
|
|
21
21
|
|
|
22
22
|
We don't take VC money. Fungi is built in the open and proudly backed by its
|
|
23
|
-
community
|
|
24
|
-
|
|
25
|
-
$SHIT is not $SHIIT.
|
|
26
|
-
|
|
27
|
-
Official contract address (Solana):
|
|
28
|
-
`412ECWXN3jGbd1kn8ck7eeupWttkmrFkNm9JqfAzpump`
|
|
29
|
-
|
|
30
|
-
This is the only official address. Fungi will never DM you about the token.
|
|
31
|
-
Nothing here is financial advice, and the token isn't a share in Fungi. More at
|
|
32
|
-
[fungi.computer/funding](https://fungi.computer/funding).
|
|
23
|
+
community.
|
|
33
24
|
|
|
34
25
|
## Recover an uncertain send
|
|
35
26
|
|
package/dist/agent-control.d.ts
CHANGED
|
@@ -136,13 +136,14 @@ export declare function waitForAgentSettlement(session: ShiitakeSession, command
|
|
|
136
136
|
}>>;
|
|
137
137
|
/**
|
|
138
138
|
* A host's refusal of an Agent creating an Agent: the creator asked to give the
|
|
139
|
-
* created Agent more than it holds,
|
|
140
|
-
* creating
|
|
139
|
+
* created Agent more than it holds, the host cannot tell which Agent is
|
|
140
|
+
* creating, or the request id belongs to another creating Agent or Session. It
|
|
141
|
+
* reaches the caller as an `agents.create` failure.
|
|
141
142
|
*/
|
|
142
143
|
export declare class AgentCreateRefused extends Error {
|
|
143
|
-
readonly reason: "exceeds_creator" | "no_creator";
|
|
144
|
+
readonly reason: "exceeds_creator" | "no_creator" | "request_conflict";
|
|
144
145
|
readonly name = "AgentCreateRefused";
|
|
145
|
-
constructor(reason: "exceeds_creator" | "no_creator", message: string);
|
|
146
|
+
constructor(reason: "exceeds_creator" | "no_creator" | "request_conflict", message: string);
|
|
146
147
|
}
|
|
147
148
|
/** Compose one authorized Agent-control projection for a single run lease. */
|
|
148
149
|
export declare function createAgentControlSource(directory: AgentControlDirectory): CapabilitySource;
|
package/dist/agent-control.js
CHANGED
|
@@ -258,8 +258,9 @@ export async function waitForAgentSettlement(session, commandId, timeoutMs, sign
|
|
|
258
258
|
}
|
|
259
259
|
/**
|
|
260
260
|
* A host's refusal of an Agent creating an Agent: the creator asked to give the
|
|
261
|
-
* created Agent more than it holds,
|
|
262
|
-
* creating
|
|
261
|
+
* created Agent more than it holds, the host cannot tell which Agent is
|
|
262
|
+
* creating, or the request id belongs to another creating Agent or Session. It
|
|
263
|
+
* reaches the caller as an `agents.create` failure.
|
|
263
264
|
*/
|
|
264
265
|
export class AgentCreateRefused extends Error {
|
|
265
266
|
reason;
|
package/dist/delegation.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
3
|
import { type BirdDogEffect } from "@fungi.computer/bird-dog/effect";
|
|
4
|
-
import type
|
|
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
6
|
import { type Accepted, type DelegationReceipt, type CancelResult } from "./client.js";
|
|
7
7
|
import type { CommandSettlement, ShiitakeHost, VerifiedAgentReply } from "./index.js";
|
|
@@ -41,8 +41,8 @@ export declare class AgentDelegation {
|
|
|
41
41
|
/**
|
|
42
42
|
* A reply-less envelope is only ever BirdDog's reply to work this Agent
|
|
43
43
|
* sent. Admit it only after the fleet authenticates it against that frozen
|
|
44
|
-
* mail
|
|
45
|
-
*
|
|
44
|
+
* mail. A recipient-left reply remains Agent-authored; a recipient-offline
|
|
45
|
+
* reply is Hub-authored. Its meta carries only the authenticated reply.
|
|
46
46
|
*/
|
|
47
47
|
private authenticatedReply;
|
|
48
48
|
private parseAddressedMail;
|
|
@@ -50,6 +50,8 @@ export declare class AgentDelegation {
|
|
|
50
50
|
receive(raw: PostEnvelope): Promise<PostDeliveryResult>;
|
|
51
51
|
private recordAcceptedNotice;
|
|
52
52
|
private acceptAdmitted;
|
|
53
|
+
/** Admit local outbox exhaustion through the same Fleet/system reply gate. */
|
|
54
|
+
localUndeliverable(original: BirdDogMail): Promise<PostDeliveryResult>;
|
|
53
55
|
}
|
|
54
56
|
/** A current correspondent no longer has disclosure authority. */
|
|
55
57
|
export declare class AgentDisclosureRefused extends Error {
|
|
@@ -82,25 +84,24 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
|
|
|
82
84
|
readonly commandId: string;
|
|
83
85
|
readonly outcome: "completed" | "failed" | "cancelled" | "interrupted" | "outcome_unknown";
|
|
84
86
|
readonly diagnostic?: string;
|
|
85
|
-
} | {
|
|
86
|
-
readonly outcome: "undeliverable";
|
|
87
|
-
readonly reason: "recipient_offline" | "recipient_left";
|
|
88
87
|
} | {
|
|
89
88
|
readonly outcome: "accepted";
|
|
90
89
|
readonly commandId: string;
|
|
90
|
+
} | {
|
|
91
|
+
readonly outcome: "undeliverable";
|
|
92
|
+
readonly reason: "recipient_left";
|
|
91
93
|
};
|
|
92
94
|
} | {
|
|
93
95
|
readonly key: string;
|
|
94
96
|
readonly reply: {
|
|
95
97
|
readonly outcome: "undeliverable";
|
|
96
|
-
readonly reason: "recipient_offline"
|
|
98
|
+
readonly reason: "recipient_offline";
|
|
97
99
|
};
|
|
98
100
|
readonly content: string;
|
|
99
101
|
readonly createdAt: number;
|
|
100
102
|
readonly from: {
|
|
101
|
-
readonly kind: "
|
|
102
|
-
readonly
|
|
103
|
-
readonly sessionId?: string;
|
|
103
|
+
readonly kind: "system";
|
|
104
|
+
readonly systemId: "hub";
|
|
104
105
|
};
|
|
105
106
|
readonly to: {
|
|
106
107
|
readonly kind: "agent";
|
|
@@ -137,25 +138,24 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
|
|
|
137
138
|
readonly commandId: string;
|
|
138
139
|
readonly outcome: "completed" | "failed" | "cancelled" | "interrupted" | "outcome_unknown";
|
|
139
140
|
readonly diagnostic?: string;
|
|
140
|
-
} | {
|
|
141
|
-
readonly outcome: "undeliverable";
|
|
142
|
-
readonly reason: "recipient_offline" | "recipient_left";
|
|
143
141
|
} | {
|
|
144
142
|
readonly outcome: "accepted";
|
|
145
143
|
readonly commandId: string;
|
|
144
|
+
} | {
|
|
145
|
+
readonly outcome: "undeliverable";
|
|
146
|
+
readonly reason: "recipient_left";
|
|
146
147
|
};
|
|
147
148
|
} | {
|
|
148
149
|
readonly key: string;
|
|
149
150
|
readonly reply: {
|
|
150
151
|
readonly outcome: "undeliverable";
|
|
151
|
-
readonly reason: "recipient_offline"
|
|
152
|
+
readonly reason: "recipient_offline";
|
|
152
153
|
};
|
|
153
154
|
readonly content: string;
|
|
154
155
|
readonly createdAt: number;
|
|
155
156
|
readonly from: {
|
|
156
|
-
readonly kind: "
|
|
157
|
-
readonly
|
|
158
|
-
readonly sessionId?: string;
|
|
157
|
+
readonly kind: "system";
|
|
158
|
+
readonly systemId: "hub";
|
|
159
159
|
};
|
|
160
160
|
readonly to: {
|
|
161
161
|
readonly kind: "agent";
|
|
@@ -192,25 +192,24 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
|
|
|
192
192
|
readonly commandId: string;
|
|
193
193
|
readonly outcome: "completed" | "failed" | "cancelled" | "interrupted" | "outcome_unknown";
|
|
194
194
|
readonly diagnostic?: string;
|
|
195
|
-
} | {
|
|
196
|
-
readonly outcome: "undeliverable";
|
|
197
|
-
readonly reason: "recipient_offline" | "recipient_left";
|
|
198
195
|
} | {
|
|
199
196
|
readonly outcome: "accepted";
|
|
200
197
|
readonly commandId: string;
|
|
198
|
+
} | {
|
|
199
|
+
readonly outcome: "undeliverable";
|
|
200
|
+
readonly reason: "recipient_left";
|
|
201
201
|
};
|
|
202
202
|
} | {
|
|
203
203
|
readonly key: string;
|
|
204
204
|
readonly reply: {
|
|
205
205
|
readonly outcome: "undeliverable";
|
|
206
|
-
readonly reason: "recipient_offline"
|
|
206
|
+
readonly reason: "recipient_offline";
|
|
207
207
|
};
|
|
208
208
|
readonly content: string;
|
|
209
209
|
readonly createdAt: number;
|
|
210
210
|
readonly from: {
|
|
211
|
-
readonly kind: "
|
|
212
|
-
readonly
|
|
213
|
-
readonly sessionId?: string;
|
|
211
|
+
readonly kind: "system";
|
|
212
|
+
readonly systemId: "hub";
|
|
214
213
|
};
|
|
215
214
|
readonly to: {
|
|
216
215
|
readonly kind: "agent";
|
|
@@ -247,25 +246,24 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
|
|
|
247
246
|
readonly commandId: string;
|
|
248
247
|
readonly outcome: "completed" | "failed" | "cancelled" | "interrupted" | "outcome_unknown";
|
|
249
248
|
readonly diagnostic?: string;
|
|
250
|
-
} | {
|
|
251
|
-
readonly outcome: "undeliverable";
|
|
252
|
-
readonly reason: "recipient_offline" | "recipient_left";
|
|
253
249
|
} | {
|
|
254
250
|
readonly outcome: "accepted";
|
|
255
251
|
readonly commandId: string;
|
|
252
|
+
} | {
|
|
253
|
+
readonly outcome: "undeliverable";
|
|
254
|
+
readonly reason: "recipient_left";
|
|
256
255
|
};
|
|
257
256
|
} | {
|
|
258
257
|
readonly key: string;
|
|
259
258
|
readonly reply: {
|
|
260
259
|
readonly outcome: "undeliverable";
|
|
261
|
-
readonly reason: "recipient_offline"
|
|
260
|
+
readonly reason: "recipient_offline";
|
|
262
261
|
};
|
|
263
262
|
readonly content: string;
|
|
264
263
|
readonly createdAt: number;
|
|
265
264
|
readonly from: {
|
|
266
|
-
readonly kind: "
|
|
267
|
-
readonly
|
|
268
|
-
readonly sessionId?: string;
|
|
265
|
+
readonly kind: "system";
|
|
266
|
+
readonly systemId: "hub";
|
|
269
267
|
};
|
|
270
268
|
readonly to: {
|
|
271
269
|
readonly kind: "agent";
|
|
@@ -295,7 +293,7 @@ export declare function agentMailRoute(namespace: PostRoute<never, never>["names
|
|
|
295
293
|
/** The host's one Post lane, as Agent mail borrows it. */
|
|
296
294
|
export type AgentMailPost = Pick<PostRuntime<{
|
|
297
295
|
agentMail: ReturnType<typeof agentMailRoute>;
|
|
298
|
-
}>, "send" | "read" | "nextWake">;
|
|
296
|
+
}>, "send" | "read" | "nextWake" | "deadLetters">;
|
|
299
297
|
/** Recipient-owned replies and receipt-bearing submissions over the host's one Post lane. */
|
|
300
298
|
export declare class AgentMail {
|
|
301
299
|
private readonly deps;
|
|
@@ -305,12 +303,16 @@ export declare class AgentMail {
|
|
|
305
303
|
identity: () => DelegationIdentity | null | Promise<DelegationIdentity | null>;
|
|
306
304
|
/** Only a typed disclosure refusal becomes undefined; missing settled facts must fail. */
|
|
307
305
|
replySettlement: AgentReplySettlement;
|
|
306
|
+
/** Route a terminal reply for this sender through its Fleet admission. */
|
|
307
|
+
onUndeliverable: (original: BirdDogMail, reply: BirdDogMail) => Promise<PostDeliveryResult>;
|
|
308
308
|
drain: () => Promise<void>;
|
|
309
309
|
wake: () => Promise<void>;
|
|
310
310
|
}>);
|
|
311
311
|
private envelope;
|
|
312
312
|
/** Submit one mail and return recipient acceptance or relay-held custody. */
|
|
313
313
|
send(mail: BirdDogMail): Promise<DelegationReceipt>;
|
|
314
|
+
/** Reconcile exhausted original outbox mail into one local system reply. */
|
|
315
|
+
stageUndeliverableReplies(): Promise<void>;
|
|
314
316
|
/** Stage the acceptance reply through this Post owner without leaving the transaction. */
|
|
315
317
|
stageAcceptedNotice(mail: BirdDogMail, scopeId: string): void;
|
|
316
318
|
/** BirdDog's mail room: disclosed settlement replies fan out over Post. */
|
package/dist/delegation.js
CHANGED
|
@@ -9,6 +9,7 @@ import { agentMailAddressSchema } from "@fungi.computer/seam/machine-link-protoc
|
|
|
9
9
|
import { BirdDogMailSchema, BirdDogError, settlementReplies, } from "@fungi.computer/bird-dog/effect";
|
|
10
10
|
import * as Effect from "effect/Effect";
|
|
11
11
|
import { messageAuthor } from "@fungi.computer/agar/agents";
|
|
12
|
+
import { undeliverableReply } from "@fungi.computer/bird-dog";
|
|
12
13
|
import { PostEnvelopeSchema, definePostRoute, } from "@fungi.computer/carrier/post";
|
|
13
14
|
import { parseDelegationReceipt, } from "./client.js";
|
|
14
15
|
import { decodeClientSessionCreationRequest } from "./internal/client-boundary.js";
|
|
@@ -135,8 +136,8 @@ export class AgentDelegation {
|
|
|
135
136
|
/**
|
|
136
137
|
* A reply-less envelope is only ever BirdDog's reply to work this Agent
|
|
137
138
|
* sent. Admit it only after the fleet authenticates it against that frozen
|
|
138
|
-
* mail
|
|
139
|
-
*
|
|
139
|
+
* mail. A recipient-left reply remains Agent-authored; a recipient-offline
|
|
140
|
+
* reply is Hub-authored. Its meta carries only the authenticated reply.
|
|
140
141
|
*/
|
|
141
142
|
async authenticatedReply(mail) {
|
|
142
143
|
const delegation = this.fleet().authenticateReply(mail);
|
|
@@ -173,19 +174,21 @@ export class AgentDelegation {
|
|
|
173
174
|
let authenticated;
|
|
174
175
|
const valid = async () => {
|
|
175
176
|
const scope = await this.scope(new AbortController().signal);
|
|
176
|
-
const
|
|
177
|
-
|
|
177
|
+
const sender = mail.from;
|
|
178
|
+
const authenticatedUndeliverable = authenticated?.reply.outcome === "undeliverable";
|
|
178
179
|
return (address.scopeId === scope.scopeId &&
|
|
179
180
|
address.agentId === scope.agentId &&
|
|
180
181
|
mail.to.agentId === scope.agentId &&
|
|
181
182
|
envelope.key === mail.key &&
|
|
182
|
-
(
|
|
183
|
-
|
|
183
|
+
(authenticatedUndeliverable ||
|
|
184
|
+
(sender.kind === "agent" &&
|
|
185
|
+
scope.agents.some(({ agentId }) => agentId === sender.agentId))) &&
|
|
184
186
|
mail.replyTo.every(({ agentId }) => scope.agents.some((agent) => agent.agentId === agentId)) &&
|
|
185
187
|
(mail.replyTo.length === 0 ||
|
|
186
188
|
(mail.replyTo.length === 1 &&
|
|
187
|
-
|
|
188
|
-
mail.replyTo[0]
|
|
189
|
+
sender.kind === "agent" &&
|
|
190
|
+
mail.replyTo[0]?.agentId === sender.agentId &&
|
|
191
|
+
mail.replyTo[0].sessionId === sender.sessionId)));
|
|
189
192
|
};
|
|
190
193
|
if (mail.replyTo.length === 0)
|
|
191
194
|
authenticated = await this.authenticatedReply(mail);
|
|
@@ -215,12 +218,37 @@ export class AgentDelegation {
|
|
|
215
218
|
this.deps.stageAcceptedNotice(notice, scopeId);
|
|
216
219
|
};
|
|
217
220
|
const receipt = await this.deps.accept(admitted.mail, admitted.reply, stageAcceptedNotice);
|
|
221
|
+
if (admitted.reply?.outcome === "undeliverable" &&
|
|
222
|
+
admitted.mail.from.kind === "system") {
|
|
223
|
+
this.fleet().recordUndeliverableReply(admitted.mail.key.slice(0, -":reply:0".length));
|
|
224
|
+
}
|
|
218
225
|
const sessionId = admitted.mail.to.sessionId ?? receipt.sessionId;
|
|
219
226
|
return {
|
|
220
227
|
status: "accepted",
|
|
221
228
|
receipt: sessionId === undefined ? receipt : { ...receipt, sessionId },
|
|
222
229
|
};
|
|
223
230
|
}
|
|
231
|
+
/** Admit local outbox exhaustion through the same Fleet/system reply gate. */
|
|
232
|
+
async localUndeliverable(original) {
|
|
233
|
+
const identity = await this.deps.identity();
|
|
234
|
+
if (identity === null)
|
|
235
|
+
return "refused";
|
|
236
|
+
let reply;
|
|
237
|
+
try {
|
|
238
|
+
reply = undeliverableReply(original, Date.now());
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return "refused";
|
|
242
|
+
}
|
|
243
|
+
if (!this.fleet().recordLocalUndeliverable(original, reply))
|
|
244
|
+
return "refused";
|
|
245
|
+
return await this.receive({
|
|
246
|
+
key: reply.key,
|
|
247
|
+
kind: "agentMail",
|
|
248
|
+
address: { scopeId: identity.scopeId, agentId: identity.agentId },
|
|
249
|
+
event: reply,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
224
252
|
}
|
|
225
253
|
/** A current correspondent no longer has disclosure authority. */
|
|
226
254
|
export class AgentDisclosureRefused extends Error {
|
|
@@ -284,6 +312,39 @@ export class AgentMail {
|
|
|
284
312
|
throw new Error(`Agent delivery ${row.reason}`);
|
|
285
313
|
throw new Error("Agent did not return a delegated work receipt");
|
|
286
314
|
}
|
|
315
|
+
/** Reconcile exhausted original outbox mail into one local system reply. */
|
|
316
|
+
async stageUndeliverableReplies() {
|
|
317
|
+
const identity = await this.deps.identity();
|
|
318
|
+
if (identity === null)
|
|
319
|
+
return;
|
|
320
|
+
const post = this.deps.post();
|
|
321
|
+
let cursor;
|
|
322
|
+
do {
|
|
323
|
+
const page = post.deadLetters(64, cursor);
|
|
324
|
+
for (const letter of page.letters) {
|
|
325
|
+
if (letter.reason !== "exhausted" ||
|
|
326
|
+
letter.envelope.kind !== "agentMail")
|
|
327
|
+
continue;
|
|
328
|
+
const envelope = Schema.decodeUnknownOption(PostEnvelopeSchema)(letter.envelope);
|
|
329
|
+
if (envelope._tag === "None" || envelope.value.kind !== "agentMail")
|
|
330
|
+
continue;
|
|
331
|
+
const original = Schema.decodeUnknownOption(BirdDogMailSchema)(envelope.value.event);
|
|
332
|
+
const address = Schema.decodeUnknownOption(agentMailAddressSchema)(envelope.value.address);
|
|
333
|
+
if (original._tag === "None" ||
|
|
334
|
+
address._tag === "None" ||
|
|
335
|
+
original.value.key !== envelope.value.key ||
|
|
336
|
+
original.value.reply !== undefined ||
|
|
337
|
+
original.value.from.kind !== "agent" ||
|
|
338
|
+
original.value.from.agentId !== identity.agentId ||
|
|
339
|
+
address.value.scopeId !== identity.scopeId ||
|
|
340
|
+
address.value.agentId !== original.value.to.agentId)
|
|
341
|
+
continue;
|
|
342
|
+
const reply = undeliverableReply(original.value, Date.now());
|
|
343
|
+
await this.deps.onUndeliverable(original.value, reply);
|
|
344
|
+
}
|
|
345
|
+
cursor = page.nextCursor;
|
|
346
|
+
} while (cursor !== undefined);
|
|
347
|
+
}
|
|
287
348
|
/** Stage the acceptance reply through this Post owner without leaving the transaction. */
|
|
288
349
|
stageAcceptedNotice(mail, scopeId) {
|
|
289
350
|
this.deps.post().send({
|
package/dist/fleet.d.ts
CHANGED
|
@@ -255,8 +255,9 @@ export declare function delegationSendMeta(sender: Readonly<{
|
|
|
255
255
|
name: string;
|
|
256
256
|
}>, invocationMeta?: Json): DelegationSendMeta;
|
|
257
257
|
/**
|
|
258
|
-
* The host metadata for one authenticated reply line
|
|
259
|
-
*
|
|
258
|
+
* The host metadata for one authenticated reply line. An ordinary reply is
|
|
259
|
+
* authored by its replying Agent; a delivery failure is a Hub/system fact,
|
|
260
|
+
* while the verified reply retains the failed recipient's identity.
|
|
260
261
|
*/
|
|
261
262
|
export declare function delegationReplyMeta(delegation: AuthenticatedDelegationReply, replierName: string): DelegationReplyMeta;
|
|
262
263
|
/**
|
|
@@ -279,6 +280,8 @@ export declare function createFleetCorrespondence(options: Omit<FleetDelegationO
|
|
|
279
280
|
start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<DelegationReceipt>;
|
|
280
281
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
281
282
|
authenticateReply: (mail: BirdDogMail) => AuthenticatedDelegationReply | undefined;
|
|
283
|
+
recordLocalUndeliverable: (original: BirdDogMail, reply: BirdDogMail) => boolean;
|
|
284
|
+
recordUndeliverableReply: (mailKey: string) => boolean;
|
|
282
285
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
283
286
|
receipt: (requestId: string) => Accepted | undefined;
|
|
284
287
|
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
|
@@ -292,6 +295,8 @@ export declare function createFleetDelegation(options: FleetDelegationOptions):
|
|
|
292
295
|
start: (grant: AgentGrant, raw: AgentDelegationTask) => Promise<DelegationReceipt>;
|
|
293
296
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
294
297
|
authenticateReply: (mail: BirdDogMail) => AuthenticatedDelegationReply | undefined;
|
|
298
|
+
recordLocalUndeliverable: (original: BirdDogMail, reply: BirdDogMail) => boolean;
|
|
299
|
+
recordUndeliverableReply: (mailKey: string) => boolean;
|
|
295
300
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
296
301
|
receipt: (requestId: string) => Accepted | undefined;
|
|
297
302
|
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
package/dist/fleet.js
CHANGED
|
@@ -318,16 +318,21 @@ export function delegationSendMeta(sender, invocationMeta) {
|
|
|
318
318
|
};
|
|
319
319
|
}
|
|
320
320
|
/**
|
|
321
|
-
* The host metadata for one authenticated reply line
|
|
322
|
-
*
|
|
321
|
+
* The host metadata for one authenticated reply line. An ordinary reply is
|
|
322
|
+
* authored by its replying Agent; a delivery failure is a Hub/system fact,
|
|
323
|
+
* while the verified reply retains the failed recipient's identity.
|
|
323
324
|
*/
|
|
324
325
|
export function delegationReplyMeta(delegation, replierName) {
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
const author = delegation.reply.outcome === "undeliverable" &&
|
|
327
|
+
delegation.reply.reason === "recipient_offline"
|
|
328
|
+
? authorMeta({ id: "hub/system", name: "Hub/system" })
|
|
329
|
+
: authorMeta({
|
|
327
330
|
id: delegation.reply.agentId,
|
|
328
331
|
name: replierName,
|
|
329
332
|
kind: "agent",
|
|
330
|
-
})
|
|
333
|
+
});
|
|
334
|
+
return {
|
|
335
|
+
...author,
|
|
331
336
|
delegation: {
|
|
332
337
|
ancestors: [...delegation.ancestors],
|
|
333
338
|
maxDepth: delegation.maxDepth,
|
|
@@ -341,7 +346,7 @@ const delegationMetadata = z.object({
|
|
|
341
346
|
const delegationRowSchema = z.lazy(() => z.strictObject({
|
|
342
347
|
input: z.string(),
|
|
343
348
|
mail: z.string(),
|
|
344
|
-
state: z.enum(["pending", "held", "accepted"]),
|
|
349
|
+
state: z.enum(["pending", "held", "accepted", "undeliverable"]),
|
|
345
350
|
receipt: z.string().nullable(),
|
|
346
351
|
}));
|
|
347
352
|
const heldRecipientOffline = {
|
|
@@ -351,7 +356,7 @@ const heldRecipientOffline = {
|
|
|
351
356
|
};
|
|
352
357
|
const decodeMail = Schema.decodeUnknownSync(Schema.fromJsonString(BirdDogMailSchema));
|
|
353
358
|
function bindRecipientSession(mail, sessionId) {
|
|
354
|
-
if (mail.from.
|
|
359
|
+
if (mail.from.kind !== "agent")
|
|
355
360
|
throw new DirectoryError(503, "partial_init");
|
|
356
361
|
return {
|
|
357
362
|
...mail,
|
|
@@ -434,7 +439,7 @@ export function createFleetCorrespondence(options) {
|
|
|
434
439
|
throw new DirectoryError(503, "partial_init");
|
|
435
440
|
if (row.input !== input)
|
|
436
441
|
throw new DirectoryError(409, "request_conflict");
|
|
437
|
-
if (row.state === "held")
|
|
442
|
+
if (row.state === "held" || row.state === "undeliverable")
|
|
438
443
|
return heldRecipientOffline;
|
|
439
444
|
if (row.state === "accepted") {
|
|
440
445
|
if (row.receipt === null)
|
|
@@ -472,17 +477,36 @@ export function createFleetCorrespondence(options) {
|
|
|
472
477
|
mail.reply.outcome === "accepted" ||
|
|
473
478
|
!mail.key.endsWith(suffix))
|
|
474
479
|
return undefined;
|
|
475
|
-
const
|
|
476
|
-
|
|
480
|
+
const mailKey = mail.key.slice(0, -suffix.length);
|
|
481
|
+
const row = read(mailKey);
|
|
482
|
+
if (row === undefined || row.state === "undeliverable")
|
|
477
483
|
return undefined;
|
|
478
484
|
const sent = decodeMail(row.mail);
|
|
479
|
-
if (
|
|
480
|
-
mail.from.sessionId === undefined)
|
|
485
|
+
if (sent.key !== mailKey)
|
|
481
486
|
return undefined;
|
|
482
|
-
|
|
483
|
-
mail.
|
|
484
|
-
mail.
|
|
485
|
-
|
|
487
|
+
const systemUndeliverable = mail.reply.outcome === "undeliverable" &&
|
|
488
|
+
mail.reply.reason === "recipient_offline" &&
|
|
489
|
+
mail.from.kind === "system";
|
|
490
|
+
if (systemUndeliverable) {
|
|
491
|
+
if (row.state !== "held" ||
|
|
492
|
+
sent.from.kind !== "agent" ||
|
|
493
|
+
mail.key !== `${sent.key}:reply:0` ||
|
|
494
|
+
mail.to.agentId !== sent.from.agentId ||
|
|
495
|
+
mail.to.sessionId !== sent.from.sessionId)
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
if (sent.from.kind !== "agent" ||
|
|
500
|
+
mail.from.kind !== "agent" ||
|
|
501
|
+
mail.from.agentId !== sent.to.agentId ||
|
|
502
|
+
mail.from.sessionId !== sent.to.sessionId ||
|
|
503
|
+
mail.to.agentId !== sent.from.agentId ||
|
|
504
|
+
mail.to.sessionId !== sent.from.sessionId)
|
|
505
|
+
return undefined;
|
|
506
|
+
}
|
|
507
|
+
if (mail.reply.outcome === "undeliverable" &&
|
|
508
|
+
mail.reply.reason === "recipient_offline" &&
|
|
509
|
+
!systemUndeliverable)
|
|
486
510
|
return undefined;
|
|
487
511
|
if (mail.reply.outcome !== "undeliverable" &&
|
|
488
512
|
row.state === "accepted" &&
|
|
@@ -496,16 +520,28 @@ export function createFleetCorrespondence(options) {
|
|
|
496
520
|
return undefined;
|
|
497
521
|
let reply;
|
|
498
522
|
if (mail.reply.outcome === "undeliverable") {
|
|
499
|
-
const
|
|
523
|
+
const agentId = systemUndeliverable
|
|
524
|
+
? sent.to.agentId
|
|
525
|
+
: mail.from.kind === "agent"
|
|
526
|
+
? mail.from.agentId
|
|
527
|
+
: undefined;
|
|
528
|
+
if (agentId === undefined)
|
|
529
|
+
return undefined;
|
|
530
|
+
const undeliverable = { agentId, ...mail.reply };
|
|
531
|
+
const sessionId = systemUndeliverable
|
|
532
|
+
? sent.to.sessionId
|
|
533
|
+
: mail.from.kind === "agent"
|
|
534
|
+
? mail.from.sessionId
|
|
535
|
+
: undefined;
|
|
500
536
|
reply =
|
|
501
|
-
|
|
537
|
+
sessionId === undefined
|
|
502
538
|
? undeliverable
|
|
503
|
-
: { ...undeliverable, sessionId
|
|
539
|
+
: { ...undeliverable, sessionId };
|
|
504
540
|
}
|
|
505
541
|
else {
|
|
506
|
-
|
|
507
|
-
if (sessionId === undefined)
|
|
542
|
+
if (mail.from.kind !== "agent")
|
|
508
543
|
return undefined;
|
|
544
|
+
const sessionId = mail.from.sessionId;
|
|
509
545
|
reply = { agentId: mail.from.agentId, sessionId, ...mail.reply };
|
|
510
546
|
}
|
|
511
547
|
return {
|
|
@@ -514,6 +550,29 @@ export function createFleetCorrespondence(options) {
|
|
|
514
550
|
reply,
|
|
515
551
|
};
|
|
516
552
|
}
|
|
553
|
+
/** Bind an exhausted local Carrier row to its frozen sender correspondence. */
|
|
554
|
+
function recordLocalUndeliverable(original, reply) {
|
|
555
|
+
const row = read(original.key);
|
|
556
|
+
if (row === undefined ||
|
|
557
|
+
(row.state !== "pending" && row.state !== "held") ||
|
|
558
|
+
original.from.kind !== "agent" ||
|
|
559
|
+
original.reply !== undefined ||
|
|
560
|
+
JSON.stringify(decodeMail(row.mail)) !== JSON.stringify(original) ||
|
|
561
|
+
reply.key !== `${original.key}:reply:0` ||
|
|
562
|
+
reply.from.kind !== "system" ||
|
|
563
|
+
reply.reply?.outcome !== "undeliverable" ||
|
|
564
|
+
reply.reply.reason !== "recipient_offline" ||
|
|
565
|
+
reply.to.agentId !== original.from.agentId ||
|
|
566
|
+
reply.to.sessionId !== original.from.sessionId ||
|
|
567
|
+
reply.replyTo.length !== 0)
|
|
568
|
+
return false;
|
|
569
|
+
store.recordHeld(original.key);
|
|
570
|
+
return read(original.key)?.state === "held";
|
|
571
|
+
}
|
|
572
|
+
/** Make a successfully admitted system disposition replay-ineligible. */
|
|
573
|
+
function recordUndeliverableReply(mailKey) {
|
|
574
|
+
return store.recordUndeliverable(mailKey);
|
|
575
|
+
}
|
|
517
576
|
/** Read only the existing acceptance receipt; do not reserve or deliver mail. */
|
|
518
577
|
function receipt(requestId) {
|
|
519
578
|
const row = read(requestId);
|
|
@@ -524,9 +583,11 @@ export function createFleetCorrespondence(options) {
|
|
|
524
583
|
/** Atomically bind the recipient Session carried by a late acceptance notice. */
|
|
525
584
|
function recordAcceptedNotice(mail) {
|
|
526
585
|
const suffix = ":accepted";
|
|
586
|
+
if (mail.from.kind !== "agent")
|
|
587
|
+
return undefined;
|
|
588
|
+
const sender = mail.from;
|
|
527
589
|
if (mail.replyTo.length !== 0 ||
|
|
528
590
|
mail.reply?.outcome !== "accepted" ||
|
|
529
|
-
mail.from.sessionId === undefined ||
|
|
530
591
|
!mail.key.endsWith(suffix))
|
|
531
592
|
return undefined;
|
|
532
593
|
const requestId = mail.key.slice(0, -suffix.length);
|
|
@@ -534,29 +595,31 @@ export function createFleetCorrespondence(options) {
|
|
|
534
595
|
if (row === undefined)
|
|
535
596
|
return undefined;
|
|
536
597
|
const sent = decodeMail(row.mail);
|
|
598
|
+
if (sent.from.kind !== "agent")
|
|
599
|
+
return undefined;
|
|
600
|
+
const sentSender = sent.from;
|
|
537
601
|
const path = delegationMetadata.safeParse(sent.meta);
|
|
538
|
-
if (
|
|
539
|
-
!path.success ||
|
|
602
|
+
if (!path.success ||
|
|
540
603
|
path.data.delegation === undefined ||
|
|
541
604
|
mail.key !== `${sent.key}:accepted` ||
|
|
542
|
-
|
|
605
|
+
sender.agentId !== sent.to.agentId ||
|
|
543
606
|
(sent.to.sessionId !== undefined &&
|
|
544
|
-
sent.to.sessionId !==
|
|
545
|
-
mail.to.agentId !==
|
|
546
|
-
mail.to.sessionId !==
|
|
607
|
+
sent.to.sessionId !== sender.sessionId) ||
|
|
608
|
+
mail.to.agentId !== sentSender.agentId ||
|
|
609
|
+
mail.to.sessionId !== sentSender.sessionId)
|
|
547
610
|
return undefined;
|
|
548
611
|
if (row.state === "accepted" && row.receipt !== null) {
|
|
549
612
|
const current = parseAccepted(row.receipt);
|
|
550
613
|
if (current.commandId !== mail.reply.commandId ||
|
|
551
|
-
current.sessionId !==
|
|
614
|
+
current.sessionId !== sender.sessionId)
|
|
552
615
|
return undefined;
|
|
553
616
|
}
|
|
554
617
|
const accepted = {
|
|
555
618
|
status: "accepted",
|
|
556
619
|
commandId: mail.reply.commandId,
|
|
557
|
-
sessionId:
|
|
620
|
+
sessionId: sender.sessionId,
|
|
558
621
|
};
|
|
559
|
-
store.recordReceipt(requestId, JSON.stringify(accepted), row.mail, JSON.stringify(bindRecipientSession(sent,
|
|
622
|
+
store.recordReceipt(requestId, JSON.stringify(accepted), row.mail, JSON.stringify(bindRecipientSession(sent, sender.sessionId)));
|
|
560
623
|
return accepted;
|
|
561
624
|
}
|
|
562
625
|
return {
|
|
@@ -564,6 +627,8 @@ export function createFleetCorrespondence(options) {
|
|
|
564
627
|
start,
|
|
565
628
|
/** Match a reply to frozen mail and restore the caller's delegation path. */
|
|
566
629
|
authenticateReply,
|
|
630
|
+
recordLocalUndeliverable,
|
|
631
|
+
recordUndeliverableReply,
|
|
567
632
|
/** Read an existing acceptance receipt without reserving or delivering mail. */
|
|
568
633
|
receipt,
|
|
569
634
|
/** Consume a recipient's stable `<mailKey>:accepted` notice. */
|
|
@@ -16,9 +16,14 @@ function decodeReply(request) {
|
|
|
16
16
|
return undefined;
|
|
17
17
|
const reply = requireDecoded(decodeVerifiedReply(request.reply), "Verified Agent reply is invalid");
|
|
18
18
|
const mail = request.mail;
|
|
19
|
+
const agentAuthored = mail?.from.kind === "agent" &&
|
|
20
|
+
mail.from.agentId === reply.agentId &&
|
|
21
|
+
mail.from.sessionId === reply.sessionId;
|
|
22
|
+
const systemUndeliverable = mail?.from.kind === "system" &&
|
|
23
|
+
reply.outcome === "undeliverable" &&
|
|
24
|
+
reply.reason === "recipient_offline";
|
|
19
25
|
if (mail?.replyTo.length !== 0 ||
|
|
20
|
-
|
|
21
|
-
mail.from.sessionId !== reply.sessionId ||
|
|
26
|
+
(!agentAuthored && !systemUndeliverable) ||
|
|
22
27
|
request.command.type !== "prompt" ||
|
|
23
28
|
request.command.delivery !== "steer")
|
|
24
29
|
throw new ShiitakeError("contract", "invalid", "Verified Agent reply does not match its mail");
|
|
@@ -34,4 +34,5 @@ export declare function createFleetDelegationStore(storage: WatchdogSqliteOwner)
|
|
|
34
34
|
reserve(requestId: string, input: string, mail: string): void;
|
|
35
35
|
recordReceipt(requestId: string, receipt: string, expectedMail: string, acceptedMail: string): void;
|
|
36
36
|
recordHeld(requestId: string): void;
|
|
37
|
+
recordUndeliverable(requestId: string): boolean;
|
|
37
38
|
};
|
|
@@ -86,11 +86,16 @@ export function createFleetDelegationStore(storage) {
|
|
|
86
86
|
) STRICT`);
|
|
87
87
|
storage.sql.exec(`CREATE TABLE IF NOT EXISTS shiitake_fleet_held_delegations (
|
|
88
88
|
request_id TEXT PRIMARY KEY
|
|
89
|
+
) STRICT`);
|
|
90
|
+
storage.sql
|
|
91
|
+
.exec(`CREATE TABLE IF NOT EXISTS shiitake_fleet_undeliverable_delegations (
|
|
92
|
+
request_id TEXT PRIMARY KEY
|
|
89
93
|
) STRICT`);
|
|
90
94
|
return {
|
|
91
95
|
read: (requestId) => storage.sql
|
|
92
96
|
.exec(`SELECT delegation.input, delegation.mail,
|
|
93
97
|
CASE
|
|
98
|
+
WHEN terminal.request_id IS NOT NULL THEN 'undeliverable'
|
|
94
99
|
WHEN held.request_id IS NOT NULL THEN 'held'
|
|
95
100
|
WHEN delegation.receipt IS NOT NULL THEN 'accepted'
|
|
96
101
|
ELSE 'pending'
|
|
@@ -99,6 +104,8 @@ export function createFleetDelegationStore(storage) {
|
|
|
99
104
|
FROM shiitake_fleet_delegations AS delegation
|
|
100
105
|
LEFT JOIN shiitake_fleet_held_delegations AS held
|
|
101
106
|
ON held.request_id = delegation.request_id
|
|
107
|
+
LEFT JOIN shiitake_fleet_undeliverable_delegations AS terminal
|
|
108
|
+
ON terminal.request_id = delegation.request_id
|
|
102
109
|
WHERE delegation.request_id = ?`, requestId)
|
|
103
110
|
.toArray()[0],
|
|
104
111
|
/** Every recipient-accepted delegation, newest first. */
|
|
@@ -135,7 +142,27 @@ export function createFleetDelegationStore(storage) {
|
|
|
135
142
|
storage.transactionSync(() => {
|
|
136
143
|
storage.sql.exec(`INSERT OR IGNORE INTO shiitake_fleet_held_delegations (request_id)
|
|
137
144
|
SELECT request_id FROM shiitake_fleet_delegations
|
|
138
|
-
WHERE request_id = ? AND receipt IS NULL
|
|
145
|
+
WHERE request_id = ? AND receipt IS NULL
|
|
146
|
+
AND NOT EXISTS (
|
|
147
|
+
SELECT 1 FROM shiitake_fleet_undeliverable_delegations
|
|
148
|
+
WHERE request_id = ?
|
|
149
|
+
)`, requestId, requestId);
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
recordUndeliverable(requestId) {
|
|
153
|
+
return storage.transactionSync(() => {
|
|
154
|
+
const held = storage.sql
|
|
155
|
+
.exec(`SELECT delegation.request_id
|
|
156
|
+
FROM shiitake_fleet_delegations AS delegation
|
|
157
|
+
JOIN shiitake_fleet_held_delegations AS held
|
|
158
|
+
ON held.request_id = delegation.request_id
|
|
159
|
+
WHERE delegation.request_id = ? AND delegation.receipt IS NULL`, requestId)
|
|
160
|
+
.toArray()[0];
|
|
161
|
+
if (held === undefined)
|
|
162
|
+
return false;
|
|
163
|
+
storage.sql.exec("INSERT OR IGNORE INTO shiitake_fleet_undeliverable_delegations(request_id) VALUES (?)", requestId);
|
|
164
|
+
storage.sql.exec("DELETE FROM shiitake_fleet_held_delegations WHERE request_id = ?", requestId);
|
|
165
|
+
return true;
|
|
139
166
|
});
|
|
140
167
|
},
|
|
141
168
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fungi.computer/shiitake",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"author": "Levi <levi@technomancy.dev>",
|
|
5
5
|
"description": "Effect-native durable Session acceptance and bounded run orchestration",
|
|
6
6
|
"type": "module",
|
|
@@ -82,10 +82,10 @@
|
|
|
82
82
|
"node": ">=22.13"
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
|
-
"@fungi.computer/bird-dog": "0.2.
|
|
85
|
+
"@fungi.computer/bird-dog": "0.2.1",
|
|
86
86
|
"@fungi.computer/cairn": "0.1.0",
|
|
87
87
|
"@fungi.computer/carrier": "0.2.0",
|
|
88
|
-
"@fungi.computer/knapsack": "0.3.
|
|
88
|
+
"@fungi.computer/knapsack": "0.3.1",
|
|
89
89
|
"@fungi.computer/mule": "0.2.0",
|
|
90
90
|
"@fungi.computer/mycelium": "0.2.2",
|
|
91
91
|
"@fungi.computer/seam": "0.2.0",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"zod": "4.4.3",
|
|
95
95
|
"@earendil-works/pi-ai": "1.3.0",
|
|
96
96
|
"@fungi.computer/gills": "0.2.0",
|
|
97
|
-
"@fungi.computer/agar": "0.
|
|
97
|
+
"@fungi.computer/agar": "0.4.0"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
100
|
"@cloudflare/workers-types": "5.20260828.1",
|