@pinet/broker-core 0.2.1 → 0.2.2
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 +3 -1
- package/dist/schema.js +226 -0
- package/dist/types.d.ts +56 -1
- package/package.json +2 -2
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, 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 } from "./types.js";
|
|
4
4
|
export interface TaskAssignmentAwaitingReplyInfo {
|
|
5
5
|
id: number;
|
|
6
6
|
agentId: string;
|
|
@@ -40,6 +40,8 @@ export declare class BrokerDB implements BrokerDBInterface {
|
|
|
40
40
|
private rowToAgentWithCurrentSessionOutboundCount;
|
|
41
41
|
getAgents(): AgentInfo[];
|
|
42
42
|
getAllAgents(): AgentInfo[];
|
|
43
|
+
private getAgentRelatedThreadIds;
|
|
44
|
+
searchAgentSessions(options?: AgentSessionSearchOptions): AgentSessionSearchInfo[];
|
|
43
45
|
getSetting<T = unknown>(key: string): T | null;
|
|
44
46
|
setSetting(key: string, value: unknown): void;
|
|
45
47
|
deleteSetting(key: string): void;
|
package/dist/schema.js
CHANGED
|
@@ -315,6 +315,101 @@ function getOptionalMetadataString(metadata, keys) {
|
|
|
315
315
|
}
|
|
316
316
|
return null;
|
|
317
317
|
}
|
|
318
|
+
function getOptionalNestedMetadataString(metadata, keys) {
|
|
319
|
+
const direct = getOptionalMetadataString(metadata ?? undefined, keys);
|
|
320
|
+
if (direct)
|
|
321
|
+
return direct;
|
|
322
|
+
const capabilities = metadata?.capabilities &&
|
|
323
|
+
typeof metadata.capabilities === "object" &&
|
|
324
|
+
!Array.isArray(metadata.capabilities)
|
|
325
|
+
? metadata.capabilities
|
|
326
|
+
: undefined;
|
|
327
|
+
return getOptionalMetadataString(capabilities, keys);
|
|
328
|
+
}
|
|
329
|
+
function normalizeSessionSearchNeedle(value) {
|
|
330
|
+
const trimmed = value?.trim();
|
|
331
|
+
return trimmed && trimmed.length > 0 ? trimmed.toLowerCase() : null;
|
|
332
|
+
}
|
|
333
|
+
function matchesSessionSearchNeedle(value, needle) {
|
|
334
|
+
if (!needle)
|
|
335
|
+
return true;
|
|
336
|
+
return Boolean(value?.toLowerCase().includes(needle));
|
|
337
|
+
}
|
|
338
|
+
function matchesSessionSearchPrefixOrExact(value, needle) {
|
|
339
|
+
if (!needle)
|
|
340
|
+
return true;
|
|
341
|
+
const normalized = value?.toLowerCase();
|
|
342
|
+
return Boolean(normalized && (normalized === needle || normalized.startsWith(needle)));
|
|
343
|
+
}
|
|
344
|
+
function parseSessionSearchTime(value) {
|
|
345
|
+
if (!value)
|
|
346
|
+
return null;
|
|
347
|
+
const parsed = Date.parse(value);
|
|
348
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
349
|
+
}
|
|
350
|
+
function normalizeSessionSearchLimit(value) {
|
|
351
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
352
|
+
return 20;
|
|
353
|
+
return Math.max(1, Math.min(100, Math.floor(value)));
|
|
354
|
+
}
|
|
355
|
+
function agentSessionOverlapsRange(agent, sinceMs, untilMs) {
|
|
356
|
+
const connectedMs = Date.parse(agent.connectedAt);
|
|
357
|
+
const lastSeenMs = Date.parse(agent.lastSeen || agent.lastHeartbeat || agent.connectedAt);
|
|
358
|
+
const startMs = Number.isNaN(connectedMs) ? null : connectedMs;
|
|
359
|
+
const endMs = Number.isNaN(lastSeenMs) ? startMs : lastSeenMs;
|
|
360
|
+
if (sinceMs !== null && endMs !== null && endMs < sinceMs)
|
|
361
|
+
return false;
|
|
362
|
+
if (untilMs !== null && startMs !== null && startMs > untilMs)
|
|
363
|
+
return false;
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
function getAgentSessionMatchedBy(input) {
|
|
367
|
+
const matchedBy = [];
|
|
368
|
+
const agentName = normalizeSessionSearchNeedle(input.options.agentName);
|
|
369
|
+
const agentId = normalizeSessionSearchNeedle(input.options.agentId);
|
|
370
|
+
const threadId = normalizeSessionSearchNeedle(input.options.threadId);
|
|
371
|
+
const repo = normalizeSessionSearchNeedle(input.options.repo);
|
|
372
|
+
const worktreePath = normalizeSessionSearchNeedle(input.options.worktreePath);
|
|
373
|
+
const tmuxSession = normalizeSessionSearchNeedle(input.options.tmuxSession);
|
|
374
|
+
if (agentName && matchesSessionSearchNeedle(input.agent.name, agentName)) {
|
|
375
|
+
matchedBy.push("agent_name");
|
|
376
|
+
}
|
|
377
|
+
if (agentId && matchesSessionSearchPrefixOrExact(input.agent.id, agentId)) {
|
|
378
|
+
matchedBy.push("agent_id");
|
|
379
|
+
}
|
|
380
|
+
if (threadId &&
|
|
381
|
+
input.relatedThreadIds.some((candidate) => candidate.toLowerCase().includes(threadId))) {
|
|
382
|
+
matchedBy.push("thread_id");
|
|
383
|
+
}
|
|
384
|
+
const repoValues = [
|
|
385
|
+
getOptionalNestedMetadataString(input.metadata, ["repo"]),
|
|
386
|
+
getOptionalNestedMetadataString(input.metadata, ["repoRoot"]),
|
|
387
|
+
getOptionalNestedMetadataString(input.metadata, ["cwd"]),
|
|
388
|
+
];
|
|
389
|
+
if (repo && repoValues.some((value) => matchesSessionSearchNeedle(value, repo))) {
|
|
390
|
+
matchedBy.push("repo");
|
|
391
|
+
}
|
|
392
|
+
const worktreeValues = [
|
|
393
|
+
getOptionalNestedMetadataString(input.metadata, ["worktreePath"]),
|
|
394
|
+
getOptionalNestedMetadataString(input.metadata, ["cwd"]),
|
|
395
|
+
getOptionalNestedMetadataString(input.metadata, ["repoRoot"]),
|
|
396
|
+
];
|
|
397
|
+
if (worktreePath &&
|
|
398
|
+
worktreeValues.some((value) => matchesSessionSearchNeedle(value, worktreePath))) {
|
|
399
|
+
matchedBy.push("worktree_path");
|
|
400
|
+
}
|
|
401
|
+
const tmux = getOptionalNestedMetadataString(input.metadata, ["tmuxSession", "tmux"]);
|
|
402
|
+
if (tmuxSession && matchesSessionSearchNeedle(tmux, tmuxSession)) {
|
|
403
|
+
matchedBy.push("tmux_session");
|
|
404
|
+
}
|
|
405
|
+
if (input.options.since || input.options.until) {
|
|
406
|
+
matchedBy.push("time_range");
|
|
407
|
+
}
|
|
408
|
+
if (matchedBy.length === 0) {
|
|
409
|
+
matchedBy.push("recent");
|
|
410
|
+
}
|
|
411
|
+
return matchedBy;
|
|
412
|
+
}
|
|
318
413
|
function rowToPinetLaneParticipant(row) {
|
|
319
414
|
return {
|
|
320
415
|
laneId: row.lane_id,
|
|
@@ -1470,6 +1565,137 @@ export class BrokerDB {
|
|
|
1470
1565
|
.all();
|
|
1471
1566
|
return rows.map((row) => this.rowToAgentWithCurrentSessionOutboundCount(row));
|
|
1472
1567
|
}
|
|
1568
|
+
getAgentRelatedThreadIds(agentId, limit = 12) {
|
|
1569
|
+
const db = this.getDb();
|
|
1570
|
+
const rows = db
|
|
1571
|
+
.prepare(`SELECT thread_id, MAX(activity_at) AS activity_at
|
|
1572
|
+
FROM (
|
|
1573
|
+
SELECT thread_id, updated_at AS activity_at
|
|
1574
|
+
FROM threads
|
|
1575
|
+
WHERE owner_agent = ? OR channel = ?
|
|
1576
|
+
UNION ALL
|
|
1577
|
+
SELECT thread_id, created_at AS activity_at
|
|
1578
|
+
FROM messages
|
|
1579
|
+
WHERE sender = ?
|
|
1580
|
+
UNION ALL
|
|
1581
|
+
SELECT m.thread_id, i.created_at AS activity_at
|
|
1582
|
+
FROM inbox i
|
|
1583
|
+
JOIN messages m ON m.id = i.message_id
|
|
1584
|
+
WHERE i.agent_id = ?
|
|
1585
|
+
UNION ALL
|
|
1586
|
+
SELECT thread_id, updated_at AS activity_at
|
|
1587
|
+
FROM threads
|
|
1588
|
+
WHERE thread_id LIKE ? OR thread_id LIKE ?
|
|
1589
|
+
) related
|
|
1590
|
+
GROUP BY thread_id
|
|
1591
|
+
ORDER BY activity_at DESC
|
|
1592
|
+
LIMIT ?`)
|
|
1593
|
+
.all(agentId, `agent:${agentId}`, agentId, agentId, `a2a:${agentId}:%`, `a2a:%:${agentId}`, limit);
|
|
1594
|
+
return rows.map((row) => row.thread_id);
|
|
1595
|
+
}
|
|
1596
|
+
searchAgentSessions(options = {}) {
|
|
1597
|
+
const agentName = normalizeSessionSearchNeedle(options.agentName);
|
|
1598
|
+
const agentId = normalizeSessionSearchNeedle(options.agentId);
|
|
1599
|
+
const threadId = normalizeSessionSearchNeedle(options.threadId);
|
|
1600
|
+
const repo = normalizeSessionSearchNeedle(options.repo);
|
|
1601
|
+
const worktreePath = normalizeSessionSearchNeedle(options.worktreePath);
|
|
1602
|
+
const tmuxSession = normalizeSessionSearchNeedle(options.tmuxSession);
|
|
1603
|
+
const sinceMs = parseSessionSearchTime(options.since);
|
|
1604
|
+
const untilMs = parseSessionSearchTime(options.until);
|
|
1605
|
+
const limit = normalizeSessionSearchLimit(options.limit);
|
|
1606
|
+
const results = this.getAllAgents()
|
|
1607
|
+
.map((agent) => {
|
|
1608
|
+
const metadata = agent.metadata ?? null;
|
|
1609
|
+
const relatedThreadIds = this.getAgentRelatedThreadIds(agent.id);
|
|
1610
|
+
const matchedBy = getAgentSessionMatchedBy({ agent, metadata, relatedThreadIds, options });
|
|
1611
|
+
return {
|
|
1612
|
+
agent,
|
|
1613
|
+
metadata,
|
|
1614
|
+
relatedThreadIds,
|
|
1615
|
+
matchedBy,
|
|
1616
|
+
lastSeenMs: Date.parse(agent.lastSeen || agent.lastHeartbeat || agent.connectedAt),
|
|
1617
|
+
};
|
|
1618
|
+
})
|
|
1619
|
+
.filter(({ agent, metadata, relatedThreadIds }) => {
|
|
1620
|
+
if (agentName && !matchesSessionSearchNeedle(agent.name, agentName))
|
|
1621
|
+
return false;
|
|
1622
|
+
if (agentId && !matchesSessionSearchPrefixOrExact(agent.id, agentId))
|
|
1623
|
+
return false;
|
|
1624
|
+
if (threadId &&
|
|
1625
|
+
!relatedThreadIds.some((candidate) => candidate.toLowerCase().includes(threadId))) {
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1628
|
+
if (repo) {
|
|
1629
|
+
const values = [
|
|
1630
|
+
getOptionalNestedMetadataString(metadata, ["repo"]),
|
|
1631
|
+
getOptionalNestedMetadataString(metadata, ["repoRoot"]),
|
|
1632
|
+
getOptionalNestedMetadataString(metadata, ["cwd"]),
|
|
1633
|
+
];
|
|
1634
|
+
if (!values.some((value) => matchesSessionSearchNeedle(value, repo)))
|
|
1635
|
+
return false;
|
|
1636
|
+
}
|
|
1637
|
+
if (worktreePath) {
|
|
1638
|
+
const values = [
|
|
1639
|
+
getOptionalNestedMetadataString(metadata, ["worktreePath"]),
|
|
1640
|
+
getOptionalNestedMetadataString(metadata, ["cwd"]),
|
|
1641
|
+
getOptionalNestedMetadataString(metadata, ["repoRoot"]),
|
|
1642
|
+
];
|
|
1643
|
+
if (!values.some((value) => matchesSessionSearchNeedle(value, worktreePath))) {
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
if (tmuxSession) {
|
|
1648
|
+
const tmux = getOptionalNestedMetadataString(metadata, ["tmuxSession", "tmux"]);
|
|
1649
|
+
if (!matchesSessionSearchNeedle(tmux, tmuxSession))
|
|
1650
|
+
return false;
|
|
1651
|
+
}
|
|
1652
|
+
return agentSessionOverlapsRange(agent, sinceMs, untilMs);
|
|
1653
|
+
})
|
|
1654
|
+
.sort((left, right) => {
|
|
1655
|
+
const liveDelta = Number(Boolean(left.agent.disconnectedAt)) - Number(Boolean(right.agent.disconnectedAt));
|
|
1656
|
+
if (liveDelta !== 0)
|
|
1657
|
+
return liveDelta;
|
|
1658
|
+
const leftSeen = Number.isNaN(left.lastSeenMs) ? 0 : left.lastSeenMs;
|
|
1659
|
+
const rightSeen = Number.isNaN(right.lastSeenMs) ? 0 : right.lastSeenMs;
|
|
1660
|
+
if (leftSeen !== rightSeen)
|
|
1661
|
+
return rightSeen - leftSeen;
|
|
1662
|
+
return left.agent.name.localeCompare(right.agent.name);
|
|
1663
|
+
})
|
|
1664
|
+
.slice(0, limit)
|
|
1665
|
+
.map(({ agent, metadata, relatedThreadIds, matchedBy }) => ({
|
|
1666
|
+
agentId: agent.id,
|
|
1667
|
+
agentName: agent.name,
|
|
1668
|
+
emoji: agent.emoji,
|
|
1669
|
+
pid: agent.pid,
|
|
1670
|
+
status: agent.status,
|
|
1671
|
+
stableId: agent.stableId ?? null,
|
|
1672
|
+
connectedAt: agent.connectedAt,
|
|
1673
|
+
lastSeen: agent.lastSeen,
|
|
1674
|
+
lastHeartbeat: agent.lastHeartbeat,
|
|
1675
|
+
disconnectedAt: agent.disconnectedAt ?? null,
|
|
1676
|
+
resumableUntil: agent.resumableUntil ?? null,
|
|
1677
|
+
idleSince: agent.idleSince ?? null,
|
|
1678
|
+
lastActivity: agent.lastActivity ?? null,
|
|
1679
|
+
cwd: getOptionalNestedMetadataString(metadata, ["cwd"]),
|
|
1680
|
+
repo: getOptionalNestedMetadataString(metadata, ["repo"]),
|
|
1681
|
+
repoRoot: getOptionalNestedMetadataString(metadata, ["repoRoot"]),
|
|
1682
|
+
worktreePath: getOptionalNestedMetadataString(metadata, ["worktreePath"]),
|
|
1683
|
+
branch: getOptionalNestedMetadataString(metadata, ["branch"]),
|
|
1684
|
+
tmuxSession: getOptionalNestedMetadataString(metadata, ["tmuxSession", "tmux"]),
|
|
1685
|
+
brokerManaged: metadata?.brokerManaged === true,
|
|
1686
|
+
brokerManagedBy: getOptionalNestedMetadataString(metadata, ["brokerManagedBy"]),
|
|
1687
|
+
launchSource: getOptionalNestedMetadataString(metadata, ["launchSource"]),
|
|
1688
|
+
parentAgentId: agent.parentAgentId ?? null,
|
|
1689
|
+
rootAgentId: agent.rootAgentId ?? null,
|
|
1690
|
+
treeDepth: agent.treeDepth ?? 0,
|
|
1691
|
+
supervisionState: agent.supervisionState ?? "root",
|
|
1692
|
+
subtreeRole: agent.subtreeRole ?? null,
|
|
1693
|
+
laneId: agent.laneId ?? null,
|
|
1694
|
+
relatedThreadIds,
|
|
1695
|
+
matchedBy,
|
|
1696
|
+
}));
|
|
1697
|
+
return results;
|
|
1698
|
+
}
|
|
1473
1699
|
getSetting(key) {
|
|
1474
1700
|
const db = this.getDb();
|
|
1475
1701
|
const row = db.prepare("SELECT value FROM settings WHERE key = ?").get(key);
|
package/dist/types.d.ts
CHANGED
|
@@ -27,7 +27,61 @@ export interface AgentInfo {
|
|
|
27
27
|
outboundCount?: number;
|
|
28
28
|
pendingInboxCount?: number;
|
|
29
29
|
}
|
|
30
|
-
export type
|
|
30
|
+
export type AgentSessionKind = "session" | "leaf" | "cwd" | "broker" | "unknown";
|
|
31
|
+
export interface AgentSessionSummary {
|
|
32
|
+
kind: AgentSessionKind;
|
|
33
|
+
/** Broker-safe, path-free stable session reference such as "session:1a2b3c4d5e6f". */
|
|
34
|
+
ref: string;
|
|
35
|
+
host?: string | null;
|
|
36
|
+
hasPath?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export type ClientAgentInfo = Omit<AgentInfo, "stableId"> & {
|
|
39
|
+
/** Redacted session indicator. Raw stableId/session paths are intentionally not exposed here. */
|
|
40
|
+
session?: AgentSessionSummary | null;
|
|
41
|
+
};
|
|
42
|
+
export interface AgentSessionSearchOptions {
|
|
43
|
+
agentName?: string;
|
|
44
|
+
agentId?: string;
|
|
45
|
+
threadId?: string;
|
|
46
|
+
repo?: string;
|
|
47
|
+
worktreePath?: string;
|
|
48
|
+
tmuxSession?: string;
|
|
49
|
+
since?: string;
|
|
50
|
+
until?: string;
|
|
51
|
+
limit?: number;
|
|
52
|
+
}
|
|
53
|
+
export interface AgentSessionSearchInfo {
|
|
54
|
+
agentId: string;
|
|
55
|
+
agentName: string;
|
|
56
|
+
emoji: string;
|
|
57
|
+
pid: number;
|
|
58
|
+
status: "working" | "idle";
|
|
59
|
+
stableId: string | null;
|
|
60
|
+
connectedAt: string;
|
|
61
|
+
lastSeen: string;
|
|
62
|
+
lastHeartbeat: string;
|
|
63
|
+
disconnectedAt: string | null;
|
|
64
|
+
resumableUntil: string | null;
|
|
65
|
+
idleSince: string | null;
|
|
66
|
+
lastActivity: string | null;
|
|
67
|
+
cwd: string | null;
|
|
68
|
+
repo: string | null;
|
|
69
|
+
repoRoot: string | null;
|
|
70
|
+
worktreePath: string | null;
|
|
71
|
+
branch: string | null;
|
|
72
|
+
tmuxSession: string | null;
|
|
73
|
+
brokerManaged: boolean;
|
|
74
|
+
brokerManagedBy: string | null;
|
|
75
|
+
launchSource: string | null;
|
|
76
|
+
parentAgentId: string | null;
|
|
77
|
+
rootAgentId: string | null;
|
|
78
|
+
treeDepth: number;
|
|
79
|
+
supervisionState: AgentSupervisionState;
|
|
80
|
+
subtreeRole: string | null;
|
|
81
|
+
laneId: string | null;
|
|
82
|
+
relatedThreadIds: string[];
|
|
83
|
+
matchedBy: string[];
|
|
84
|
+
}
|
|
31
85
|
export interface ThreadInfo {
|
|
32
86
|
threadId: string;
|
|
33
87
|
source: string;
|
|
@@ -296,6 +350,7 @@ export interface BrokerDBInterface {
|
|
|
296
350
|
getAgentById(agentId: string): AgentInfo | null;
|
|
297
351
|
getAgentByStableId(stableId: string): AgentInfo | null;
|
|
298
352
|
getAgents(): AgentInfo[];
|
|
353
|
+
searchAgentSessions?(options?: AgentSessionSearchOptions): AgentSessionSearchInfo[];
|
|
299
354
|
getChannelAssignment(channel: string): ChannelAssignment | null;
|
|
300
355
|
acquirePortLease?(input: PortLeaseAcquireInput): PortLeaseInfo;
|
|
301
356
|
renewPortLease?(input: PortLeaseRenewInput): PortLeaseInfo;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pinet/broker-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Transport-neutral broker kernel primitives for pi transports",
|
|
6
6
|
"author": "Will Porcellini <5994936+gugu91@users.noreply.github.com>",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"test": "vitest run *.test.ts"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@pinet/transport-core": "0.2.
|
|
46
|
+
"@pinet/transport-core": "0.2.2"
|
|
47
47
|
},
|
|
48
48
|
"types": "./dist/index.d.ts"
|
|
49
49
|
}
|