@fungi.computer/bird-dog 0.0.1 → 0.2.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 CHANGED
@@ -163,6 +163,13 @@ schedules an immediate wake and continues from the same place on the next drain.
163
163
  receipt under the same key. Replaying the same settlement after a restart finds
164
164
  the receipt and does not post the reply again. Keep those receipts as long as
165
165
  source settlements can replay. BirdDog does not expire them.
166
+ `hasPendingDelivery(outbox)` reads only whether any reply still awaits a
167
+ delivery attempt, so a host can tell retained receipts and dead letters from
168
+ owed mail.
169
+
170
+ The Effect mail room's `nextWakeAt()` reads Carrier's earliest owed delivery,
171
+ including future retries. Runtime hosts include that hint in their pending-work
172
+ answer rather than clearing a retry when other runtime work is idle.
166
173
 
167
174
  **Host ports.** `BirdDog.make` takes four things:
168
175
 
@@ -182,11 +189,12 @@ The generated reference is in [docs/api](docs/api/README.md).
182
189
  `BirdDogAgentJobPayload`, `BirdDogReply`, `BirdDogOutbox`, `BirdDogOptions`,
183
190
  `BirdDogSettlement` and `BirdDogRuntime` types.
184
191
  - `@fungi.computer/bird-dog/effect`: `make`, `agentJobFromInput`,
185
- `decodeAgentJob`, `settlementReplies`, `boundedDiagnostic`,
186
- `BIRD_DOG_DIAGNOSTIC_MAX_LENGTH`, `BirdDogError`, `EffectBirdDogOptions`,
187
- `BirdDogEffect` and the schemas `BirdDogAgentReferenceSchema`,
188
- `BirdDogReplySchema`, `BirdDogMailSchema`, `BirdDogAgentWorkSchema`,
189
- `BirdDogAgentJobPayloadSchema` and `BirdDogAgentJobInputSchema`.
192
+ `decodeAgentJob`, `settlementReplies`, `hasPendingDelivery`,
193
+ `boundedDiagnostic`, `BIRD_DOG_DIAGNOSTIC_MAX_LENGTH`, `BirdDogError`,
194
+ `EffectBirdDogOptions`, `BirdDogEffect` and the schemas
195
+ `BirdDogAgentReferenceSchema`, `BirdDogReplySchema`, `BirdDogMailSchema`,
196
+ `BirdDogAgentWorkSchema`, `BirdDogAgentJobPayloadSchema` and
197
+ `BirdDogAgentJobInputSchema`.
190
198
 
191
199
  ## Examples and checks
192
200
 
package/dist/effect.d.ts CHANGED
@@ -2,12 +2,20 @@ import type { WatchdogQueuedJob, WatchdogStoredJob } from "@fungi.computer/watch
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Schema from "effect/Schema";
4
4
  import type { BirdDogAgentJobInput, BirdDogAgentJobPayload, BirdDogMail, BirdDogSettlement } from "./index.js";
5
+ /** The maximum UTF-8 size of one mail body. */
6
+ export declare const BIRD_DOG_MAIL_CONTENT_MAX_BYTES: number;
5
7
  /** Parser for an ordinary agent Session address. */
6
8
  export declare const BirdDogAgentReferenceSchema: Schema.Struct<{
7
9
  readonly kind: Schema.Literal<"agent">;
8
10
  readonly agentId: Schema.String;
9
11
  readonly sessionId: Schema.String;
10
12
  }>;
13
+ /** An addressed Agent may create its first Session when accepting this mail. */
14
+ export declare const BirdDogAgentRecipientReferenceSchema: Schema.Struct<{
15
+ readonly kind: Schema.Literal<"agent">;
16
+ readonly agentId: Schema.String;
17
+ readonly sessionId: Schema.optionalKey<Schema.String>;
18
+ }>;
11
19
  /** The most characters a reply's failure diagnostic may carry. */
12
20
  export declare const BIRD_DOG_DIAGNOSTIC_MAX_LENGTH = 200;
13
21
  /**
@@ -15,11 +23,33 @@ export declare const BIRD_DOG_DIAGNOSTIC_MAX_LENGTH = 200;
15
23
  * replying Session's command, its exact Watchdog outcome and, for work that did
16
24
  * not complete, the Session's bounded terminal diagnostic.
17
25
  */
