openclaw-bridge 0.3.2 → 0.4.1
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 +43 -16
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +843 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +64 -0
- package/dist/discovery.d.ts +4 -0
- package/dist/discovery.js +6 -0
- package/dist/file-ops.d.ts +22 -0
- package/dist/file-ops.js +253 -0
- package/dist/heartbeat.d.ts +21 -0
- package/dist/heartbeat.js +152 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +624 -0
- package/dist/manager/hub-client.d.ts +18 -0
- package/dist/manager/hub-client.js +89 -0
- package/dist/manager/local-manager.d.ts +12 -0
- package/dist/manager/local-manager.js +117 -0
- package/dist/manager/pm2-bridge.d.ts +17 -0
- package/dist/manager/pm2-bridge.js +113 -0
- package/dist/message-relay.d.ts +32 -0
- package/dist/message-relay.js +229 -0
- package/dist/permissions.d.ts +3 -0
- package/dist/permissions.js +14 -0
- package/dist/registry.d.ts +13 -0
- package/dist/registry.js +103 -0
- package/dist/restart.d.ts +15 -0
- package/dist/restart.js +107 -0
- package/dist/router.d.ts +11 -0
- package/dist/router.js +18 -0
- package/dist/session.d.ts +11 -0
- package/dist/session.js +21 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +1 -0
- package/openclaw.plugin.json +6 -92
- package/package.json +15 -5
- package/src/cli.ts +0 -842
- package/src/config.ts +0 -72
- package/src/discovery.ts +0 -17
- package/src/file-ops.ts +0 -320
- package/src/heartbeat.ts +0 -196
- package/src/index.ts +0 -681
- package/src/manager/hub-client.ts +0 -114
- package/src/manager/local-manager.ts +0 -121
- package/src/manager/pm2-bridge.ts +0 -125
- package/src/message-relay.ts +0 -184
- package/src/permissions.ts +0 -18
- package/src/registry.ts +0 -107
- package/src/restart.ts +0 -137
- package/src/router.ts +0 -40
- package/src/session.ts +0 -33
- package/src/types.ts +0 -100
- package/tsconfig.json +0 -14
package/dist/registry.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export class BridgeRegistry {
|
|
2
|
+
baseUrl;
|
|
3
|
+
apiKey;
|
|
4
|
+
logger;
|
|
5
|
+
constructor(config, logger) {
|
|
6
|
+
// Use fileRelay URL for registry (not OpenViking)
|
|
7
|
+
if (!config.fileRelay?.baseUrl) {
|
|
8
|
+
throw new Error("openclaw-bridge: fileRelay.baseUrl is required for registry");
|
|
9
|
+
}
|
|
10
|
+
this.baseUrl = config.fileRelay.baseUrl.replace(/\/+$/, "");
|
|
11
|
+
this.apiKey = config.fileRelay.apiKey;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
headers() {
|
|
15
|
+
const h = { "Content-Type": "application/json" };
|
|
16
|
+
if (this.apiKey)
|
|
17
|
+
h["X-API-Key"] = this.apiKey;
|
|
18
|
+
return h;
|
|
19
|
+
}
|
|
20
|
+
async register(entry) {
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(`${this.baseUrl}/api/v1/registry/register`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: this.headers(),
|
|
25
|
+
body: JSON.stringify(entry),
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error(`HTTP ${res.status}`);
|
|
29
|
+
}
|
|
30
|
+
this.logger.info(`openclaw-bridge: registered ${entry.agentId} to registry`);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
this.logger.error(`openclaw-bridge: registration failed: ${String(err)}`);
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async update(entry) {
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(`${this.baseUrl}/api/v1/registry/heartbeat`, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: this.headers(),
|
|
42
|
+
body: JSON.stringify(entry),
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
// Re-register if heartbeat fails
|
|
46
|
+
await this.register(entry);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
this.logger.warn(`openclaw-bridge: heartbeat failed, re-registering: ${String(err)}`);
|
|
51
|
+
await this.register(entry);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async deregister(agentId) {
|
|
55
|
+
try {
|
|
56
|
+
await fetch(`${this.baseUrl}/api/v1/registry/${agentId}`, {
|
|
57
|
+
method: "DELETE",
|
|
58
|
+
headers: this.headers(),
|
|
59
|
+
});
|
|
60
|
+
this.logger.info(`openclaw-bridge: deregistered ${agentId}`);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
this.logger.warn(`openclaw-bridge: deregistration failed: ${String(err)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async discover(offlineThresholdMs) {
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch(`${this.baseUrl}/api/v1/registry/discover`, {
|
|
69
|
+
headers: this.headers(),
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok)
|
|
72
|
+
return [];
|
|
73
|
+
const data = (await res.json());
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
return (data.agents ?? []).map((entry) => {
|
|
76
|
+
const lastBeat = new Date(entry.lastHeartbeat).getTime();
|
|
77
|
+
entry.status = (now - lastBeat > offlineThresholdMs) ? "offline" : "online";
|
|
78
|
+
return entry;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
this.logger.error(`openclaw-bridge: discover failed: ${String(err)}`);
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async findAgent(agentId, offlineThresholdMs) {
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetch(`${this.baseUrl}/api/v1/registry/whois/${agentId}`, {
|
|
89
|
+
headers: this.headers(),
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok)
|
|
92
|
+
return null;
|
|
93
|
+
const entry = (await res.json());
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const lastBeat = new Date(entry.lastHeartbeat).getTime();
|
|
96
|
+
entry.status = (now - lastBeat > offlineThresholdMs) ? "offline" : "online";
|
|
97
|
+
return entry;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BridgeConfig, RegistryEntry, PluginLogger } from "./types.js";
|
|
2
|
+
import type { BridgeRegistry } from "./registry.js";
|
|
3
|
+
export declare class BridgeRestart {
|
|
4
|
+
private config;
|
|
5
|
+
private machineId;
|
|
6
|
+
private registry;
|
|
7
|
+
private logger;
|
|
8
|
+
constructor(config: BridgeConfig, machineId: string, registry: BridgeRegistry, logger: PluginLogger);
|
|
9
|
+
restart(target: RegistryEntry): Promise<{
|
|
10
|
+
success: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
}>;
|
|
13
|
+
private restartLocal;
|
|
14
|
+
private restartRemote;
|
|
15
|
+
}
|
package/dist/restart.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execAsync = promisify(exec);
|
|
4
|
+
const IS_WIN = process.platform === "win32";
|
|
5
|
+
export class BridgeRestart {
|
|
6
|
+
config;
|
|
7
|
+
machineId;
|
|
8
|
+
registry;
|
|
9
|
+
logger;
|
|
10
|
+
constructor(config, machineId, registry, logger) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.machineId = machineId;
|
|
13
|
+
this.registry = registry;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
async restart(target) {
|
|
17
|
+
if (target.machineId !== this.machineId) {
|
|
18
|
+
return this.restartRemote(target);
|
|
19
|
+
}
|
|
20
|
+
return this.restartLocal(target);
|
|
21
|
+
}
|
|
22
|
+
async restartLocal(target) {
|
|
23
|
+
this.logger.info(`openclaw-bridge: restarting ${target.agentId} on local machine`);
|
|
24
|
+
try {
|
|
25
|
+
if (IS_WIN) {
|
|
26
|
+
const { stdout } = await execAsync(`netstat -ano | findstr :${target.port} | findstr LISTENING`);
|
|
27
|
+
const lines = stdout.trim().split("\n");
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
const pid = line.trim().split(/\s+/).pop();
|
|
30
|
+
if (pid && /^\d+$/.test(pid)) {
|
|
31
|
+
await execAsync(`taskkill /F /PID ${pid}`).catch(() => { });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
await execAsync(`lsof -ti:${target.port} | xargs kill -9`).catch(() => { });
|
|
37
|
+
}
|
|
38
|
+
const instanceDir = target.workspacePath.replace(/[\\/]workspace[\\/]?$/, "");
|
|
39
|
+
const runScript = IS_WIN
|
|
40
|
+
? `${instanceDir}\\run.ps1`
|
|
41
|
+
: `${instanceDir}/run.sh`;
|
|
42
|
+
if (IS_WIN) {
|
|
43
|
+
await execAsync(`powershell -Command "Start-Process powershell -ArgumentList '-File','${runScript}' -WindowStyle Hidden"`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
await execAsync(`nohup bash "${runScript}" > /dev/null 2>&1 &`);
|
|
47
|
+
}
|
|
48
|
+
const deadline = Date.now() + 60_000;
|
|
49
|
+
while (Date.now() < deadline) {
|
|
50
|
+
await new Promise((r) => setTimeout(r, 3_000));
|
|
51
|
+
const found = await this.registry.findAgent(target.agentId, this.config.offlineThresholdMs ?? 120_000);
|
|
52
|
+
if (found && found.status === "online") {
|
|
53
|
+
this.logger.info(`openclaw-bridge: ${target.agentId} restarted successfully`);
|
|
54
|
+
return { success: true, message: `${target.agentId} restarted and back online` };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
success: false,
|
|
59
|
+
message: `${target.agentId} process started but did not re-register within 60s`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return { success: false, message: `Restart failed: ${String(err)}` };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async restartRemote(target) {
|
|
67
|
+
if (!this.config.fileRelay?.baseUrl) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
message: "Cannot restart remote gateway: fileRelay not configured",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const baseUrl = this.config.fileRelay.baseUrl.replace(/\/+$/, "");
|
|
74
|
+
const headers = { "Content-Type": "application/json" };
|
|
75
|
+
if (this.config.fileRelay.apiKey)
|
|
76
|
+
headers["X-API-Key"] = this.config.fileRelay.apiKey;
|
|
77
|
+
const res = await fetch(`${baseUrl}/api/v1/commands/enqueue`, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers,
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
fromAgent: this.config.agentId,
|
|
82
|
+
toAgent: target.agentId,
|
|
83
|
+
type: "restart",
|
|
84
|
+
payload: {},
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
return { success: false, message: `FileRelay command enqueue failed: ${res.status}` };
|
|
89
|
+
}
|
|
90
|
+
const { id: cmdId } = (await res.json());
|
|
91
|
+
const deadline = Date.now() + 90_000;
|
|
92
|
+
while (Date.now() < deadline) {
|
|
93
|
+
await new Promise((r) => setTimeout(r, 5_000));
|
|
94
|
+
const resultRes = await fetch(`${baseUrl}/api/v1/commands/result/${cmdId}`, { headers });
|
|
95
|
+
if (!resultRes.ok)
|
|
96
|
+
continue;
|
|
97
|
+
const result = (await resultRes.json());
|
|
98
|
+
if (result.status === "ok") {
|
|
99
|
+
return { success: true, message: `Restart command acknowledged by ${target.agentId}` };
|
|
100
|
+
}
|
|
101
|
+
if (result.status === "error") {
|
|
102
|
+
return { success: false, message: `Remote restart failed` };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { success: false, message: "Restart command timed out (90s)" };
|
|
106
|
+
}
|
|
107
|
+
}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RegistryEntry, ChannelInfo } from './types.js';
|
|
2
|
+
export interface MessageContext {
|
|
3
|
+
channel: ChannelInfo;
|
|
4
|
+
isGroupChannel: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface RouteDecision {
|
|
7
|
+
method: 'channel_direct' | 'hub_relay';
|
|
8
|
+
channel?: ChannelInfo;
|
|
9
|
+
fallback: 'hub_relay';
|
|
10
|
+
}
|
|
11
|
+
export declare function decideRoute(currentContext: MessageContext, targetAgentId: string, registry: RegistryEntry[]): RouteDecision;
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function decideRoute(currentContext, targetAgentId, registry) {
|
|
2
|
+
const target = registry.find(a => a.agentId === targetAgentId);
|
|
3
|
+
if (!target || target.status !== 'online') {
|
|
4
|
+
return { method: 'hub_relay', fallback: 'hub_relay' };
|
|
5
|
+
}
|
|
6
|
+
if (currentContext.isGroupChannel && target.channels) {
|
|
7
|
+
const targetInSameChannel = target.channels.some(ch => ch.type === currentContext.channel.type
|
|
8
|
+
&& ch.channelId === currentContext.channel.channelId);
|
|
9
|
+
if (targetInSameChannel) {
|
|
10
|
+
return {
|
|
11
|
+
method: 'channel_direct',
|
|
12
|
+
channel: currentContext.channel,
|
|
13
|
+
fallback: 'hub_relay',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { method: 'hub_relay', fallback: 'hub_relay' };
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ProxySession {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
originAgent: string;
|
|
4
|
+
currentAgent: string;
|
|
5
|
+
currentAgentName: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function setSession(session: ProxySession): void;
|
|
8
|
+
export declare function getSession(): ProxySession | null;
|
|
9
|
+
export declare function clearSession(): void;
|
|
10
|
+
export declare function updateCurrentAgent(agentId: string, agentName: string): void;
|
|
11
|
+
export declare function isInHandoff(): boolean;
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
let activeSession = null;
|
|
2
|
+
export function setSession(session) {
|
|
3
|
+
activeSession = session;
|
|
4
|
+
console.log(`[session] SET handoff: sessionId=${session.sessionId} target=${session.currentAgent}`);
|
|
5
|
+
}
|
|
6
|
+
export function getSession() {
|
|
7
|
+
return activeSession;
|
|
8
|
+
}
|
|
9
|
+
export function clearSession() {
|
|
10
|
+
console.log(`[session] CLEAR handoff (was: ${activeSession?.sessionId || 'none'})`);
|
|
11
|
+
activeSession = null;
|
|
12
|
+
}
|
|
13
|
+
export function updateCurrentAgent(agentId, agentName) {
|
|
14
|
+
if (activeSession) {
|
|
15
|
+
activeSession.currentAgent = agentId;
|
|
16
|
+
activeSession.currentAgentName = agentName;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function isInHandoff() {
|
|
20
|
+
return activeSession !== null;
|
|
21
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export interface MessageRelayConfig {
|
|
2
|
+
url: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
}
|
|
5
|
+
export interface LocalManagerConfig {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
hubUrl: string;
|
|
8
|
+
managerPass: string;
|
|
9
|
+
}
|
|
10
|
+
export interface BridgeConfig {
|
|
11
|
+
role: "normal" | "superuser";
|
|
12
|
+
agentId: string;
|
|
13
|
+
agentName: string;
|
|
14
|
+
registry: {
|
|
15
|
+
provider?: string;
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
};
|
|
19
|
+
fileRelay?: {
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
};
|
|
23
|
+
messageRelay?: MessageRelayConfig;
|
|
24
|
+
heartbeatIntervalMs?: number;
|
|
25
|
+
offlineThresholdMs?: number;
|
|
26
|
+
description?: string;
|
|
27
|
+
supportsVision?: boolean;
|
|
28
|
+
localManager?: LocalManagerConfig;
|
|
29
|
+
}
|
|
30
|
+
export interface ChannelInfo {
|
|
31
|
+
type: string;
|
|
32
|
+
channelId: string;
|
|
33
|
+
name: string;
|
|
34
|
+
}
|
|
35
|
+
export interface RegistryEntry {
|
|
36
|
+
type: "gateway-registry";
|
|
37
|
+
agentId: string;
|
|
38
|
+
agentName: string;
|
|
39
|
+
machineId: string;
|
|
40
|
+
host: string;
|
|
41
|
+
port: number;
|
|
42
|
+
workspacePath: string;
|
|
43
|
+
discordId: string | null;
|
|
44
|
+
discordConnected?: boolean;
|
|
45
|
+
role: "normal" | "superuser";
|
|
46
|
+
capabilities: string[];
|
|
47
|
+
channels: ChannelInfo[];
|
|
48
|
+
registeredAt: string;
|
|
49
|
+
lastHeartbeat: string;
|
|
50
|
+
status: "online" | "offline";
|
|
51
|
+
memMB?: number;
|
|
52
|
+
description?: string;
|
|
53
|
+
supportsVision?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface DiscoverResult {
|
|
56
|
+
agents: RegistryEntry[];
|
|
57
|
+
}
|
|
58
|
+
export type PluginLogger = {
|
|
59
|
+
debug?: (message: string) => void;
|
|
60
|
+
info: (message: string) => void;
|
|
61
|
+
warn: (message: string) => void;
|
|
62
|
+
error: (message: string) => void;
|
|
63
|
+
};
|
|
64
|
+
export type HookAgentContext = {
|
|
65
|
+
agentId?: string;
|
|
66
|
+
sessionId?: string;
|
|
67
|
+
sessionKey?: string;
|
|
68
|
+
};
|
|
69
|
+
export type OpenClawPluginApi = {
|
|
70
|
+
pluginConfig?: unknown;
|
|
71
|
+
logger: PluginLogger;
|
|
72
|
+
registerTool: (tool: {
|
|
73
|
+
name: string;
|
|
74
|
+
label: string;
|
|
75
|
+
description: string;
|
|
76
|
+
parameters: unknown;
|
|
77
|
+
execute: (_toolCallId: string, params: Record<string, unknown>) => Promise<unknown>;
|
|
78
|
+
}, opts?: {
|
|
79
|
+
name?: string;
|
|
80
|
+
names?: string[];
|
|
81
|
+
}) => void;
|
|
82
|
+
registerService: (service: {
|
|
83
|
+
id: string;
|
|
84
|
+
start: (ctx?: unknown) => void | Promise<void>;
|
|
85
|
+
stop?: (ctx?: unknown) => void | Promise<void>;
|
|
86
|
+
}) => void;
|
|
87
|
+
on: (hookName: string, handler: (event: unknown, ctx?: HookAgentContext) => unknown, opts?: {
|
|
88
|
+
priority?: number;
|
|
89
|
+
}) => void;
|
|
90
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,101 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-bridge",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"role": {
|
|
6
|
-
"label": "Role",
|
|
7
|
-
"help": "normal = isolated workspace access only; superuser = cross-gateway access"
|
|
8
|
-
},
|
|
9
|
-
"agentId": {
|
|
10
|
-
"label": "Agent ID",
|
|
11
|
-
"help": "Unique identifier for this agent (e.g., pm, bot1, main)"
|
|
12
|
-
},
|
|
13
|
-
"agentName": {
|
|
14
|
-
"label": "Agent Display Name",
|
|
15
|
-
"help": "Human-readable name (e.g., PM Bot, Director Ma)"
|
|
16
|
-
},
|
|
17
|
-
"registry.baseUrl": {
|
|
18
|
-
"label": "OpenViking URL",
|
|
19
|
-
"placeholder": "http://your-server:1933",
|
|
20
|
-
"help": "OpenViking server for gateway registry"
|
|
21
|
-
},
|
|
22
|
-
"registry.apiKey": {
|
|
23
|
-
"label": "OpenViking API Key",
|
|
24
|
-
"sensitive": true
|
|
25
|
-
},
|
|
26
|
-
"fileRelay.baseUrl": {
|
|
27
|
-
"label": "FileRelay URL",
|
|
28
|
-
"placeholder": "http://your-server:3080",
|
|
29
|
-
"help": "FileRelay server for cross-machine file transfer"
|
|
30
|
-
},
|
|
31
|
-
"fileRelay.apiKey": {
|
|
32
|
-
"label": "FileRelay API Key",
|
|
33
|
-
"sensitive": true
|
|
34
|
-
},
|
|
35
|
-
"description": {
|
|
36
|
-
"label": "Agent Description",
|
|
37
|
-
"help": "Short description of what this agent does (shown on Hub dashboard)"
|
|
38
|
-
},
|
|
39
|
-
"supportsVision": {
|
|
40
|
-
"label": "Supports Vision",
|
|
41
|
-
"help": "Whether the agent's model can process images (auto-detected if not set)"
|
|
42
|
-
},
|
|
43
|
-
"localManager.enabled": {
|
|
44
|
-
"label": "Enable Local Manager",
|
|
45
|
-
"help": "Start PM2 process manager for this machine"
|
|
46
|
-
},
|
|
47
|
-
"localManager.hubUrl": {
|
|
48
|
-
"label": "Hub URL for Local Manager",
|
|
49
|
-
"placeholder": "http://your-server:3080"
|
|
50
|
-
},
|
|
51
|
-
"localManager.managerPass": {
|
|
52
|
-
"label": "Manager Password",
|
|
53
|
-
"sensitive": true
|
|
54
|
-
}
|
|
55
|
-
},
|
|
3
|
+
"name": "OpenClaw Bridge",
|
|
4
|
+
"description": "Cross-gateway communication, file transfer, message relay, session handoff, and local process management",
|
|
56
5
|
"configSchema": {
|
|
57
6
|
"type": "object",
|
|
58
|
-
"
|
|
59
|
-
"required": ["role", "agentId", "agentName", "registry"],
|
|
7
|
+
"required": ["role", "agentId", "agentName"],
|
|
60
8
|
"properties": {
|
|
61
9
|
"role": { "type": "string", "enum": ["normal", "superuser"] },
|
|
62
10
|
"agentId": { "type": "string" },
|
|
63
|
-
"agentName": { "type": "string" }
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"required": ["baseUrl"],
|
|
67
|
-
"properties": {
|
|
68
|
-
"provider": { "type": "string" },
|
|
69
|
-
"baseUrl": { "type": "string" },
|
|
70
|
-
"apiKey": { "type": "string" }
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
"fileRelay": {
|
|
74
|
-
"type": "object",
|
|
75
|
-
"properties": {
|
|
76
|
-
"baseUrl": { "type": "string" },
|
|
77
|
-
"apiKey": { "type": "string" }
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
"messageRelay": {
|
|
81
|
-
"type": "object",
|
|
82
|
-
"properties": {
|
|
83
|
-
"url": { "type": "string" },
|
|
84
|
-
"apiKey": { "type": "string" }
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
"heartbeatIntervalMs": { "type": "number" },
|
|
88
|
-
"offlineThresholdMs": { "type": "number" },
|
|
89
|
-
"description": { "type": "string" },
|
|
90
|
-
"supportsVision": { "type": "boolean" },
|
|
91
|
-
"localManager": {
|
|
92
|
-
"type": "object",
|
|
93
|
-
"properties": {
|
|
94
|
-
"enabled": { "type": "boolean" },
|
|
95
|
-
"hubUrl": { "type": "string" },
|
|
96
|
-
"managerPass": { "type": "string" }
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
11
|
+
"agentName": { "type": "string" }
|
|
12
|
+
},
|
|
13
|
+
"additionalProperties": true
|
|
100
14
|
}
|
|
101
15
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"author": "Bill Zhao (https://www.linkedin.com/in/billzhaodi/)",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "OpenClaw plugin for cross-gateway communication — agent discovery, file transfer, real-time messaging, session handoff, and local process management. Install as plugin: openclaw plugins install openclaw-bridge",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"main": "
|
|
7
|
+
"main": "dist/index.js",
|
|
8
8
|
"bin": {
|
|
9
|
-
"openclaw-bridge": "
|
|
9
|
+
"openclaw-bridge": "dist/cli.js"
|
|
10
10
|
},
|
|
11
11
|
"openclaw": {
|
|
12
12
|
"extensions": [
|
|
13
|
-
"./
|
|
13
|
+
"./dist/index.js"
|
|
14
14
|
]
|
|
15
15
|
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"openclaw.plugin.json",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
16
25
|
"dependencies": {
|
|
17
26
|
"@sinclair/typebox": "^0.34.0",
|
|
18
27
|
"ws": "^8.20.0"
|
|
@@ -23,6 +32,7 @@
|
|
|
23
32
|
},
|
|
24
33
|
"keywords": [
|
|
25
34
|
"openclaw",
|
|
35
|
+
"openclaw-plugin",
|
|
26
36
|
"bridge",
|
|
27
37
|
"multi-agent",
|
|
28
38
|
"gateway",
|