@relaycast/engine 12.0.0 → 12.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/dist/adapters/node/index.d.ts.map +1 -1
- package/dist/adapters/node/index.js +1 -0
- package/dist/adapters/node/index.js.map +1 -1
- package/dist/adapters/node/presence.d.ts +18 -0
- package/dist/adapters/node/presence.d.ts.map +1 -1
- package/dist/adapters/node/presence.js +82 -0
- package/dist/adapters/node/presence.js.map +1 -1
- package/dist/adapters/node/realtime.d.ts +4 -0
- package/dist/adapters/node/realtime.d.ts.map +1 -1
- package/dist/adapters/node/realtime.js +32 -5
- package/dist/adapters/node/realtime.js.map +1 -1
- package/dist/db/schema.d.ts +2 -2
- package/dist/engine/a2a.d.ts +3 -0
- package/dist/engine/a2a.d.ts.map +1 -1
- package/dist/engine/a2a.js +1 -0
- package/dist/engine/a2a.js.map +1 -1
- package/dist/engine/action.d.ts +14 -9
- package/dist/engine/action.d.ts.map +1 -1
- package/dist/engine/action.js +335 -74
- package/dist/engine/action.js.map +1 -1
- package/dist/engine/agent.d.ts +9 -3
- package/dist/engine/agent.d.ts.map +1 -1
- package/dist/engine/agent.js +21 -3
- package/dist/engine/agent.js.map +1 -1
- package/dist/engine/delivery.d.ts.map +1 -1
- package/dist/engine/delivery.js +4 -0
- package/dist/engine/delivery.js.map +1 -1
- package/dist/engine/eventDelivery.d.ts +1 -1
- package/dist/engine/inboundWebhook.d.ts.map +1 -1
- package/dist/engine/inboundWebhook.js +12 -3
- package/dist/engine/inboundWebhook.js.map +1 -1
- package/dist/engine/inbox.d.ts.map +1 -1
- package/dist/engine/inbox.js +17 -0
- package/dist/engine/inbox.js.map +1 -1
- package/dist/engine/invocationClaim.d.ts +32 -0
- package/dist/engine/invocationClaim.d.ts.map +1 -0
- package/dist/engine/invocationClaim.js +42 -0
- package/dist/engine/invocationClaim.js.map +1 -0
- package/dist/engine/messageMetadata.d.ts +2 -0
- package/dist/engine/messageMetadata.d.ts.map +1 -1
- package/dist/engine/messageMetadata.js +3 -2
- package/dist/engine/messageMetadata.js.map +1 -1
- package/dist/engine/node.d.ts +12 -2
- package/dist/engine/node.d.ts.map +1 -1
- package/dist/engine/node.js +83 -4
- package/dist/engine/node.js.map +1 -1
- package/dist/engine/presence.d.ts +173 -1
- package/dist/engine/presence.d.ts.map +1 -1
- package/dist/engine/presence.js +81 -4
- package/dist/engine/presence.js.map +1 -1
- package/dist/engine/registrationAdmission.d.ts +40 -0
- package/dist/engine/registrationAdmission.d.ts.map +1 -0
- package/dist/engine/registrationAdmission.js +78 -0
- package/dist/engine/registrationAdmission.js.map +1 -0
- package/dist/engine/taskInvocation.d.ts +1 -1
- package/dist/engine/workspace.d.ts +26 -1
- package/dist/engine/workspace.d.ts.map +1 -1
- package/dist/engine/workspace.js +108 -7
- package/dist/engine/workspace.js.map +1 -1
- package/dist/engine/workspaceDrain.d.ts +38 -0
- package/dist/engine/workspaceDrain.d.ts.map +1 -0
- package/dist/engine/workspaceDrain.js +202 -0
- package/dist/engine/workspaceDrain.js.map +1 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +28 -0
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/engineDiagnostics.d.ts +26 -0
- package/dist/lib/engineDiagnostics.d.ts.map +1 -0
- package/dist/lib/engineDiagnostics.js +100 -0
- package/dist/lib/engineDiagnostics.js.map +1 -0
- package/dist/lib/httpError.d.ts.map +1 -1
- package/dist/lib/httpError.js +5 -1
- package/dist/lib/httpError.js.map +1 -1
- package/dist/middleware/admission.d.ts +7 -0
- package/dist/middleware/admission.d.ts.map +1 -1
- package/dist/middleware/admission.js +19 -1
- package/dist/middleware/admission.js.map +1 -1
- package/dist/middleware/rateLimit.d.ts.map +1 -1
- package/dist/middleware/rateLimit.js +8 -1
- package/dist/middleware/rateLimit.js.map +1 -1
- package/dist/ports/admission.d.ts +24 -3
- package/dist/ports/admission.d.ts.map +1 -1
- package/dist/ports/diagnostics.d.ts +42 -0
- package/dist/ports/diagnostics.d.ts.map +1 -0
- package/dist/ports/diagnostics.js +13 -0
- package/dist/ports/diagnostics.js.map +1 -0
- package/dist/ports/index.d.ts +9 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/presence.d.ts +14 -0
- package/dist/ports/presence.d.ts.map +1 -1
- package/dist/ports/realtime.d.ts +18 -0
- package/dist/ports/realtime.d.ts.map +1 -1
- package/dist/ports/realtime.js.map +1 -1
- package/dist/routes/a2a.d.ts.map +1 -1
- package/dist/routes/a2a.js +2 -0
- package/dist/routes/a2a.js.map +1 -1
- package/dist/routes/action.js +3 -0
- package/dist/routes/action.js.map +1 -1
- package/dist/routes/agent.d.ts.map +1 -1
- package/dist/routes/agent.js +5 -1
- package/dist/routes/agent.js.map +1 -1
- package/dist/routes/node.d.ts.map +1 -1
- package/dist/routes/node.js +2 -1
- package/dist/routes/node.js.map +1 -1
- package/dist/routes/workspace.js +10 -2
- package/dist/routes/workspace.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,12 +1,184 @@
|
|
|
1
1
|
import type { getDb } from '../db/index.js';
|
|
2
2
|
import type { PresenceTracker } from '../ports/presence.js';
|
|
3
|
+
type Db = ReturnType<typeof getDb>;
|
|
4
|
+
/**
|
|
5
|
+
* Active bindings on nodes that might pass {@link isNodeLive}.
|
|
6
|
+
*
|
|
7
|
+
* One join for the whole roster: `idx_nodes_status_heartbeat` ranges the live
|
|
8
|
+
* heartbeat window, then `idx_agent_node_bindings_node` seeks each node's
|
|
9
|
+
* active bindings. The agent seek is its primary key, and the provider seek is
|
|
10
|
+
* `node_providers_node_name_unique` for `agents.provider_name`. A nested
|
|
11
|
+
* `EXISTS` on `idx_node_providers_node` tells a provider-less direct node from
|
|
12
|
+
* a broker whose provider row is simply missing. SQLite stores heartbeats as
|
|
13
|
+
* whole seconds, so the lower bound is up to 999ms wider than the millisecond
|
|
14
|
+
* TTL; {@link isNodeLive} and {@link isProviderLive} are the decisions and drop
|
|
15
|
+
* that extra second. The read does not write.
|
|
16
|
+
*/
|
|
17
|
+
export declare function nodeHostedPresenceQuery(db: Db, workspaceId: string, now: number): Omit<import("drizzle-orm/sqlite-core").SQLiteSelectBase<undefined, "async", unknown, {
|
|
18
|
+
agentId: import("drizzle-orm").SQL<string>;
|
|
19
|
+
nodeLastHeartbeatAt: import("drizzle-orm").SQL<Date>;
|
|
20
|
+
nodeHasProviders: import("drizzle-orm").SQL<number>;
|
|
21
|
+
providerStatus: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
22
|
+
name: "status";
|
|
23
|
+
tableName: "node_providers";
|
|
24
|
+
dataType: "string";
|
|
25
|
+
columnType: "SQLiteText";
|
|
26
|
+
data: string;
|
|
27
|
+
driverParam: string;
|
|
28
|
+
notNull: true;
|
|
29
|
+
hasDefault: true;
|
|
30
|
+
isPrimaryKey: false;
|
|
31
|
+
isAutoincrement: false;
|
|
32
|
+
hasRuntimeDefault: false;
|
|
33
|
+
enumValues: [string, ...string[]];
|
|
34
|
+
baseColumn: never;
|
|
35
|
+
identity: undefined;
|
|
36
|
+
generated: undefined;
|
|
37
|
+
}, {}, {
|
|
38
|
+
length: number | undefined;
|
|
39
|
+
}>;
|
|
40
|
+
providerHandlersLive: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
41
|
+
name: "handlers_live";
|
|
42
|
+
tableName: "node_providers";
|
|
43
|
+
dataType: "boolean";
|
|
44
|
+
columnType: "SQLiteBoolean";
|
|
45
|
+
data: boolean;
|
|
46
|
+
driverParam: number;
|
|
47
|
+
notNull: true;
|
|
48
|
+
hasDefault: true;
|
|
49
|
+
isPrimaryKey: false;
|
|
50
|
+
isAutoincrement: false;
|
|
51
|
+
hasRuntimeDefault: false;
|
|
52
|
+
enumValues: undefined;
|
|
53
|
+
baseColumn: never;
|
|
54
|
+
identity: undefined;
|
|
55
|
+
generated: undefined;
|
|
56
|
+
}, {}, {}>;
|
|
57
|
+
providerLastHeartbeatAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
58
|
+
name: "last_heartbeat_at";
|
|
59
|
+
tableName: "node_providers";
|
|
60
|
+
dataType: "date";
|
|
61
|
+
columnType: "SQLiteTimestamp";
|
|
62
|
+
data: Date;
|
|
63
|
+
driverParam: number;
|
|
64
|
+
notNull: false;
|
|
65
|
+
hasDefault: false;
|
|
66
|
+
isPrimaryKey: false;
|
|
67
|
+
isAutoincrement: false;
|
|
68
|
+
hasRuntimeDefault: false;
|
|
69
|
+
enumValues: undefined;
|
|
70
|
+
baseColumn: never;
|
|
71
|
+
identity: undefined;
|
|
72
|
+
generated: undefined;
|
|
73
|
+
}, {}, {}>;
|
|
74
|
+
}, "partial", {
|
|
75
|
+
agents: "not-null";
|
|
76
|
+
} & {
|
|
77
|
+
node_providers: "nullable";
|
|
78
|
+
}, false, "where", {
|
|
79
|
+
agentId: string;
|
|
80
|
+
nodeLastHeartbeatAt: Date;
|
|
81
|
+
nodeHasProviders: number;
|
|
82
|
+
providerStatus: string | null;
|
|
83
|
+
providerHandlersLive: boolean | null;
|
|
84
|
+
providerLastHeartbeatAt: Date | null;
|
|
85
|
+
}[], {
|
|
86
|
+
agentId: import("drizzle-orm").DrizzleTypeError<"You cannot reference this field without assigning it an alias first - use `.as(<alias>)`">;
|
|
87
|
+
nodeLastHeartbeatAt: import("drizzle-orm").DrizzleTypeError<"You cannot reference this field without assigning it an alias first - use `.as(<alias>)`">;
|
|
88
|
+
nodeHasProviders: import("drizzle-orm").DrizzleTypeError<"You cannot reference this field without assigning it an alias first - use `.as(<alias>)`">;
|
|
89
|
+
providerStatus: import("drizzle-orm").Column<import("drizzle-orm").UpdateColConfig<import("drizzle-orm").ColumnTypeConfig<{
|
|
90
|
+
name: "status";
|
|
91
|
+
tableName: "node_providers";
|
|
92
|
+
dataType: "string";
|
|
93
|
+
columnType: "SQLiteText";
|
|
94
|
+
data: string;
|
|
95
|
+
driverParam: string;
|
|
96
|
+
notNull: true;
|
|
97
|
+
hasDefault: true;
|
|
98
|
+
isPrimaryKey: false;
|
|
99
|
+
isAutoincrement: false;
|
|
100
|
+
hasRuntimeDefault: false;
|
|
101
|
+
enumValues: [string, ...string[]];
|
|
102
|
+
baseColumn: never;
|
|
103
|
+
identity: undefined;
|
|
104
|
+
generated: undefined;
|
|
105
|
+
}, {
|
|
106
|
+
length: number | undefined;
|
|
107
|
+
} & {
|
|
108
|
+
dialect: "sqlite";
|
|
109
|
+
}>, {
|
|
110
|
+
notNull: false;
|
|
111
|
+
}>, object, object>;
|
|
112
|
+
providerHandlersLive: import("drizzle-orm").Column<import("drizzle-orm").UpdateColConfig<import("drizzle-orm").ColumnTypeConfig<{
|
|
113
|
+
name: "handlers_live";
|
|
114
|
+
tableName: "node_providers";
|
|
115
|
+
dataType: "boolean";
|
|
116
|
+
columnType: "SQLiteBoolean";
|
|
117
|
+
data: boolean;
|
|
118
|
+
driverParam: number;
|
|
119
|
+
notNull: true;
|
|
120
|
+
hasDefault: true;
|
|
121
|
+
isPrimaryKey: false;
|
|
122
|
+
isAutoincrement: false;
|
|
123
|
+
hasRuntimeDefault: false;
|
|
124
|
+
enumValues: undefined;
|
|
125
|
+
baseColumn: never;
|
|
126
|
+
identity: undefined;
|
|
127
|
+
generated: undefined;
|
|
128
|
+
}, {} & {
|
|
129
|
+
dialect: "sqlite";
|
|
130
|
+
}>, {
|
|
131
|
+
notNull: false;
|
|
132
|
+
}>, object, object>;
|
|
133
|
+
providerLastHeartbeatAt: import("drizzle-orm").Column<import("drizzle-orm").UpdateColConfig<import("drizzle-orm").ColumnTypeConfig<{
|
|
134
|
+
name: "last_heartbeat_at";
|
|
135
|
+
tableName: "node_providers";
|
|
136
|
+
dataType: "date";
|
|
137
|
+
columnType: "SQLiteTimestamp";
|
|
138
|
+
data: Date;
|
|
139
|
+
driverParam: number;
|
|
140
|
+
notNull: false;
|
|
141
|
+
hasDefault: false;
|
|
142
|
+
isPrimaryKey: false;
|
|
143
|
+
isAutoincrement: false;
|
|
144
|
+
hasRuntimeDefault: false;
|
|
145
|
+
enumValues: undefined;
|
|
146
|
+
baseColumn: never;
|
|
147
|
+
identity: undefined;
|
|
148
|
+
generated: undefined;
|
|
149
|
+
}, {} & {
|
|
150
|
+
dialect: "sqlite";
|
|
151
|
+
}>, {
|
|
152
|
+
notNull: false;
|
|
153
|
+
}>, object, object>;
|
|
154
|
+
}>, "where">;
|
|
155
|
+
type NodeHostedPresenceRow = {
|
|
156
|
+
agentId: string;
|
|
157
|
+
nodeLastHeartbeatAt: Date | null;
|
|
158
|
+
nodeHasProviders: number | boolean;
|
|
159
|
+
providerStatus: string | null;
|
|
160
|
+
providerHandlersLive: boolean | null;
|
|
161
|
+
providerLastHeartbeatAt: Date | null;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* A bound agent is online when its node is live and, on a node that has
|
|
165
|
+
* provider rows, the provider named by `agents.provider_name` is live too.
|
|
166
|
+
* Implicit direct nodes have no provider row; node liveness is the whole
|
|
167
|
+
* signal. A sibling provider's heartbeat must not count an agent whose own
|
|
168
|
+
* provider is disconnected or absent.
|
|
169
|
+
*/
|
|
170
|
+
export declare function nodeHostedAgentOnline(row: NodeHostedPresenceRow, now: number): boolean;
|
|
3
171
|
/**
|
|
4
172
|
* Get presence status for all agents in a workspace.
|
|
5
173
|
* Queries the presence tracker for online agents and merges with the DB roster.
|
|
174
|
+
* An agent located on a live node, with an active binding there, is online even
|
|
175
|
+
* when it has never heartbeated over HTTP — unless it explicitly disconnected,
|
|
176
|
+
* or its own provider (when the node has any) is not live.
|
|
6
177
|
*/
|
|
7
|
-
export declare function getPresence(db:
|
|
178
|
+
export declare function getPresence(db: Db, presence: PresenceTracker, workspaceId: string, now?: number): Promise<Array<{
|
|
8
179
|
agent_id: string;
|
|
9
180
|
agent_name: string;
|
|
10
181
|
status: 'online' | 'offline';
|
|
11
182
|
}>>;
|
|
183
|
+
export {};
|
|
12
184
|
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/engine/presence.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/engine/presence.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAK5D,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA4C/E;AAED,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,mBAAmB,EAAE,IAAI,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oBAAoB,EAAE,OAAO,GAAG,IAAI,CAAC;IACrC,uBAAuB,EAAE,IAAI,GAAG,IAAI,CAAC;CACtC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,qBAAqB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAYtF;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,eAAe,EACzB,WAAW,EAAE,MAAM,EACnB,GAAG,SAAa,GACf,OAAO,CAAC,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,CAAC,CAAC,CA2BxF"}
|
package/dist/engine/presence.js
CHANGED
|
@@ -1,12 +1,80 @@
|
|
|
1
|
-
import { and, eq, ne } from 'drizzle-orm';
|
|
2
|
-
import { agents } from '../db/schema.js';
|
|
1
|
+
import { and, eq, ne, sql } from 'drizzle-orm';
|
|
2
|
+
import { agentNodeBindings, agents, nodeProviders, nodes } from '../db/schema.js';
|
|
3
3
|
import { RELEASED_AGENT_STATUS } from './agent.js';
|
|
4
|
+
import { isProviderLive } from './nodeProvider.js';
|
|
5
|
+
import { isNodeLive, NODE_LIVENESS_TTL_MS } from './placement.js';
|
|
6
|
+
/**
|
|
7
|
+
* Active bindings on nodes that might pass {@link isNodeLive}.
|
|
8
|
+
*
|
|
9
|
+
* One join for the whole roster: `idx_nodes_status_heartbeat` ranges the live
|
|
10
|
+
* heartbeat window, then `idx_agent_node_bindings_node` seeks each node's
|
|
11
|
+
* active bindings. The agent seek is its primary key, and the provider seek is
|
|
12
|
+
* `node_providers_node_name_unique` for `agents.provider_name`. A nested
|
|
13
|
+
* `EXISTS` on `idx_node_providers_node` tells a provider-less direct node from
|
|
14
|
+
* a broker whose provider row is simply missing. SQLite stores heartbeats as
|
|
15
|
+
* whole seconds, so the lower bound is up to 999ms wider than the millisecond
|
|
16
|
+
* TTL; {@link isNodeLive} and {@link isProviderLive} are the decisions and drop
|
|
17
|
+
* that extra second. The read does not write.
|
|
18
|
+
*/
|
|
19
|
+
export function nodeHostedPresenceQuery(db, workspaceId, now) {
|
|
20
|
+
const newestSecond = Math.floor(now / 1000);
|
|
21
|
+
const oldestSecond = Math.floor((now - NODE_LIVENESS_TTL_MS) / 1000);
|
|
22
|
+
// CROSS JOIN is not reordered by SQLite, so the live-node index stays the
|
|
23
|
+
// outer loop and each node's bindings are a seek. INNER JOIN would let the
|
|
24
|
+
// planner start at every active binding in the workspace.
|
|
25
|
+
return db
|
|
26
|
+
.select({
|
|
27
|
+
// `nodes` and `agent_node_bindings` are indexed-by fragments, so their
|
|
28
|
+
// columns are not join-tracked Column fields. mapWith keeps the driver
|
|
29
|
+
// codecs (dates, text) the table columns already declare.
|
|
30
|
+
agentId: sql `${agentNodeBindings.agentId}`.mapWith(agentNodeBindings.agentId),
|
|
31
|
+
nodeLastHeartbeatAt: sql `${nodes.lastHeartbeatAt}`.mapWith(nodes.lastHeartbeatAt),
|
|
32
|
+
nodeHasProviders: sql `EXISTS (
|
|
33
|
+
SELECT 1 FROM node_providers AS np
|
|
34
|
+
WHERE np.workspace_id = ${nodes.workspaceId}
|
|
35
|
+
AND np.node_id = ${nodes.id}
|
|
36
|
+
)`.mapWith(Number),
|
|
37
|
+
providerStatus: nodeProviders.status,
|
|
38
|
+
providerHandlersLive: nodeProviders.handlersLive,
|
|
39
|
+
providerLastHeartbeatAt: nodeProviders.lastHeartbeatAt,
|
|
40
|
+
})
|
|
41
|
+
.from(sql `${nodes} INDEXED BY idx_nodes_status_heartbeat`)
|
|
42
|
+
.crossJoin(sql `${agentNodeBindings} INDEXED BY idx_agent_node_bindings_node`)
|
|
43
|
+
.crossJoin(agents)
|
|
44
|
+
.leftJoin(nodeProviders, and(eq(nodeProviders.workspaceId, workspaceId), eq(nodeProviders.nodeId, nodes.id), eq(nodeProviders.name, agents.providerName)))
|
|
45
|
+
.where(and(eq(nodes.workspaceId, workspaceId), eq(nodes.status, 'online'), sql `${nodes.lastHeartbeatAt} >= ${oldestSecond}`, sql `${nodes.lastHeartbeatAt} <= ${newestSecond}`, eq(agentNodeBindings.workspaceId, workspaceId), eq(agentNodeBindings.nodeId, nodes.id), eq(agentNodeBindings.status, 'active'), eq(agents.workspaceId, workspaceId), eq(agents.id, agentNodeBindings.agentId), eq(agents.locationType, 'via_node'), eq(agents.locationNodeId, nodes.id), ne(agents.status, RELEASED_AGENT_STATUS)));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A bound agent is online when its node is live and, on a node that has
|
|
49
|
+
* provider rows, the provider named by `agents.provider_name` is live too.
|
|
50
|
+
* Implicit direct nodes have no provider row; node liveness is the whole
|
|
51
|
+
* signal. A sibling provider's heartbeat must not count an agent whose own
|
|
52
|
+
* provider is disconnected or absent.
|
|
53
|
+
*/
|
|
54
|
+
export function nodeHostedAgentOnline(row, now) {
|
|
55
|
+
if (!isNodeLive({ status: 'online', lastHeartbeatAt: row.nodeLastHeartbeatAt }, now))
|
|
56
|
+
return false;
|
|
57
|
+
const hasProviders = row.nodeHasProviders === true || Number(row.nodeHasProviders) === 1;
|
|
58
|
+
if (!hasProviders)
|
|
59
|
+
return true;
|
|
60
|
+
if (row.providerStatus == null || row.providerHandlersLive == null || row.providerLastHeartbeatAt == null) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return isProviderLive({
|
|
64
|
+
status: row.providerStatus,
|
|
65
|
+
handlersLive: row.providerHandlersLive,
|
|
66
|
+
lastHeartbeatAt: row.providerLastHeartbeatAt,
|
|
67
|
+
}, now);
|
|
68
|
+
}
|
|
4
69
|
/**
|
|
5
70
|
* Get presence status for all agents in a workspace.
|
|
6
71
|
* Queries the presence tracker for online agents and merges with the DB roster.
|
|
72
|
+
* An agent located on a live node, with an active binding there, is online even
|
|
73
|
+
* when it has never heartbeated over HTTP — unless it explicitly disconnected,
|
|
74
|
+
* or its own provider (when the node has any) is not live.
|
|
7
75
|
*/
|
|
8
|
-
export async function getPresence(db, presence, workspaceId) {
|
|
9
|
-
const [allAgents, onlineIds] = await Promise.all([
|
|
76
|
+
export async function getPresence(db, presence, workspaceId, now = Date.now()) {
|
|
77
|
+
const [allAgents, onlineIds, nodeHosted, suppressedIds] = await Promise.all([
|
|
10
78
|
db
|
|
11
79
|
.select({ id: agents.id, name: agents.name })
|
|
12
80
|
.from(agents)
|
|
@@ -14,10 +82,19 @@ export async function getPresence(db, presence, workspaceId) {
|
|
|
14
82
|
// They are not roster members and must not appear as presence entries.
|
|
15
83
|
.where(and(eq(agents.workspaceId, workspaceId), ne(agents.status, RELEASED_AGENT_STATUS))),
|
|
16
84
|
presence.getOnline(workspaceId),
|
|
85
|
+
nodeHostedPresenceQuery(db, workspaceId, now),
|
|
86
|
+
presence.explicitlyDisconnected?.(workspaceId) ?? Promise.resolve([]),
|
|
17
87
|
]);
|
|
18
88
|
if (allAgents.length === 0)
|
|
19
89
|
return [];
|
|
20
90
|
const onlineSet = new Set(onlineIds);
|
|
91
|
+
const suppressed = new Set(suppressedIds);
|
|
92
|
+
for (const row of nodeHosted) {
|
|
93
|
+
if (suppressed.has(row.agentId))
|
|
94
|
+
continue;
|
|
95
|
+
if (nodeHostedAgentOnline(row, now))
|
|
96
|
+
onlineSet.add(row.agentId);
|
|
97
|
+
}
|
|
21
98
|
return allAgents.map((agent) => ({
|
|
22
99
|
agent_id: agent.id,
|
|
23
100
|
agent_name: agent.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/engine/presence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/engine/presence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { EngineDb } from '../ports/database.js';
|
|
2
|
+
import type { AdmissionHooks, RegistrationAdmissionAction } from '../ports/admission.js';
|
|
3
|
+
import type { Workspace } from '../ports/auth.js';
|
|
4
|
+
/**
|
|
5
|
+
* How many of `action`'s kind the workspace currently has registered — the
|
|
6
|
+
* `registered` count a registration is admitted against.
|
|
7
|
+
*
|
|
8
|
+
* - `register_device`: every node a host enrolled, whatever its role or
|
|
9
|
+
* transport. Every agent also owns an implicit direct node
|
|
10
|
+
* (`node_direct_<agent id>`) for its own WebSocket; those are not devices.
|
|
11
|
+
* The prefix is reserved at enrollment (`createNodeToken`), so a host cannot
|
|
12
|
+
* enroll into it.
|
|
13
|
+
* - `register_agent`: non-human identities that have not been released, except
|
|
14
|
+
* the engine-provisioned inbound-webhook identity ({@link WEBHOOK_AGENT_NAME}).
|
|
15
|
+
* `createWebhook` provisions it on a workspace's first webhook; no caller
|
|
16
|
+
* registers it, and its name is reserved at registration
|
|
17
|
+
* (`assertRegistrableAgentName`), so it never holds a slot.
|
|
18
|
+
* - `register_human`: `type = 'human'` identities that have not been released.
|
|
19
|
+
*/
|
|
20
|
+
export declare function countRegistrations(db: EngineDb, workspaceId: string, action: RegistrationAdmissionAction): Promise<number>;
|
|
21
|
+
/**
|
|
22
|
+
* Admits a NEW registration through the host's `admit` hook, throwing
|
|
23
|
+
* {@link AdmissionDeniedError} on a denial. Callers invoke it only on the
|
|
24
|
+
* create path — re-registering an existing node or agent is never new.
|
|
25
|
+
*/
|
|
26
|
+
export type RegistrationAdmission = (action: RegistrationAdmissionAction) => Promise<void>;
|
|
27
|
+
export interface RegistrationAdmissionArgs {
|
|
28
|
+
db: EngineDb;
|
|
29
|
+
admission: AdmissionHooks | undefined;
|
|
30
|
+
/** The authenticated workspace row, or its id when the caller only has that. */
|
|
31
|
+
workspace: Workspace | string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build the admission check the registration paths run before inserting a
|
|
35
|
+
* node or agent, or `undefined` when the host has no `admit` hook (nothing to
|
|
36
|
+
* count for). A hook or count that throws admits the registration, like every
|
|
37
|
+
* other admission: a host's metering outage must not block registration.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createRegistrationAdmission(args: RegistrationAdmissionArgs): RegistrationAdmission | undefined;
|
|
40
|
+
//# sourceMappingURL=registrationAdmission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registrationAdmission.d.ts","sourceRoot":"","sources":["../../src/engine/registrationAdmission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAKlD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,QAAQ,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,2BAA2B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3F,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,QAAQ,CAAC;IACb,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,gFAAgF;IAChF,SAAS,EAAE,SAAS,GAAG,MAAM,CAAC;CAC/B;AAQD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,yBAAyB,GAAG,qBAAqB,GAAG,SAAS,CAoB9G"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { and, eq, ne, not, sql } from 'drizzle-orm';
|
|
2
|
+
import { agents, nodes, workspaces } from '../db/schema.js';
|
|
3
|
+
import { AdmissionDeniedError } from '../lib/admissionError.js';
|
|
4
|
+
import { WEBHOOK_AGENT_NAME } from './messageMetadata.js';
|
|
5
|
+
/**
|
|
6
|
+
* How many of `action`'s kind the workspace currently has registered — the
|
|
7
|
+
* `registered` count a registration is admitted against.
|
|
8
|
+
*
|
|
9
|
+
* - `register_device`: every node a host enrolled, whatever its role or
|
|
10
|
+
* transport. Every agent also owns an implicit direct node
|
|
11
|
+
* (`node_direct_<agent id>`) for its own WebSocket; those are not devices.
|
|
12
|
+
* The prefix is reserved at enrollment (`createNodeToken`), so a host cannot
|
|
13
|
+
* enroll into it.
|
|
14
|
+
* - `register_agent`: non-human identities that have not been released, except
|
|
15
|
+
* the engine-provisioned inbound-webhook identity ({@link WEBHOOK_AGENT_NAME}).
|
|
16
|
+
* `createWebhook` provisions it on a workspace's first webhook; no caller
|
|
17
|
+
* registers it, and its name is reserved at registration
|
|
18
|
+
* (`assertRegistrableAgentName`), so it never holds a slot.
|
|
19
|
+
* - `register_human`: `type = 'human'` identities that have not been released.
|
|
20
|
+
*/
|
|
21
|
+
export async function countRegistrations(db, workspaceId, action) {
|
|
22
|
+
const count = sql `count(*)`;
|
|
23
|
+
const rows = action === 'register_device'
|
|
24
|
+
? await db
|
|
25
|
+
.select({ count })
|
|
26
|
+
.from(nodes)
|
|
27
|
+
.where(and(eq(nodes.workspaceId, workspaceId),
|
|
28
|
+
// GLOB, not LIKE: in LIKE `_` is a wildcard, so `nodeXdirectX…` would
|
|
29
|
+
// slip out of the count while passing the literal reserved-prefix check.
|
|
30
|
+
not(and(eq(nodes.role, 'direct'), sql `${nodes.id} GLOB 'node_direct_*'`))))
|
|
31
|
+
: await db
|
|
32
|
+
.select({ count })
|
|
33
|
+
.from(agents)
|
|
34
|
+
.where(and(eq(agents.workspaceId, workspaceId),
|
|
35
|
+
// Tombstoned identities (`RELEASED_AGENT_STATUS` in agent.ts) hold no slot.
|
|
36
|
+
ne(agents.status, 'released'), action === 'register_human'
|
|
37
|
+
? eq(agents.type, 'human')
|
|
38
|
+
: and(ne(agents.type, 'human'), ne(agents.name, WEBHOOK_AGENT_NAME))));
|
|
39
|
+
return Number(rows[0]?.count ?? 0);
|
|
40
|
+
}
|
|
41
|
+
async function resolveWorkspace(db, workspace) {
|
|
42
|
+
if (typeof workspace !== 'string')
|
|
43
|
+
return workspace;
|
|
44
|
+
const [row] = await db.select().from(workspaces).where(eq(workspaces.id, workspace));
|
|
45
|
+
return row ?? null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the admission check the registration paths run before inserting a
|
|
49
|
+
* node or agent, or `undefined` when the host has no `admit` hook (nothing to
|
|
50
|
+
* count for). A hook or count that throws admits the registration, like every
|
|
51
|
+
* other admission: a host's metering outage must not block registration.
|
|
52
|
+
*/
|
|
53
|
+
export function createRegistrationAdmission(args) {
|
|
54
|
+
const admit = args.admission?.admit;
|
|
55
|
+
if (!admit)
|
|
56
|
+
return undefined;
|
|
57
|
+
return async (action) => {
|
|
58
|
+
let denial;
|
|
59
|
+
try {
|
|
60
|
+
const workspace = await resolveWorkspace(args.db, args.workspace);
|
|
61
|
+
if (!workspace)
|
|
62
|
+
return;
|
|
63
|
+
const registered = await countRegistrations(args.db, workspace.id, action);
|
|
64
|
+
denial = await admit.call(args.admission, { workspace, action, registered });
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.warn('[admission] registration admission failed; admitting', {
|
|
68
|
+
action,
|
|
69
|
+
workspaceId: typeof args.workspace === 'string' ? args.workspace : args.workspace.id,
|
|
70
|
+
error: err instanceof Error ? err.message : String(err),
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (denial)
|
|
75
|
+
throw new AdmissionDeniedError(denial);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=registrationAdmission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registrationAdmission.js","sourceRoot":"","sources":["../../src/engine/registrationAdmission.ts"],"names":[],"mappings":"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"}
|
|
@@ -77,11 +77,11 @@ export declare function completeTaskInvocation(db: EngineDb, workspaceId: string
|
|
|
77
77
|
};
|
|
78
78
|
completed: {
|
|
79
79
|
error: string | null;
|
|
80
|
+
workspaceId: string;
|
|
80
81
|
id: string;
|
|
81
82
|
createdAt: Date;
|
|
82
83
|
output: unknown;
|
|
83
84
|
input: unknown;
|
|
84
|
-
workspaceId: string;
|
|
85
85
|
status: string;
|
|
86
86
|
nodeLivenessRevision: number;
|
|
87
87
|
handlerAgentId: string | null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { getDb } from '../db/index.js';
|
|
2
2
|
import type { FileStorage } from '../ports/files.js';
|
|
3
3
|
import type { WorkspaceProvenanceRecord } from '../db/schema.js';
|
|
4
|
+
import { type WorkspaceDrainOptions } from './workspaceDrain.js';
|
|
4
5
|
type Db = ReturnType<typeof getDb>;
|
|
5
6
|
type CreateWorkspaceOptions = string | {
|
|
6
7
|
ownerApiKey?: string;
|
|
@@ -186,7 +187,28 @@ export declare function drainFileCleanup(db: Db, storage: FileStorage, options?:
|
|
|
186
187
|
now?: Date;
|
|
187
188
|
limit?: number;
|
|
188
189
|
}): Promise<number>;
|
|
189
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Delete a workspace.
|
|
192
|
+
*
|
|
193
|
+
* A workspace small enough to delete in one transaction keeps the atomic
|
|
194
|
+
* contract: everything is removed (or nothing, on failure) before this
|
|
195
|
+
* returns. A larger one would hold the shared database for the whole cascade,
|
|
196
|
+
* so it is tombstoned instead: the existing expiry deadline is pulled forward
|
|
197
|
+
* to now, which every credential path already rejects (`workspace_expired`),
|
|
198
|
+
* and its data is removed by bounded drains (`reapWorkspace`, the reaper).
|
|
199
|
+
* Returns `deferred: true` when data removal continues in the background.
|
|
200
|
+
*/
|
|
201
|
+
export declare function deleteWorkspace(db: Db, storage: FileStorage, workspaceId: string, options?: {
|
|
202
|
+
now?: Date;
|
|
203
|
+
}): Promise<{
|
|
204
|
+
deferred: boolean;
|
|
205
|
+
}>;
|
|
206
|
+
/**
|
|
207
|
+
* Drain one workspace within a time budget (only when it is too large to
|
|
208
|
+
* delete atomically), then delete it once its bulk rows are gone. Returns true when the workspace row was deleted. Safe to call
|
|
209
|
+
* repeatedly and concurrently with the reaper: every step is idempotent.
|
|
210
|
+
*/
|
|
211
|
+
export declare function reapWorkspace(db: Db, storage: FileStorage, workspaceId: string, options?: WorkspaceDrainOptions): Promise<boolean>;
|
|
190
212
|
/**
|
|
191
213
|
* Delete a bounded batch of workspaces whose callers explicitly opted into an
|
|
192
214
|
* expiry deadline at creation. No inferred signal (name, age, agents, or
|
|
@@ -195,6 +217,9 @@ export declare function deleteWorkspace(db: Db, storage: FileStorage, workspaceI
|
|
|
195
217
|
export declare function reapExpiredWorkspaces(db: Db, storage: FileStorage, options?: {
|
|
196
218
|
now?: Date;
|
|
197
219
|
limit?: number;
|
|
220
|
+
} & Omit<WorkspaceDrainOptions, 'deadline' | 'now'> & {
|
|
221
|
+
/** Wall clock for the drain budget (ms); `now` above is only the expiry cutoff. */
|
|
222
|
+
clock?: () => number;
|
|
198
223
|
}): Promise<string[]>;
|
|
199
224
|
export {};
|
|
200
225
|
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/engine/workspace.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/engine/workspace.ts"],"names":[],"mappings":"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"}
|