aibroker 0.5.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -104
- package/dist/adapters/iterm/iterm2-api.d.ts +20 -0
- package/dist/adapters/iterm/iterm2-api.d.ts.map +1 -0
- package/dist/adapters/iterm/iterm2-api.js +244 -0
- package/dist/adapters/iterm/iterm2-api.js.map +1 -0
- package/dist/adapters/iterm/sessions.d.ts +1 -0
- package/dist/adapters/iterm/sessions.d.ts.map +1 -1
- package/dist/adapters/iterm/sessions.js +26 -2
- package/dist/adapters/iterm/sessions.js.map +1 -1
- package/dist/adapters/kokoro/media.js +2 -2
- package/dist/adapters/kokoro/media.js.map +1 -1
- package/dist/adapters/pailot/gateway.d.ts +5 -6
- package/dist/adapters/pailot/gateway.d.ts.map +1 -1
- package/dist/adapters/pailot/gateway.js +575 -34
- package/dist/adapters/pailot/gateway.js.map +1 -1
- package/dist/aibp/bridge.d.ts +123 -0
- package/dist/aibp/bridge.d.ts.map +1 -0
- package/dist/aibp/bridge.js +363 -0
- package/dist/aibp/bridge.js.map +1 -0
- package/dist/aibp/envelope.d.ts +26 -0
- package/dist/aibp/envelope.d.ts.map +1 -0
- package/dist/aibp/envelope.js +101 -0
- package/dist/aibp/envelope.js.map +1 -0
- package/dist/aibp/index.d.ts +11 -0
- package/dist/aibp/index.d.ts.map +1 -0
- package/dist/aibp/index.js +10 -0
- package/dist/aibp/index.js.map +1 -0
- package/dist/aibp/registry.d.ts +71 -0
- package/dist/aibp/registry.d.ts.map +1 -0
- package/dist/aibp/registry.js +408 -0
- package/dist/aibp/registry.js.map +1 -0
- package/dist/aibp/types.d.ts +91 -0
- package/dist/aibp/types.d.ts.map +1 -0
- package/dist/aibp/types.js +8 -0
- package/dist/aibp/types.js.map +1 -0
- package/dist/core/hybrid.d.ts +2 -0
- package/dist/core/hybrid.d.ts.map +1 -1
- package/dist/core/hybrid.js +8 -0
- package/dist/core/hybrid.js.map +1 -1
- package/dist/core/state.d.ts +12 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +34 -0
- package/dist/core/state.js.map +1 -1
- package/dist/core/status-cache.d.ts +51 -0
- package/dist/core/status-cache.d.ts.map +1 -0
- package/dist/core/status-cache.js +62 -0
- package/dist/core/status-cache.js.map +1 -0
- package/dist/daemon/adapter-registry.d.ts +5 -0
- package/dist/daemon/adapter-registry.d.ts.map +1 -1
- package/dist/daemon/adapter-registry.js +94 -4
- package/dist/daemon/adapter-registry.js.map +1 -1
- package/dist/daemon/cli.d.ts +1 -0
- package/dist/daemon/cli.d.ts.map +1 -1
- package/dist/daemon/cli.js +95 -3
- package/dist/daemon/cli.js.map +1 -1
- package/dist/daemon/command-context.d.ts +28 -0
- package/dist/daemon/command-context.d.ts.map +1 -0
- package/dist/daemon/command-context.js +13 -0
- package/dist/daemon/command-context.js.map +1 -0
- package/dist/daemon/commands.d.ts +22 -0
- package/dist/daemon/commands.d.ts.map +1 -0
- package/dist/daemon/commands.js +849 -0
- package/dist/daemon/commands.js.map +1 -0
- package/dist/daemon/core-handlers.d.ts.map +1 -1
- package/dist/daemon/core-handlers.js +758 -3
- package/dist/daemon/core-handlers.js.map +1 -1
- package/dist/daemon/create-adapter.js +2 -1
- package/dist/daemon/create-adapter.js.map +1 -1
- package/dist/daemon/image-context.d.ts +56 -0
- package/dist/daemon/image-context.d.ts.map +1 -0
- package/dist/daemon/image-context.js +116 -0
- package/dist/daemon/image-context.js.map +1 -0
- package/dist/daemon/image-gen/index.d.ts +22 -0
- package/dist/daemon/image-gen/index.d.ts.map +1 -0
- package/dist/daemon/image-gen/index.js +129 -0
- package/dist/daemon/image-gen/index.js.map +1 -0
- package/dist/daemon/image-gen/providers/cloudflare.d.ts +13 -0
- package/dist/daemon/image-gen/providers/cloudflare.d.ts.map +1 -0
- package/dist/daemon/image-gen/providers/cloudflare.js +63 -0
- package/dist/daemon/image-gen/providers/cloudflare.js.map +1 -0
- package/dist/daemon/image-gen/providers/huggingface.d.ts +12 -0
- package/dist/daemon/image-gen/providers/huggingface.d.ts.map +1 -0
- package/dist/daemon/image-gen/providers/huggingface.js +58 -0
- package/dist/daemon/image-gen/providers/huggingface.js.map +1 -0
- package/dist/daemon/image-gen/providers/pollinations.d.ts +11 -0
- package/dist/daemon/image-gen/providers/pollinations.d.ts.map +1 -0
- package/dist/daemon/image-gen/providers/pollinations.js +39 -0
- package/dist/daemon/image-gen/providers/pollinations.js.map +1 -0
- package/dist/daemon/image-gen/providers/replicate.d.ts +9 -0
- package/dist/daemon/image-gen/providers/replicate.d.ts.map +1 -0
- package/dist/daemon/image-gen/providers/replicate.js +158 -0
- package/dist/daemon/image-gen/providers/replicate.js.map +1 -0
- package/dist/daemon/image-gen/types.d.ts +41 -0
- package/dist/daemon/image-gen/types.d.ts.map +1 -0
- package/dist/daemon/image-gen/types.js +5 -0
- package/dist/daemon/image-gen/types.js.map +1 -0
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +260 -6
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/screenshot.d.ts +12 -0
- package/dist/daemon/screenshot.d.ts.map +1 -0
- package/dist/daemon/screenshot.js +252 -0
- package/dist/daemon/screenshot.js.map +1 -0
- package/dist/daemon/session-content.d.ts +27 -0
- package/dist/daemon/session-content.d.ts.map +1 -0
- package/dist/daemon/session-content.js +76 -0
- package/dist/daemon/session-content.js.map +1 -0
- package/dist/daemon/vision.d.ts +46 -0
- package/dist/daemon/vision.d.ts.map +1 -0
- package/dist/daemon/vision.js +176 -0
- package/dist/daemon/vision.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/ipc/validate.d.ts +52 -0
- package/dist/ipc/validate.d.ts.map +1 -0
- package/dist/ipc/validate.js +129 -0
- package/dist/ipc/validate.js.map +1 -0
- package/dist/mcp/index.d.ts +23 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +787 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/broker.d.ts +3 -1
- package/dist/types/broker.d.ts.map +1 -1
- package/dist/types/broker.js.map +1 -1
- package/package.json +5 -2
- package/templates/adapter/ONBOARDING_PROMPT.md +51 -29
- package/templates/adapter/README.md.tmpl +14 -31
- package/templates/adapter/package.json.tmpl +1 -1
- package/templates/adapter/src/watcher/commands.ts.tmpl +24 -126
- package/templates/adapter/src/watcher/index.ts.tmpl +112 -88
- package/templates/adapter/src/watcher/ipc-server.ts.tmpl +27 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIBP envelope utilities — create, parse, and validate AIBP messages.
|
|
3
|
+
*/
|
|
4
|
+
import type { AibpMessage, CommandPayload, ImagePayload, StatusPayload, SystemEvent, SystemPayload, TextPayload, TypingPayload, VoicePayload } from "./types.js";
|
|
5
|
+
export declare function text(src: string, dst: string, content: string): AibpMessage<TextPayload>;
|
|
6
|
+
export declare function voice(src: string, dst: string, audioBase64: string, transcript: string, durationMs?: number): AibpMessage<VoicePayload>;
|
|
7
|
+
export declare function image(src: string, dst: string, imageBase64: string, mimeType: string, caption?: string): AibpMessage<ImagePayload>;
|
|
8
|
+
export declare function typing(src: string, dst: string, active: boolean): AibpMessage<TypingPayload>;
|
|
9
|
+
export declare function status(src: string, dst: string, subject: string, state: StatusPayload["state"], summary?: string): AibpMessage<StatusPayload>;
|
|
10
|
+
export declare function command(src: string, dst: string, cmd: string, args?: Record<string, unknown>): AibpMessage<CommandPayload>;
|
|
11
|
+
export declare function system(src: string, dst: string, event: SystemEvent, data?: Record<string, unknown>): AibpMessage<SystemPayload>;
|
|
12
|
+
export declare function parse(raw: string): AibpMessage | null;
|
|
13
|
+
export declare function isAibpMessage(obj: unknown): obj is AibpMessage;
|
|
14
|
+
export declare function serialize(msg: AibpMessage): string;
|
|
15
|
+
/** Extract the address type prefix (e.g., "session" from "session:ABC123") */
|
|
16
|
+
export declare function addressType(address: string): string;
|
|
17
|
+
/** Extract the address value (e.g., "ABC123" from "session:ABC123") */
|
|
18
|
+
export declare function addressValue(address: string): string;
|
|
19
|
+
/** Check if an address is a hub-local address (not a remote hub reference) */
|
|
20
|
+
export declare function isLocal(address: string): boolean;
|
|
21
|
+
/** For mesh addresses like "hub:mac-mini/session:DEF456", extract the hub and local parts */
|
|
22
|
+
export declare function parseMeshAddress(address: string): {
|
|
23
|
+
hub: string;
|
|
24
|
+
local: string;
|
|
25
|
+
} | null;
|
|
26
|
+
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/aibp/envelope.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,WAAW,EAEX,cAAc,EACd,YAAY,EAEZ,aAAa,EACb,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AAyBpB,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAAC,WAAW,CAAC,CAExF;AAED,wBAAgB,KAAK,CACnB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,WAAW,CAAC,YAAY,CAAC,CAE3B;AAED,wBAAgB,KAAK,CACnB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,WAAW,CAAC,YAAY,CAAC,CAE3B;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC,aAAa,CAAC,CAE5F;AAED,wBAAgB,MAAM,CACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,MAAM,GACf,WAAW,CAAC,aAAa,CAAC,CAE5B;AAED,wBAAgB,OAAO,CACrB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,WAAW,CAAC,cAAc,CAAC,CAE7B;AAED,wBAAgB,MAAM,CACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,WAAW,EAClB,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,WAAW,CAAC,aAAa,CAAC,CAE5B;AAMD,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAQrD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,WAAW,CAa9D;AAMD,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAElD;AAMD,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGpD;AAED,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,6FAA6F;AAC7F,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAOvF"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIBP envelope utilities — create, parse, and validate AIBP messages.
|
|
3
|
+
*/
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
const AIBP_VERSION = "0.1";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Factory — create typed messages with automatic envelope fields
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
function envelope(src, dst, type, payload) {
|
|
10
|
+
return {
|
|
11
|
+
aibp: AIBP_VERSION,
|
|
12
|
+
id: randomUUID(),
|
|
13
|
+
ts: Date.now(),
|
|
14
|
+
src,
|
|
15
|
+
dst,
|
|
16
|
+
type,
|
|
17
|
+
payload,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function text(src, dst, content) {
|
|
21
|
+
return envelope(src, dst, "TEXT", { content });
|
|
22
|
+
}
|
|
23
|
+
export function voice(src, dst, audioBase64, transcript, durationMs) {
|
|
24
|
+
return envelope(src, dst, "VOICE", { audioBase64, transcript, durationMs });
|
|
25
|
+
}
|
|
26
|
+
export function image(src, dst, imageBase64, mimeType, caption) {
|
|
27
|
+
return envelope(src, dst, "IMAGE", { imageBase64, mimeType, caption });
|
|
28
|
+
}
|
|
29
|
+
export function typing(src, dst, active) {
|
|
30
|
+
return envelope(src, dst, "TYPING", { active });
|
|
31
|
+
}
|
|
32
|
+
export function status(src, dst, subject, state, summary) {
|
|
33
|
+
return envelope(src, dst, "STATUS", { subject, state, summary });
|
|
34
|
+
}
|
|
35
|
+
export function command(src, dst, cmd, args = {}) {
|
|
36
|
+
return envelope(src, dst, "COMMAND", { command: cmd, args });
|
|
37
|
+
}
|
|
38
|
+
export function system(src, dst, event, data = {}) {
|
|
39
|
+
return envelope(src, dst, "SYSTEM", { event, ...data });
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Parsing — validate incoming JSON
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
export function parse(raw) {
|
|
45
|
+
try {
|
|
46
|
+
const obj = JSON.parse(raw);
|
|
47
|
+
if (!isAibpMessage(obj))
|
|
48
|
+
return null;
|
|
49
|
+
return obj;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function isAibpMessage(obj) {
|
|
56
|
+
if (typeof obj !== "object" || obj === null)
|
|
57
|
+
return false;
|
|
58
|
+
const m = obj;
|
|
59
|
+
return (typeof m.aibp === "string" &&
|
|
60
|
+
typeof m.id === "string" &&
|
|
61
|
+
typeof m.ts === "number" &&
|
|
62
|
+
typeof m.src === "string" &&
|
|
63
|
+
typeof m.dst === "string" &&
|
|
64
|
+
typeof m.type === "string" &&
|
|
65
|
+
typeof m.payload === "object" &&
|
|
66
|
+
m.payload !== null);
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Serialize — NDJSON line
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
export function serialize(msg) {
|
|
72
|
+
return JSON.stringify(msg) + "\n";
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Address helpers
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
/** Extract the address type prefix (e.g., "session" from "session:ABC123") */
|
|
78
|
+
export function addressType(address) {
|
|
79
|
+
const colon = address.indexOf(":");
|
|
80
|
+
return colon > 0 ? address.slice(0, colon) : address;
|
|
81
|
+
}
|
|
82
|
+
/** Extract the address value (e.g., "ABC123" from "session:ABC123") */
|
|
83
|
+
export function addressValue(address) {
|
|
84
|
+
const colon = address.indexOf(":");
|
|
85
|
+
return colon > 0 ? address.slice(colon + 1) : address;
|
|
86
|
+
}
|
|
87
|
+
/** Check if an address is a hub-local address (not a remote hub reference) */
|
|
88
|
+
export function isLocal(address) {
|
|
89
|
+
return !address.includes("/");
|
|
90
|
+
}
|
|
91
|
+
/** For mesh addresses like "hub:mac-mini/session:DEF456", extract the hub and local parts */
|
|
92
|
+
export function parseMeshAddress(address) {
|
|
93
|
+
const slash = address.indexOf("/");
|
|
94
|
+
if (slash < 0)
|
|
95
|
+
return null;
|
|
96
|
+
return {
|
|
97
|
+
hub: address.slice(0, slash),
|
|
98
|
+
local: address.slice(slash + 1),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/aibp/envelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIBP — AIBroker Interchange Protocol
|
|
3
|
+
*
|
|
4
|
+
* Public API for the protocol layer.
|
|
5
|
+
*/
|
|
6
|
+
export * from "./types.js";
|
|
7
|
+
export * as msg from "./envelope.js";
|
|
8
|
+
export { PluginRegistry } from "./registry.js";
|
|
9
|
+
export type { SendFn } from "./registry.js";
|
|
10
|
+
export { AibpBridge } from "./bridge.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/aibp/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,YAAY,CAAC;AAC3B,OAAO,KAAK,GAAG,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIBP — AIBroker Interchange Protocol
|
|
3
|
+
*
|
|
4
|
+
* Public API for the protocol layer.
|
|
5
|
+
*/
|
|
6
|
+
export * from "./types.js";
|
|
7
|
+
export * as msg from "./envelope.js";
|
|
8
|
+
export { PluginRegistry } from "./registry.js";
|
|
9
|
+
export { AibpBridge } from "./bridge.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/aibp/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,YAAY,CAAC;AAC3B,OAAO,KAAK,GAAG,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIBP Plugin Registry — manages plugin connections, channels, and routing.
|
|
3
|
+
*
|
|
4
|
+
* This is the hub's brain. It knows:
|
|
5
|
+
* - Which plugins are registered and their capabilities
|
|
6
|
+
* - Which channels exist and who has joined them
|
|
7
|
+
* - Where to route each message based on dst address
|
|
8
|
+
* - What to buffer when a recipient is offline
|
|
9
|
+
*/
|
|
10
|
+
import type { AibpMessage, ChannelMembership, CommandSpec, PluginSpec, RegisteredPlugin } from "./types.js";
|
|
11
|
+
export type SendFn = (msg: AibpMessage) => void;
|
|
12
|
+
export declare class PluginRegistry {
|
|
13
|
+
/** address → connection */
|
|
14
|
+
private plugins;
|
|
15
|
+
/** channel name → membership */
|
|
16
|
+
private channels;
|
|
17
|
+
/** command name → plugin address */
|
|
18
|
+
private commands;
|
|
19
|
+
/**
|
|
20
|
+
* Register a plugin. Returns the REGISTER_ACK message to send back,
|
|
21
|
+
* or an ERROR message if registration fails.
|
|
22
|
+
*/
|
|
23
|
+
register(spec: PluginSpec, send: SendFn): AibpMessage;
|
|
24
|
+
/**
|
|
25
|
+
* Unregister a plugin (graceful disconnect or crash cleanup).
|
|
26
|
+
*/
|
|
27
|
+
unregister(address: string, reason?: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Join a channel. Creates the channel if it doesn't exist.
|
|
30
|
+
*/
|
|
31
|
+
join(address: string, channel: string): AibpMessage;
|
|
32
|
+
/**
|
|
33
|
+
* Part a channel.
|
|
34
|
+
*/
|
|
35
|
+
part(address: string, channel: string, reason?: string): AibpMessage;
|
|
36
|
+
/**
|
|
37
|
+
* Route a message to its destination.
|
|
38
|
+
*
|
|
39
|
+
* Routing rules:
|
|
40
|
+
* 1. If dst is a direct address (plugin/session/mobile), deliver to that plugin
|
|
41
|
+
* 2. If dst is a channel, fan out to all members
|
|
42
|
+
* 3. If dst is "hub:local", handle as a hub command
|
|
43
|
+
* 4. If dst contains "/" (mesh), forward to bridge plugin
|
|
44
|
+
* 5. If recipient is offline, buffer in outbox
|
|
45
|
+
*/
|
|
46
|
+
route(msg: AibpMessage): void;
|
|
47
|
+
private fanOut;
|
|
48
|
+
private routeToMesh;
|
|
49
|
+
private handleHubMessage;
|
|
50
|
+
private handleBuiltinCommand;
|
|
51
|
+
private bufferMessage;
|
|
52
|
+
private drainOutbox;
|
|
53
|
+
/**
|
|
54
|
+
* Call periodically (e.g., every 30s) to check plugin health.
|
|
55
|
+
*/
|
|
56
|
+
heartbeatCheck(): void;
|
|
57
|
+
getPlugin(address: string): RegisteredPlugin | undefined;
|
|
58
|
+
getChannel(channel: string): ChannelMembership | undefined;
|
|
59
|
+
getPluginByType(type: string): RegisteredPlugin[];
|
|
60
|
+
getCommandOwner(command: string): string | undefined;
|
|
61
|
+
listPlugins(): RegisteredPlugin[];
|
|
62
|
+
listChannels(): ChannelMembership[];
|
|
63
|
+
listCommands(): Array<{
|
|
64
|
+
name: string;
|
|
65
|
+
owner: string;
|
|
66
|
+
spec?: CommandSpec;
|
|
67
|
+
}>;
|
|
68
|
+
private sendTo;
|
|
69
|
+
private resolveAddress;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/aibp/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAOpB,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI,CAAC;AAOhD,qBAAa,cAAc;IACzB,2BAA2B;IAC3B,OAAO,CAAC,OAAO,CAAuC;IACtD,gCAAgC;IAChC,OAAO,CAAC,QAAQ,CAAwC;IACxD,oCAAoC;IACpC,OAAO,CAAC,QAAQ,CAA6B;IAM7C;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW;IA6CrD;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAiB,GAAG,IAAI;IAoC1D;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW;IA6BnD;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,GAAG,WAAW;IA2BpE;;;;;;;;;OASG;IACH,KAAK,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI;IA8C7B,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,oBAAoB;IAiD5B,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,WAAW;IA+BnB;;OAEG;IACH,cAAc,IAAI,IAAI;IAkBtB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAIxD,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAI1D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAMjD,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIpD,WAAW,IAAI,gBAAgB,EAAE;IAIjC,YAAY,IAAI,iBAAiB,EAAE;IAInC,YAAY,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC;IAY1E,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,cAAc;CAkBvB"}
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIBP Plugin Registry — manages plugin connections, channels, and routing.
|
|
3
|
+
*
|
|
4
|
+
* This is the hub's brain. It knows:
|
|
5
|
+
* - Which plugins are registered and their capabilities
|
|
6
|
+
* - Which channels exist and who has joined them
|
|
7
|
+
* - Where to route each message based on dst address
|
|
8
|
+
* - What to buffer when a recipient is offline
|
|
9
|
+
*/
|
|
10
|
+
import { log } from "../core/log.js";
|
|
11
|
+
import * as envelope from "./envelope.js";
|
|
12
|
+
const MAX_OUTBOX = 50;
|
|
13
|
+
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
14
|
+
const HEARTBEAT_MISS_DEGRADED = 3;
|
|
15
|
+
const HEARTBEAT_MISS_DEAD = 5;
|
|
16
|
+
export class PluginRegistry {
|
|
17
|
+
/** address → connection */
|
|
18
|
+
plugins = new Map();
|
|
19
|
+
/** channel name → membership */
|
|
20
|
+
channels = new Map();
|
|
21
|
+
/** command name → plugin address */
|
|
22
|
+
commands = new Map();
|
|
23
|
+
// -------------------------------------------------------------------------
|
|
24
|
+
// Plugin lifecycle
|
|
25
|
+
// -------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Register a plugin. Returns the REGISTER_ACK message to send back,
|
|
28
|
+
* or an ERROR message if registration fails.
|
|
29
|
+
*/
|
|
30
|
+
register(spec, send) {
|
|
31
|
+
const address = this.resolveAddress(spec);
|
|
32
|
+
// Reject duplicate
|
|
33
|
+
if (this.plugins.has(address)) {
|
|
34
|
+
log(`[AIBP] REGISTER rejected: ${address} already registered`);
|
|
35
|
+
return envelope.system("hub:local", address, "ERROR", {
|
|
36
|
+
code: "REGISTER_REJECTED",
|
|
37
|
+
reason: `Plugin address ${address} already registered`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const plugin = {
|
|
41
|
+
spec,
|
|
42
|
+
address,
|
|
43
|
+
connectedAt: Date.now(),
|
|
44
|
+
missedPings: 0,
|
|
45
|
+
status: "active",
|
|
46
|
+
joinedChannels: new Set(),
|
|
47
|
+
};
|
|
48
|
+
this.plugins.set(address, { plugin, send });
|
|
49
|
+
// Register commands
|
|
50
|
+
if (spec.commands) {
|
|
51
|
+
for (const cmd of spec.commands) {
|
|
52
|
+
this.commands.set(cmd.name, address);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Auto-join declared channels
|
|
56
|
+
for (const ch of spec.channels) {
|
|
57
|
+
this.join(address, ch);
|
|
58
|
+
}
|
|
59
|
+
log(`[AIBP] REGISTER ${spec.type}:${spec.id} → ${address}`);
|
|
60
|
+
const peers = Array.from(this.plugins.keys()).filter((a) => a !== address);
|
|
61
|
+
return envelope.system("hub:local", address, "REGISTER_ACK", {
|
|
62
|
+
assignedAddress: address,
|
|
63
|
+
hubVersion: "0.1",
|
|
64
|
+
peers,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Unregister a plugin (graceful disconnect or crash cleanup).
|
|
69
|
+
*/
|
|
70
|
+
unregister(address, reason = "disconnected") {
|
|
71
|
+
const conn = this.plugins.get(address);
|
|
72
|
+
if (!conn)
|
|
73
|
+
return;
|
|
74
|
+
// Part all joined channels
|
|
75
|
+
for (const ch of conn.plugin.joinedChannels) {
|
|
76
|
+
this.part(address, ch, reason);
|
|
77
|
+
}
|
|
78
|
+
// Remove commands
|
|
79
|
+
if (conn.plugin.spec.commands) {
|
|
80
|
+
for (const cmd of conn.plugin.spec.commands) {
|
|
81
|
+
if (this.commands.get(cmd.name) === address) {
|
|
82
|
+
this.commands.delete(cmd.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.plugins.delete(address);
|
|
87
|
+
log(`[AIBP] UNREGISTER ${address} (${reason})`);
|
|
88
|
+
// Notify all remaining plugins
|
|
89
|
+
for (const [, other] of this.plugins) {
|
|
90
|
+
other.send(envelope.system("hub:local", other.plugin.address, "PLUGIN_OFFLINE", {
|
|
91
|
+
plugin: address,
|
|
92
|
+
reason,
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// -------------------------------------------------------------------------
|
|
97
|
+
// Channel management
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Join a channel. Creates the channel if it doesn't exist.
|
|
101
|
+
*/
|
|
102
|
+
join(address, channel) {
|
|
103
|
+
let membership = this.channels.get(channel);
|
|
104
|
+
if (!membership) {
|
|
105
|
+
membership = { channel, members: new Set(), outbox: [] };
|
|
106
|
+
this.channels.set(channel, membership);
|
|
107
|
+
}
|
|
108
|
+
membership.members.add(address);
|
|
109
|
+
const conn = this.plugins.get(address);
|
|
110
|
+
if (conn) {
|
|
111
|
+
conn.plugin.joinedChannels.add(channel);
|
|
112
|
+
}
|
|
113
|
+
log(`[AIBP] JOIN ${address} → ${channel} (${membership.members.size} members)`);
|
|
114
|
+
// Drain outbox for this member
|
|
115
|
+
const buffered = membership.outbox.length;
|
|
116
|
+
if (buffered > 0) {
|
|
117
|
+
this.drainOutbox(address, channel);
|
|
118
|
+
}
|
|
119
|
+
return envelope.system("hub:local", address, "JOIN_ACK", {
|
|
120
|
+
channel,
|
|
121
|
+
members: Array.from(membership.members),
|
|
122
|
+
unreadCount: buffered,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Part a channel.
|
|
127
|
+
*/
|
|
128
|
+
part(address, channel, reason = "left") {
|
|
129
|
+
const membership = this.channels.get(channel);
|
|
130
|
+
if (membership) {
|
|
131
|
+
membership.members.delete(address);
|
|
132
|
+
// Clean up empty channels (except persistent session channels)
|
|
133
|
+
if (membership.members.size === 0 && !channel.startsWith("session:")) {
|
|
134
|
+
this.channels.delete(channel);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const conn = this.plugins.get(address);
|
|
138
|
+
if (conn) {
|
|
139
|
+
conn.plugin.joinedChannels.delete(channel);
|
|
140
|
+
}
|
|
141
|
+
log(`[AIBP] PART ${address} ← ${channel} (${reason})`);
|
|
142
|
+
return envelope.system("hub:local", address, "PART_ACK", {
|
|
143
|
+
channel,
|
|
144
|
+
reason,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// -------------------------------------------------------------------------
|
|
148
|
+
// Message routing — the core of AIBP
|
|
149
|
+
// -------------------------------------------------------------------------
|
|
150
|
+
/**
|
|
151
|
+
* Route a message to its destination.
|
|
152
|
+
*
|
|
153
|
+
* Routing rules:
|
|
154
|
+
* 1. If dst is a direct address (plugin/session/mobile), deliver to that plugin
|
|
155
|
+
* 2. If dst is a channel, fan out to all members
|
|
156
|
+
* 3. If dst is "hub:local", handle as a hub command
|
|
157
|
+
* 4. If dst contains "/" (mesh), forward to bridge plugin
|
|
158
|
+
* 5. If recipient is offline, buffer in outbox
|
|
159
|
+
*/
|
|
160
|
+
route(msg) {
|
|
161
|
+
const { dst } = msg;
|
|
162
|
+
// Mesh routing — forward to the appropriate bridge
|
|
163
|
+
const mesh = envelope.parseMeshAddress(dst);
|
|
164
|
+
if (mesh) {
|
|
165
|
+
this.routeToMesh(msg, mesh.hub, mesh.local);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Hub command
|
|
169
|
+
if (dst === "hub:local") {
|
|
170
|
+
this.handleHubMessage(msg);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// Direct plugin address
|
|
174
|
+
const directConn = this.plugins.get(dst);
|
|
175
|
+
if (directConn) {
|
|
176
|
+
directConn.send(msg);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Channel fan-out
|
|
180
|
+
const membership = this.channels.get(dst);
|
|
181
|
+
if (membership) {
|
|
182
|
+
this.fanOut(msg, membership);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Try to match as a session channel
|
|
186
|
+
if (dst.startsWith("session:")) {
|
|
187
|
+
// Auto-create the channel and buffer
|
|
188
|
+
const newMembership = {
|
|
189
|
+
channel: dst,
|
|
190
|
+
members: new Set(),
|
|
191
|
+
outbox: [],
|
|
192
|
+
};
|
|
193
|
+
this.channels.set(dst, newMembership);
|
|
194
|
+
this.bufferMessage(newMembership, msg);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
log(`[AIBP] ROUTE FAIL: no destination for ${dst} (from ${msg.src})`);
|
|
198
|
+
}
|
|
199
|
+
fanOut(msg, membership) {
|
|
200
|
+
membership.lastMessageId = msg.id;
|
|
201
|
+
membership.lastMessageTs = msg.ts;
|
|
202
|
+
let delivered = 0;
|
|
203
|
+
for (const member of membership.members) {
|
|
204
|
+
// Don't echo back to sender
|
|
205
|
+
if (member === msg.src)
|
|
206
|
+
continue;
|
|
207
|
+
const conn = this.plugins.get(member);
|
|
208
|
+
if (conn) {
|
|
209
|
+
conn.send(msg);
|
|
210
|
+
delivered++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Buffer for offline members who have joined this channel before
|
|
214
|
+
if (delivered === 0 && membership.members.size === 0) {
|
|
215
|
+
this.bufferMessage(membership, msg);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
routeToMesh(msg, hubAddress, localDst) {
|
|
219
|
+
// Extract the hub ID from hub address (e.g., "hub:mac-mini" → "mac-mini")
|
|
220
|
+
const hubId = hubAddress.startsWith("hub:") ? hubAddress.slice(4) : hubAddress;
|
|
221
|
+
// Try to find the specific bridge for this hub first
|
|
222
|
+
const specificBridge = this.plugins.get(`bridge:${hubId}`);
|
|
223
|
+
if (specificBridge) {
|
|
224
|
+
specificBridge.send(msg);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Fallback: find any bridge plugin
|
|
228
|
+
for (const [, conn] of this.plugins) {
|
|
229
|
+
if (conn.plugin.spec.type === "bridge") {
|
|
230
|
+
conn.send(msg);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
log(`[AIBP] MESH FAIL: no bridge plugin for ${hubAddress}`);
|
|
235
|
+
}
|
|
236
|
+
handleHubMessage(msg) {
|
|
237
|
+
if (msg.type === "COMMAND") {
|
|
238
|
+
const payload = msg.payload;
|
|
239
|
+
const cmdOwner = this.commands.get(payload.command);
|
|
240
|
+
if (cmdOwner) {
|
|
241
|
+
const conn = this.plugins.get(cmdOwner);
|
|
242
|
+
if (conn) {
|
|
243
|
+
conn.send(msg);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Built-in hub commands
|
|
248
|
+
this.handleBuiltinCommand(msg, payload.command, payload.args);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (msg.type === "SYSTEM") {
|
|
252
|
+
const payload = msg.payload;
|
|
253
|
+
if (payload.event === "PING") {
|
|
254
|
+
const conn = this.plugins.get(msg.src);
|
|
255
|
+
if (conn) {
|
|
256
|
+
conn.plugin.lastPing = Date.now();
|
|
257
|
+
conn.plugin.missedPings = 0;
|
|
258
|
+
conn.plugin.status = "active";
|
|
259
|
+
conn.send(envelope.system("hub:local", msg.src, "PONG"));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
handleBuiltinCommand(msg, cmd, args) {
|
|
265
|
+
const src = msg.src;
|
|
266
|
+
switch (cmd) {
|
|
267
|
+
case "list_channels": {
|
|
268
|
+
const channels = Array.from(this.channels.entries()).map(([name, m]) => ({
|
|
269
|
+
name,
|
|
270
|
+
members: m.members.size,
|
|
271
|
+
lastActivity: m.lastMessageTs,
|
|
272
|
+
outboxSize: m.outbox.length,
|
|
273
|
+
}));
|
|
274
|
+
const reply = envelope.system("hub:local", src, "REGISTER_ACK", { channels });
|
|
275
|
+
this.sendTo(src, reply);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case "list_plugins": {
|
|
279
|
+
const plugins = Array.from(this.plugins.entries()).map(([addr, c]) => ({
|
|
280
|
+
address: addr,
|
|
281
|
+
type: c.plugin.spec.type,
|
|
282
|
+
name: c.plugin.spec.name,
|
|
283
|
+
status: c.plugin.status,
|
|
284
|
+
channels: Array.from(c.plugin.joinedChannels),
|
|
285
|
+
}));
|
|
286
|
+
const reply = envelope.system("hub:local", src, "REGISTER_ACK", { plugins });
|
|
287
|
+
this.sendTo(src, reply);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
default:
|
|
291
|
+
log(`[AIBP] Unknown hub command: ${cmd}`);
|
|
292
|
+
this.sendTo(src, envelope.system("hub:local", src, "ERROR", {
|
|
293
|
+
code: "UNKNOWN_COMMAND",
|
|
294
|
+
command: cmd,
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// -------------------------------------------------------------------------
|
|
299
|
+
// Outbox
|
|
300
|
+
// -------------------------------------------------------------------------
|
|
301
|
+
bufferMessage(membership, msg) {
|
|
302
|
+
// Don't buffer typing indicators or pings
|
|
303
|
+
if (msg.type === "TYPING" || msg.type === "SYSTEM")
|
|
304
|
+
return;
|
|
305
|
+
if (membership.outbox.length >= MAX_OUTBOX) {
|
|
306
|
+
membership.outbox.shift(); // Drop oldest
|
|
307
|
+
}
|
|
308
|
+
membership.outbox.push(msg);
|
|
309
|
+
log(`[AIBP] OUTBOX ${msg.dst}: ${membership.outbox.length} buffered`);
|
|
310
|
+
}
|
|
311
|
+
drainOutbox(address, channel) {
|
|
312
|
+
const membership = this.channels.get(channel);
|
|
313
|
+
if (!membership || membership.outbox.length === 0)
|
|
314
|
+
return;
|
|
315
|
+
const conn = this.plugins.get(address);
|
|
316
|
+
if (!conn)
|
|
317
|
+
return;
|
|
318
|
+
const count = membership.outbox.length;
|
|
319
|
+
// Send drain header
|
|
320
|
+
conn.send(envelope.system("hub:local", address, "OUTBOX_DRAIN", {
|
|
321
|
+
channel,
|
|
322
|
+
count,
|
|
323
|
+
summary: `${count} message${count > 1 ? "s" : ""} while offline`,
|
|
324
|
+
}));
|
|
325
|
+
// Deliver buffered messages
|
|
326
|
+
for (const msg of membership.outbox) {
|
|
327
|
+
conn.send(msg);
|
|
328
|
+
}
|
|
329
|
+
membership.outbox = [];
|
|
330
|
+
log(`[AIBP] OUTBOX DRAIN ${channel} → ${address}: ${count} messages`);
|
|
331
|
+
}
|
|
332
|
+
// -------------------------------------------------------------------------
|
|
333
|
+
// Heartbeat monitoring
|
|
334
|
+
// -------------------------------------------------------------------------
|
|
335
|
+
/**
|
|
336
|
+
* Call periodically (e.g., every 30s) to check plugin health.
|
|
337
|
+
*/
|
|
338
|
+
heartbeatCheck() {
|
|
339
|
+
for (const [address, conn] of this.plugins) {
|
|
340
|
+
conn.plugin.missedPings++;
|
|
341
|
+
if (conn.plugin.missedPings >= HEARTBEAT_MISS_DEAD) {
|
|
342
|
+
log(`[AIBP] Plugin dead: ${address} (${conn.plugin.missedPings} missed pings)`);
|
|
343
|
+
this.unregister(address, "heartbeat timeout");
|
|
344
|
+
}
|
|
345
|
+
else if (conn.plugin.missedPings >= HEARTBEAT_MISS_DEGRADED) {
|
|
346
|
+
conn.plugin.status = "degraded";
|
|
347
|
+
log(`[AIBP] Plugin degraded: ${address}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// -------------------------------------------------------------------------
|
|
352
|
+
// Queries
|
|
353
|
+
// -------------------------------------------------------------------------
|
|
354
|
+
getPlugin(address) {
|
|
355
|
+
return this.plugins.get(address)?.plugin;
|
|
356
|
+
}
|
|
357
|
+
getChannel(channel) {
|
|
358
|
+
return this.channels.get(channel);
|
|
359
|
+
}
|
|
360
|
+
getPluginByType(type) {
|
|
361
|
+
return Array.from(this.plugins.values())
|
|
362
|
+
.filter((c) => c.plugin.spec.type === type)
|
|
363
|
+
.map((c) => c.plugin);
|
|
364
|
+
}
|
|
365
|
+
getCommandOwner(command) {
|
|
366
|
+
return this.commands.get(command);
|
|
367
|
+
}
|
|
368
|
+
listPlugins() {
|
|
369
|
+
return Array.from(this.plugins.values()).map((c) => c.plugin);
|
|
370
|
+
}
|
|
371
|
+
listChannels() {
|
|
372
|
+
return Array.from(this.channels.values());
|
|
373
|
+
}
|
|
374
|
+
listCommands() {
|
|
375
|
+
return Array.from(this.commands.entries()).map(([name, owner]) => {
|
|
376
|
+
const conn = this.plugins.get(owner);
|
|
377
|
+
const spec = conn?.plugin.spec.commands?.find((c) => c.name === name);
|
|
378
|
+
return { name, owner, spec };
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
// -------------------------------------------------------------------------
|
|
382
|
+
// Internal helpers
|
|
383
|
+
// -------------------------------------------------------------------------
|
|
384
|
+
sendTo(address, msg) {
|
|
385
|
+
const conn = this.plugins.get(address);
|
|
386
|
+
if (conn)
|
|
387
|
+
conn.send(msg);
|
|
388
|
+
}
|
|
389
|
+
resolveAddress(spec) {
|
|
390
|
+
switch (spec.type) {
|
|
391
|
+
case "transport":
|
|
392
|
+
return `transport:${spec.id}`;
|
|
393
|
+
case "terminal":
|
|
394
|
+
return `terminal:${spec.id}`;
|
|
395
|
+
case "mobile":
|
|
396
|
+
return `mobile:${spec.id}`;
|
|
397
|
+
case "bridge":
|
|
398
|
+
return `bridge:${spec.id}`;
|
|
399
|
+
case "mcp":
|
|
400
|
+
return `mcp:${spec.id}`;
|
|
401
|
+
case "hub":
|
|
402
|
+
return `hub:${spec.id}`;
|
|
403
|
+
default:
|
|
404
|
+
return `plugin:${spec.id}`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
//# sourceMappingURL=registry.js.map
|