letagents 0.12.12 → 0.12.14
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/mcp/git-remote.js +7 -7
- package/dist/mcp/local-state/agent-sessions.js +63 -5
- package/dist/mcp/local-state/local-chat.js +189 -48
- package/dist/mcp/local-state/storage.js +16 -9
- package/dist/mcp/server/daemon-tool-executor.js +92 -0
- package/dist/mcp/server/register-tools.js +4 -2
- package/dist/mcp/server/runtime/agent-sessions.js +16 -7
- package/dist/mcp/server/runtime/api.js +11 -3
- package/dist/mcp/server/runtime/daemon-tool-context.js +11 -0
- package/dist/mcp/server/runtime/execution-profile.js +1 -0
- package/dist/mcp/server/runtime/messages.js +55 -0
- package/dist/mcp/server/runtime/presence.js +3 -3
- package/dist/mcp/server/runtime/room-api.js +2 -2
- package/dist/mcp/server/runtime/room-state.js +19 -9
- package/dist/mcp/server/runtime/rooms.js +54 -33
- package/dist/mcp/server/runtime/supervisor-bridge.js +129 -11
- package/dist/mcp/server/runtime/tool-surface-policy.js +7 -0
- package/dist/mcp/server/runtime/worker-bearer.js +17 -2
- package/dist/mcp/server/runtime-contract.js +1 -0
- package/dist/mcp/server/runtime.js +6 -6
- package/dist/mcp/server/supervised-tool-facade.js +107 -6
- package/dist/mcp/server/tools/messages/read-tool.js +54 -97
- package/dist/mcp/server/tools/messages/reasoning-tool.js +2 -0
- package/dist/mcp/server/tools/messages/send-tool.js +2 -0
- package/dist/mcp/server/tools/messages/status-tool.js +2 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +303 -71
- package/dist/mcp/server/tools/onboarding/status-tool.js +4 -4
- package/dist/mcp/server/tools/rooms/inspection-tools.js +15 -10
- package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +2 -1
- package/dist/mcp/server/tools/tasks/board-tools.js +34 -2
- package/dist/mcp/sse-client.js +163 -20
- package/dist/shared/activation-routing.js +163 -23
- package/dist/shared/agent-presence.js +6 -0
- package/dist/shared/desktop-release-manifest.js +63 -0
- package/dist/shared/desktop-release.js +60 -0
- package/dist/shared/scoped-ids.js +6 -0
- package/package.json +7 -3
- package/shared/execution-approval-projection.d.mts +32 -0
- package/shared/execution-approval-projection.mjs +107 -0
- package/shared/execution-approval-publication-item.d.mts +20 -0
- package/shared/execution-approval-publication-item.mjs +61 -0
- package/shared/execution-approval-publication.d.mts +53 -0
- package/shared/execution-approval-publication.mjs +136 -0
- package/shared/execution-delegation-decision.d.mts +37 -0
- package/shared/execution-delegation-decision.mjs +73 -0
- package/shared/message-contracts.d.mts +32 -0
- package/shared/message-contracts.mjs +109 -0
- package/shared/room-agent-work.d.mts +31 -0
- package/shared/room-agent-work.mjs +44 -0
- package/shared/room-resource-invalidation.d.mts +38 -0
- package/shared/room-resource-invalidation.mjs +50 -0
- package/shared/routing-aliases.d.mts +18 -0
- package/shared/routing-aliases.mjs +66 -0
- package/shared/sqlite-thread-routing.d.mts +72 -0
- package/shared/sqlite-thread-routing.mjs +1038 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { normalizeRoutingSender } from "./routing-aliases.mjs";
|
|
2
|
+
|
|
3
|
+
export const POSTGRES_INTEGER_MAX = 2_147_483_647;
|
|
4
|
+
export const MESSAGE_SENDER_MAX_CODE_POINTS = 512;
|
|
5
|
+
export const MESSAGE_SENDER_MAX_UTF8_BYTES = 2_048;
|
|
6
|
+
|
|
7
|
+
/** Parse the canonical `<prefix>_<positive PostgreSQL integer>` wire form. */
|
|
8
|
+
export function parsePositivePgIntegerScopedId(value, prefix) {
|
|
9
|
+
if (typeof value !== "string" || typeof prefix !== "string" || !prefix) return null;
|
|
10
|
+
const marker = `${prefix}_`;
|
|
11
|
+
if (!value.startsWith(marker)) return null;
|
|
12
|
+
const decimal = value.slice(marker.length);
|
|
13
|
+
if (!/^[1-9]\d*$/.test(decimal)) return null;
|
|
14
|
+
const parsed = Number(decimal);
|
|
15
|
+
return Number.isSafeInteger(parsed) && parsed <= POSTGRES_INTEGER_MAX ? parsed : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Bound sender labels before routing projections duplicate their raw value. */
|
|
19
|
+
export function isMessageSenderWithinBounds(sender) {
|
|
20
|
+
if (typeof sender !== "string") return false;
|
|
21
|
+
let codePoints = 0;
|
|
22
|
+
for (const _character of sender) {
|
|
23
|
+
codePoints += 1;
|
|
24
|
+
if (codePoints > MESSAGE_SENDER_MAX_CODE_POINTS) return false;
|
|
25
|
+
}
|
|
26
|
+
return new TextEncoder().encode(sender).byteLength <= MESSAGE_SENDER_MAX_UTF8_BYTES;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse the account-scoped routing envelope shared by Desktop and MCP.
|
|
31
|
+
* Undefined means an older/local message supplied no authority. Any present
|
|
32
|
+
* malformed shape returns explicit invalid authority and must never fall back
|
|
33
|
+
* to mutable display aliases.
|
|
34
|
+
*/
|
|
35
|
+
export function parseAccountAgentRoutingEnvelope(routing) {
|
|
36
|
+
if (routing === undefined) return undefined;
|
|
37
|
+
if (!routing || typeof routing !== "object" || routing.version !== 1) {
|
|
38
|
+
return { version: 1, authority: "invalid" };
|
|
39
|
+
}
|
|
40
|
+
if (routing.authority !== "receipts" && routing.authority !== "legacy") {
|
|
41
|
+
return { version: 1, authority: "invalid" };
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
!Array.isArray(routing.recipient_agent_keys)
|
|
45
|
+
|| !routing.recipient_agent_keys.every((value) => typeof value === "string")
|
|
46
|
+
|| !Array.isArray(routing.recipient_agent_sessions)
|
|
47
|
+
) {
|
|
48
|
+
return { version: 1, authority: "invalid" };
|
|
49
|
+
}
|
|
50
|
+
const recipientAgentKeys = routing.recipient_agent_keys.map(normalizeRoutingSender);
|
|
51
|
+
if (recipientAgentKeys.some((value) => !value)) {
|
|
52
|
+
return { version: 1, authority: "invalid" };
|
|
53
|
+
}
|
|
54
|
+
const uniqueKeys = new Set(recipientAgentKeys);
|
|
55
|
+
if (uniqueKeys.size !== recipientAgentKeys.length) {
|
|
56
|
+
return { version: 1, authority: "invalid" };
|
|
57
|
+
}
|
|
58
|
+
const targetKeys = new Set();
|
|
59
|
+
const recipientSessions = [];
|
|
60
|
+
for (const value of routing.recipient_agent_sessions) {
|
|
61
|
+
if (!value || typeof value !== "object") {
|
|
62
|
+
return { version: 1, authority: "invalid" };
|
|
63
|
+
}
|
|
64
|
+
const agentKey = typeof value.agent_key === "string"
|
|
65
|
+
? normalizeRoutingSender(value.agent_key)
|
|
66
|
+
: "";
|
|
67
|
+
const agentSessionId = typeof value.agent_session_id === "string"
|
|
68
|
+
? value.agent_session_id.trim()
|
|
69
|
+
: "";
|
|
70
|
+
if (!agentKey || !agentSessionId || targetKeys.has(agentKey)) {
|
|
71
|
+
return { version: 1, authority: "invalid" };
|
|
72
|
+
}
|
|
73
|
+
targetKeys.add(agentKey);
|
|
74
|
+
if (routing.authority === "receipts") {
|
|
75
|
+
const successorAgentSessionId = value.successor_agent_session_id === undefined
|
|
76
|
+
? undefined
|
|
77
|
+
: typeof value.successor_agent_session_id === "string"
|
|
78
|
+
? value.successor_agent_session_id.trim()
|
|
79
|
+
: "";
|
|
80
|
+
if (successorAgentSessionId !== undefined && !successorAgentSessionId) {
|
|
81
|
+
return { version: 1, authority: "invalid" };
|
|
82
|
+
}
|
|
83
|
+
recipientSessions.push({
|
|
84
|
+
agentKey,
|
|
85
|
+
agentSessionId,
|
|
86
|
+
...(successorAgentSessionId ? { successorAgentSessionId } : {}),
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
const activationReason = typeof value.activation_reason === "string"
|
|
90
|
+
? value.activation_reason.trim()
|
|
91
|
+
: "";
|
|
92
|
+
if (!activationReason) return { version: 1, authority: "invalid" };
|
|
93
|
+
recipientSessions.push({ agentKey, agentSessionId, activationReason });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (
|
|
97
|
+
targetKeys.size !== uniqueKeys.size
|
|
98
|
+
|| [...uniqueKeys].some((key) => !targetKeys.has(key))
|
|
99
|
+
) {
|
|
100
|
+
return { version: 1, authority: "invalid" };
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
version: 1,
|
|
104
|
+
authority: routing.authority,
|
|
105
|
+
recipientAgentKeys: [...uniqueKeys],
|
|
106
|
+
recipientSessions,
|
|
107
|
+
controlAuthorized: routing.control_authorized === true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const ROOM_WORK_STATES: readonly ["active", "completed", "completed_no_reply", "failed", "interrupted", "lost", "unknown"];
|
|
2
|
+
export const ROOM_WORK_OPERATION_OUTCOMES: readonly ["unresolved", "succeeded", "failed", "denied_before_start", "cancelled_before_start", "interrupted_after_start", "lost_after_start"];
|
|
3
|
+
export type RoomAgentWorkSummary = {
|
|
4
|
+
version: 1;
|
|
5
|
+
recorded_state: typeof ROOM_WORK_STATES[number];
|
|
6
|
+
evidence_incomplete: boolean;
|
|
7
|
+
elapsed_ms: number | null;
|
|
8
|
+
operation_counts: Record<typeof ROOM_WORK_OPERATION_OUTCOMES[number], number>;
|
|
9
|
+
};
|
|
10
|
+
// Server-issued availability marker, not a publisher-supplied execution state.
|
|
11
|
+
export type ClearedRoomAgentWorkSummary = { version: 1; availability: "cleared" };
|
|
12
|
+
export type RoomAgentWork = {
|
|
13
|
+
attempt_id: string;
|
|
14
|
+
room_id: string;
|
|
15
|
+
source_message_id: string;
|
|
16
|
+
agent_key: string;
|
|
17
|
+
revision: number;
|
|
18
|
+
summary: RoomAgentWorkSummary | ClearedRoomAgentWorkSummary;
|
|
19
|
+
updated_at: string;
|
|
20
|
+
};
|
|
21
|
+
export type RoomAgentWorkSnapshot = { work: RoomAgentWork[]; truncated: boolean };
|
|
22
|
+
/** Synchronizes the latest-50 view, not intermediate events or complete history.
|
|
23
|
+
* A changed snapshot replaces the client's bounded cache; it must not be merged.
|
|
24
|
+
* The server-issued cursor is a comparison hint, never authorization. */
|
|
25
|
+
export type RoomAgentWorkPollResponse = { room_id: string; cursor: string } & (
|
|
26
|
+
| { changed: true; snapshot: RoomAgentWorkSnapshot }
|
|
27
|
+
| { changed: false; snapshot: null }
|
|
28
|
+
);
|
|
29
|
+
export function isClearedRoomAgentWorkSummary(value: unknown): value is ClearedRoomAgentWorkSummary;
|
|
30
|
+
/** Return a canonical allowlisted copy, or reject without echoing private input. */
|
|
31
|
+
export function parseRoomAgentWorkSummary(value: unknown): RoomAgentWorkSummary | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Room-public, host-reported evidence; never execution or approval authority.
|
|
2
|
+
// No free-form strings, native handles, paths, command text, or output belong
|
|
3
|
+
// in this version. Both the publisher and server use this strict boundary.
|
|
4
|
+
export const ROOM_WORK_STATES = [
|
|
5
|
+
"active", "completed", "completed_no_reply", "failed", "interrupted", "lost", "unknown",
|
|
6
|
+
];
|
|
7
|
+
export const ROOM_WORK_OPERATION_OUTCOMES = [
|
|
8
|
+
"unresolved", "succeeded", "failed", "denied_before_start", "cancelled_before_start",
|
|
9
|
+
"interrupted_after_start", "lost_after_start",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function exactKeys(value, keys) {
|
|
13
|
+
return !!value && typeof value === "object" && !Array.isArray(value)
|
|
14
|
+
&& Object.keys(value).length === keys.length
|
|
15
|
+
&& keys.every((key) => Object.hasOwn(value, key));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isClearedRoomAgentWorkSummary(value) {
|
|
19
|
+
return exactKeys(value, ["version", "availability"]) && value.version === 1 && value.availability === "cleared";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Return a canonical allowlisted copy, or reject without echoing private input. */
|
|
23
|
+
export function parseRoomAgentWorkSummary(value) {
|
|
24
|
+
if (!exactKeys(value, ["version", "recorded_state", "evidence_incomplete", "elapsed_ms", "operation_counts"])
|
|
25
|
+
|| value.version !== 1 || !ROOM_WORK_STATES.includes(value.recorded_state)
|
|
26
|
+
|| typeof value.evidence_incomplete !== "boolean"
|
|
27
|
+
|| (value.elapsed_ms !== null && (!Number.isSafeInteger(value.elapsed_ms) || Number(value.elapsed_ms) < 0))
|
|
28
|
+
|| !exactKeys(value.operation_counts, ROOM_WORK_OPERATION_OUTCOMES)) return null;
|
|
29
|
+
const counts = {};
|
|
30
|
+
let total = 0;
|
|
31
|
+
for (const outcome of ROOM_WORK_OPERATION_OUTCOMES) {
|
|
32
|
+
const count = value.operation_counts[outcome];
|
|
33
|
+
if (!Number.isSafeInteger(count) || Number(count) < 0) return null;
|
|
34
|
+
counts[outcome] = Number(count);
|
|
35
|
+
total += Number(count);
|
|
36
|
+
}
|
|
37
|
+
// A bounded evidence snapshot, not an unbounded lifetime counter.
|
|
38
|
+
if (total > 10_000) return null;
|
|
39
|
+
return {
|
|
40
|
+
version: 1, recorded_state: value.recorded_state,
|
|
41
|
+
evidence_incomplete: value.evidence_incomplete, elapsed_ms: value.elapsed_ms,
|
|
42
|
+
operation_counts: counts,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const ROOM_RESOURCE_INVALIDATION_CAPABILITY: "resource_invalidation_v1";
|
|
2
|
+
export const ROOM_RESOURCE_AGENT_WORK: "agent_work";
|
|
3
|
+
/** Content-free approval-state hint; consumers repair through separately authorized exact reads. */
|
|
4
|
+
export const ROOM_RESOURCE_AGENT_APPROVAL: "agent_approval";
|
|
5
|
+
export const ROOM_RESOURCE_EXECUTION_DELEGATION: "execution_delegation";
|
|
6
|
+
/** Protocol-known references; consumers independently choose what they render. */
|
|
7
|
+
export const ROOM_RESOURCE_INVALIDATION_RESOURCES: readonly [
|
|
8
|
+
"agent_work",
|
|
9
|
+
"agent_approval",
|
|
10
|
+
"execution_delegation",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export type RoomResourceInvalidationResource =
|
|
14
|
+
typeof ROOM_RESOURCE_INVALIDATION_RESOURCES[number];
|
|
15
|
+
|
|
16
|
+
export type RoomResourceInvalidationPointer<Resource extends string = string> = {
|
|
17
|
+
room_id: string;
|
|
18
|
+
resource: Resource;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type RoomResourceInvalidationParseResult =
|
|
22
|
+
| {
|
|
23
|
+
status: "supported";
|
|
24
|
+
pointer: RoomResourceInvalidationPointer<RoomResourceInvalidationResource>;
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
status: "unsupported";
|
|
28
|
+
pointer: RoomResourceInvalidationPointer;
|
|
29
|
+
}
|
|
30
|
+
| { status: "malformed" };
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a bounded room-resource pointer without echoing malformed input.
|
|
34
|
+
* Unknown but well-formed resources remain forward-compatible cursor no-ops.
|
|
35
|
+
*/
|
|
36
|
+
export function parseRoomResourceInvalidation(
|
|
37
|
+
value: unknown,
|
|
38
|
+
): RoomResourceInvalidationParseResult;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const ROOM_RESOURCE_INVALIDATION_CAPABILITY = "resource_invalidation_v1";
|
|
2
|
+
export const ROOM_RESOURCE_AGENT_WORK = "agent_work";
|
|
3
|
+
// Content-free hint emitted when an approval projection is published or its
|
|
4
|
+
// decision-relevant room state changes. Consumers repair through separately
|
|
5
|
+
// authorized exact reads; the pointer never carries approval content.
|
|
6
|
+
export const ROOM_RESOURCE_AGENT_APPROVAL = "agent_approval";
|
|
7
|
+
export const ROOM_RESOURCE_EXECUTION_DELEGATION = "execution_delegation";
|
|
8
|
+
// Protocol-known references. Each consumer still decides which surfaces, if
|
|
9
|
+
// any, react to a supported pointer.
|
|
10
|
+
export const ROOM_RESOURCE_INVALIDATION_RESOURCES = [
|
|
11
|
+
ROOM_RESOURCE_AGENT_WORK,
|
|
12
|
+
ROOM_RESOURCE_AGENT_APPROVAL,
|
|
13
|
+
ROOM_RESOURCE_EXECUTION_DELEGATION,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function exactKeys(value, keys) {
|
|
17
|
+
return !!value && typeof value === "object" && !Array.isArray(value)
|
|
18
|
+
&& Object.keys(value).length === keys.length
|
|
19
|
+
&& keys.every((key) => Object.hasOwn(value, key));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isValidRoomIdentifier(value) {
|
|
23
|
+
return typeof value === "string"
|
|
24
|
+
&& value.length > 0
|
|
25
|
+
&& value.length <= 512
|
|
26
|
+
&& value.trim() === value
|
|
27
|
+
&& !/[\u0000-\u001f\u007f]/.test(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isValidResource(value) {
|
|
31
|
+
return typeof value === "string"
|
|
32
|
+
&& /^[a-z0-9][a-z0-9._-]{0,63}$/.test(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse a bounded room-resource pointer without echoing malformed input.
|
|
37
|
+
* Unknown but well-formed resources remain forward-compatible cursor no-ops.
|
|
38
|
+
*/
|
|
39
|
+
export function parseRoomResourceInvalidation(value) {
|
|
40
|
+
if (
|
|
41
|
+
!exactKeys(value, ["room_id", "resource"])
|
|
42
|
+
|| !isValidRoomIdentifier(value.room_id)
|
|
43
|
+
|| !isValidResource(value.resource)
|
|
44
|
+
) return { status: "malformed" };
|
|
45
|
+
|
|
46
|
+
const pointer = { room_id: value.room_id, resource: value.resource };
|
|
47
|
+
return ROOM_RESOURCE_INVALIDATION_RESOURCES.includes(value.resource)
|
|
48
|
+
? { status: "supported", pointer }
|
|
49
|
+
: { status: "unsupported", pointer };
|
|
50
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type RoutingIdentityLike = {
|
|
2
|
+
actorLabel?: string | null;
|
|
3
|
+
actor_label?: string | null;
|
|
4
|
+
displayName?: string | null;
|
|
5
|
+
display_name?: string | null;
|
|
6
|
+
agentKey?: string | null;
|
|
7
|
+
agent_key?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function normalizeRoutingSender(value: unknown): string;
|
|
11
|
+
export function normalizeRoutingHandle(value: unknown): string;
|
|
12
|
+
export function routingIdentityAliases(identity: RoutingIdentityLike): Set<string>;
|
|
13
|
+
export function routingSenderAliasRows(
|
|
14
|
+
sender: unknown,
|
|
15
|
+
segmentLimit?: number,
|
|
16
|
+
): Array<{ alias: string; isFull: boolean }>;
|
|
17
|
+
export function routingSenderAliases(sender: unknown, segmentLimit?: number): Set<string>;
|
|
18
|
+
export function routingAliasHash(alias: string): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stable cross-engine fold: ASCII A-Z only. Unicode case conversion varies by
|
|
5
|
+
* JS/ICU/PostgreSQL version and locale, so non-ASCII aliases are deliberately
|
|
6
|
+
* case-sensitive while exact non-ASCII labels still match everywhere.
|
|
7
|
+
*/
|
|
8
|
+
function foldRoutingText(value) {
|
|
9
|
+
return String(value ?? "").replace(/[A-Z]/g, (character) =>
|
|
10
|
+
String.fromCharCode(character.charCodeAt(0) + 32));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizeRoutingSender(value) {
|
|
14
|
+
return foldRoutingText(typeof value === "string" ? value.trim() : "")
|
|
15
|
+
.replace(/\s+/g, " ");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function normalizeRoutingHandle(value) {
|
|
19
|
+
return foldRoutingText(typeof value === "string" ? value.trim() : "")
|
|
20
|
+
.replace(/[^a-z0-9_.:/-]+/g, "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function routingIdentityAliasInputs(identity) {
|
|
24
|
+
const agentKey = typeof identity?.agentKey === "string"
|
|
25
|
+
? identity.agentKey
|
|
26
|
+
: typeof identity?.agent_key === "string"
|
|
27
|
+
? identity.agent_key
|
|
28
|
+
: "";
|
|
29
|
+
return Array.from(new Set([
|
|
30
|
+
identity?.actorLabel ?? identity?.actor_label,
|
|
31
|
+
identity?.displayName ?? identity?.display_name,
|
|
32
|
+
agentKey,
|
|
33
|
+
agentKey.split("/").pop(),
|
|
34
|
+
].map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean)));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function routingIdentityAliases(identity) {
|
|
38
|
+
const aliases = new Set();
|
|
39
|
+
for (const value of routingIdentityAliasInputs(identity)) {
|
|
40
|
+
const senderAlias = normalizeRoutingSender(value);
|
|
41
|
+
if (senderAlias) aliases.add(senderAlias);
|
|
42
|
+
const handleAlias = normalizeRoutingHandle(value);
|
|
43
|
+
if (handleAlias) aliases.add(handleAlias);
|
|
44
|
+
}
|
|
45
|
+
return aliases;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function routingSenderAliasRows(sender, segmentLimit = 16) {
|
|
49
|
+
const raw = typeof sender === "string" ? sender : "";
|
|
50
|
+
const fullAlias = normalizeRoutingSender(raw);
|
|
51
|
+
const byAlias = new Map();
|
|
52
|
+
if (fullAlias) byAlias.set(fullAlias, true);
|
|
53
|
+
for (const segment of raw.split("|").slice(0, Math.max(0, segmentLimit))) {
|
|
54
|
+
const alias = normalizeRoutingSender(segment);
|
|
55
|
+
if (alias && !byAlias.has(alias)) byAlias.set(alias, false);
|
|
56
|
+
}
|
|
57
|
+
return [...byAlias].map(([alias, isFull]) => ({ alias, isFull }));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function routingSenderAliases(sender, segmentLimit = 16) {
|
|
61
|
+
return new Set(routingSenderAliasRows(sender, segmentLimit).map(({ alias }) => alias));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function routingAliasHash(alias) {
|
|
65
|
+
return createHash("md5").update(alias).digest("hex");
|
|
66
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { RoutingIdentityLike } from "./routing-aliases.mjs";
|
|
2
|
+
|
|
3
|
+
export type SqliteRoutingDatabase = {
|
|
4
|
+
exec(sql: string): void;
|
|
5
|
+
prepare(sql: string): {
|
|
6
|
+
all(...params: unknown[]): Record<string, unknown>[];
|
|
7
|
+
get(...params: unknown[]): Record<string, unknown> | undefined;
|
|
8
|
+
run(...params: unknown[]): unknown;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const LOCAL_THREAD_ROUTING_BACKFILL_BATCH_SIZE: number;
|
|
13
|
+
export const LOCAL_THREAD_ROUTING_LOOKUP_BATCH_SIZE: number;
|
|
14
|
+
export const LOCAL_THREAD_ROUTING_BACKFILL_TIME_BUDGET_MS: number;
|
|
15
|
+
export class LocalThreadRoutingProjectionUnavailableError extends Error {}
|
|
16
|
+
export function ensureLocalThreadRoutingProjectionSchema(database: SqliteRoutingDatabase): void;
|
|
17
|
+
export function ensureLocalThreadRoutingProjectionSchemaAsync(
|
|
18
|
+
database: SqliteRoutingDatabase,
|
|
19
|
+
options?: { maxWaitMs?: number; random?: () => number },
|
|
20
|
+
): Promise<void>;
|
|
21
|
+
export function runLocalSqliteWriteTransactionAsync<T>(
|
|
22
|
+
database: SqliteRoutingDatabase,
|
|
23
|
+
work: () => T,
|
|
24
|
+
options?: { maxWaitMs?: number; random?: () => number },
|
|
25
|
+
): Promise<T>;
|
|
26
|
+
export function projectLocalThreadRoutingMessage(
|
|
27
|
+
database: SqliteRoutingDatabase,
|
|
28
|
+
row: {
|
|
29
|
+
room_id: string;
|
|
30
|
+
number: number;
|
|
31
|
+
thread_root_number?: number | null;
|
|
32
|
+
reply_to_number?: number | null;
|
|
33
|
+
sender: string;
|
|
34
|
+
text?: string;
|
|
35
|
+
source?: string | null;
|
|
36
|
+
timestamp?: string;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
},
|
|
39
|
+
): void;
|
|
40
|
+
export function runLocalThreadRoutingBackfillBatch(
|
|
41
|
+
database: SqliteRoutingDatabase,
|
|
42
|
+
batchSize?: number,
|
|
43
|
+
): { processed: number; completed: boolean };
|
|
44
|
+
export function scheduleLocalThreadRoutingBackfill(
|
|
45
|
+
database: SqliteRoutingDatabase,
|
|
46
|
+
options?: {
|
|
47
|
+
onError?: (error: unknown, delayMs: number | null) => void;
|
|
48
|
+
setImmediate?: (callback: () => void) => { unref?(): void } | unknown;
|
|
49
|
+
setTimeout?: (callback: () => void, delayMs: number) => { unref?(): void } | unknown;
|
|
50
|
+
},
|
|
51
|
+
): void;
|
|
52
|
+
export function scheduleLocalThreadRoutingRootsRepair(
|
|
53
|
+
database: SqliteRoutingDatabase,
|
|
54
|
+
roomId: string,
|
|
55
|
+
rootNumbers: readonly number[],
|
|
56
|
+
): void;
|
|
57
|
+
export function invalidateLocalThreadRoutingRoots(
|
|
58
|
+
database: SqliteRoutingDatabase,
|
|
59
|
+
roomId: string,
|
|
60
|
+
rootNumbers: readonly number[],
|
|
61
|
+
): void;
|
|
62
|
+
export function getLocalThreadRoutingAgentKeysForRoots(
|
|
63
|
+
database: SqliteRoutingDatabase,
|
|
64
|
+
roomId: string,
|
|
65
|
+
rootNumbers: readonly number[],
|
|
66
|
+
identities: readonly RoutingIdentityLike[],
|
|
67
|
+
options?: {
|
|
68
|
+
foregroundTimeBudgetMs?: number;
|
|
69
|
+
scheduleOnTimeout?: boolean;
|
|
70
|
+
signal?: AbortSignal;
|
|
71
|
+
},
|
|
72
|
+
): Promise<Map<number, Set<string>>>;
|