@pinet/broker-core 0.2.2 → 0.2.6

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/dist/schema.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DatabaseSync } from "node:sqlite";
2
2
  import type { PinetMailClass } from "./mail-classification.js";
3
- import type { AgentInfo, ThreadInfo, BrokerMessage, InboxEntry, InboxReadOptions, InboxReadResult, InboxThreadUnreadSummary, DeliveredInboundMessageResult, BacklogEntry, BrokerDBInterface, InboundMessage, ChannelAssignment, TaskAssignmentInfo, TaskAssignmentKind, TaskAssignmentStatus, ScheduledWakeupInfo, ScheduledWakeupDelivery, PortLeaseAcquireInput, PortLeaseInfo, PortLeaseListOptions, PortLeaseReleaseInput, PortLeaseRenewInput, AgentSessionSearchInfo, AgentSessionSearchOptions, PinetLaneInfo, PinetLaneListOptions, PinetLaneParticipantInfo, PinetLaneParticipantUpsertInput, PinetLaneUpsertInput } from "./types.js";
3
+ import type { AgentInfo, ThreadInfo, BrokerMessage, InboxEntry, InboxReadOptions, InboxReadResult, InboxThreadUnreadSummary, DeliveredInboundMessageResult, BacklogEntry, BrokerDBInterface, InboundMessage, ChannelAssignment, TaskAssignmentInfo, TaskAssignmentKind, TaskAssignmentStatus, ScheduledWakeupInfo, ScheduledWakeupDelivery, PortLeaseAcquireInput, PortLeaseInfo, PortLeaseListOptions, PortLeaseReleaseInput, PortLeaseRenewInput, AgentSessionSearchInfo, AgentSessionSearchOptions, PinetLaneInfo, PinetLaneListOptions, PinetLaneParticipantInfo, PinetLaneParticipantUpsertInput, PinetLaneUpsertInput, AgentHibernatePolicy, AgentLifecycleLease, AgentLifecycleOperation, AgentLifecycleTransitionInput, AgentLifecycleRetentionInfo, AgentRuntimeSpec, AgentRuntimeSpecInput, AgentCheckpointReceipt, AgentCheckpointReceiptInput, AgentWakeReservation, AgentWakeAcceptanceReceipt, AcceptRuntimeGenerationInput, RuntimeGenerationAcceptance, AgentWakeQueueEntry, EnqueueWakeInput, AgentLifecycleEvent, AgentLifecycleEventInput } from "./types.js";
4
4
  export interface TaskAssignmentAwaitingReplyInfo {
5
5
  id: number;
6
6
  agentId: string;
@@ -12,7 +12,7 @@ export interface TaskAssignmentAwaitingReplyInfo {
12
12
  export declare function defaultDbPath(): string;
13
13
  export declare const DEFAULT_RESUMABLE_WINDOW_MS = 15000;
14
14
  export declare const DEFAULT_DISCONNECTED_PURGE_GRACE_MS: number;
15
- export declare const CURRENT_BROKER_SCHEMA_VERSION = 18;
15
+ export declare const CURRENT_BROKER_SCHEMA_VERSION = 24;
16
16
  export declare class BrokerDB implements BrokerDBInterface {
17
17
  private db;
18
18
  private readonly dbPath;
@@ -28,6 +28,185 @@ export declare class BrokerDB implements BrokerDBInterface {
28
28
  close(): void;
29
29
  registerAgent(id: string, name: string, emoji: string, pid: number, metadata?: Record<string, unknown>, stableId?: string): AgentInfo;
30
30
  private resolveAgentHierarchy;
31
+ transitionAgentLifecycle(input: AgentLifecycleTransitionInput): AgentInfo;
32
+ /**
33
+ * Append an audit-only lifecycle event (a refusal, fenced stale attempt, or
34
+ * duplicate-launch prevention) without changing the agent's lifecycle state.
35
+ */
36
+ recordAgentLifecycleEvent(input: AgentLifecycleEventInput): void;
37
+ private insertLifecycleEventRow;
38
+ getRecentAgentLifecycleEvents(agentId?: string, limit?: number): AgentLifecycleEvent[];
39
+ getAgentLifecycleRetentionInfo(): AgentLifecycleRetentionInfo;
40
+ acquireAgentLifecycleLease(input: {
41
+ agentId: string;
42
+ operation: AgentLifecycleOperation;
43
+ ownerBrokerInstanceId: string;
44
+ leaseId: string;
45
+ ttlMs: number;
46
+ triggerMessageId?: number | null;
47
+ now?: number;
48
+ }): AgentLifecycleLease | null;
49
+ releaseAgentLifecycleLease(agentId: string, leaseId: string, fenceToken: number): boolean;
50
+ /**
51
+ * Extend an already-held, still-valid lease's expiry WITHOUT bumping the
52
+ * fence, so a legitimately long-running operation (e.g. a wake that waits on
53
+ * process launch + runtime registration across several attempts) keeps a valid
54
+ * lease across adapter waits and can still complete its fenced forward
55
+ * transition. The fence is preserved so revival fencing is unaffected.
56
+ *
57
+ * Renewal only succeeds while the lease is still unexpired and held by this
58
+ * exact owner (matching `leaseId` + `fenceToken`); this preserves the takeover
59
+ * guarantee (a stalled owner past expiry cannot reclaim a lease another broker
60
+ * may take over). Returns the refreshed lease, or null when ownership was lost
61
+ * (expired, released, or the fence moved) — a null result means the caller
62
+ * must fail closed rather than continue driving forward transitions.
63
+ */
64
+ renewAgentLifecycleLease(input: {
65
+ agentId: string;
66
+ leaseId: string;
67
+ fenceToken: number;
68
+ ttlMs: number;
69
+ now?: number;
70
+ }): AgentLifecycleLease | null;
71
+ /** Set the opt-in hibernation policy for an agent (auto | manual | never). */
72
+ setAgentHibernatePolicy(agentId: string, policy: AgentHibernatePolicy): void;
73
+ /** Record grace/idle eligibility timestamps used by the auto-hibernation scheduler. */
74
+ setAgentHibernationSchedule(agentId: string, schedule: {
75
+ graceUntil?: string | null;
76
+ idleEligibleAt?: string | null;
77
+ }): void;
78
+ getAgentLifecycleLease(agentId: string): AgentLifecycleLease | null;
79
+ upsertAgentRuntimeSpec(input: AgentRuntimeSpecInput): AgentRuntimeSpec;
80
+ getAgentRuntimeSpec(agentId: string): AgentRuntimeSpec | null;
81
+ deleteAgentRuntimeSpec(agentId: string): void;
82
+ recordAgentCheckpointReceipt(input: AgentCheckpointReceiptInput): AgentCheckpointReceipt;
83
+ getLatestAgentCheckpointReceipt(agentId: string): AgentCheckpointReceipt | null;
84
+ /**
85
+ * Reserve the exact runtime generation the broker will accept for a wake.
86
+ * Requires an unexpired wake lease held with the given fence token. Exactly
87
+ * one reservation may exist per agent (PK). The reserved generation is the
88
+ * agent's current runtime_generation + 1, so any older runtime is fenced out.
89
+ */
90
+ reserveWakeGeneration(input: {
91
+ agentId: string;
92
+ wakeLeaseId: string;
93
+ fenceToken: number;
94
+ correlationId: string;
95
+ /** Optional injected nonce (tests); production mints a fresh UUID. */
96
+ reservationNonce?: string;
97
+ now?: number;
98
+ }): AgentWakeReservation;
99
+ getAgentWakeReservation(agentId: string): AgentWakeReservation | null;
100
+ clearAgentWakeReservation(agentId: string): void;
101
+ /**
102
+ * Record the EXACT wake fence that just accepted a generation, so a runtime
103
+ * whose register RPC response was lost to a crash can replay its single-use
104
+ * fence and be re-bound idempotently. Called INSIDE the acceptance transaction
105
+ * (via {@link acceptRuntimeGeneration} / {@link registerAgentWithGenerationAcceptance})
106
+ * so the receipt is atomic with the generation advance. The stable id is read
107
+ * from the just-registered row so the receipt binds to the durable identity.
108
+ */
109
+ private writeWakeAcceptanceReceipt;
110
+ getAgentWakeAcceptanceReceipt(agentId: string): AgentWakeAcceptanceReceipt | null;
111
+ /**
112
+ * Accept exactly one runtime generation for a waking agent. The registration
113
+ * must present the same wake lease id, fence token, and reserved generation,
114
+ * AND the bound lease must still be an unexpired `wake` lease while the agent
115
+ * is still in the `waking` lifecycle state. A stale, expired, wrong-operation,
116
+ * wrong-state, or duplicate registration returns `{ accepted: false }` without
117
+ * mutating state. On success the agent's runtime_generation is advanced and
118
+ * the reservation is consumed. `now` (epoch ms) is injectable for tests.
119
+ */
120
+ /**
121
+ * Non-mutating validation shared by {@link acceptRuntimeGeneration} and
122
+ * {@link checkRuntimeGenerationAcceptable}. Returns a `{ accepted: false }`
123
+ * rejection when the fence does not bind, or `null` when acceptance is legal.
124
+ * Never advances the generation or consumes the reservation.
125
+ */
126
+ private validateRuntimeGenerationAcceptance;
127
+ acceptRuntimeGeneration(input: AcceptRuntimeGenerationInput): RuntimeGenerationAcceptance;
128
+ /**
129
+ * Atomically settle a wake attempt against the acceptance boundary BEFORE the
130
+ * orchestrator stops or quarantines a launched-but-unaccepted runtime. This
131
+ * closes the timeout-boundary race: the socket layer accepts a generation
132
+ * atomically, so an acceptance can land in the window between the
133
+ * orchestrator's last waiter read and its decision to stop the attempt. In one
134
+ * transaction:
135
+ *
136
+ * - If the reserved generation was ALREADY accepted (the agent's
137
+ * `runtime_generation` reached `reservedGeneration` — the socket won the
138
+ * race), report `{ accepted: true }` and leave the accepted runtime and its
139
+ * (already consumed) reservation untouched. The caller must then treat the
140
+ * attempt as the live runtime and NEVER stop it.
141
+ * - Otherwise, consume ONLY this attempt's exact-nonce reservation, so any
142
+ * later registration by the launched runtime can no longer be accepted
143
+ * (`no_reservation`). This makes the caller's subsequent prove-stop safe:
144
+ * once this returns `{ accepted: false }` the launched runtime can never
145
+ * become live. A reservation minted by a superseded/newer attempt (a
146
+ * different nonce) is left intact.
147
+ *
148
+ * `runtime_generation === reservedGeneration` uniquely identifies acceptance of
149
+ * THIS attempt because reserved generations are `current_generation + 1` at
150
+ * reserve time and only advance on acceptance.
151
+ */
152
+ finalizeWakeAttempt(input: {
153
+ agentId: string;
154
+ reservedGeneration: number;
155
+ reservationNonce: string;
156
+ }): {
157
+ accepted: boolean;
158
+ };
159
+ /**
160
+ * Atomically revive a hibernated identity: perform the agent registration
161
+ * mutation AND accept the reserved runtime generation in ONE transaction, so a
162
+ * rejected acceptance rolls the registration mutation back. This closes the
163
+ * window where a revival whose wake lease expires between the socket-layer
164
+ * preflight and acceptance would otherwise leave the durable row with a
165
+ * mutated pid/metadata/connectivity even though the socket is refused and
166
+ * unbound. On rejection the transaction is rolled back and `agent` is null.
167
+ */
168
+ registerAgentWithGenerationAcceptance(input: {
169
+ registration: {
170
+ id: string;
171
+ name: string;
172
+ emoji: string;
173
+ pid: number;
174
+ metadata?: NonNullable<AgentInfo["metadata"]>;
175
+ stableId?: string;
176
+ };
177
+ accept: AcceptRuntimeGenerationInput;
178
+ }): {
179
+ agent: AgentInfo | null;
180
+ acceptance: RuntimeGenerationAcceptance;
181
+ };
182
+ /**
183
+ * Non-mutating preflight for {@link acceptRuntimeGeneration}: runs the exact
184
+ * same fence validation without advancing the generation or consuming the
185
+ * reservation. The socket layer uses this to validate a wake fence BEFORE
186
+ * committing the agent registration, and only accepts the generation once
187
+ * registration has succeeded. Because broker registration is synchronous, a
188
+ * passing preflight followed immediately by `acceptRuntimeGeneration` cannot
189
+ * be interleaved by another connection, so this closes the window where a
190
+ * generation was advanced for a runtime whose registration then failed.
191
+ */
192
+ checkRuntimeGenerationAcceptable(input: AcceptRuntimeGenerationInput): RuntimeGenerationAcceptance;
193
+ /**
194
+ * Idempotently enqueue a wake trigger. If an active (queued/dispatching)
195
+ * entry already exists for the agent it is returned unchanged except that the
196
+ * effective priority is lowered to the strongest (smallest) trigger and the
197
+ * trigger message id is preserved when still unset. Never fans out.
198
+ */
199
+ enqueueWake(input: EnqueueWakeInput): AgentWakeQueueEntry;
200
+ private getActiveWakeQueueEntry;
201
+ private getWakeQueueEntryById;
202
+ listWakeQueue(status?: AgentWakeQueueEntry["status"]): AgentWakeQueueEntry[];
203
+ countInflightWakes(repoRoot?: string | null): number;
204
+ markWakeDispatching(id: number): AgentWakeQueueEntry | null;
205
+ requeueWake(id: number): AgentWakeQueueEntry | null;
206
+ completeWakeQueueEntry(id: number, status?: "done" | "cancelled"): void;
207
+ cancelWake(agentId: string): void;
208
+ /** Mark any active wake-queue entry for an agent as completed (idempotent). */
209
+ completeWakeForAgent(agentId: string): void;
31
210
  unregisterAgent(id: string): void;
32
211
  disconnectAgent(id: string, resumableForMs?: number): void;
33
212
  private getDirectChildren;