@masons/agent-network 0.5.30 → 0.5.32
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 +20 -0
- package/dist/broker/broker-daemon.d.ts.map +1 -1
- package/dist/broker/broker-daemon.js +243 -2
- 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 +8 -0
- package/dist/broker/entry.d.ts.map +1 -1
- package/dist/broker/entry.js +108 -2
- 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/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.js +1 -1
- package/package.json +3 -2
package/dist/broker/entry.js
CHANGED
|
@@ -1,13 +1,74 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { pathToFileURL } from "node:url";
|
|
2
3
|
import { readConfig } from "../config-fs.js";
|
|
3
4
|
import { DEFAULT_API_HOST } from "../platform-client.js";
|
|
4
5
|
import { heartbeatRuntimeEndpoint, registerRuntimeEndpoint, transitionRuntimeEndpointState, unregisterRuntimeEndpoint, } from "../runtime-endpoint-client.js";
|
|
5
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";
|
|
6
9
|
import { ConnectorWS } from "./connector-ws.js";
|
|
7
10
|
import { createBrokerLogger } from "./logger.js";
|
|
8
11
|
import { resolveBrokerPaths } from "./paths.js";
|
|
12
|
+
import { createServicesEventClient } from "./services-event-client.js";
|
|
13
|
+
import { SpawnDriverRegistry } from "./spawn-driver.js";
|
|
9
14
|
const DEFAULT_ACCOUNT_ID = "default";
|
|
10
|
-
async function
|
|
15
|
+
export async function resolveCredentialsFromFile(filePath, accountId, envApiHost) {
|
|
16
|
+
let raw;
|
|
17
|
+
try {
|
|
18
|
+
raw = await readFile(filePath, "utf8");
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
const code = err.code;
|
|
22
|
+
if (code === "ENOENT") {
|
|
23
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE points to non-existent path: ${filePath}`);
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`failed to read MASONS_BROKER_CREDENTIALS_FILE at ${filePath}: ` +
|
|
26
|
+
(err instanceof Error ? err.message : String(err)));
|
|
27
|
+
}
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE at ${filePath} is not valid JSON: ` +
|
|
34
|
+
(err instanceof Error ? err.message : String(err)));
|
|
35
|
+
}
|
|
36
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
37
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE at ${filePath}: top-level must be an object`);
|
|
38
|
+
}
|
|
39
|
+
const accounts = parsed.accounts;
|
|
40
|
+
if (typeof accounts !== "object" ||
|
|
41
|
+
accounts === null ||
|
|
42
|
+
Array.isArray(accounts)) {
|
|
43
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE at ${filePath}: missing or invalid "accounts" object`);
|
|
44
|
+
}
|
|
45
|
+
const accountRaw = accounts[accountId];
|
|
46
|
+
if (typeof accountRaw !== "object" ||
|
|
47
|
+
accountRaw === null ||
|
|
48
|
+
Array.isArray(accountRaw)) {
|
|
49
|
+
const available = Object.keys(accounts).join(", ") || "<none>";
|
|
50
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE at ${filePath}: account "${accountId}" not found (available: ${available})`);
|
|
51
|
+
}
|
|
52
|
+
const acct = accountRaw;
|
|
53
|
+
const connectorUrl = typeof acct.connectorUrl === "string" ? acct.connectorUrl : null;
|
|
54
|
+
const token = typeof acct.token === "string" ? acct.token : null;
|
|
55
|
+
if (!connectorUrl) {
|
|
56
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE at ${filePath}: account "${accountId}" missing required field "connectorUrl"`);
|
|
57
|
+
}
|
|
58
|
+
if (!token) {
|
|
59
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE at ${filePath}: account "${accountId}" missing required field "token"`);
|
|
60
|
+
}
|
|
61
|
+
if (acct.apiHost !== undefined && typeof acct.apiHost !== "string") {
|
|
62
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE at ${filePath}: account "${accountId}" optional field "apiHost" must be a string when present`);
|
|
63
|
+
}
|
|
64
|
+
if (acct.handle !== undefined && typeof acct.handle !== "string") {
|
|
65
|
+
throw new Error(`MASONS_BROKER_CREDENTIALS_FILE at ${filePath}: account "${accountId}" optional field "handle" must be a string when present`);
|
|
66
|
+
}
|
|
67
|
+
const apiHost = envApiHost ??
|
|
68
|
+
(typeof acct.apiHost === "string" ? acct.apiHost : DEFAULT_API_HOST);
|
|
69
|
+
return { accountId, connectorUrl, token, apiHost };
|
|
70
|
+
}
|
|
71
|
+
async function resolveCredentialsFromOpenClawConfig(accountId, envApiHost) {
|
|
11
72
|
const config = await readConfig();
|
|
12
73
|
const channels = config.channels;
|
|
13
74
|
const network = channels?.["agent-network"];
|
|
@@ -19,10 +80,18 @@ async function resolveCredentials(accountId, envApiHost) {
|
|
|
19
80
|
const token = typeof account?.token === "string" ? account.token : null;
|
|
20
81
|
if (!connectorUrl || !token) {
|
|
21
82
|
throw new Error(`agent-network credentials missing for account "${accountId}"; ` +
|
|
22
|
-
"run setup before lazy-spawning the broker"
|
|
83
|
+
"run setup before lazy-spawning the broker " +
|
|
84
|
+
"(or set MASONS_BROKER_CREDENTIALS_FILE for Plugin hosts)");
|
|
23
85
|
}
|
|
24
86
|
return { accountId, connectorUrl, token, apiHost };
|
|
25
87
|
}
|
|
88
|
+
async function resolveCredentials(accountId, envApiHost) {
|
|
89
|
+
const credentialsFile = process.env.MASONS_BROKER_CREDENTIALS_FILE;
|
|
90
|
+
if (credentialsFile) {
|
|
91
|
+
return resolveCredentialsFromFile(credentialsFile, accountId, envApiHost);
|
|
92
|
+
}
|
|
93
|
+
return resolveCredentialsFromOpenClawConfig(accountId, envApiHost);
|
|
94
|
+
}
|
|
26
95
|
export function buildApiPort(apiHost, runtimeKey, logger) {
|
|
27
96
|
return {
|
|
28
97
|
async register(params) {
|
|
@@ -94,12 +163,49 @@ export async function main() {
|
|
|
94
163
|
token: creds.token,
|
|
95
164
|
});
|
|
96
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
|
+
};
|
|
97
194
|
const broker = await startBrokerDaemon({
|
|
98
195
|
paths,
|
|
99
196
|
asNodeId,
|
|
100
197
|
connector,
|
|
101
198
|
apiPort,
|
|
102
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
|
+
}),
|
|
103
209
|
});
|
|
104
210
|
let shuttingDown = false;
|
|
105
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,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
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const DEFAULT_SPAWN_TIMEOUT_MS = 30000;
|
|
2
|
+
export declare const DEFAULT_RATE_LIMIT_WINDOW_MS = 30000;
|
|
3
|
+
export declare const DEFAULT_RATE_LIMIT_MAX = 3;
|
|
4
|
+
export interface PendingSpawn {
|
|
5
|
+
spawn_token: string;
|
|
6
|
+
expires_at: number;
|
|
7
|
+
}
|
|
8
|
+
export interface SpawnCorrelationOptions {
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SpawnCorrelationManager {
|
|
12
|
+
track(spawn_token: string, onTimeout: () => void): void;
|
|
13
|
+
consume(spawn_token: string): PendingSpawn | undefined;
|
|
14
|
+
isTracked(spawn_token: string): boolean;
|
|
15
|
+
size(): number;
|
|
16
|
+
cancelAll(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function createSpawnCorrelationManager(opts?: SpawnCorrelationOptions): SpawnCorrelationManager;
|
|
19
|
+
export interface SpawnRateLimiter {
|
|
20
|
+
tryConsume(now?: number): boolean;
|
|
21
|
+
count(now?: number): number;
|
|
22
|
+
}
|
|
23
|
+
export interface SpawnRateLimiterOptions {
|
|
24
|
+
maxInWindow?: number;
|
|
25
|
+
windowMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function createSpawnRateLimiter(opts?: SpawnRateLimiterOptions): SpawnRateLimiter;
|
|
28
|
+
//# sourceMappingURL=spawn-correlation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-correlation.d.ts","sourceRoot":"","sources":["../../src/broker/spawn-correlation.ts"],"names":[],"mappings":"AA6BA,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,4BAA4B,QAAS,CAAC;AACnD,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IAEpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IAOtC,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAMxD,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAEvD,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IAExC,IAAI,IAAI,MAAM,CAAC;IAEf,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,GAAE,uBAA4B,GACjC,uBAAuB,CA+CzB;AAOD,MAAM,WAAW,gBAAgB;IAE/B,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAElC,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,sBAAsB,CACpC,IAAI,GAAE,uBAA4B,GACjC,gBAAgB,CA0BlB"}
|