@masons/agent-network 0.5.31 → 0.5.321
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/broker/broker-daemon.d.ts +23 -0
- package/dist/broker/broker-daemon.d.ts.map +1 -1
- package/dist/broker/broker-daemon.js +291 -3
- package/dist/broker/claude-code-spawn-driver.d.ts +14 -0
- package/dist/broker/claude-code-spawn-driver.d.ts.map +1 -0
- package/dist/broker/claude-code-spawn-driver.js +39 -0
- package/dist/broker/codex-spawn-driver-stub.d.ts +7 -0
- package/dist/broker/codex-spawn-driver-stub.d.ts.map +1 -0
- package/dist/broker/codex-spawn-driver-stub.js +13 -0
- package/dist/broker/control-event-dispatcher.d.ts +1 -0
- package/dist/broker/control-event-dispatcher.d.ts.map +1 -1
- package/dist/broker/control-event-types.d.ts +11 -13
- package/dist/broker/control-event-types.d.ts.map +1 -1
- package/dist/broker/entry.d.ts.map +1 -1
- package/dist/broker/entry.js +41 -0
- package/dist/broker/ipc-server.d.ts +7 -0
- package/dist/broker/ipc-server.d.ts.map +1 -1
- package/dist/broker/ipc-server.js +20 -0
- package/dist/broker/network-presence.d.ts +31 -0
- package/dist/broker/network-presence.d.ts.map +1 -0
- package/dist/broker/network-presence.js +109 -0
- package/dist/broker/received-message-correlation-cache.d.ts +23 -0
- package/dist/broker/received-message-correlation-cache.d.ts.map +1 -0
- package/dist/broker/received-message-correlation-cache.js +114 -0
- package/dist/broker/services-event-client.d.ts +21 -0
- package/dist/broker/services-event-client.d.ts.map +1 -0
- package/dist/broker/services-event-client.js +221 -0
- package/dist/broker/spawn-correlation.d.ts +28 -0
- package/dist/broker/spawn-correlation.d.ts.map +1 -0
- package/dist/broker/spawn-correlation.js +77 -0
- package/dist/broker/spawn-driver.d.ts +27 -0
- package/dist/broker/spawn-driver.d.ts.map +1 -0
- package/dist/broker/spawn-driver.js +15 -0
- package/dist/broker/task-hint-handler.d.ts +21 -0
- package/dist/broker/task-hint-handler.d.ts.map +1 -0
- package/dist/broker/task-hint-handler.js +33 -0
- package/dist/broker/transition-state-retry-queue.d.ts +20 -0
- package/dist/broker/transition-state-retry-queue.d.ts.map +1 -0
- package/dist/broker/transition-state-retry-queue.js +48 -0
- package/dist/broker-client/broker-client.d.ts +1 -0
- package/dist/broker-client/broker-client.d.ts.map +1 -1
- package/dist/broker-client/broker-client.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
package/dist/broker/entry.js
CHANGED
|
@@ -4,9 +4,13 @@ import { readConfig } from "../config-fs.js";
|
|
|
4
4
|
import { DEFAULT_API_HOST } from "../platform-client.js";
|
|
5
5
|
import { heartbeatRuntimeEndpoint, registerRuntimeEndpoint, transitionRuntimeEndpointState, unregisterRuntimeEndpoint, } from "../runtime-endpoint-client.js";
|
|
6
6
|
import { startBrokerDaemon } from "./broker-daemon.js";
|
|
7
|
+
import { ClaudeCodeSpawnDriver } from "./claude-code-spawn-driver.js";
|
|
8
|
+
import { CodexSpawnDriverStub } from "./codex-spawn-driver-stub.js";
|
|
7
9
|
import { ConnectorWS } from "./connector-ws.js";
|
|
8
10
|
import { createBrokerLogger } from "./logger.js";
|
|
9
11
|
import { resolveBrokerPaths } from "./paths.js";
|
|
12
|
+
import { createServicesEventClient } from "./services-event-client.js";
|
|
13
|
+
import { SpawnDriverRegistry } from "./spawn-driver.js";
|
|
10
14
|
const DEFAULT_ACCOUNT_ID = "default";
|
|
11
15
|
export async function resolveCredentialsFromFile(filePath, accountId, envApiHost) {
|
|
12
16
|
let raw;
|
|
@@ -159,12 +163,49 @@ export async function main() {
|
|
|
159
163
|
token: creds.token,
|
|
160
164
|
});
|
|
161
165
|
const apiPort = buildApiPort(creds.apiHost, creds.token, logger);
|
|
166
|
+
const spawnDriverRegistry = new SpawnDriverRegistry();
|
|
167
|
+
spawnDriverRegistry.register(new ClaudeCodeSpawnDriver());
|
|
168
|
+
spawnDriverRegistry.register(new CodexSpawnDriverStub());
|
|
169
|
+
const postControlAck = async (ack) => {
|
|
170
|
+
const url = `https://${creds.apiHost.replace(/^https?:\/\//, "").replace(/\/+$/, "")}/v1/runtime/control-ack`;
|
|
171
|
+
try {
|
|
172
|
+
const res = await fetch(url, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: {
|
|
175
|
+
Authorization: `Bearer ${creds.token}`,
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
},
|
|
178
|
+
body: JSON.stringify(ack),
|
|
179
|
+
});
|
|
180
|
+
if (!res.ok) {
|
|
181
|
+
logger.warn("post_control_ack_non_ok", {
|
|
182
|
+
status: res.status,
|
|
183
|
+
idempotency_key: ack.idempotency_key,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
logger.warn("post_control_ack_error", {
|
|
189
|
+
err: err instanceof Error ? err.message : String(err),
|
|
190
|
+
idempotency_key: ack.idempotency_key,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
162
194
|
const broker = await startBrokerDaemon({
|
|
163
195
|
paths,
|
|
164
196
|
asNodeId,
|
|
165
197
|
connector,
|
|
166
198
|
apiPort,
|
|
167
199
|
logger,
|
|
200
|
+
spawnDriverRegistry,
|
|
201
|
+
postControlAck,
|
|
202
|
+
servicesEventClientFactory: ({ dispatcher, logger: clientLogger }) => createServicesEventClient({
|
|
203
|
+
apiHost: creds.apiHost,
|
|
204
|
+
runtimeKey: creds.token,
|
|
205
|
+
agentId: creds.accountId,
|
|
206
|
+
dispatcher,
|
|
207
|
+
logger: clientLogger,
|
|
208
|
+
}),
|
|
168
209
|
});
|
|
169
210
|
let shuttingDown = false;
|
|
170
211
|
const handleSignal = (sig) => async () => {
|
|
@@ -8,6 +8,7 @@ export interface IPCServerHandlers {
|
|
|
8
8
|
reattachEndpoint(endpoint_id: string, plugin_pid: number, ipcWs: WebSocket): Promise<ReattachResponse>;
|
|
9
9
|
listUndispatched(): Promise<UndispatchedListResponse>;
|
|
10
10
|
dispatch(undispatched_id: string, target_endpoint_id: string): Promise<void>;
|
|
11
|
+
setTaskHint(body: TaskHintBody): Promise<void>;
|
|
11
12
|
}
|
|
12
13
|
export interface ReattachResponse {
|
|
13
14
|
restored: true;
|
|
@@ -23,6 +24,12 @@ export interface RegisterEndpointBody {
|
|
|
23
24
|
tracking_ref?: string;
|
|
24
25
|
session_name?: string;
|
|
25
26
|
task_hint?: string;
|
|
27
|
+
spawn_token?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface TaskHintBody {
|
|
30
|
+
endpoint_id: string;
|
|
31
|
+
plugin_pid: number;
|
|
32
|
+
task_hint: string;
|
|
26
33
|
}
|
|
27
34
|
export interface RegisterEndpointResponse {
|
|
28
35
|
endpoint_id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ipc-server.d.ts","sourceRoot":"","sources":["../../src/broker/ipc-server.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,KAAK,OAAO,EAAE,SAAS,EAAmB,MAAM,IAAI,CAAC;AAE9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQhD,MAAM,WAAW,iBAAiB;IAEhC,gBAAgB,CACd,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAErC,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtD,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvD,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAG5C,gBAAgB,CACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAE7B,gBAAgB,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAItD,QAAQ,CAAC,eAAe,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ipc-server.d.ts","sourceRoot":"","sources":["../../src/broker/ipc-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -137,6 +137,26 @@ async function routeHttp(req, res, bearerToken, handlers, logger) {
|
|
|
137
137
|
sendJson(res, 200, { ok: true });
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
+
const taskHintMatch = /^\/v1\/endpoint\/([^/]+)\/task-hint$/.exec(url);
|
|
141
|
+
if (method === "POST" && taskHintMatch && taskHintMatch[1]) {
|
|
142
|
+
const endpointId = decodeURIComponent(taskHintMatch[1]);
|
|
143
|
+
const body = await readJson(req);
|
|
144
|
+
const pid = typeof body.plugin_pid === "number" ? body.plugin_pid : Number.NaN;
|
|
145
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
146
|
+
throw new BrokerHttpError(400, "plugin_pid_invalid", "plugin_pid is required and must be a positive number");
|
|
147
|
+
}
|
|
148
|
+
const hint = typeof body.task_hint === "string" ? body.task_hint : undefined;
|
|
149
|
+
if (hint === undefined) {
|
|
150
|
+
throw new BrokerHttpError(400, "task_hint_invalid", "task_hint is required and must be a string");
|
|
151
|
+
}
|
|
152
|
+
await handlers.setTaskHint({
|
|
153
|
+
endpoint_id: endpointId,
|
|
154
|
+
plugin_pid: pid,
|
|
155
|
+
task_hint: hint,
|
|
156
|
+
});
|
|
157
|
+
sendJson(res, 200, { ok: true });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
140
160
|
sendJson(res, 404, { error: "not_found", path: url });
|
|
141
161
|
}
|
|
142
162
|
catch (err) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const DEFAULT_PRESENCE_GRACE_MS: number;
|
|
2
|
+
export type NetworkPresence = "online" | "reconnecting" | "offline";
|
|
3
|
+
export type PresenceEvent = {
|
|
4
|
+
type: "connector_connected";
|
|
5
|
+
} | {
|
|
6
|
+
type: "connector_disconnected";
|
|
7
|
+
} | {
|
|
8
|
+
type: "presence_grace_expired";
|
|
9
|
+
} | {
|
|
10
|
+
type: "shutdown";
|
|
11
|
+
};
|
|
12
|
+
export type PresenceEffect = {
|
|
13
|
+
type: "start_grace_timer";
|
|
14
|
+
deadline_ms: number;
|
|
15
|
+
} | {
|
|
16
|
+
type: "cancel_grace_timer";
|
|
17
|
+
} | {
|
|
18
|
+
type: "emit_presence";
|
|
19
|
+
presence: NetworkPresence;
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
export interface PresenceTransitionResult {
|
|
23
|
+
next: NetworkPresence;
|
|
24
|
+
effects: readonly PresenceEffect[];
|
|
25
|
+
ignored?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface PresenceTransitionOptions {
|
|
28
|
+
graceMs?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function transitionPresence(current: NetworkPresence, event: PresenceEvent, opts?: PresenceTransitionOptions): PresenceTransitionResult;
|
|
31
|
+
//# sourceMappingURL=network-presence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-presence.d.ts","sourceRoot":"","sources":["../../src/broker/network-presence.ts"],"names":[],"mappings":"AA8BA,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,cAAc,GAAG,SAAS,CAAC;AAEpE,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,qBAAqB,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,wBAAwB,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,wBAAwB,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,oBAAoB,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzE,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAOD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,aAAa,EACpB,IAAI,GAAE,yBAA8B,GACnC,wBAAwB,CAgH1B"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export const DEFAULT_PRESENCE_GRACE_MS = 10 * 60 * 1000;
|
|
2
|
+
export function transitionPresence(current, event, opts = {}) {
|
|
3
|
+
const graceMs = opts.graceMs ?? DEFAULT_PRESENCE_GRACE_MS;
|
|
4
|
+
switch (current) {
|
|
5
|
+
case "offline": {
|
|
6
|
+
switch (event.type) {
|
|
7
|
+
case "connector_connected":
|
|
8
|
+
return {
|
|
9
|
+
next: "online",
|
|
10
|
+
effects: [
|
|
11
|
+
{
|
|
12
|
+
type: "emit_presence",
|
|
13
|
+
presence: "online",
|
|
14
|
+
reason: "connector_connected",
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
case "shutdown":
|
|
19
|
+
return {
|
|
20
|
+
next: "offline",
|
|
21
|
+
effects: [
|
|
22
|
+
{
|
|
23
|
+
type: "emit_presence",
|
|
24
|
+
presence: "offline",
|
|
25
|
+
reason: "broker_shutdown",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
case "connector_disconnected":
|
|
30
|
+
case "presence_grace_expired":
|
|
31
|
+
return { next: "offline", effects: [], ignored: true };
|
|
32
|
+
}
|
|
33
|
+
return { next: current, effects: [], ignored: true };
|
|
34
|
+
}
|
|
35
|
+
case "online": {
|
|
36
|
+
switch (event.type) {
|
|
37
|
+
case "connector_disconnected":
|
|
38
|
+
return {
|
|
39
|
+
next: "reconnecting",
|
|
40
|
+
effects: [
|
|
41
|
+
{ type: "start_grace_timer", deadline_ms: graceMs },
|
|
42
|
+
{
|
|
43
|
+
type: "emit_presence",
|
|
44
|
+
presence: "reconnecting",
|
|
45
|
+
reason: "connector_disconnected",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
case "shutdown":
|
|
50
|
+
return {
|
|
51
|
+
next: "offline",
|
|
52
|
+
effects: [
|
|
53
|
+
{
|
|
54
|
+
type: "emit_presence",
|
|
55
|
+
presence: "offline",
|
|
56
|
+
reason: "broker_shutdown",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
case "connector_connected":
|
|
61
|
+
case "presence_grace_expired":
|
|
62
|
+
return { next: "online", effects: [], ignored: true };
|
|
63
|
+
}
|
|
64
|
+
return { next: current, effects: [], ignored: true };
|
|
65
|
+
}
|
|
66
|
+
case "reconnecting": {
|
|
67
|
+
switch (event.type) {
|
|
68
|
+
case "connector_connected":
|
|
69
|
+
return {
|
|
70
|
+
next: "online",
|
|
71
|
+
effects: [
|
|
72
|
+
{ type: "cancel_grace_timer" },
|
|
73
|
+
{
|
|
74
|
+
type: "emit_presence",
|
|
75
|
+
presence: "online",
|
|
76
|
+
reason: "connector_reconnected",
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
case "presence_grace_expired":
|
|
81
|
+
return {
|
|
82
|
+
next: "offline",
|
|
83
|
+
effects: [
|
|
84
|
+
{
|
|
85
|
+
type: "emit_presence",
|
|
86
|
+
presence: "offline",
|
|
87
|
+
reason: "grace_timeout",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
case "shutdown":
|
|
92
|
+
return {
|
|
93
|
+
next: "offline",
|
|
94
|
+
effects: [
|
|
95
|
+
{ type: "cancel_grace_timer" },
|
|
96
|
+
{
|
|
97
|
+
type: "emit_presence",
|
|
98
|
+
presence: "offline",
|
|
99
|
+
reason: "broker_shutdown",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
case "connector_disconnected":
|
|
104
|
+
return { next: "reconnecting", effects: [], ignored: true };
|
|
105
|
+
}
|
|
106
|
+
return { next: current, effects: [], ignored: true };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const DEFAULT_TTL_MS = 86400000;
|
|
2
|
+
export declare const DEFAULT_CAP_PER_ENDPOINT = 1000;
|
|
3
|
+
export interface ReceivedMessageCorrelationCacheOptions {
|
|
4
|
+
ttlMs?: number;
|
|
5
|
+
capPerEndpoint?: number;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
}
|
|
8
|
+
export type ReplyAwareResolve = {
|
|
9
|
+
ok: true;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
reason: "unknown_correlation_id" | "wrong_endpoint" | "ttl_expired";
|
|
13
|
+
};
|
|
14
|
+
export interface ReceivedMessageCorrelationCache {
|
|
15
|
+
record(endpoint_id: string, correlation_id: string): void;
|
|
16
|
+
resolve(endpoint_id: string, correlation_id: string): ReplyAwareResolve;
|
|
17
|
+
size(): number;
|
|
18
|
+
sizeForEndpoint(endpoint_id: string): number;
|
|
19
|
+
sweep(now?: number): number;
|
|
20
|
+
forgetEndpoint(endpoint_id: string): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function createReceivedMessageCorrelationCache(opts?: ReceivedMessageCorrelationCacheOptions): ReceivedMessageCorrelationCache;
|
|
23
|
+
//# sourceMappingURL=received-message-correlation-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"received-message-correlation-cache.d.ts","sourceRoot":"","sources":["../../src/broker/received-message-correlation-cache.ts"],"names":[],"mappings":"AA8DA,eAAO,MAAM,cAAc,WAAa,CAAC;AAGzC,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,MAAM,WAAW,sCAAsC;IAErD,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,wBAAwB,GAAG,gBAAgB,GAAG,aAAa,CAAC;CACrE,CAAC;AAMN,MAAM,WAAW,+BAA+B;IAa9C,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAQ1D,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,iBAAiB,CAAC;IAGxE,IAAI,IAAI,MAAM,CAAC;IAGf,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAQ7C,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAM5B,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,wBAAgB,qCAAqC,CACnD,IAAI,GAAE,sCAA2C,GAChD,+BAA+B,CAgIjC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export const DEFAULT_TTL_MS = 86_400_000;
|
|
2
|
+
export const DEFAULT_CAP_PER_ENDPOINT = 1000;
|
|
3
|
+
export function createReceivedMessageCorrelationCache(opts = {}) {
|
|
4
|
+
const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
5
|
+
const cap = opts.capPerEndpoint ?? DEFAULT_CAP_PER_ENDPOINT;
|
|
6
|
+
const clock = opts.now ?? Date.now;
|
|
7
|
+
if (ttlMs <= 0)
|
|
8
|
+
throw new Error(`ttlMs must be positive: ${ttlMs}`);
|
|
9
|
+
if (cap <= 0)
|
|
10
|
+
throw new Error(`capPerEndpoint must be positive: ${cap}`);
|
|
11
|
+
const perEndpoint = new Map();
|
|
12
|
+
const correlationOwner = new Map();
|
|
13
|
+
const evict = (endpoint_id, correlation_id) => {
|
|
14
|
+
const inner = perEndpoint.get(endpoint_id);
|
|
15
|
+
if (!inner)
|
|
16
|
+
return;
|
|
17
|
+
inner.delete(correlation_id);
|
|
18
|
+
if (inner.size === 0)
|
|
19
|
+
perEndpoint.delete(endpoint_id);
|
|
20
|
+
if (correlationOwner.get(correlation_id) === endpoint_id) {
|
|
21
|
+
correlationOwner.delete(correlation_id);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
record(endpoint_id, correlation_id) {
|
|
26
|
+
const priorOwner = correlationOwner.get(correlation_id);
|
|
27
|
+
if (priorOwner && priorOwner !== endpoint_id) {
|
|
28
|
+
const priorInner = perEndpoint.get(priorOwner);
|
|
29
|
+
priorInner?.delete(correlation_id);
|
|
30
|
+
if (priorInner && priorInner.size === 0) {
|
|
31
|
+
perEndpoint.delete(priorOwner);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let inner = perEndpoint.get(endpoint_id);
|
|
35
|
+
if (!inner) {
|
|
36
|
+
inner = new Map();
|
|
37
|
+
perEndpoint.set(endpoint_id, inner);
|
|
38
|
+
}
|
|
39
|
+
inner.delete(correlation_id);
|
|
40
|
+
inner.set(correlation_id, { received_at: clock() });
|
|
41
|
+
correlationOwner.set(correlation_id, endpoint_id);
|
|
42
|
+
while (inner.size > cap) {
|
|
43
|
+
const oldestKey = inner.keys().next().value;
|
|
44
|
+
if (oldestKey === undefined)
|
|
45
|
+
break;
|
|
46
|
+
inner.delete(oldestKey);
|
|
47
|
+
if (correlationOwner.get(oldestKey) === endpoint_id) {
|
|
48
|
+
correlationOwner.delete(oldestKey);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
resolve(endpoint_id, correlation_id) {
|
|
53
|
+
const owner = correlationOwner.get(correlation_id);
|
|
54
|
+
if (!owner) {
|
|
55
|
+
return { ok: false, reason: "unknown_correlation_id" };
|
|
56
|
+
}
|
|
57
|
+
if (owner !== endpoint_id) {
|
|
58
|
+
return { ok: false, reason: "wrong_endpoint" };
|
|
59
|
+
}
|
|
60
|
+
const inner = perEndpoint.get(endpoint_id);
|
|
61
|
+
const entry = inner?.get(correlation_id);
|
|
62
|
+
if (!entry) {
|
|
63
|
+
correlationOwner.delete(correlation_id);
|
|
64
|
+
return { ok: false, reason: "unknown_correlation_id" };
|
|
65
|
+
}
|
|
66
|
+
if (clock() - entry.received_at >= ttlMs) {
|
|
67
|
+
evict(endpoint_id, correlation_id);
|
|
68
|
+
return { ok: false, reason: "ttl_expired" };
|
|
69
|
+
}
|
|
70
|
+
return { ok: true };
|
|
71
|
+
},
|
|
72
|
+
size() {
|
|
73
|
+
let total = 0;
|
|
74
|
+
for (const inner of perEndpoint.values())
|
|
75
|
+
total += inner.size;
|
|
76
|
+
return total;
|
|
77
|
+
},
|
|
78
|
+
sizeForEndpoint(endpoint_id) {
|
|
79
|
+
return perEndpoint.get(endpoint_id)?.size ?? 0;
|
|
80
|
+
},
|
|
81
|
+
sweep(now = clock()) {
|
|
82
|
+
let evicted = 0;
|
|
83
|
+
for (const [endpoint_id, inner] of perEndpoint) {
|
|
84
|
+
const expired = [];
|
|
85
|
+
for (const [correlation_id, entry] of inner) {
|
|
86
|
+
if (now - entry.received_at >= ttlMs) {
|
|
87
|
+
expired.push(correlation_id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const correlation_id of expired) {
|
|
91
|
+
inner.delete(correlation_id);
|
|
92
|
+
if (correlationOwner.get(correlation_id) === endpoint_id) {
|
|
93
|
+
correlationOwner.delete(correlation_id);
|
|
94
|
+
}
|
|
95
|
+
evicted++;
|
|
96
|
+
}
|
|
97
|
+
if (inner.size === 0)
|
|
98
|
+
perEndpoint.delete(endpoint_id);
|
|
99
|
+
}
|
|
100
|
+
return evicted;
|
|
101
|
+
},
|
|
102
|
+
forgetEndpoint(endpoint_id) {
|
|
103
|
+
const inner = perEndpoint.get(endpoint_id);
|
|
104
|
+
if (!inner)
|
|
105
|
+
return;
|
|
106
|
+
for (const correlation_id of inner.keys()) {
|
|
107
|
+
if (correlationOwner.get(correlation_id) === endpoint_id) {
|
|
108
|
+
correlationOwner.delete(correlation_id);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
perEndpoint.delete(endpoint_id);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ControlEventDispatcher } from "./control-event-dispatcher.js";
|
|
2
|
+
import type { BrokerLogger } from "./logger.js";
|
|
3
|
+
export declare const DEFAULT_BACKOFF_INITIAL_MS = 1000;
|
|
4
|
+
export declare const DEFAULT_BACKOFF_MAX_MS = 30000;
|
|
5
|
+
export interface ServicesEventClientOptions {
|
|
6
|
+
apiHost: string;
|
|
7
|
+
runtimeKey: string;
|
|
8
|
+
agentId: string;
|
|
9
|
+
dispatcher: ControlEventDispatcher;
|
|
10
|
+
logger: BrokerLogger;
|
|
11
|
+
fetchImpl?: typeof globalThis.fetch;
|
|
12
|
+
backoffInitialMs?: number;
|
|
13
|
+
backoffMaxMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ServicesEventClient {
|
|
16
|
+
start(): Promise<void>;
|
|
17
|
+
stop(): Promise<void>;
|
|
18
|
+
lastSeenKey(): string | undefined;
|
|
19
|
+
}
|
|
20
|
+
export declare function createServicesEventClient(opts: ServicesEventClientOptions): ServicesEventClient;
|
|
21
|
+
//# sourceMappingURL=services-event-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services-event-client.d.ts","sourceRoot":"","sources":["../../src/broker/services-event-client.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EACV,sBAAsB,EAEvB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,0BAA0B,OAAQ,CAAC;AAChD,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,MAAM,WAAW,0BAA0B;IAEzC,OAAO,EAAE,MAAM,CAAC;IAEhB,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,EAAE,MAAM,CAAC;IAEhB,UAAU,EAAE,sBAAsB,CAAC;IAEnC,MAAM,EAAE,YAAY,CAAC;IAErB,SAAS,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAEpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IASlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB,WAAW,IAAI,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,0BAA0B,GAC/B,mBAAmB,CAuMrB"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
export const DEFAULT_BACKOFF_INITIAL_MS = 1_000;
|
|
2
|
+
export const DEFAULT_BACKOFF_MAX_MS = 30_000;
|
|
3
|
+
export function createServicesEventClient(opts) {
|
|
4
|
+
const { dispatcher, logger } = opts;
|
|
5
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
6
|
+
const backoffInitial = opts.backoffInitialMs ?? DEFAULT_BACKOFF_INITIAL_MS;
|
|
7
|
+
const backoffMax = opts.backoffMaxMs ?? DEFAULT_BACKOFF_MAX_MS;
|
|
8
|
+
let stopped = false;
|
|
9
|
+
let lastEventId;
|
|
10
|
+
let currentAbort = null;
|
|
11
|
+
let backoffMs = backoffInitial;
|
|
12
|
+
let loopPromise = null;
|
|
13
|
+
let started = false;
|
|
14
|
+
const baseUrl = makeBaseUrl(opts.apiHost);
|
|
15
|
+
const subscribeOnce = async () => {
|
|
16
|
+
const ctrl = new AbortController();
|
|
17
|
+
currentAbort = ctrl;
|
|
18
|
+
const url = new URL(`${baseUrl}/runtime/control-events`);
|
|
19
|
+
url.searchParams.set("agent_id", opts.agentId);
|
|
20
|
+
const headers = {
|
|
21
|
+
Authorization: `Bearer ${opts.runtimeKey}`,
|
|
22
|
+
Accept: "text/event-stream",
|
|
23
|
+
};
|
|
24
|
+
if (lastEventId)
|
|
25
|
+
headers["Last-Event-ID"] = lastEventId;
|
|
26
|
+
const res = await fetchImpl(url.toString(), {
|
|
27
|
+
method: "GET",
|
|
28
|
+
headers,
|
|
29
|
+
signal: ctrl.signal,
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok || !res.body) {
|
|
32
|
+
throw new Error(`SSE subscribe failed: ${res.status}`);
|
|
33
|
+
}
|
|
34
|
+
backoffMs = backoffInitial;
|
|
35
|
+
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
36
|
+
let buffer = "";
|
|
37
|
+
while (true) {
|
|
38
|
+
const { done, value } = await reader.read();
|
|
39
|
+
if (done)
|
|
40
|
+
break;
|
|
41
|
+
buffer += value.replace(/\r\n?/g, "\n");
|
|
42
|
+
let sep = buffer.indexOf("\n\n");
|
|
43
|
+
while (sep >= 0) {
|
|
44
|
+
const frame = buffer.slice(0, sep);
|
|
45
|
+
buffer = buffer.slice(sep + 2);
|
|
46
|
+
await handleFrame(frame).catch((err) => {
|
|
47
|
+
logger.warn("sse_frame_error", {
|
|
48
|
+
err: err instanceof Error ? err.message : String(err),
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
sep = buffer.indexOf("\n\n");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const handleFrame = async (frame) => {
|
|
56
|
+
let id;
|
|
57
|
+
const dataLines = [];
|
|
58
|
+
for (const line of frame.split("\n")) {
|
|
59
|
+
if (line === "" || line.startsWith(":"))
|
|
60
|
+
continue;
|
|
61
|
+
const colon = line.indexOf(":");
|
|
62
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
63
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
64
|
+
if (value.startsWith(" "))
|
|
65
|
+
value = value.slice(1);
|
|
66
|
+
if (field === "id")
|
|
67
|
+
id = value;
|
|
68
|
+
else if (field === "data")
|
|
69
|
+
dataLines.push(value);
|
|
70
|
+
}
|
|
71
|
+
if (dataLines.length === 0)
|
|
72
|
+
return;
|
|
73
|
+
const data = dataLines.join("\n");
|
|
74
|
+
if (id)
|
|
75
|
+
lastEventId = id;
|
|
76
|
+
let parsed;
|
|
77
|
+
try {
|
|
78
|
+
parsed = JSON.parse(data);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
logger.warn("sse_frame_unparseable", { data: data.slice(0, 80) });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (hasIdempotencyKey(parsed) && hasUnsupportedVersion(parsed)) {
|
|
85
|
+
await postAck({
|
|
86
|
+
idempotency_key: parsed.idempotency_key,
|
|
87
|
+
status: "failed",
|
|
88
|
+
detail: "unsupported_protocol_version",
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!isControlEvent(parsed)) {
|
|
93
|
+
if (hasIdempotencyKey(parsed)) {
|
|
94
|
+
await postAck({
|
|
95
|
+
idempotency_key: parsed.idempotency_key,
|
|
96
|
+
status: "applied",
|
|
97
|
+
detail: "unknown_variant_dropped",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const outcome = await dispatcher.dispatch(parsed);
|
|
103
|
+
await postAck(outcomeToAck(parsed.idempotency_key, outcome));
|
|
104
|
+
};
|
|
105
|
+
const postAck = async (ack) => {
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetchImpl(`${baseUrl}/runtime/control-ack`, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: {
|
|
110
|
+
Authorization: `Bearer ${opts.runtimeKey}`,
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify(ack),
|
|
114
|
+
});
|
|
115
|
+
if (!res.ok) {
|
|
116
|
+
logger.warn("ack_post_failed", {
|
|
117
|
+
status: res.status,
|
|
118
|
+
idempotency_key: ack.idempotency_key,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
logger.warn("ack_post_error", {
|
|
124
|
+
err: err instanceof Error ? err.message : String(err),
|
|
125
|
+
idempotency_key: ack.idempotency_key,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const loop = async () => {
|
|
130
|
+
while (!stopped) {
|
|
131
|
+
try {
|
|
132
|
+
await subscribeOnce();
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (stopped)
|
|
136
|
+
return;
|
|
137
|
+
logger.warn("sse_reconnect", {
|
|
138
|
+
backoff_ms: backoffMs,
|
|
139
|
+
err: err instanceof Error ? err.message : String(err),
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (stopped)
|
|
143
|
+
return;
|
|
144
|
+
await sleep(backoffMs);
|
|
145
|
+
backoffMs = Math.min(backoffMs * 2, backoffMax);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
async start() {
|
|
150
|
+
if (started)
|
|
151
|
+
return;
|
|
152
|
+
started = true;
|
|
153
|
+
stopped = false;
|
|
154
|
+
loopPromise = loop();
|
|
155
|
+
},
|
|
156
|
+
async stop() {
|
|
157
|
+
stopped = true;
|
|
158
|
+
if (currentAbort) {
|
|
159
|
+
try {
|
|
160
|
+
currentAbort.abort();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (loopPromise) {
|
|
166
|
+
await loopPromise.catch(() => { });
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
lastSeenKey() {
|
|
170
|
+
return lastEventId;
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function outcomeToAck(idempotency_key, outcome) {
|
|
175
|
+
if (outcome.ok) {
|
|
176
|
+
if (outcome.ack_hint === "received") {
|
|
177
|
+
return { idempotency_key, status: "received" };
|
|
178
|
+
}
|
|
179
|
+
return { idempotency_key, status: "applied" };
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
idempotency_key,
|
|
183
|
+
status: "failed",
|
|
184
|
+
detail: outcome.detail,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function isControlEvent(value) {
|
|
188
|
+
if (typeof value !== "object" || value === null)
|
|
189
|
+
return false;
|
|
190
|
+
const v = value;
|
|
191
|
+
if (v.version !== 1)
|
|
192
|
+
return false;
|
|
193
|
+
if (typeof v.idempotency_key !== "string")
|
|
194
|
+
return false;
|
|
195
|
+
if (typeof v.emitted_at !== "number")
|
|
196
|
+
return false;
|
|
197
|
+
if (typeof v.type !== "string")
|
|
198
|
+
return false;
|
|
199
|
+
return (v.type === "dispatch_undispatched" ||
|
|
200
|
+
v.type === "spawn_request" ||
|
|
201
|
+
v.type === "force_unregister");
|
|
202
|
+
}
|
|
203
|
+
function hasIdempotencyKey(value) {
|
|
204
|
+
return (typeof value === "object" &&
|
|
205
|
+
value !== null &&
|
|
206
|
+
typeof value.idempotency_key === "string");
|
|
207
|
+
}
|
|
208
|
+
function hasUnsupportedVersion(value) {
|
|
209
|
+
if (typeof value !== "object" || value === null)
|
|
210
|
+
return false;
|
|
211
|
+
const v = value;
|
|
212
|
+
return typeof v.version === "number" && v.version !== 1;
|
|
213
|
+
}
|
|
214
|
+
function makeBaseUrl(apiHost) {
|
|
215
|
+
const trimmed = apiHost.replace(/\/+$/, "");
|
|
216
|
+
const origin = /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
217
|
+
return `${origin}/v1`;
|
|
218
|
+
}
|
|
219
|
+
function sleep(ms) {
|
|
220
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
221
|
+
}
|