18
- export declare const BirdDogReplySchema: Schema.Struct<{
26
+ export declare const SettledBirdDogReplySchema: Schema.Struct<{
19
27
  readonly commandId: Schema.String;
20
28
  readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
21
29
  readonly diagnostic: Schema.optionalKey<Schema.String>;
22
30
  }>;
31
+ /** A terminal delivery failure, correlated by the original mail key only. */
32
+ export declare const BirdDogUndeliverableReasonSchema: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
33
+ export declare const UndeliverableBirdDogReplySchema: Schema.Struct<{
34
+ readonly outcome: Schema.Literal<"undeliverable">;
35
+ readonly reason: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
36
+ }>;
37
+ /** Recipient acceptance, before the command has settled. */
38
+ export declare const AcceptedBirdDogReplySchema: Schema.Struct<{
39
+ readonly outcome: Schema.Literal<"accepted">;
40
+ readonly commandId: Schema.String;
41
+ }>;
42
+ export declare const BirdDogReplySchema: Schema.Union<readonly [Schema.Struct<{
43
+ readonly commandId: Schema.String;
44
+ readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
45
+ readonly diagnostic: Schema.optionalKey<Schema.String>;
46
+ }>, Schema.Struct<{
47
+ readonly outcome: Schema.Literal<"undeliverable">;
48
+ readonly reason: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
49
+ }>, Schema.Struct<{
50
+ readonly outcome: Schema.Literal<"accepted">;
51
+ readonly commandId: Schema.String;
52
+ }>]>;
23
53
  /**
24
54
  * One secret-safe line of at most `BIRD_DOG_DIAGNOSTIC_MAX_LENGTH` characters
25
55
  * from a Session's terminal diagnostic, or nothing when it is blank. Control
@@ -28,18 +58,54 @@ export declare const BirdDogReplySchema: Schema.Struct<{
28
58
  * line is cut, so a cut never exposes part of one.
29
59
  */
30
60
  export declare function boundedDiagnostic(text: string): string | undefined;
31
- /** Parser for addressed agent correspondence. */
32
- export declare const BirdDogMailSchema: Schema.Struct<{
61
+ export declare const BirdDogMailSchema: Schema.Union<readonly [Schema.Struct<{
62
+ readonly from: Schema.Struct<{
63
+ readonly kind: Schema.Literal<"agent">;
64
+ readonly agentId: Schema.String;
65
+ readonly sessionId: Schema.String;
66
+ }>;
67
+ /** Settlement or acceptance facts, present only on a reply. */
68
+ readonly reply: Schema.optionalKey<Schema.Union<readonly [Schema.Struct<{
69
+ readonly commandId: Schema.String;
70
+ readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
71
+ readonly diagnostic: Schema.optionalKey<Schema.String>;
72
+ }>, Schema.Struct<{
73
+ readonly outcome: Schema.Literal<"undeliverable">;
74
+ readonly reason: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
75
+ }>, Schema.Struct<{
76
+ readonly outcome: Schema.Literal<"accepted">;
77
+ readonly commandId: Schema.String;
78
+ }>]>>;
33
79
  readonly key: Schema.String;
34
80
  readonly to: Schema.Struct<{
35
81
  readonly kind: Schema.Literal<"agent">;
36
82
  readonly agentId: Schema.String;
37
- readonly sessionId: Schema.String;
83
+ readonly sessionId: Schema.optionalKey<Schema.String>;
38
84
  }>;
39
- readonly from: Schema.Struct<{
85
+ readonly replyTo: Schema.$Array<Schema.Struct<{
40
86
  readonly kind: Schema.Literal<"agent">;
41
87
  readonly agentId: Schema.String;
42
88
  readonly sessionId: Schema.String;
89
+ }>>;
90
+ readonly content: Schema.String;
91
+ readonly createdAt: Schema.Number;
92
+ readonly meta: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
93
+ }>, Schema.Struct<{
94
+ readonly from: Schema.Struct<{
95
+ readonly kind: Schema.Literal<"agent">;
96
+ readonly agentId: Schema.String;
97
+ readonly sessionId: Schema.optionalKey<Schema.String>;
98
+ }>;
99
+ /** An unstarted recipient can only author an undeliverable reply. */
100
+ readonly reply: Schema.Struct<{
101
+ readonly outcome: Schema.Literal<"undeliverable">;
102
+ readonly reason: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
103
+ }>;
104
+ readonly key: Schema.String;
105
+ readonly to: Schema.Struct<{
106
+ readonly kind: Schema.Literal<"agent">;
107
+ readonly agentId: Schema.String;
108
+ readonly sessionId: Schema.optionalKey<Schema.String>;
43
109
  }>;
44
110
  readonly replyTo: Schema.$Array<Schema.Struct<{
45
111
  readonly kind: Schema.Literal<"agent">;
@@ -48,15 +114,8 @@ export declare const BirdDogMailSchema: Schema.Struct<{
48
114
  }>>;
49
115
  readonly content: Schema.String;
50
116
  readonly createdAt: Schema.Number;
51
- /** The sender's opaque host metadata; BirdDog never reads it. */
52
117
  readonly meta: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
53
- /** Settlement facts, present only on a reply BirdDog built. */
54
- readonly reply: Schema.optionalKey<Schema.Struct<{
55
- readonly commandId: Schema.String;
56
- readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
57
- readonly diagnostic: Schema.optionalKey<Schema.String>;
58
- }>>;
59
- }>;
118
+ }>]>;
60
119
  /** Parser for accepted Session work. */
61
120
  export declare const BirdDogAgentWorkSchema: Schema.Struct<{
62
121
  readonly jobId: Schema.String;
@@ -72,17 +131,54 @@ export declare const BirdDogAgentJobPayloadSchema: Schema.Struct<{
72
131
  readonly sessionId: Schema.String;
73
132
  readonly commandFactId: Schema.String;
74
133
  }>;
75
- readonly mail: Schema.optional<Schema.Struct<{
134
+ readonly mail: Schema.optional<Schema.Union<readonly [Schema.Struct<{
135
+ readonly from: Schema.Struct<{
136
+ readonly kind: Schema.Literal<"agent">;
137
+ readonly agentId: Schema.String;
138
+ readonly sessionId: Schema.String;
139
+ }>;
140
+ /** Settlement or acceptance facts, present only on a reply. */
141
+ readonly reply: Schema.optionalKey<Schema.Union<readonly [Schema.Struct<{
142
+ readonly commandId: Schema.String;
143
+ readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
144
+ readonly diagnostic: Schema.optionalKey<Schema.String>;
145
+ }>, Schema.Struct<{
146
+ readonly outcome: Schema.Literal<"undeliverable">;
147
+ readonly reason: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
148
+ }>, Schema.Struct<{
149
+ readonly outcome: Schema.Literal<"accepted">;
150
+ readonly commandId: Schema.String;
151
+ }>]>>;
76
152
  readonly key: Schema.String;
77
153
  readonly to: Schema.Struct<{
78
154
  readonly kind: Schema.Literal<"agent">;
79
155
  readonly agentId: Schema.String;
80
- readonly sessionId: Schema.String;
156
+ readonly sessionId: Schema.optionalKey<Schema.String>;
81
157
  }>;
82
- readonly from: Schema.Struct<{
158
+ readonly replyTo: Schema.$Array<Schema.Struct<{
83
159
  readonly kind: Schema.Literal<"agent">;
84
160
  readonly agentId: Schema.String;
85
161
  readonly sessionId: Schema.String;
162
+ }>>;
163
+ readonly content: Schema.String;
164
+ readonly createdAt: Schema.Number;
165
+ readonly meta: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
166
+ }>, Schema.Struct<{
167
+ readonly from: Schema.Struct<{
168
+ readonly kind: Schema.Literal<"agent">;
169
+ readonly agentId: Schema.String;
170
+ readonly sessionId: Schema.optionalKey<Schema.String>;
171
+ }>;
172
+ /** An unstarted recipient can only author an undeliverable reply. */
173
+ readonly reply: Schema.Struct<{
174
+ readonly outcome: Schema.Literal<"undeliverable">;
175
+ readonly reason: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
176
+ }>;
177
+ readonly key: Schema.String;
178
+ readonly to: Schema.Struct<{
179
+ readonly kind: Schema.Literal<"agent">;
180
+ readonly agentId: Schema.String;
181
+ readonly sessionId: Schema.optionalKey<Schema.String>;
86
182
  }>;
87
183
  readonly replyTo: Schema.$Array<Schema.Struct<{
88
184
  readonly kind: Schema.Literal<"agent">;
@@ -91,15 +187,8 @@ export declare const BirdDogAgentJobPayloadSchema: Schema.Struct<{
91
187
  }>>;
92
188
  readonly content: Schema.String;
93
189
  readonly createdAt: Schema.Number;
94
- /** The sender's opaque host metadata; BirdDog never reads it. */
95
190
  readonly meta: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
96
- /** Settlement facts, present only on a reply BirdDog built. */
97
- readonly reply: Schema.optionalKey<Schema.Struct<{
98
- readonly commandId: Schema.String;
99
- readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
100
- readonly diagnostic: Schema.optionalKey<Schema.String>;
101
- }>>;
102
- }>>;
191
+ }>]>>;
103
192
  }>;
104
193
  /** Parser for accepted work entering BirdDog's transactional projection. */
105
194
  export declare const BirdDogAgentJobInputSchema: Schema.Struct<{
@@ -111,17 +200,54 @@ export declare const BirdDogAgentJobInputSchema: Schema.Struct<{
111
200
  readonly maxRecoveries: Schema.Int;
112
201
  }>>;
113
202
  }>;
114
- readonly mail: Schema.optional<Schema.Struct<{
203
+ readonly mail: Schema.optional<Schema.Union<readonly [Schema.Struct<{
204
+ readonly from: Schema.Struct<{
205
+ readonly kind: Schema.Literal<"agent">;
206
+ readonly agentId: Schema.String;
207
+ readonly sessionId: Schema.String;
208
+ }>;
209
+ /** Settlement or acceptance facts, present only on a reply. */
210
+ readonly reply: Schema.optionalKey<Schema.Union<readonly [Schema.Struct<{
211
+ readonly commandId: Schema.String;
212
+ readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
213
+ readonly diagnostic: Schema.optionalKey<Schema.String>;
214
+ }>, Schema.Struct<{
215
+ readonly outcome: Schema.Literal<"undeliverable">;
216
+ readonly reason: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
217
+ }>, Schema.Struct<{
218
+ readonly outcome: Schema.Literal<"accepted">;
219
+ readonly commandId: Schema.String;
220
+ }>]>>;
115
221
  readonly key: Schema.String;
116
222
  readonly to: Schema.Struct<{
117
223
  readonly kind: Schema.Literal<"agent">;
118
224
  readonly agentId: Schema.String;
119
- readonly sessionId: Schema.String;
225
+ readonly sessionId: Schema.optionalKey<Schema.String>;
120
226
  }>;
121
- readonly from: Schema.Struct<{
227
+ readonly replyTo: Schema.$Array<Schema.Struct<{
122
228
  readonly kind: Schema.Literal<"agent">;
123
229
  readonly agentId: Schema.String;
124
230
  readonly sessionId: Schema.String;
231
+ }>>;
232
+ readonly content: Schema.String;
233
+ readonly createdAt: Schema.Number;
234
+ readonly meta: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
235
+ }>, Schema.Struct<{
236
+ readonly from: Schema.Struct<{
237
+ readonly kind: Schema.Literal<"agent">;
238
+ readonly agentId: Schema.String;
239
+ readonly sessionId: Schema.optionalKey<Schema.String>;
240
+ }>;
241
+ /** An unstarted recipient can only author an undeliverable reply. */
242
+ readonly reply: Schema.Struct<{
243
+ readonly outcome: Schema.Literal<"undeliverable">;
244
+ readonly reason: Schema.Literals<readonly ["recipient_offline", "recipient_left"]>;
245
+ }>;
246
+ readonly key: Schema.String;
247
+ readonly to: Schema.Struct<{
248
+ readonly kind: Schema.Literal<"agent">;
249
+ readonly agentId: Schema.String;
250
+ readonly sessionId: Schema.optionalKey<Schema.String>;
125
251
  }>;
126
252
  readonly replyTo: Schema.$Array<Schema.Struct<{
127
253
  readonly kind: Schema.Literal<"agent">;
@@ -130,15 +256,8 @@ export declare const BirdDogAgentJobInputSchema: Schema.Struct<{
130
256
  }>>;
131
257
  readonly content: Schema.String;
132
258
  readonly createdAt: Schema.Number;
133
- /** The sender's opaque host metadata; BirdDog never reads it. */
134
259
  readonly meta: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
135
- /** Settlement facts, present only on a reply BirdDog built. */
136
- readonly reply: Schema.optionalKey<Schema.Struct<{
137
- readonly commandId: Schema.String;
138
- readonly outcome: Schema.Literals<readonly ["completed", "failed", "interrupted", "cancelled", "outcome_unknown"]>;
139
- readonly diagnostic: Schema.optionalKey<Schema.String>;
140
- }>>;
141
- }>>;
260
+ }>]>>;
142
261
  }>;
143
262
  /** BirdDog could not complete one owned store or transport operation. */
144
263
  export declare class BirdDogError extends Error {
@@ -172,8 +291,12 @@ export type EffectBirdDogOptions = Readonly<{
172
291
  export type BirdDogEffect = Readonly<{
173
292
  /** Queue replies owed by a settled agent job. */
174
293
  fanOut: (settlement: BirdDogSettlement) => Effect.Effect<void, BirdDogError>;
294
+ /** Durably queue one authored mail item in this Agent's existing outbox. */
295
+ postMail?: ((mail: BirdDogMail) => Effect.Effect<void, BirdDogError>) | undefined;
175
296
  /** Deliver due replies and schedule any required retry wake. */
176
297
  drainOutbox: () => Effect.Effect<void, BirdDogError>;
298
+ /** Earliest owed reply delivery, including retries; absent after delivery. */
299
+ nextWakeAt: () => Effect.Effect<number | undefined, BirdDogError>;
177
300
  }>;
178
301
  /** Validate and translate agent work into Watchdog's opaque job grammar. */
179
302
  export declare const agentJobFromInput: (input: BirdDogAgentJobInput) => WatchdogQueuedJob;
@@ -187,5 +310,12 @@ export declare const decodeAgentJob: (job: WatchdogStoredJob) => BirdDogAgentJob
187
310
  * are presented. A reply never carries the settled work's host metadata.
188
311
  */
189
312
  export declare function settlementReplies(settlement: BirdDogSettlement, createdAt: number): readonly BirdDogMail[];
313
+ /** Build the recipient's one stable acceptance notice for one original mail. */
314
+ export declare function acceptedNotice(mail: BirdDogMail, sessionId: string, commandId: string, createdAt: number): BirdDogMail;
315
+ /**
316
+ * Whether this mail room's outbox still owes any reply a delivery attempt.
317
+ * Delivered receipts and exhausted dead letters stay but owe nothing.
318
+ */
319
+ export declare const hasPendingDelivery: (outbox: Pick<EffectBirdDogOptions["outbox"], "list">) => Effect.Effect<boolean, BirdDogError>;
190
320
  /** Construct BirdDog's Effect-native mail room over Post's shared outbox. */
191
321
  export declare const make: (options: EffectBirdDogOptions) => Effect.Effect<BirdDogEffect>;
package/dist/effect.js CHANGED
@@ -1,13 +1,25 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
- import { makePostOutbox } from "@fungi.computer/carrier/post";
3
+ import { makePostOutbox, postOutboxPending, } from "@fungi.computer/carrier/post";
4
4
  const Identifier = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256));
5
+ /** The maximum UTF-8 size of one mail body. */
6
+ export const BIRD_DOG_MAIL_CONTENT_MAX_BYTES = 256 * 1024;
7
+ const MailContent = Schema.String.check(Schema.makeFilter((content) => new TextEncoder().encode(content).byteLength <=
8
+ BIRD_DOG_MAIL_CONTENT_MAX_BYTES, {
9
+ message: `Mail content must be at most ${String(BIRD_DOG_MAIL_CONTENT_MAX_BYTES)} UTF-8 bytes`,
10
+ }));
5
11
  /** Parser for an ordinary agent Session address. */
6
12
  export const BirdDogAgentReferenceSchema = Schema.Struct({
7
13
  kind: Schema.Literal("agent"),
8
14
  agentId: Identifier,
9
15
  sessionId: Identifier,
10
16
  });
17
+ /** An addressed Agent may create its first Session when accepting this mail. */
18
+ export const BirdDogAgentRecipientReferenceSchema = Schema.Struct({
19
+ kind: Schema.Literal("agent"),
20
+ agentId: Identifier,
21
+ sessionId: Schema.optionalKey(Identifier),
22
+ });
11
23
  /** The most characters a reply's failure diagnostic may carry. */
12
24
  export const BIRD_DOG_DIAGNOSTIC_MAX_LENGTH = 200;
13
25
  /**
@@ -15,7 +27,7 @@ export const BIRD_DOG_DIAGNOSTIC_MAX_LENGTH = 200;
15
27
  * replying Session's command, its exact Watchdog outcome and, for work that did
16
28
  * not complete, the Session's bounded terminal diagnostic.
17
29
  */
18
- export const BirdDogReplySchema = Schema.Struct({
30
+ export const SettledBirdDogReplySchema = Schema.Struct({
19
31
  commandId: Identifier,
20
32
  outcome: Schema.Literals([
21
33
  "completed",
@@ -26,6 +38,25 @@ export const BirdDogReplySchema = Schema.Struct({
26
38
  ]),
27
39
  diagnostic: Schema.optionalKey(Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(BIRD_DOG_DIAGNOSTIC_MAX_LENGTH))),
28
40
  });
41
+ /** A terminal delivery failure, correlated by the original mail key only. */
42
+ export const BirdDogUndeliverableReasonSchema = Schema.Literals([
43
+ "recipient_offline",
44
+ "recipient_left",
45
+ ]);
46
+ export const UndeliverableBirdDogReplySchema = Schema.Struct({
47
+ outcome: Schema.Literal("undeliverable"),
48
+ reason: BirdDogUndeliverableReasonSchema,
49
+ });
50
+ /** Recipient acceptance, before the command has settled. */
51
+ export const AcceptedBirdDogReplySchema = Schema.Struct({
52
+ outcome: Schema.Literal("accepted"),
53
+ commandId: Identifier,
54
+ });
55
+ export const BirdDogReplySchema = Schema.Union([
56
+ SettledBirdDogReplySchema,
57
+ UndeliverableBirdDogReplySchema,
58
+ AcceptedBirdDogReplySchema,
59
+ ]);
29
60
  const REDACTED = "[redacted]";
30
61
  // An authorization scheme's credential, e.g. `Bearer <token>`.
31
62
  const schemeCredential = /\b(bearer|basic)\s+[^\s"',;]+/giu;
@@ -53,18 +84,29 @@ export function boundedDiagnostic(text) {
53
84
  return line.length === 0 ? undefined : line;
54
85
  }
55
86
  /** Parser for addressed agent correspondence. */
56
- export const BirdDogMailSchema = Schema.Struct({
87
+ const BirdDogMailFields = {
57
88
  key: Identifier,
58
- to: BirdDogAgentReferenceSchema,
59
- from: BirdDogAgentReferenceSchema,
89
+ to: BirdDogAgentRecipientReferenceSchema,
60
90
  replyTo: Schema.Array(BirdDogAgentReferenceSchema),
61
- content: Schema.String,
91
+ content: MailContent,
62
92
  createdAt: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)),
63
93
  /** The sender's opaque host metadata; BirdDog never reads it. */
64
94
  meta: Schema.optionalKey(Schema.Json),
65
- /** Settlement facts, present only on a reply BirdDog built. */
66
- reply: Schema.optionalKey(BirdDogReplySchema),
67
- });
95
+ };
96
+ export const BirdDogMailSchema = Schema.Union([
97
+ Schema.Struct({
98
+ ...BirdDogMailFields,
99
+ from: BirdDogAgentReferenceSchema,
100
+ /** Settlement or acceptance facts, present only on a reply. */
101
+ reply: Schema.optionalKey(BirdDogReplySchema),
102
+ }),
103
+ Schema.Struct({
104
+ ...BirdDogMailFields,
105
+ from: BirdDogAgentRecipientReferenceSchema,
106
+ /** An unstarted recipient can only author an undeliverable reply. */
107
+ reply: UndeliverableBirdDogReplySchema,
108
+ }),
109
+ ]);
68
110
  /** Parser for accepted Session work. */
69
111
  export const BirdDogAgentWorkSchema = Schema.Struct({
70
112
  jobId: Identifier,
@@ -141,7 +183,7 @@ export function settlementReplies(settlement, createdAt) {
141
183
  return mail.replyTo.map((destination, index) => ({
142
184
  key: `${mail.key}:reply:${String(index)}`,
143
185
  to: destination,
144
- from: mail.to,
186
+ from: { ...mail.to, sessionId: work.sessionId },
145
187
  replyTo: [],
146
188
  content: settlement.content.length > 0
147
189
  ? settlement.content
@@ -150,6 +192,29 @@ export function settlementReplies(settlement, createdAt) {
150
192
  reply,
151
193
  }));
152
194
  }
195
+ /** Build the recipient's one stable acceptance notice for one original mail. */
196
+ export function acceptedNotice(mail, sessionId, commandId, createdAt) {
197
+ const recipientSession = Schema.decodeUnknownSync(Identifier)(sessionId);
198
+ const sender = Schema.decodeUnknownSync(BirdDogAgentReferenceSchema)(mail.from);
199
+ const reply = Schema.decodeUnknownSync(AcceptedBirdDogReplySchema)({
200
+ outcome: "accepted",
201
+ commandId,
202
+ });
203
+ return {
204
+ key: `${mail.key}:accepted`,
205
+ to: sender,
206
+ from: { ...mail.to, sessionId: recipientSession },
207
+ replyTo: [],
208
+ content: "Work accepted.",
209
+ createdAt,
210
+ reply,
211
+ };
212
+ }
213
+ /**
214
+ * Whether this mail room's outbox still owes any reply a delivery attempt.
215
+ * Delivered receipts and exhausted dead letters stay but owe nothing.
216
+ */
217
+ export const hasPendingDelivery = (outbox) => postOutboxPending({ message: BirdDogMailSchema, outbox });
153
218
  /** Construct BirdDog's Effect-native mail room over Post's shared outbox. */
154
219
  export const make = (options) => {
155
220
  const post = makePostOutbox({
@@ -177,5 +242,17 @@ export const make = (options) => {
177
242
  if (wake !== undefined)
178
243
  yield* options.scheduleWake(wake);
179
244
  });
180
- return Effect.succeed({ fanOut, drainOutbox: post.drain });
245
+ const postMail = (mail) => Effect.gen(function* () {
246
+ const createdAt = options.now();
247
+ const hint = yield* post.enqueue(mail, createdAt);
248
+ if (hint !== undefined)
249
+ yield* options.scheduleWake(hint);
250
+ return;
251
+ });
252
+ return Effect.succeed({
253
+ fanOut,
254
+ postMail,
255
+ drainOutbox: post.drain,
256
+ nextWakeAt: post.nextWakeAt,
257
+ });
181
258
  };
package/dist/index.d.ts CHANGED
@@ -6,18 +6,11 @@ export type BirdDogAgentReference = Readonly<{
6
6
  agentId: string;
7
7
  sessionId: string;
8
8
  }>;
9
- /** Addressed agent correspondence carried with durable work when a reply is owed. */
10
- export type BirdDogMail = Readonly<{
11
- key: string;
12
- to: BirdDogAgentReference;
13
- from: BirdDogAgentReference;
14
- replyTo: readonly BirdDogAgentReference[];
15
- content: string;
16
- createdAt: number;
17
- /** The sender's opaque host metadata; BirdDog never reads it. */
18
- meta?: BirdDogMeta;
19
- /** Settlement facts, present only on a reply BirdDog built. */
20
- reply?: BirdDogReply;
9
+ /** An addressed Agent whose first Session may be created on acceptance. */
10
+ export type BirdDogAgentRecipientReference = Readonly<{
11
+ kind: "agent";
12
+ agentId: string;
13
+ sessionId?: string;
21
14
  }>;
22
15
  /**
23
16
  * What a reply reports about the settled work beside its content. The
@@ -25,12 +18,45 @@ export type BirdDogMail = Readonly<{
25
18
  * against the mail it sent.
26
19
  */
27
20
  export type BirdDogReply = Readonly<{
21
+ /** The recipient accepted the work into the named Session. */
22
+ commandId: string;
23
+ outcome: "accepted";
24
+ }> | Readonly<{
28
25
  /** The replying Session's accepted command: the sender's receipt `commandId`. */
29
26
  commandId: string;
30
27
  /** The exact Watchdog outcome; failures are never collapsed. */
31
28
  outcome: "completed" | "failed" | "interrupted" | "cancelled" | "outcome_unknown";
32
29
  /** Bounded terminal diagnostic for work that did not complete or cancel. */
33
30
  diagnostic?: string;
31
+ }> | Readonly<{
32
+ outcome: "undeliverable";
33
+ reason: "recipient_offline" | "recipient_left";
34
+ }>;
35
+ /** Addressed mail; an unstarted Agent may author only an undeliverable reply. */
36
+ export type BirdDogMail = Readonly<{
37
+ key: string;
38
+ to: BirdDogAgentRecipientReference;
39
+ replyTo: readonly BirdDogAgentReference[];
40
+ content: string;
41
+ createdAt: number;
42
+ /** The sender's opaque host metadata; BirdDog never reads it. */
43
+ meta?: BirdDogMeta;
44
+ from: BirdDogAgentReference;
45
+ /** Settlement or acceptance facts, present only on a reply. */
46
+ reply?: BirdDogReply;
47
+ }> | Readonly<{
48
+ key: string;
49
+ to: BirdDogAgentRecipientReference;
50
+ replyTo: readonly BirdDogAgentReference[];
51
+ content: string;
52
+ createdAt: number;
53
+ /** The sender's opaque host metadata; BirdDog never reads it. */
54
+ meta?: BirdDogMeta;
55
+ /** No Session exists when a never-started recipient is undeliverable. */
56
+ from: BirdDogAgentRecipientReference;
57
+ reply: Extract<BirdDogReply, {
58
+ outcome: "undeliverable";
59
+ }>;
34
60
  }>;
35
61
  /** Opaque JSON a host attached to the work; BirdDog only carries it. */
36
62
  export type BirdDogMeta = typeof Schema.Json.Type;
@@ -90,6 +90,7 @@ Effect host ports for the durable mail room.
90
90
  type BirdDogEffect = Readonly<{
91
91
  fanOut: (settlement) => Effect.Effect<void, BirdDogError>;
92
92
  drainOutbox: () => Effect.Effect<void, BirdDogError>;
93
+ nextWakeAt: () => Effect.Effect<number | undefined, BirdDogError>;
93
94
  }>;
94
95
  ```
95
96
 
@@ -455,6 +456,27 @@ readonly `Readonly`\<\{ `key`: `string`; `to`:
455
456
 
456
457
  ---
457
458
 
459
+ ### hasPendingDelivery()
460
+
461
+ ```ts
462
+ function hasPendingDelivery(outbox): Effect<boolean, BirdDogError>;
463
+ ```
464
+
465
+ Whether this mail room's outbox still owes any reply a delivery attempt.
466
+ Delivered receipts and exhausted dead letters stay but owe nothing.
467
+
468
+ #### Parameters
469
+
470
+ | Parameter | Type |
471
+ | --------- | --------------------------------------------------------------------------------- |
472
+ | `outbox` | `Pick`\<[`EffectBirdDogOptions`](#effectbirddogoptions)\[`"outbox"`\], `"list"`\> |
473
+
474
+ #### Returns
475
+
476
+ `Effect`\<`boolean`, [`BirdDogError`](#birddogerror)\>
477
+
478
+ ---
479
+
458
480
  ### make()
459
481
 
460
482
  ```ts
@@ -462,6 +484,7 @@ function make(options): Effect<
462
484
  Readonly<{
463
485
  fanOut: (settlement) => Effect<void, BirdDogError>;
464
486
  drainOutbox: () => Effect<void, BirdDogError>;
487
+ nextWakeAt: () => Effect<number | undefined, BirdDogError>;
465
488
  }>
466
489
  >;
467
490
  ```
@@ -478,4 +501,5 @@ Construct BirdDog's Effect-native mail room over Post's shared outbox.
478
501
 
479
502
  `Effect`\<`Readonly`\<\{ `fanOut`: (`settlement`) => `Effect`\<`void`,
480
503
  [`BirdDogError`](#birddogerror)\>; `drainOutbox`: () => `Effect`\<`void`,
481
- [`BirdDogError`](#birddogerror)\>; \}\>\>
504
+ [`BirdDogError`](#birddogerror)\>; `nextWakeAt`: () => `Effect`\<`number` \|
505
+ `undefined`, [`BirdDogError`](#birddogerror)\>; \}\>\>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/bird-dog",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "Durable addressed agent mail over generic Watchdog work",
6
6
  "type": "module",
@@ -23,8 +23,8 @@
23
23
  "LICENSE"
24
24
  ],
25
25
  "dependencies": {
26
- "@fungi.computer/carrier": "0.0.1",
27
- "@fungi.computer/watchdog": "0.0.1",
26
+ "@fungi.computer/carrier": "0.2.0",
27
+ "@fungi.computer/watchdog": "0.1.0",
28
28
  "effect": "4.0.2"
29
29
  },
30
30
  "devDependencies": {