@fungi.computer/oyster 0.4.1 → 0.6.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 (86) hide show
  1. package/README.md +46 -8
  2. package/dist/agents.d.ts +2 -1
  3. package/dist/agents.js +67 -8
  4. package/dist/apps/gate.d.ts +4 -0
  5. package/dist/apps/gate.js +3 -0
  6. package/dist/auth.d.ts +24 -1
  7. package/dist/auth.js +38 -0
  8. package/dist/boot-host-script.js +1 -1
  9. package/dist/build/candidate-cli.mjs +27 -5
  10. package/dist/channels/binding.d.ts +70 -0
  11. package/dist/channels/binding.js +97 -0
  12. package/dist/channels/conversations.d.ts +246 -0
  13. package/dist/channels/conversations.js +705 -0
  14. package/dist/channels/ids.d.ts +8 -0
  15. package/dist/channels/ids.js +15 -0
  16. package/dist/channels/visitor-channel.d.ts +53 -0
  17. package/dist/channels/visitor-channel.js +124 -0
  18. package/dist/channels/visitor-edge.d.ts +115 -0
  19. package/dist/channels/visitor-edge.js +582 -0
  20. package/dist/channels/visitor-request.d.ts +7 -0
  21. package/dist/channels/visitor-request.js +17 -0
  22. package/dist/channels/visitor-stream-slots.d.ts +2 -0
  23. package/dist/channels/visitor-stream-slots.js +33 -0
  24. package/dist/channels/visitors.d.ts +47 -0
  25. package/dist/channels/visitors.js +181 -0
  26. package/dist/cloudflare.d.ts +8 -2
  27. package/dist/cloudflare.js +27 -1
  28. package/dist/computer-target.d.ts +5 -0
  29. package/dist/computer-target.js +11 -0
  30. package/dist/host.d.ts +7 -4
  31. package/dist/host.js +96 -17
  32. package/dist/internal/host-gate.d.ts +6 -4
  33. package/dist/internal/host-gate.js +14 -7
  34. package/dist/invocation.d.ts +11 -4
  35. package/dist/invocation.js +134 -91
  36. package/dist/keys.d.ts +4 -1
  37. package/dist/keys.js +15 -4
  38. package/dist/machine-link/authority.d.ts +7 -0
  39. package/dist/machine-link/delivery.d.ts +12 -0
  40. package/dist/machine-link/delivery.js +22 -1
  41. package/dist/machine-link/disposition.d.ts +16 -0
  42. package/dist/machine-link/disposition.js +65 -0
  43. package/dist/machine-link/host.d.ts +11 -2
  44. package/dist/machine-link/host.js +125 -5
  45. package/dist/machine-link/link.d.ts +4 -0
  46. package/dist/machine-link/link.js +47 -1
  47. package/dist/page/capabilities.js +1 -1
  48. package/dist/page/child-selection.d.ts +27 -0
  49. package/dist/page/child-selection.js +38 -0
  50. package/dist/page/mount.js +1 -1
  51. package/dist/page-script.js +4 -4
  52. package/dist/release.d.ts +13 -0
  53. package/dist/release.js +10 -0
  54. package/dist/request-origin.d.ts +4 -0
  55. package/dist/request-origin.js +10 -0
  56. package/dist/standalone/agent-mail.js +4 -1
  57. package/dist/standalone/agents.d.ts +8 -0
  58. package/dist/standalone/agents.js +30 -11
  59. package/dist/standalone/channel-session-door.d.ts +4 -0
  60. package/dist/standalone/channel-session-door.js +72 -0
  61. package/dist/standalone/computer-fleet.js +7 -1
  62. package/dist/standalone/machine-link.d.ts +2 -0
  63. package/dist/standalone/machine-link.js +34 -1
  64. package/dist/standalone/tenancy.d.ts +3 -0
  65. package/dist/standalone/tenancy.js +7 -2
  66. package/dist/testing/capabilities.d.ts +2 -0
  67. package/dist/testing/capabilities.js +3 -1
  68. package/dist/turnstile.d.ts +27 -0
  69. package/dist/turnstile.js +51 -0
  70. package/dist/workers-git.js +21 -2
  71. package/dist/workers.d.ts +3 -0
  72. package/dist/workers.js +15 -12
  73. package/docs/api/agents.md +8 -4
  74. package/docs/api/apps.md +8 -4
  75. package/docs/api/auth.md +55 -0
  76. package/docs/api/browser.md +40 -16
  77. package/docs/api/build-contract.md +2 -2
  78. package/docs/api/cloudflare.md +26 -1
  79. package/docs/api/host.md +22 -13
  80. package/docs/api/invocation.md +7 -3
  81. package/docs/api/keys.md +25 -3
  82. package/docs/api/workers.md +38 -30
  83. package/docs/site/agent-quickstart.mdx +29 -2
  84. package/examples/standalone/wrangler.celld.jsonc +66 -0
  85. package/examples/standalone/wrangler.jsonc +3 -4
  86. package/package.json +53 -17
