@pinet/broker-core 0.1.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/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/agent-messaging.d.ts +47 -0
- package/dist/agent-messaging.js +176 -0
- package/dist/auth.d.ts +7 -0
- package/dist/auth.js +59 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/leader.d.ts +29 -0
- package/dist/leader.js +95 -0
- package/dist/mail-classification.d.ts +17 -0
- package/dist/mail-classification.js +103 -0
- package/dist/maintenance.d.ts +50 -0
- package/dist/maintenance.js +134 -0
- package/dist/message-send.d.ts +31 -0
- package/dist/message-send.js +75 -0
- package/dist/paths.d.ts +14 -0
- package/dist/paths.js +29 -0
- package/dist/raw-tcp-loopback.d.ts +2 -0
- package/dist/raw-tcp-loopback.js +39 -0
- package/dist/router.d.ts +60 -0
- package/dist/router.js +336 -0
- package/dist/schema.d.ts +155 -0
- package/dist/schema.js +3076 -0
- package/dist/types.d.ts +312 -0
- package/dist/types.js +16 -0
- package/package.json +49 -0
package/dist/router.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { DEFAULT_EXTERNAL_THREAD_SOURCE } from "./types.js";
|
|
2
|
+
// ─── Helpers ─────────────────────────────────────────────
|
|
3
|
+
function hashString(input) {
|
|
4
|
+
let hash = 2166136261;
|
|
5
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
6
|
+
hash ^= input.charCodeAt(i);
|
|
7
|
+
hash = Math.imul(hash, 16777619);
|
|
8
|
+
}
|
|
9
|
+
return hash >>> 0;
|
|
10
|
+
}
|
|
11
|
+
function buildPinetOwnerToken(stableId) {
|
|
12
|
+
const primary = hashString(stableId).toString(16).padStart(8, "0");
|
|
13
|
+
const secondary = hashString(`${stableId}:owner`).toString(16).padStart(8, "0");
|
|
14
|
+
return `owner:${primary}${secondary}`;
|
|
15
|
+
}
|
|
16
|
+
const THREAD_STAND_DOWN_REGEX = /\bstand down\b/i;
|
|
17
|
+
const THREAD_RETARGET_REGEX = /\b(?:take over|pick (?:this|it) up|pick this up|pick it up|handle this|grab this|you take this|reassign(?: this)?(?: to)?|switch(?: this)?(?: to)?|move(?: this)?(?: to)?|route(?: this)?(?: to)?|transfer(?: this)?(?: to)?|pass(?: this)?(?: to)?|hand(?: this)?(?: to)?|give(?: this)?(?: to)?)\b/i;
|
|
18
|
+
/**
|
|
19
|
+
* Extract an agent name mention from message text.
|
|
20
|
+
* Matches patterns like "hey AgentName," or "@AgentName" or just "AgentName"
|
|
21
|
+
* at word boundaries (case-insensitive).
|
|
22
|
+
*
|
|
23
|
+
* When multiple agents match, the longest name wins so that "CodeBot" is
|
|
24
|
+
* preferred over "Code" and similar-prefix collisions are avoided.
|
|
25
|
+
*/
|
|
26
|
+
export function findAgentMention(text, agents) {
|
|
27
|
+
return findBestAgentMention(text, buildAgentMentionCandidates(agents, false));
|
|
28
|
+
}
|
|
29
|
+
export function extractPiAgentThreadOwnerHint(replies) {
|
|
30
|
+
for (let index = replies.length - 1; index >= 0; index -= 1) {
|
|
31
|
+
const message = replies[index];
|
|
32
|
+
if (!message.bot_id)
|
|
33
|
+
continue;
|
|
34
|
+
const metadata = message.metadata;
|
|
35
|
+
if (metadata?.event_type !== "pi_agent_msg")
|
|
36
|
+
continue;
|
|
37
|
+
const agentOwner = typeof metadata.event_payload?.agent_owner === "string" &&
|
|
38
|
+
metadata.event_payload.agent_owner.trim().length > 0
|
|
39
|
+
? metadata.event_payload.agent_owner.trim()
|
|
40
|
+
: undefined;
|
|
41
|
+
const agentName = typeof metadata.event_payload?.agent === "string" &&
|
|
42
|
+
metadata.event_payload.agent.trim().length > 0
|
|
43
|
+
? metadata.event_payload.agent.trim()
|
|
44
|
+
: undefined;
|
|
45
|
+
if (agentOwner || agentName) {
|
|
46
|
+
return {
|
|
47
|
+
...(agentOwner ? { agentOwner } : {}),
|
|
48
|
+
...(agentName ? { agentName } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
export function findExplicitThreadDirective(text, agents) {
|
|
55
|
+
if (!THREAD_STAND_DOWN_REGEX.test(text) && !THREAD_RETARGET_REGEX.test(text)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const mention = findBestAgentMention(text, buildAgentMentionCandidates(agents, true));
|
|
59
|
+
if (!mention)
|
|
60
|
+
return null;
|
|
61
|
+
if (THREAD_STAND_DOWN_REGEX.test(text)) {
|
|
62
|
+
return { kind: "stand_down", agent: mention };
|
|
63
|
+
}
|
|
64
|
+
if (THREAD_RETARGET_REGEX.test(text)) {
|
|
65
|
+
return { kind: "retarget", agent: mention };
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function buildAgentMentionCandidates(agents, includeUniqueTailAlias) {
|
|
70
|
+
const candidates = [];
|
|
71
|
+
const tailCounts = new Map();
|
|
72
|
+
for (const agent of agents) {
|
|
73
|
+
const tail = getAgentTailAlias(agent.name);
|
|
74
|
+
if (tail) {
|
|
75
|
+
tailCounts.set(tail, (tailCounts.get(tail) ?? 0) + 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const agent of agents) {
|
|
79
|
+
if (agent.name.trim()) {
|
|
80
|
+
candidates.push({ agent, term: agent.name.trim() });
|
|
81
|
+
}
|
|
82
|
+
if (!includeUniqueTailAlias)
|
|
83
|
+
continue;
|
|
84
|
+
const tail = getAgentTailAlias(agent.name);
|
|
85
|
+
if (!tail || tailCounts.get(tail) !== 1)
|
|
86
|
+
continue;
|
|
87
|
+
candidates.push({ agent, term: tail });
|
|
88
|
+
}
|
|
89
|
+
return candidates;
|
|
90
|
+
}
|
|
91
|
+
function getAgentTailAlias(name) {
|
|
92
|
+
const tokens = name
|
|
93
|
+
.trim()
|
|
94
|
+
.split(/\s+/)
|
|
95
|
+
.map((token) => token.trim())
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
const tail = tokens.at(-1)?.toLowerCase() ?? "";
|
|
98
|
+
return tail.length >= 4 ? tail : null;
|
|
99
|
+
}
|
|
100
|
+
function findBestAgentMention(text, candidates) {
|
|
101
|
+
const lower = text.toLowerCase();
|
|
102
|
+
let bestMatch = null;
|
|
103
|
+
let bestLength = 0;
|
|
104
|
+
for (const candidate of candidates) {
|
|
105
|
+
const term = candidate.term.toLowerCase();
|
|
106
|
+
if (!term)
|
|
107
|
+
continue;
|
|
108
|
+
const pattern = new RegExp(`\\b${escapeRegExp(term)}\\b`, "i");
|
|
109
|
+
if (pattern.test(lower) && term.length > bestLength) {
|
|
110
|
+
bestMatch = candidate.agent;
|
|
111
|
+
bestLength = term.length;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return bestMatch;
|
|
115
|
+
}
|
|
116
|
+
function asNonEmptyString(value) {
|
|
117
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
118
|
+
}
|
|
119
|
+
function normalizeThreadOwnerHint(metadata) {
|
|
120
|
+
const embeddedHint = metadata?.threadOwnerHint &&
|
|
121
|
+
typeof metadata.threadOwnerHint === "object" &&
|
|
122
|
+
!Array.isArray(metadata.threadOwnerHint)
|
|
123
|
+
? metadata.threadOwnerHint
|
|
124
|
+
: undefined;
|
|
125
|
+
const agentId = asNonEmptyString(embeddedHint?.agentId ?? metadata?.threadOwnerAgentId);
|
|
126
|
+
const stableId = asNonEmptyString(embeddedHint?.stableId ?? metadata?.threadOwnerStableId);
|
|
127
|
+
const agentOwner = asNonEmptyString(embeddedHint?.agentOwner ?? metadata?.threadOwnerAgentOwner);
|
|
128
|
+
const agentName = asNonEmptyString(embeddedHint?.agentName ?? metadata?.threadOwnerAgentName);
|
|
129
|
+
const hint = {
|
|
130
|
+
...(agentId ? { agentId } : {}),
|
|
131
|
+
...(stableId ? { stableId } : {}),
|
|
132
|
+
...(agentOwner ? { agentOwner } : {}),
|
|
133
|
+
...(agentName ? { agentName } : {}),
|
|
134
|
+
};
|
|
135
|
+
return Object.keys(hint).length > 0 ? hint : null;
|
|
136
|
+
}
|
|
137
|
+
function resolveAgentFromThreadOwnerHint(metadata, agents) {
|
|
138
|
+
const hint = normalizeThreadOwnerHint(metadata);
|
|
139
|
+
if (!hint)
|
|
140
|
+
return null;
|
|
141
|
+
if (hint.agentId) {
|
|
142
|
+
const idMatch = agents.find((agent) => agent.id === hint.agentId);
|
|
143
|
+
if (idMatch)
|
|
144
|
+
return idMatch;
|
|
145
|
+
}
|
|
146
|
+
if (hint.stableId) {
|
|
147
|
+
const stableMatch = agents.find((agent) => agent.stableId === hint.stableId);
|
|
148
|
+
if (stableMatch)
|
|
149
|
+
return stableMatch;
|
|
150
|
+
}
|
|
151
|
+
if (hint.agentOwner) {
|
|
152
|
+
const ownerMatch = agents.find((agent) => agent.stableId && buildPinetOwnerToken(agent.stableId) === hint.agentOwner);
|
|
153
|
+
if (ownerMatch)
|
|
154
|
+
return ownerMatch;
|
|
155
|
+
}
|
|
156
|
+
if (!hint.agentName) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return findBestAgentMention(hint.agentName, buildAgentMentionCandidates(agents, false));
|
|
160
|
+
}
|
|
161
|
+
function resolveRoutableThreadOwner(db, threadOwnerAgentId, now = new Date().toISOString()) {
|
|
162
|
+
if (!threadOwnerAgentId)
|
|
163
|
+
return null;
|
|
164
|
+
const owner = db.getAgentById(threadOwnerAgentId);
|
|
165
|
+
if (owner && isRoutableOwner(owner, now)) {
|
|
166
|
+
return owner;
|
|
167
|
+
}
|
|
168
|
+
if (!owner) {
|
|
169
|
+
const reconnectedOwner = db.getAgentByStableId(threadOwnerAgentId);
|
|
170
|
+
if (reconnectedOwner &&
|
|
171
|
+
reconnectedOwner.id !== threadOwnerAgentId &&
|
|
172
|
+
isRoutableOwner(reconnectedOwner, now)) {
|
|
173
|
+
return reconnectedOwner;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
function escapeRegExp(s) {
|
|
179
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
180
|
+
}
|
|
181
|
+
function isRoutableOwner(agent, now = new Date().toISOString()) {
|
|
182
|
+
if (!agent.disconnectedAt)
|
|
183
|
+
return true;
|
|
184
|
+
return agent.resumableUntil != null && agent.resumableUntil > now;
|
|
185
|
+
}
|
|
186
|
+
// ─── MessageRouter ───────────────────────────────────────
|
|
187
|
+
export class MessageRouter {
|
|
188
|
+
db;
|
|
189
|
+
constructor(db) {
|
|
190
|
+
this.db = db;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Route an inbound message to the right agent.
|
|
194
|
+
*
|
|
195
|
+
* Priority order:
|
|
196
|
+
* 1. User allowlist — reject if user not allowed
|
|
197
|
+
* 2. Explicit thread control — stand-down / reassignment signals in-thread
|
|
198
|
+
* 3. Known-thread ownership — authoritative owner hint, then broker DB owner
|
|
199
|
+
* 4. New-thread channel assignment / direct address
|
|
200
|
+
* 5. Unrouted — no match found
|
|
201
|
+
*/
|
|
202
|
+
route(msg) {
|
|
203
|
+
// 0. Check user allowlist
|
|
204
|
+
const allowedUsers = this.db.getAllowedUsers();
|
|
205
|
+
if (allowedUsers !== null && !allowedUsers.has(msg.userId)) {
|
|
206
|
+
return { action: "reject", reason: "User not in allowlist" };
|
|
207
|
+
}
|
|
208
|
+
const agents = this.db.getAgents();
|
|
209
|
+
const thread = this.db.getThread(msg.threadId);
|
|
210
|
+
const explicitDirective = findExplicitThreadDirective(msg.text, agents);
|
|
211
|
+
if (explicitDirective) {
|
|
212
|
+
if (thread) {
|
|
213
|
+
if (explicitDirective.kind === "retarget") {
|
|
214
|
+
this.db.updateThread(msg.threadId, {
|
|
215
|
+
ownerAgent: explicitDirective.agent.id,
|
|
216
|
+
ownerBinding: "explicit",
|
|
217
|
+
channel: msg.channel,
|
|
218
|
+
source: msg.source,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return { action: "deliver", agentId: explicitDirective.agent.id };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (thread) {
|
|
225
|
+
if (thread.ownerBinding === "explicit") {
|
|
226
|
+
const explicitOwner = resolveRoutableThreadOwner(this.db, thread.ownerAgent);
|
|
227
|
+
if (explicitOwner) {
|
|
228
|
+
if (thread.ownerAgent !== explicitOwner.id) {
|
|
229
|
+
this.db.updateThread(msg.threadId, { ownerAgent: explicitOwner.id });
|
|
230
|
+
}
|
|
231
|
+
return { action: "deliver", agentId: explicitOwner.id };
|
|
232
|
+
}
|
|
233
|
+
if (thread.ownerAgent !== null) {
|
|
234
|
+
this.db.updateThread(msg.threadId, { ownerAgent: null });
|
|
235
|
+
}
|
|
236
|
+
// Explicit takeovers stay authoritative for the thread. If that owner is
|
|
237
|
+
// unavailable later, require another explicit retarget instead of snapping
|
|
238
|
+
// back to a stale historical adapter owner hint.
|
|
239
|
+
return { action: "unrouted" };
|
|
240
|
+
}
|
|
241
|
+
if (thread.ownerAgent) {
|
|
242
|
+
const owner = resolveRoutableThreadOwner(this.db, thread.ownerAgent);
|
|
243
|
+
if (owner) {
|
|
244
|
+
if (thread.ownerAgent !== owner.id) {
|
|
245
|
+
this.db.updateThread(msg.threadId, { ownerAgent: owner.id });
|
|
246
|
+
}
|
|
247
|
+
return { action: "deliver", agentId: owner.id };
|
|
248
|
+
}
|
|
249
|
+
// Owner is gone or no longer routable — clear ownership and stop. Known
|
|
250
|
+
// transport-thread replies must not leak to another worker through latest
|
|
251
|
+
// adapter owner hints or channel-assignment fallback; a human must
|
|
252
|
+
// explicitly retarget the thread if the owner is unavailable.
|
|
253
|
+
this.db.updateThread(msg.threadId, { ownerAgent: null });
|
|
254
|
+
return { action: "unrouted" };
|
|
255
|
+
}
|
|
256
|
+
const hintedOwner = resolveAgentFromThreadOwnerHint(msg.metadata, agents);
|
|
257
|
+
if (hintedOwner && isRoutableOwner(hintedOwner)) {
|
|
258
|
+
this.db.updateThread(msg.threadId, { ownerAgent: hintedOwner.id, channel: msg.channel });
|
|
259
|
+
return { action: "deliver", agentId: hintedOwner.id };
|
|
260
|
+
}
|
|
261
|
+
const mentioned = findAgentMention(msg.text, agents);
|
|
262
|
+
if (mentioned) {
|
|
263
|
+
const claimed = this.db.claimThread(msg.threadId, mentioned.id, msg.source, msg.channel);
|
|
264
|
+
if (claimed) {
|
|
265
|
+
return { action: "deliver", agentId: mentioned.id };
|
|
266
|
+
}
|
|
267
|
+
const claimedThread = this.db.getThread(msg.threadId);
|
|
268
|
+
const claimedOwner = resolveRoutableThreadOwner(this.db, claimedThread?.ownerAgent ?? null);
|
|
269
|
+
if (claimedOwner) {
|
|
270
|
+
return { action: "deliver", agentId: claimedOwner.id };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { action: "unrouted" };
|
|
274
|
+
}
|
|
275
|
+
// New thread / top-level message: channel assignment can still steer work.
|
|
276
|
+
// Persist that assignment as thread ownership so later generic replies in
|
|
277
|
+
// the same transport thread route back to the same agent without another
|
|
278
|
+
// manual broker/human assignment. Existing known threads stay protected by
|
|
279
|
+
// the `thread` branch above and do not fall back to channel assignment.
|
|
280
|
+
const assignment = this.db.getChannelAssignment(msg.channel);
|
|
281
|
+
if (assignment) {
|
|
282
|
+
const assigned = agents.find((agent) => agent.id === assignment.agentId);
|
|
283
|
+
if (assigned) {
|
|
284
|
+
const claimed = this.db.claimThread(msg.threadId, assigned.id, msg.source, msg.channel);
|
|
285
|
+
if (claimed) {
|
|
286
|
+
return { action: "deliver", agentId: assigned.id };
|
|
287
|
+
}
|
|
288
|
+
const claimedThread = this.db.getThread(msg.threadId);
|
|
289
|
+
const claimedOwner = resolveRoutableThreadOwner(this.db, claimedThread?.ownerAgent ?? null);
|
|
290
|
+
if (claimedOwner) {
|
|
291
|
+
return { action: "deliver", agentId: claimedOwner.id };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const mentioned = findAgentMention(msg.text, agents);
|
|
296
|
+
if (mentioned) {
|
|
297
|
+
const claimed = this.db.claimThread(msg.threadId, mentioned.id, msg.source, msg.channel);
|
|
298
|
+
if (claimed) {
|
|
299
|
+
return { action: "deliver", agentId: mentioned.id };
|
|
300
|
+
}
|
|
301
|
+
const claimedThread = this.db.getThread(msg.threadId);
|
|
302
|
+
const claimedOwner = resolveRoutableThreadOwner(this.db, claimedThread?.ownerAgent ?? null);
|
|
303
|
+
if (claimedOwner) {
|
|
304
|
+
return { action: "deliver", agentId: claimedOwner.id };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return { action: "unrouted" };
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Claim a thread for an agent (first-responder-wins).
|
|
311
|
+
* Optionally provide the transport source and channel to store when creating
|
|
312
|
+
* a new thread. Defaults to a neutral external source when callers do not
|
|
313
|
+
* provide one; Slack call sites should continue passing `source: "slack"`
|
|
314
|
+
* explicitly through inbound messages or compatibility wrappers.
|
|
315
|
+
* Returns true if the claim succeeded, false if another agent already owns it.
|
|
316
|
+
*
|
|
317
|
+
* Delegates to the DB layer which performs the claim atomically
|
|
318
|
+
* (single SQL statement) to avoid TOCTOU races. (#125)
|
|
319
|
+
*/
|
|
320
|
+
claimThread(threadId, agentId, channel, source = DEFAULT_EXTERNAL_THREAD_SOURCE) {
|
|
321
|
+
return this.db.claimThread(threadId, agentId, source, channel ?? "");
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get the owner of a thread, or null if unclaimed / nonexistent.
|
|
325
|
+
*/
|
|
326
|
+
getThreadOwner(threadId) {
|
|
327
|
+
const thread = this.db.getThread(threadId);
|
|
328
|
+
return thread?.ownerAgent ?? null;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* List available (connected) agents for routing.
|
|
332
|
+
*/
|
|
333
|
+
getAvailableAgents() {
|
|
334
|
+
return this.db.getAgents();
|
|
335
|
+
}
|
|
336
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
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, PinetLaneInfo, PinetLaneListOptions, PinetLaneParticipantInfo, PinetLaneParticipantUpsertInput, PinetLaneUpsertInput } from "./types.js";
|
|
4
|
+
export interface TaskAssignmentAwaitingReplyInfo {
|
|
5
|
+
id: number;
|
|
6
|
+
agentId: string;
|
|
7
|
+
issueNumber: number;
|
|
8
|
+
status: TaskAssignmentStatus;
|
|
9
|
+
sourceMessageId: number;
|
|
10
|
+
originalSenderAgentId: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function defaultDbPath(): string;
|
|
13
|
+
export declare const DEFAULT_RESUMABLE_WINDOW_MS = 15000;
|
|
14
|
+
export declare const DEFAULT_DISCONNECTED_PURGE_GRACE_MS: number;
|
|
15
|
+
export declare const CURRENT_BROKER_SCHEMA_VERSION = 17;
|
|
16
|
+
export declare class BrokerDB implements BrokerDBInterface {
|
|
17
|
+
private db;
|
|
18
|
+
private readonly dbPath;
|
|
19
|
+
private allowedUsers;
|
|
20
|
+
constructor(dbPath?: string);
|
|
21
|
+
initialize(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Mark all previously connected agents as resumably disconnected on broker
|
|
24
|
+
* startup. Their inbox/thread ownership stays intact during the lease window
|
|
25
|
+
* so reconnecting workers can resume by stableId.
|
|
26
|
+
*/
|
|
27
|
+
reconcileStartupAgents(resumableForMs?: number): void;
|
|
28
|
+
close(): void;
|
|
29
|
+
registerAgent(id: string, name: string, emoji: string, pid: number, metadata?: Record<string, unknown>, stableId?: string): AgentInfo;
|
|
30
|
+
unregisterAgent(id: string): void;
|
|
31
|
+
disconnectAgent(id: string, resumableForMs?: number): void;
|
|
32
|
+
getAgentById(id: string): AgentInfo | null;
|
|
33
|
+
private getCurrentSessionOutboundCount;
|
|
34
|
+
private rowToAgentWithCurrentSessionOutboundCount;
|
|
35
|
+
getAgents(): AgentInfo[];
|
|
36
|
+
getAllAgents(): AgentInfo[];
|
|
37
|
+
getSetting<T = unknown>(key: string): T | null;
|
|
38
|
+
setSetting(key: string, value: unknown): void;
|
|
39
|
+
deleteSetting(key: string): void;
|
|
40
|
+
acquirePortLease(input: PortLeaseAcquireInput): PortLeaseInfo;
|
|
41
|
+
renewPortLease(input: PortLeaseRenewInput): PortLeaseInfo;
|
|
42
|
+
releasePortLease(input: PortLeaseReleaseInput): PortLeaseInfo;
|
|
43
|
+
getPortLease(leaseId: string): PortLeaseInfo | null;
|
|
44
|
+
listPortLeases(options?: PortLeaseListOptions): PortLeaseInfo[];
|
|
45
|
+
expirePortLeases(nowIso?: string): PortLeaseInfo[];
|
|
46
|
+
private expirePortLeasesInternal;
|
|
47
|
+
private findAvailablePortLeasePort;
|
|
48
|
+
private getPortLeaseRowById;
|
|
49
|
+
touchAgent(id: string): void;
|
|
50
|
+
heartbeatAgent(id: string): void;
|
|
51
|
+
pruneStaleAgents(staleAfterMs: number): string[];
|
|
52
|
+
purgeDisconnectedAgents(graceMs?: number): string[];
|
|
53
|
+
updateAgentStatus(id: string, status: "working" | "idle"): void;
|
|
54
|
+
updateAgentMetadata(id: string, metadata: Record<string, unknown> | null): AgentInfo | null;
|
|
55
|
+
updateAgentIdentity(id: string, identity: {
|
|
56
|
+
name: string;
|
|
57
|
+
emoji: string;
|
|
58
|
+
metadata?: Record<string, unknown> | null;
|
|
59
|
+
}): AgentInfo | null;
|
|
60
|
+
touchAgentActivity(id: string): void;
|
|
61
|
+
private ensureUniqueAgentName;
|
|
62
|
+
private getAgentRowById;
|
|
63
|
+
getAgentByStableId(stableId: string): AgentInfo | null;
|
|
64
|
+
findAgentNameConflict(name: string, id: string, stableId?: string): {
|
|
65
|
+
id: string;
|
|
66
|
+
stableId: string | null;
|
|
67
|
+
name: string;
|
|
68
|
+
} | null;
|
|
69
|
+
private getAgentRowByStableId;
|
|
70
|
+
createThread(thread: ThreadInfo): ThreadInfo;
|
|
71
|
+
createThread(threadId: string, source: string, channel: string, ownerAgent: string | null): ThreadInfo;
|
|
72
|
+
updateThread(threadId: string, updates: Partial<ThreadInfo>): void;
|
|
73
|
+
transferThreadOwnership(threadId: string, ownerAgent: string): {
|
|
74
|
+
reassignedInboxCount: number;
|
|
75
|
+
updatedMessageCount: number;
|
|
76
|
+
};
|
|
77
|
+
claimThread(threadId: string, agentId: string, source?: string, channel?: string): boolean;
|
|
78
|
+
setAllowedUsers(users: Iterable<string> | null): void;
|
|
79
|
+
getAllowedUsers(): Set<string> | null;
|
|
80
|
+
getChannelAssignment(_channel: string): ChannelAssignment | null;
|
|
81
|
+
getThread(threadId: string): ThreadInfo | null;
|
|
82
|
+
getThreads(ownerAgent?: string): ThreadInfo[];
|
|
83
|
+
getPendingBacklog(limit?: number): BacklogEntry[];
|
|
84
|
+
getBacklogCount(status?: BacklogEntry["status"]): number;
|
|
85
|
+
queueUnroutedMessage(message: InboundMessage, reason?: string): BacklogEntry;
|
|
86
|
+
assignBacklogEntry(id: number, agentId: string): BacklogEntry | null;
|
|
87
|
+
recoverPendingTargetedBacklog(agentId: string): number;
|
|
88
|
+
dropBacklogEntry(id: number, reason: string): BacklogEntry | null;
|
|
89
|
+
repairOrphanedAssignedBacklog(): {
|
|
90
|
+
resetToPendingCount: number;
|
|
91
|
+
droppedCount: number;
|
|
92
|
+
};
|
|
93
|
+
requeueUndeliveredMessages(agentId: string, reason?: string): number;
|
|
94
|
+
getPendingInboxCount(agentId: string): number;
|
|
95
|
+
getOwnedThreadCount(agentId: string): number;
|
|
96
|
+
releaseThreadClaims(agentId: string): number;
|
|
97
|
+
recordTaskAssignment(agentId: string, issueNumber: number, branch: string | null, threadId: string, sourceMessageId: number | null, options?: {
|
|
98
|
+
repoOwner?: string | null;
|
|
99
|
+
repoName?: string | null;
|
|
100
|
+
repoRoot?: string | null;
|
|
101
|
+
taskKind?: TaskAssignmentKind;
|
|
102
|
+
}): TaskAssignmentInfo;
|
|
103
|
+
listTaskAssignments(): TaskAssignmentInfo[];
|
|
104
|
+
listTaskAssignmentsAwaitingFirstReply(): TaskAssignmentAwaitingReplyInfo[];
|
|
105
|
+
updateTaskAssignmentProgress(id: number, status: TaskAssignmentStatus, prNumber: number | null): void;
|
|
106
|
+
upsertPinetLane(input: PinetLaneUpsertInput): PinetLaneInfo;
|
|
107
|
+
setPinetLaneParticipant(input: PinetLaneParticipantUpsertInput): PinetLaneParticipantInfo;
|
|
108
|
+
getPinetLane(laneId: string): PinetLaneInfo | null;
|
|
109
|
+
listPinetLanes(options?: PinetLaneListOptions): PinetLaneInfo[];
|
|
110
|
+
scheduleWakeup(agentId: string, body: string, fireAt: string, threadId?: string): ScheduledWakeupInfo;
|
|
111
|
+
listScheduledWakeups(agentId?: string): ScheduledWakeupInfo[];
|
|
112
|
+
deliverDueScheduledWakeups(now?: string, limit?: number): ScheduledWakeupDelivery[];
|
|
113
|
+
repairThreadOwnership(): {
|
|
114
|
+
releasedClaimCount: number;
|
|
115
|
+
releasedAgentIds: string[];
|
|
116
|
+
};
|
|
117
|
+
queueMessage(agentId: string, message: InboundMessage): void;
|
|
118
|
+
queueDeliveredMessage(agentId: string, message: InboundMessage): DeliveredInboundMessageResult;
|
|
119
|
+
private buildInboundMessageMetadata;
|
|
120
|
+
private withInboundMailClassMetadata;
|
|
121
|
+
private reclassifyReferencedMessageFromReaction;
|
|
122
|
+
private getUnreadReactionEscalationTargets;
|
|
123
|
+
private redeliverReclassifiedReactionMessage;
|
|
124
|
+
reclassifyMessageByExternalId(source: string, externalId: string, mailClass: PinetMailClass, audit: Record<string, unknown>): BrokerMessage | null;
|
|
125
|
+
private getInboxEntryById;
|
|
126
|
+
insertMessage(threadId: string, source: string, direction: "inbound" | "outbound", sender: string, body: string, targetAgentIds: string[], metadata?: Record<string, unknown>): BrokerMessage;
|
|
127
|
+
private getExistingMessageIdForIdentity;
|
|
128
|
+
private getMessageByExternalId;
|
|
129
|
+
private getMessageById;
|
|
130
|
+
private dropStaleTransportInboxRows;
|
|
131
|
+
getInbox(agentId: string, limit?: number): {
|
|
132
|
+
entry: InboxEntry;
|
|
133
|
+
message: BrokerMessage;
|
|
134
|
+
}[];
|
|
135
|
+
readInbox(agentId: string, options?: InboxReadOptions): InboxReadResult;
|
|
136
|
+
getUnreadInboxCount(agentId: string): number;
|
|
137
|
+
getUnreadThreadSummary(agentId: string, limit?: number): InboxThreadUnreadSummary[];
|
|
138
|
+
markRead(inboxIds: number[], agentId: string): void;
|
|
139
|
+
getMessagesByIds(messageIds: number[]): BrokerMessage[];
|
|
140
|
+
markDelivered(inboxIds: number[], agentId?: string): void;
|
|
141
|
+
/** Mark all undelivered inbox rows for a given message+agent as delivered. */
|
|
142
|
+
markDeliveredByMessageId(messageId: number, agentId: string): void;
|
|
143
|
+
private completeTargetedBacklogAssignment;
|
|
144
|
+
private requeueUndeliveredMessagesInternal;
|
|
145
|
+
private getBacklogById;
|
|
146
|
+
private getBacklogByMessageId;
|
|
147
|
+
private upsertBacklogEntry;
|
|
148
|
+
private withTransaction;
|
|
149
|
+
private openAndMigrate;
|
|
150
|
+
private openDatabase;
|
|
151
|
+
private resetDatabaseFiles;
|
|
152
|
+
private getMissingRequiredAgentLifecycleColumns;
|
|
153
|
+
private ensureRequiredAgentLifecycleColumns;
|
|
154
|
+
protected getDb(): DatabaseSync;
|
|
155
|
+
}
|