@efengx/openclaw-channel-dragon 0.4.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/bridge/BridgeComponent.d.ts +1 -0
- package/dist/components/bridge/BridgeComponent.js +2 -1
- package/dist/components/http/HttpComponent.d.ts +1 -1
- package/dist/components/sync/PollingComponent.d.ts +3 -3
- package/dist/components/sync/PollingComponent.js +28 -101
- package/dist/components/sync/SseComponent.d.ts +21 -0
- package/dist/components/sync/SseComponent.js +104 -0
- package/dist/index.js +13 -22
- package/dist/ws.d.ts +1 -0
- package/dist/ws.js +2 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -6
|
@@ -6,8 +6,9 @@ export class BridgeComponent {
|
|
|
6
6
|
this.options = options;
|
|
7
7
|
}
|
|
8
8
|
async start() {
|
|
9
|
-
const { port, token, agentId, gatewayPort, gatewayToken, logger } = this.options;
|
|
9
|
+
const { host, port, token, agentId, gatewayPort, gatewayToken, logger } = this.options;
|
|
10
10
|
this.client = connectToDragonBridge({
|
|
11
|
+
host,
|
|
11
12
|
port,
|
|
12
13
|
token,
|
|
13
14
|
onConnected: () => {
|
|
@@ -6,16 +6,16 @@ export declare class PollingComponent implements IComponent {
|
|
|
6
6
|
private channel;
|
|
7
7
|
private options;
|
|
8
8
|
private pollInterval;
|
|
9
|
+
private heartbeatInterval;
|
|
9
10
|
constructor(http: HttpComponent, channel: ChannelComponent, options: {
|
|
10
11
|
agentId: string;
|
|
11
12
|
accountId: string;
|
|
13
|
+
channelRuntime?: any;
|
|
12
14
|
abortSignal: AbortSignal;
|
|
13
15
|
logger?: any;
|
|
14
16
|
});
|
|
15
17
|
start(): Promise<void>;
|
|
16
18
|
stop(): Promise<void>;
|
|
17
19
|
private consumePendingMessages;
|
|
18
|
-
private
|
|
19
|
-
private connectOnce;
|
|
20
|
-
private connectHttp2;
|
|
20
|
+
private sendHeartbeat;
|
|
21
21
|
}
|
|
@@ -1,26 +1,31 @@
|
|
|
1
|
-
import * as http2 from "node:http2";
|
|
2
1
|
export class PollingComponent {
|
|
3
2
|
http;
|
|
4
3
|
channel;
|
|
5
4
|
options;
|
|
6
5
|
pollInterval = null;
|
|
6
|
+
heartbeatInterval = null;
|
|
7
7
|
constructor(http, channel, options) {
|
|
8
8
|
this.http = http;
|
|
9
9
|
this.channel = channel;
|
|
10
10
|
this.options = options;
|
|
11
11
|
}
|
|
12
12
|
async start() {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// 1. Initial consume
|
|
14
|
+
void this.consumePendingMessages();
|
|
15
|
+
// 2. Periodic recovery polling (every 60s)
|
|
15
16
|
this.pollInterval = setInterval(() => {
|
|
16
17
|
void this.consumePendingMessages();
|
|
17
18
|
}, 60_000);
|
|
19
|
+
// 3. Heartbeat for OpenClaw keep-alive (every 30s)
|
|
20
|
+
this.heartbeatInterval = setInterval(() => {
|
|
21
|
+
void this.sendHeartbeat();
|
|
22
|
+
}, 30_000);
|
|
18
23
|
}
|
|
19
24
|
async stop() {
|
|
20
|
-
if (this.pollInterval)
|
|
25
|
+
if (this.pollInterval)
|
|
21
26
|
clearInterval(this.pollInterval);
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
if (this.heartbeatInterval)
|
|
28
|
+
clearInterval(this.heartbeatInterval);
|
|
24
29
|
}
|
|
25
30
|
async consumePendingMessages() {
|
|
26
31
|
const { agentId, logger } = this.options;
|
|
@@ -30,7 +35,7 @@ export class PollingComponent {
|
|
|
30
35
|
const data = (await res.json());
|
|
31
36
|
const messages = data.messages || [];
|
|
32
37
|
if (messages.length > 0) {
|
|
33
|
-
logger?.
|
|
38
|
+
logger?.debug?.(`[Dragon Plugin] [Recovery] Polled ${messages.length} pending messages.`);
|
|
34
39
|
for (const m of messages) {
|
|
35
40
|
await this.channel.deliverToOpenClaw(String(m.content || ''), String(m.sessionId || 'default'), m.modelId, m.attachments, m.id);
|
|
36
41
|
}
|
|
@@ -38,104 +43,26 @@ export class PollingComponent {
|
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
catch (e) {
|
|
41
|
-
|
|
46
|
+
// Quiet fail for recovery polling
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
|
-
async
|
|
45
|
-
const { logger,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
logger?.error?.(`dragon channel: SSE loop error: ${e?.message || e}`);
|
|
57
|
-
const delayMs = Math.min(10_000, 500 + attempt * 500);
|
|
58
|
-
await new Promise((r) => setTimeout(r, delayMs));
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
async connectOnce() {
|
|
63
|
-
const { agentId, logger, abortSignal } = this.options;
|
|
64
|
-
const ssePath = `/api/agents/events`;
|
|
65
|
-
void this.consumePendingMessages();
|
|
66
|
-
const handleSseText = async (chunkText, bufRef) => {
|
|
67
|
-
bufRef.buf += chunkText;
|
|
68
|
-
let idx;
|
|
69
|
-
while ((idx = bufRef.buf.indexOf('\n\n')) >= 0) {
|
|
70
|
-
const raw = bufRef.buf.slice(0, idx);
|
|
71
|
-
bufRef.buf = bufRef.buf.slice(idx + 2);
|
|
72
|
-
if (!raw.trim() || raw.trim().startsWith(':'))
|
|
73
|
-
continue;
|
|
74
|
-
const dataLines = raw
|
|
75
|
-
.split('\n')
|
|
76
|
-
.filter((l) => l.startsWith('data:'))
|
|
77
|
-
.map((l) => l.slice('data:'.length).trim());
|
|
78
|
-
if (!dataLines.length)
|
|
79
|
-
continue;
|
|
80
|
-
const dataStr = dataLines.join('\n');
|
|
81
|
-
try {
|
|
82
|
-
const evt = JSON.parse(dataStr);
|
|
83
|
-
if (evt?.type === 'WORKBENCH_MESSAGE' && evt.agentId === agentId) {
|
|
84
|
-
await this.channel.deliverToOpenClaw(String(evt?.payload?.content || ''), String(evt?.payload?.sessionId || 'default'), evt?.payload?.modelId, evt?.payload?.attachments, evt?.payload?.id);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (e) { }
|
|
49
|
+
async sendHeartbeat() {
|
|
50
|
+
const { agentId, logger, channelRuntime } = this.options;
|
|
51
|
+
try {
|
|
52
|
+
// Orchestrator Heartbeat
|
|
53
|
+
await this.http.fetch(`/api/agents/${agentId}/telemetry`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify({ type: "plugin_heartbeat", value: { timestamp: Date.now() } }),
|
|
57
|
+
});
|
|
58
|
+
// OpenClaw Runtime Activity Report (if supported by SDK)
|
|
59
|
+
if (typeof channelRuntime?.reportActivity === 'function') {
|
|
60
|
+
channelRuntime.reportActivity();
|
|
88
61
|
}
|
|
89
|
-
|
|
90
|
-
const res = await this.http.fetch(ssePath, {
|
|
91
|
-
signal: abortSignal,
|
|
92
|
-
headers: { Accept: 'text/event-stream' },
|
|
93
|
-
});
|
|
94
|
-
if (res.status === 404) {
|
|
95
|
-
return;
|
|
62
|
+
logger?.debug?.(`[Dragon Plugin] Heartbeat sent for ${agentId}`);
|
|
96
63
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const body = res.body;
|
|
100
|
-
const reader = body.getReader();
|
|
101
|
-
const decoder = new TextDecoder();
|
|
102
|
-
const bufRef = { buf: '' };
|
|
103
|
-
while (!abortSignal.aborted) {
|
|
104
|
-
const { value, done } = await reader.read();
|
|
105
|
-
if (done)
|
|
106
|
-
break;
|
|
107
|
-
await handleSseText(decoder.decode(value, { stream: true }), bufRef);
|
|
64
|
+
catch (e) {
|
|
65
|
+
logger?.warn?.(`[Dragon Plugin] Heartbeat failed: ${e.message}`);
|
|
108
66
|
}
|
|
109
67
|
}
|
|
110
|
-
async connectHttp2(sseUrl, handleSseText) {
|
|
111
|
-
const { logger, abortSignal } = this.options;
|
|
112
|
-
const u = new URL(sseUrl);
|
|
113
|
-
const origin = `${u.protocol}//${u.host}`;
|
|
114
|
-
const pathWithQuery = `${u.pathname}${u.search || ''}`;
|
|
115
|
-
const bufRef = { buf: '' };
|
|
116
|
-
return new Promise((resolve, reject) => {
|
|
117
|
-
const session = http2.connect(origin);
|
|
118
|
-
let done = false;
|
|
119
|
-
const finish = (err) => {
|
|
120
|
-
if (done)
|
|
121
|
-
return;
|
|
122
|
-
done = true;
|
|
123
|
-
try {
|
|
124
|
-
session.close();
|
|
125
|
-
}
|
|
126
|
-
catch { }
|
|
127
|
-
if (err)
|
|
128
|
-
reject(err);
|
|
129
|
-
else
|
|
130
|
-
resolve();
|
|
131
|
-
};
|
|
132
|
-
abortSignal?.addEventListener?.('abort', () => finish({ name: 'AbortError' }));
|
|
133
|
-
session.on('error', (err) => finish(err));
|
|
134
|
-
const req = session.request({ ':method': 'GET', ':path': pathWithQuery, accept: 'text/event-stream' });
|
|
135
|
-
req.setEncoding('utf8');
|
|
136
|
-
req.on('data', (chunk) => handleSseText(chunk, bufRef));
|
|
137
|
-
req.on('end', () => finish());
|
|
138
|
-
req.end();
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
68
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IComponent } from "../../core/IComponent.js";
|
|
2
|
+
import { HttpComponent } from "../http/HttpComponent.js";
|
|
3
|
+
import { ChannelComponent } from "../channel/ChannelComponent.js";
|
|
4
|
+
export declare class SseComponent implements IComponent {
|
|
5
|
+
private http;
|
|
6
|
+
private channel;
|
|
7
|
+
private options;
|
|
8
|
+
private eventSource;
|
|
9
|
+
private shouldReconnect;
|
|
10
|
+
private reconnectTimer;
|
|
11
|
+
constructor(http: HttpComponent, channel: ChannelComponent, options: {
|
|
12
|
+
agentId: string;
|
|
13
|
+
logger?: any;
|
|
14
|
+
});
|
|
15
|
+
start(): Promise<void>;
|
|
16
|
+
stop(): Promise<void>;
|
|
17
|
+
private connect;
|
|
18
|
+
private startSseReader;
|
|
19
|
+
private handleEvent;
|
|
20
|
+
private scheduleReconnect;
|
|
21
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export class SseComponent {
|
|
2
|
+
http;
|
|
3
|
+
channel;
|
|
4
|
+
options;
|
|
5
|
+
eventSource;
|
|
6
|
+
shouldReconnect = true;
|
|
7
|
+
reconnectTimer;
|
|
8
|
+
constructor(http, channel, options) {
|
|
9
|
+
this.http = http;
|
|
10
|
+
this.channel = channel;
|
|
11
|
+
this.options = options;
|
|
12
|
+
}
|
|
13
|
+
async start() {
|
|
14
|
+
this.connect();
|
|
15
|
+
}
|
|
16
|
+
async stop() {
|
|
17
|
+
this.shouldReconnect = false;
|
|
18
|
+
if (this.reconnectTimer)
|
|
19
|
+
clearTimeout(this.reconnectTimer);
|
|
20
|
+
this.eventSource?.close();
|
|
21
|
+
}
|
|
22
|
+
connect() {
|
|
23
|
+
if (!this.shouldReconnect)
|
|
24
|
+
return;
|
|
25
|
+
const { agentId, logger } = this.options;
|
|
26
|
+
const url = `${this.http.options.baseURL}/api/agents/events`;
|
|
27
|
+
logger?.info?.(`[Dragon Plugin] Connecting to Orchestrator SSE: ${url}`);
|
|
28
|
+
try {
|
|
29
|
+
// In Node 22, we might need a fetch-based SSE or a library,
|
|
30
|
+
// but for simplicity we'll use the same pattern as Bridge if possible.
|
|
31
|
+
// Actually, standard EventSource is not in Node globals yet.
|
|
32
|
+
// We'll use a simple fetch-based stream reader.
|
|
33
|
+
this.startSseReader(url);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
logger?.error?.(`[Dragon Plugin] SSE Init Failed: ${err.message}`);
|
|
37
|
+
this.scheduleReconnect();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async startSseReader(url) {
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(url, {
|
|
43
|
+
headers: {
|
|
44
|
+
'Accept': 'text/event-stream',
|
|
45
|
+
'Authorization': `Bearer ${this.http.options.authToken || ''}`
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`SSE connection failed with status ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
const reader = response.body?.getReader();
|
|
52
|
+
if (!reader)
|
|
53
|
+
throw new Error("Response body is null");
|
|
54
|
+
this.options.logger?.debug?.(`[Dragon Plugin] SSE Stream Established.`);
|
|
55
|
+
const decoder = new TextDecoder();
|
|
56
|
+
let buffer = "";
|
|
57
|
+
while (this.shouldReconnect) {
|
|
58
|
+
const { value, done } = await reader.read();
|
|
59
|
+
if (done)
|
|
60
|
+
break;
|
|
61
|
+
buffer += decoder.decode(value, { stream: true });
|
|
62
|
+
const lines = buffer.split("\n");
|
|
63
|
+
buffer = lines.pop() || "";
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
if (line.startsWith("data: ")) {
|
|
66
|
+
try {
|
|
67
|
+
const data = JSON.parse(line.slice(6));
|
|
68
|
+
this.handleEvent(data);
|
|
69
|
+
}
|
|
70
|
+
catch (e) { }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (this.shouldReconnect) {
|
|
77
|
+
this.options.logger?.warn?.(`[Dragon Plugin] SSE Stream Disconnected: ${err.message}`);
|
|
78
|
+
this.scheduleReconnect();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
handleEvent(data) {
|
|
83
|
+
const { agentId, logger } = this.options;
|
|
84
|
+
// 1. Filter for events belonging to this agent
|
|
85
|
+
if (data.agentId && data.agentId !== agentId)
|
|
86
|
+
return;
|
|
87
|
+
// 2. Handle specific types
|
|
88
|
+
if (data.type === 'WORKBENCH_MESSAGE') {
|
|
89
|
+
const { content, sessionId, attachments, id } = data.payload || {};
|
|
90
|
+
logger?.info?.(`[Dragon Plugin] Received Workbench Message (via SSE): ${content?.substring(0, 20)}...`);
|
|
91
|
+
this.channel.deliverToOpenClaw(content, sessionId, undefined, attachments, id);
|
|
92
|
+
}
|
|
93
|
+
else if (data.type === 'FETCH_HISTORY') {
|
|
94
|
+
const { sessionId } = data.payload || {};
|
|
95
|
+
logger?.info?.(`[Dragon Plugin] Triggering history sync for session: ${sessionId}`);
|
|
96
|
+
// Handle history sync if needed, but deliverToOpenClaw usually handles normal chat
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
scheduleReconnect() {
|
|
100
|
+
if (this.reconnectTimer)
|
|
101
|
+
clearTimeout(this.reconnectTimer);
|
|
102
|
+
this.reconnectTimer = setTimeout(() => this.connect(), 5000);
|
|
103
|
+
}
|
|
104
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { PollingComponent } from "./components/sync/PollingComponent.js";
|
|
|
7
7
|
import { TelemetryComponent } from "./components/telemetry/TelemetryComponent.js";
|
|
8
8
|
import { HttpComponent } from "./components/http/HttpComponent.js";
|
|
9
9
|
import { ChannelComponent } from "./components/channel/ChannelComponent.js";
|
|
10
|
+
import { SseComponent } from "./components/sync/SseComponent.js";
|
|
10
11
|
const channelId = "dragon";
|
|
11
12
|
let cachedRuntime;
|
|
12
13
|
const containers = new Map();
|
|
@@ -24,6 +25,7 @@ async function getOrCreateContainer(account, ctx) {
|
|
|
24
25
|
}));
|
|
25
26
|
const telemetry = container.register('telemetry', new TelemetryComponent(http, account.agentId));
|
|
26
27
|
const bridge = container.register('bridge', new BridgeComponent({
|
|
28
|
+
host: account.bridgeHost,
|
|
27
29
|
port: parseInt(account.bridgePort || "18799", 10),
|
|
28
30
|
token: account.bridgeToken || "",
|
|
29
31
|
agentId: account.agentId,
|
|
@@ -39,10 +41,15 @@ async function getOrCreateContainer(account, ctx) {
|
|
|
39
41
|
cfg: ctx.cfg,
|
|
40
42
|
logger
|
|
41
43
|
}, bridge, telemetry));
|
|
42
|
-
// 3.
|
|
44
|
+
// 3. Sync Infrastructure (SSE + Polling)
|
|
45
|
+
container.register('sse', new SseComponent(http, channel, {
|
|
46
|
+
agentId: account.agentId,
|
|
47
|
+
logger
|
|
48
|
+
}));
|
|
43
49
|
container.register('polling', new PollingComponent(http, channel, {
|
|
44
50
|
agentId: account.agentId,
|
|
45
51
|
accountId: account.accountId,
|
|
52
|
+
channelRuntime: ctx.channelRuntime,
|
|
46
53
|
abortSignal: ctx.abortSignal,
|
|
47
54
|
logger
|
|
48
55
|
}));
|
|
@@ -59,13 +66,18 @@ const base = createChannelPluginBase({
|
|
|
59
66
|
finalize: async () => ({ ok: true })
|
|
60
67
|
},
|
|
61
68
|
config: {
|
|
69
|
+
listAccountIds: (cfg) => {
|
|
70
|
+
return Object.keys(cfg.channels?.[channelId]?.accounts || { "default": {} });
|
|
71
|
+
},
|
|
62
72
|
resolveAccount: (cfg, accountId = "default") => {
|
|
63
73
|
const accountConfig = cfg.channels?.[channelId]?.accounts?.[accountId] || {};
|
|
74
|
+
const host = accountConfig.bridgeHost || accountConfig.host;
|
|
64
75
|
return {
|
|
65
76
|
accountId,
|
|
66
77
|
agentId: accountConfig.agentId || accountId,
|
|
67
78
|
orchestratorUrl: accountConfig.orchestratorUrl || "http://127.0.0.1:4000",
|
|
68
79
|
orchestratorAuthToken: accountConfig.orchestratorAuthToken || accountConfig.authToken,
|
|
80
|
+
bridgeHost: host,
|
|
69
81
|
bridgePort: accountConfig.bridgePort,
|
|
70
82
|
bridgeToken: accountConfig.bridgeToken,
|
|
71
83
|
gatewayPort: accountConfig.gatewayPort,
|
|
@@ -96,33 +108,12 @@ const plugin = createChatChannelPlugin({
|
|
|
96
108
|
},
|
|
97
109
|
}),
|
|
98
110
|
},
|
|
99
|
-
config: {
|
|
100
|
-
get: (cfg) => cfg.channels?.[channelId] || {},
|
|
101
|
-
set: (cfg, value) => {
|
|
102
|
-
cfg.channels = cfg.channels || {};
|
|
103
|
-
cfg.channels[channelId] = value;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
111
|
});
|
|
107
112
|
const entry = defineChannelPluginEntry({
|
|
108
113
|
id: channelId,
|
|
109
114
|
name: "Dragon Workbench Channel",
|
|
110
115
|
description: "Connect OpenClaw to the Dragon agent-client workbench chat.",
|
|
111
116
|
plugin,
|
|
112
|
-
config: {
|
|
113
|
-
get: (cfg) => cfg.channels?.[channelId] || {},
|
|
114
|
-
set: (cfg, value) => {
|
|
115
|
-
cfg.channels = cfg.channels || {};
|
|
116
|
-
cfg.channels[channelId] = value;
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
configHelpers: {
|
|
120
|
-
get: (cfg) => cfg.channels?.[channelId] || {},
|
|
121
|
-
set: (cfg, value) => {
|
|
122
|
-
cfg.channels = cfg.channels || {};
|
|
123
|
-
cfg.channels[channelId] = value;
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
117
|
registerFull(api) {
|
|
127
118
|
cachedRuntime = api.runtime;
|
|
128
119
|
const agentEventHandler = api.runtime?.events?.onAgentEvent || InfraRuntime.onAgentEvent;
|
package/dist/ws.d.ts
CHANGED
package/dist/ws.js
CHANGED
|
@@ -6,7 +6,8 @@ export function connectToDragonBridge(params) {
|
|
|
6
6
|
if (!shouldReconnect)
|
|
7
7
|
return;
|
|
8
8
|
// Node 22 global WebSocket
|
|
9
|
-
const
|
|
9
|
+
const host = params.host || '127.0.0.1';
|
|
10
|
+
const url = `ws://${host}:${params.port}/__dragon/ws?token=${params.token || ""}`;
|
|
10
11
|
console.log(`[Dragon Plugin] Connecting to bridge at ${url}...`);
|
|
11
12
|
try {
|
|
12
13
|
ws = new global.WebSocket(url);
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@efengx/openclaw-channel-dragon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Dragon workbench channel for OpenClaw",
|
|
5
5
|
"author": "feng xiang <ofengx@gmail.com>",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"openclaw.plugin.json",
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"lint": "echo \"(skip)\""
|
|
17
|
+
},
|
|
14
18
|
"dependencies": {
|
|
15
19
|
"openclaw": "^2026.4.12"
|
|
16
20
|
},
|
|
@@ -32,9 +36,5 @@
|
|
|
32
36
|
},
|
|
33
37
|
"publishConfig": {
|
|
34
38
|
"access": "public"
|
|
35
|
-
},
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "tsc -p tsconfig.json",
|
|
38
|
-
"lint": "echo \"(skip)\""
|
|
39
39
|
}
|
|
40
|
-
}
|
|
40
|
+
}
|