@@ -21740,7 +21740,17 @@ var BirdDogAgentRecipientReferenceSchema = Struct({
21740
21740
  agentId: Identifier,
21741
21741
  sessionId: optionalKey2(Identifier)
21742
21742
  });
21743
+ var BirdDogSystemIdentitySchema = Struct({
21744
+ kind: Literal2("system"),
21745
+ systemId: Literal2("hub")
21746
+ });
21743
21747
  var BIRD_DOG_DIAGNOSTIC_MAX_LENGTH = 200;
21748
+ var BIRD_DOG_WAITING_MAX_ITEMS = 8;
21749
+ var BirdDogWaitingItemSchema = Struct({
21750
+ itemId: Identifier,
21751
+ kind: String4.check(isMinLength(1), isMaxLength(64)),
21752
+ summary: String4.check(isMinLength(1), isMaxLength(200))
21753
+ });
21744
21754
  var SettledBirdDogReplySchema = Struct({
21745
21755
  commandId: Identifier,
21746
21756
  outcome: Literals([
@@ -21750,7 +21760,8 @@ var SettledBirdDogReplySchema = Struct({
21750
21760
  "cancelled",
21751
21761
  "outcome_unknown"
21752
21762
  ]),
21753
- diagnostic: optionalKey2(String4.check(isMinLength(1), isMaxLength(BIRD_DOG_DIAGNOSTIC_MAX_LENGTH)))
21763
+ diagnostic: optionalKey2(String4.check(isMinLength(1), isMaxLength(BIRD_DOG_DIAGNOSTIC_MAX_LENGTH))),
21764
+ waitingOnYou: optionalKey2(ArraySchema(BirdDogWaitingItemSchema).check(isMinLength(1), isMaxLength(BIRD_DOG_WAITING_MAX_ITEMS)))
21754
21765
  });
21755
21766
  var BirdDogUndeliverableReasonSchema = Literals([
21756
21767
  "recipient_offline",
@@ -21769,6 +21780,14 @@ var BirdDogReplySchema = Union2([
21769
21780
  UndeliverableBirdDogReplySchema,
21770
21781
  AcceptedBirdDogReplySchema
21771
21782
  ]);
21783
+ var BirdDogAgentReplySchema = Union2([
21784
+ SettledBirdDogReplySchema,
21785
+ AcceptedBirdDogReplySchema,
21786
+ Struct({
21787
+ outcome: Literal2("undeliverable"),
21788
+ reason: Literal2("recipient_left")
21789
+ })
21790
+ ]);
21772
21791
  var REDACTED = "[redacted]";
21773
21792
  var schemeCredential = /\b(bearer|basic)\s+[^\s"',;]+/giu;
21774
21793
  var namedCredential = /\b((?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|authorization)["']?\s*[:=]\s*["']?)(?!(?:bearer|basic)\s)[^\s"',;&]+/giu;
@@ -21794,13 +21813,16 @@ var BirdDogMailSchema = Union2([
21794
21813
  ...BirdDogMailFields,
21795
21814
  from: BirdDogAgentReferenceSchema,
21796
21815
  /** Settlement or acceptance facts, present only on a reply. */
21797
- reply: optionalKey2(BirdDogReplySchema)
21816
+ reply: optionalKey2(BirdDogAgentReplySchema)
21798
21817
  }),
21799
21818
  Struct({
21800
21819
  ...BirdDogMailFields,
21801
- from: BirdDogAgentRecipientReferenceSchema,
21802
- /** An unstarted recipient can only author an undeliverable reply. */
21803
- reply: UndeliverableBirdDogReplySchema
21820
+ from: BirdDogSystemIdentitySchema,
21821
+ /** Only the Hub may author this offline disposition. */
21822
+ reply: Struct({
21823
+ outcome: Literal2("undeliverable"),
21824
+ reason: Literal2("recipient_offline")
21825
+ })
21804
21826
  })
21805
21827
  ]);
21806
21828
  var BirdDogAgentWorkSchema = Struct({
@@ -0,0 +1,70 @@
1
+ import { z } from "zod";
2
+ /** The runtime needs a binding, not a Team or an authority projection. */
3
+ export type ConversationBinding = Readonly<{
4
+ bindingId: string;
5
+ lifecycle: "active" | "suspended";
6
+ selector: Readonly<{
7
+ provider: "event";
8
+ target: "new" | "named";
9
+ verifier: "fungi-v1" | "github" | "secret-url";
10
+ eventsPerMinute: number;
11
+ }> | Readonly<{
12
+ provider: "web";
13
+ siteKey: string;
14
+ origins: readonly string[];
15
+ accent?: string | undefined;
16
+ }> | Readonly<{
17
+ provider: "discord" | "email" | "game";
18
+ }>;
19
+ /** Zod's optional binding projection may carry an explicit undefined. */
20
+ limits?: BindingLimits | undefined;
21
+ }>;
22
+ /** Does one allowlisted origin admit this exact browser origin? */
23
+ export declare function originAllowed(allowed: readonly string[], origin: string): boolean;
24
+ /** Per-party edge limits applied before any work is accepted. */
25
+ export declare const bindingLimitsSchema: z.ZodObject<{
26
+ messagesPerPartyPerDay: z.ZodNumber;
27
+ charsPerMessage: z.ZodNumber;
28
+ newPartiesPerHour: z.ZodNumber;
29
+ }, z.core.$strict>;
30
+ /** Per-binding edge limits. */
31
+ export type BindingLimits = z.infer<typeof bindingLimitsSchema>;
32
+ /** Edge limits of a binding whose owner set none. */
33
+ export declare const DEFAULT_BINDING_LIMITS: BindingLimits;
34
+ /**
35
+ * How long a GitHub body digest keeps a changed-id replay out: the verifiers'
36
+ * signed-timestamp skew (Veil's `WEBHOOK_SIGNATURE_MAX_SKEW_SECONDS`; a test
37
+ * pins them equal). GitHub signs no timestamp, so this window is the bound.
38
+ */
39
+ export declare const EVENT_REPLAY_WINDOW_SECONDS = 300;
40
+ /** The edge limits one binding runs under: its owner's, else its kind's default. */
41
+ export declare function limitsOf(binding: ConversationBinding): BindingLimits;
42
+ export declare const discordSnowflakeSchema: z.ZodString;
43
+ /** The party on the other side of a channel conversation; never a member. */
44
+ export declare const channelPartySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
45
+ kind: z.ZodLiteral<"visitor">;
46
+ visitorId: z.ZodString;
47
+ }, z.core.$strict>, z.ZodObject<{
48
+ kind: z.ZodLiteral<"discord-user">;
49
+ guildId: z.ZodString;
50
+ userId: z.ZodString;
51
+ }, z.core.$strict>, z.ZodObject<{
52
+ kind: z.ZodLiteral<"email-sender">;
53
+ address: z.ZodEmail;
54
+ dmarc: z.ZodEnum<{
55
+ none: "none";
56
+ pass: "pass";
57
+ fail: "fail";
58
+ }>;
59
+ }, z.core.$strict>, z.ZodObject<{
60
+ kind: z.ZodLiteral<"player">;
61
+ worldId: z.ZodString;
62
+ playerId: z.ZodString;
63
+ }, z.core.$strict>, z.ZodObject<{
64
+ kind: z.ZodLiteral<"source">;
65
+ source: z.ZodString;
66
+ }, z.core.$strict>], "kind">;
67
+ /** One channel party. */
68
+ export type ChannelParty = z.infer<typeof channelPartySchema>;
69
+ /** Short human label for a party, used in author metadata and owner rows. */
70
+ export declare function partyLabel(party: ChannelParty): string;
@@ -0,0 +1,97 @@
1
+ import { z } from "zod";
2
+ import { channelIds } from "./ids.js";
3
+ /** Does one allowlisted origin admit this exact browser origin? */
4
+ export function originAllowed(allowed, origin) {
5
+ return allowed.some((entry) => {
6
+ if (!entry.startsWith("https://*."))
7
+ return entry === origin;
8
+ const suffix = entry.slice("https://*".length);
9
+ if (!origin.startsWith("https://") || !origin.endsWith(suffix))
10
+ return false;
11
+ const label = origin.slice("https://".length, -suffix.length);
12
+ return label.length > 0 && !label.includes(".");
13
+ });
14
+ }
15
+ /** Per-party edge limits applied before any work is accepted. */
16
+ export const bindingLimitsSchema = z.strictObject({
17
+ messagesPerPartyPerDay: z.number().int().min(1).max(10_000),
18
+ charsPerMessage: z.number().int().min(1).max(16_000),
19
+ newPartiesPerHour: z.number().int().min(1).max(100_000),
20
+ });
21
+ /** Edge limits of a binding whose owner set none. */
22
+ export const DEFAULT_BINDING_LIMITS = Object.freeze({
23
+ messagesPerPartyPerDay: 30,
24
+ charsPerMessage: 4_000,
25
+ newPartiesPerHour: 200,
26
+ });
27
+ /**
28
+ * Edge limits of an incoming-events binding whose owner set none. Events are
29
+ * not people: `eventsPerMinute` bounds intake, so the
30
+ * per-party and new-party limits sit at their ceilings, and one event may
31
+ * carry the longest message intake accepts.
32
+ */
33
+ const DEFAULT_EVENT_LIMITS = Object.freeze({
34
+ messagesPerPartyPerDay: 10_000,
35
+ charsPerMessage: 16_000,
36
+ newPartiesPerHour: 100_000,
37
+ });
38
+ /**
39
+ * How long a GitHub body digest keeps a changed-id replay out: the verifiers'
40
+ * signed-timestamp skew (Veil's `WEBHOOK_SIGNATURE_MAX_SKEW_SECONDS`; a test
41
+ * pins them equal). GitHub signs no timestamp, so this window is the bound.
42
+ */
43
+ export const EVENT_REPLAY_WINDOW_SECONDS = 300;
44
+ /** The edge limits one binding runs under: its owner's, else its kind's default. */
45
+ export function limitsOf(binding) {
46
+ return (binding.limits ??
47
+ (binding.selector.provider === "event"
48
+ ? DEFAULT_EVENT_LIMITS
49
+ : DEFAULT_BINDING_LIMITS));
50
+ }
51
+ export const discordSnowflakeSchema = z.string().regex(/^\d{17,20}$/u);
52
+ /** The party on the other side of a channel conversation; never a member. */
53
+ export const channelPartySchema = z.discriminatedUnion("kind", [
54
+ z.strictObject({
55
+ kind: z.literal("visitor"),
56
+ visitorId: z
57
+ .string()
58
+ .refine((value) => channelIds.visitor.is(value), "Invalid visitor"),
59
+ }),
60
+ z.strictObject({
61
+ kind: z.literal("discord-user"),
62
+ guildId: discordSnowflakeSchema,
63
+ userId: discordSnowflakeSchema,
64
+ }),
65
+ z.strictObject({
66
+ kind: z.literal("email-sender"),
67
+ address: z.email(),
68
+ dmarc: z.enum(["pass", "fail", "none"]),
69
+ }),
70
+ z.strictObject({
71
+ kind: z.literal("player"),
72
+ worldId: z.string().min(1).max(160),
73
+ playerId: z.string().min(1).max(160),
74
+ }),
75
+ z.strictObject({
76
+ kind: z.literal("source"),
77
+ /** The sender's declared source identity, such as `github`. */
78
+ source: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/u),
79
+ }),
80
+ ]);
81
+ /** Short human label for a party, used in author metadata and owner rows. */
82
+ export function partyLabel(party) {
83
+ switch (party.kind) {
84
+ case "visitor":
85
+ return `Visitor ${party.visitorId.slice(-4)}`;
86
+ case "discord-user":
87
+ return `Discord user ${party.userId}`;
88
+ case "email-sender":
89
+ return party.dmarc === "pass"
90
+ ? party.address
91
+ : `${party.address} (unverified)`;
92
+ case "player":
93
+ return `Player ${party.playerId}`;
94
+ case "source":
95
+ return party.source;
96
+ }
97
+ }
@@ -0,0 +1,246 @@
1
+ import { z } from "zod";
2
+ import { type FungiAuthor, type AuthorMeta } from "@fungi.computer/agar/agents";
3
+ import { type Handoff } from "@fungi.computer/agar/channels";
4
+ import { type ConversationBinding, type ChannelParty } from "./binding.js";
5
+ export type HandoffEvent = Readonly<{
6
+ expectedVersion: number;
7
+ }> & (Readonly<{
8
+ kind: "request";
9
+ reason: string;
10
+ at: number;
11
+ }> | Readonly<{
12
+ kind: "take_over";
13
+ member: FungiAuthor;
14
+ confirm: boolean;
15
+ at: number;
16
+ }> | Readonly<{
17
+ kind: "hand_back";
18
+ memberId: string;
19
+ confirm: boolean;
20
+ }> | Readonly<{
21
+ kind: "dismiss";
22
+ }>);
23
+ export type HandoffTransition = Readonly<{
24
+ kind: "ok";
25
+ handoff: Handoff;
26
+ }> | Readonly<{
27
+ kind: "conflict" | "confirmation_required" | "not_holder" | "not_found";
28
+ }>;
29
+ /** Decide one handoff; persistence, notifications and cancellation stay with the owner. */
30
+ export declare function transitionHandoff(current: Handoff | null, event: HandoffEvent): HandoffTransition;
31
+ export type ConversationSql = Readonly<{
32
+ exec: (statement: string, ...bindings: (ArrayBuffer | string | number | null)[]) => Readonly<{
33
+ toArray: () => readonly unknown[];
34
+ }>;
35
+ }>;
36
+ /** One verified inbound event from a provider edge. */
37
+ declare const inboundEventSchema: z.ZodObject<{
38
+ bindingId: z.ZodString;
39
+ conversation: z.ZodString;
40
+ eventId: z.ZodString;
41
+ party: z.ZodDiscriminatedUnion<[z.ZodObject<{
42
+ kind: z.ZodLiteral<"visitor">;
43
+ visitorId: z.ZodString;
44
+ }, z.core.$strict>, z.ZodObject<{
45
+ kind: z.ZodLiteral<"discord-user">;
46
+ guildId: z.ZodString;
47
+ userId: z.ZodString;
48
+ }, z.core.$strict>, z.ZodObject<{
49
+ kind: z.ZodLiteral<"email-sender">;
50
+ address: z.ZodEmail;
51
+ dmarc: z.ZodEnum<{
52
+ none: "none";
53
+ pass: "pass";
54
+ fail: "fail";
55
+ }>;
56
+ }, z.core.$strict>, z.ZodObject<{
57
+ kind: z.ZodLiteral<"player">;
58
+ worldId: z.ZodString;
59
+ playerId: z.ZodString;
60
+ }, z.core.$strict>, z.ZodObject<{
61
+ kind: z.ZodLiteral<"source">;
62
+ source: z.ZodString;
63
+ }, z.core.$strict>], "kind">;
64
+ text: z.ZodString;
65
+ sourceRequestId: z.ZodOptional<z.ZodLazy<z.ZodString>>;
66
+ bodyDigest: z.ZodOptional<z.ZodString>;
67
+ }, z.core.$strict>;
68
+ /** One verified inbound event. */
69
+ export type InboundEvent = z.infer<typeof inboundEventSchema>;
70
+ /** Channel events and composer requests do not share the Session owner's key space. */
71
+ export declare function channelRequestId(bindingId: string, eventId: string): string;
72
+ /** Closed intake result; "said no" is distinct from "couldn't answer". */
73
+ export type IntakeResult = Readonly<{
74
+ kind: "accepted";
75
+ sessionId: string;
76
+ commandId: string;
77
+ delivery: "new" | "replay";
78
+ }> | Readonly<{
79
+ /** Held for a person: recorded as a party line, no run started. */
80
+ kind: "held";
81
+ sessionId: string;
82
+ delivery: "new" | "replay";
83
+ }> | Readonly<{
84
+ kind: "denied";
85
+ reason: "unmapped" | "suspended" | "banned" | "too_long" | "party_daily_limit" | "new_party_limit" | "rate_limited" | "event_conflict";
86
+ }>;
87
+ /** One channel conversation row, as owner surfaces label it. */
88
+ export type ChannelConversation = Readonly<{
89
+ bindingId: string;
90
+ conversation: string;
91
+ sessionId: string;
92
+ party: ChannelParty;
93
+ label: string;
94
+ createdAt: number;
95
+ lastEventAt: number;
96
+ handoff: Handoff;
97
+ }>;
98
+ export type ChannelHandoffResult = Readonly<{
99
+ kind: "ok";
100
+ conversation: ChannelConversation;
101
+ }> | Exclude<HandoffTransition, {
102
+ kind: "ok";
103
+ }>;
104
+ type ChannelTakeoverReceipt = Readonly<{
105
+ version: number;
106
+ memberId: string;
107
+ memberName: string;
108
+ previousMemberId: string | null;
109
+ at: number;
110
+ }>;
111
+ /** Payload-free event metadata for a Team member's channel log. */
112
+ export type ChannelEventReceipt = Readonly<{
113
+ eventId: string;
114
+ receivedAt: number;
115
+ status: "pending" | "accepted" | "held";
116
+ size: number;
117
+ sessionId: string;
118
+ }>;
119
+ /** Only the withdrawal outcomes a channel visitor can observe. */
120
+ export type ChannelWithdrawResult = Readonly<{
121
+ status: "unknown";
122
+ }> | Readonly<{
123
+ status: "accepted";
124
+ commandId: string;
125
+ sessionId?: string;
126
+ }>;
127
+ /** A host-projected intent contains no private metadata or internal request key. */
128
+ export type VisitorPendingPrompt = Readonly<{
129
+ requestId: string;
130
+ commandId: string;
131
+ text: string;
132
+ delivery: "steer" | "followUp";
133
+ images?: readonly Readonly<{
134
+ type: "image";
135
+ data: string;
136
+ mimeType: string;
137
+ }>[] | undefined;
138
+ }>;
139
+ /** Only public baseline pagination crosses the visitor Session port. */
140
+ export type VisitorReadOptions = Readonly<{
141
+ limit?: number;
142
+ before?: string;
143
+ }>;
144
+ /** The channel's narrow Session port; hosts translate to their durable client. */
145
+ export type SessionDoor = Readonly<{
146
+ withdraw: (sessionId: string, requestId: string) => Promise<ChannelWithdrawResult>;
147
+ latestPending: (sessionId: string, visitor: FungiAuthor) => Promise<VisitorPendingPrompt | null>;
148
+ participantDocument: (sessionId: string, visitorId: string, options: VisitorReadOptions) => Promise<string>;
149
+ participantStream: (sessionId: string, visitorId: string, options: Readonly<{
150
+ expiresAt: number;
151
+ now: () => number;
152
+ release: () => void;
153
+ canRead: () => Promise<boolean>;
154
+ }>) => Promise<ReadableStream<Uint8Array>>;
155
+ createSession: (requestId: string) => Promise<string>;
156
+ /** Deliver this conversation Session's tool grants to the runtime. */
157
+ deliverGrants: (sessionId: string) => void;
158
+ prompt: (sessionId: string, input: Readonly<{
159
+ requestId: string;
160
+ text: string;
161
+ meta: AuthorMeta & Readonly<{
162
+ sourceRequestId?: string;
163
+ }>;
164
+ }>) => Promise<Readonly<{
165
+ commandId: string;
166
+ }>>;
167
+ /** Record one run-free operator or party line (Shiitake `operator`). */
168
+ line: (sessionId: string, input: Readonly<{
169
+ requestId: string;
170
+ text: string;
171
+ }> & (Readonly<{
172
+ author: "operator";
173
+ identity: FungiAuthor;
174
+ }> | Readonly<{
175
+ author: "party";
176
+ meta: AuthorMeta & Readonly<{
177
+ sourceRequestId?: string;
178
+ }>;
179
+ }>)) => Promise<void>;
180
+ /** Only a matching, contract-closed request clears a visitor reservation. */
181
+ errorKind: (error: Error, requestId: string) => "closed_request" | null;
182
+ }>;
183
+ export declare function createChannelConversations(options: Readonly<{
184
+ sql: ConversationSql;
185
+ transactionSync: <T>(body: () => T) => T;
186
+ binding: (bindingId: string) => ConversationBinding | null;
187
+ door: Pick<SessionDoor, "createSession" | "deliverGrants" | "prompt" | "line" | "errorKind">;
188
+ now: () => number;
189
+ /** Commit hook for accepted work, in the acceptance transaction. */
190
+ onAccepted?: (accepted: Readonly<{
191
+ binding: ConversationBinding;
192
+ event: InboundEvent;
193
+ sessionId: string;
194
+ commandId: string;
195
+ }>) => void;
196
+ }>): {
197
+ ensure: () => void;
198
+ open: (binding: ConversationBinding, conversation: string, party: ChannelParty) => Promise<Readonly<{
199
+ kind: "open";
200
+ conversation: ChannelConversation;
201
+ }> | Readonly<{
202
+ kind: "denied";
203
+ reason: "new_party_limit" | "banned";
204
+ }>>;
205
+ receive: (rawEvent: InboundEvent) => Promise<IntakeResult>;
206
+ /** A visitor bearer may never resolve another conversation's intake key. */
207
+ visitorEventConflict: (bindingId: string, visitorId: string, eventId: string) => boolean;
208
+ ban: (bindingId: string, conversation: string) => void;
209
+ list: (bindingId?: string) => readonly ChannelConversation[];
210
+ recentEvents: (bindingId: string) => readonly ChannelEventReceipt[];
211
+ requestHandoff: (sessionId: string, reason: string) => ChannelHandoffResult;
212
+ captureTurn: (sessionId: string, commandId: string) => void;
213
+ turnIsCurrent: (sessionId: string, commandId: string) => boolean;
214
+ deliverAgentReply: (sessionId: string, commandId: string, reply: Readonly<{
215
+ kind: "message";
216
+ messageId: string;
217
+ }> | Readonly<{
218
+ kind: "settlement";
219
+ }>) => boolean;
220
+ dueNotifications: (now: number) => readonly ChannelConversation[];
221
+ nextNotification: () => number | null;
222
+ scheduleNotification: (sessionId: string, dueAt: number | null) => void;
223
+ handoffOpen: (sessionId: string, version: number) => boolean;
224
+ takeOver: (sessionId: string, member: FungiAuthor, expectedVersion: number, confirm: boolean) => ChannelHandoffResult;
225
+ handBack: (sessionId: string, memberId: string, expectedVersion: number, confirm: boolean) => ChannelHandoffResult;
226
+ dismiss: (sessionId: string, expectedVersion: number) => ChannelHandoffResult;
227
+ takeovers: (sessionId: string) => readonly ChannelTakeoverReceipt[];
228
+ expired: (now: number, retentionMs: (bindingId: string) => number) => readonly ChannelConversation[];
229
+ nextExpiry: (retentionMs: (bindingId: string) => number) => number | null;
230
+ forget: (conversation: ChannelConversation) => void;
231
+ find: (bindingId: string, conversation: string) => Readonly<{
232
+ bindingId: string;
233
+ conversation: string;
234
+ sessionId: string;
235
+ party: ChannelParty;
236
+ label: string;
237
+ createdAt: number;
238
+ lastEventAt: number;
239
+ handoff: Handoff;
240
+ }> | null;
241
+ sessionConversation: (sessionId: string) => ChannelConversation | null;
242
+ sessionIds: () => readonly string[];
243
+ };
244
+ /** AgentDO's channel conversation owner. */
245
+ export type ChannelConversations = ReturnType<typeof createChannelConversations>;
246
+ export {};