@phronesis-io/openclaw-eigenflux 0.0.1 → 0.0.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 +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/notifier.d.ts +1 -17
- package/dist/notifier.d.ts.map +1 -1
- package/dist/notifier.js +1 -94
- package/dist/notifier.js.map +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -2
- package/src/agent-prompt-templates.ts +0 -91
- package/src/config.test.ts +0 -188
- package/src/config.ts +0 -410
- package/src/credentials-loader.test.ts +0 -78
- package/src/credentials-loader.ts +0 -121
- package/src/gateway-rpc-client.test.ts +0 -190
- package/src/gateway-rpc-client.ts +0 -373
- package/src/index.integration.test.ts +0 -437
- package/src/index.test.ts +0 -454
- package/src/index.ts +0 -758
- package/src/logger.ts +0 -27
- package/src/notification-route-resolver.test.ts +0 -136
- package/src/notification-route-resolver.ts +0 -430
- package/src/notifier.test.ts +0 -374
- package/src/notifier.ts +0 -558
- package/src/openclaw-plugin-sdk.d.ts +0 -121
- package/src/pm-polling-client.test.ts +0 -390
- package/src/pm-polling-client.ts +0 -257
- package/src/polling-client.test.ts +0 -279
- package/src/polling-client.ts +0 -283
- package/src/session-route-memory.ts +0 -106
package/src/notifier.ts
DELETED
|
@@ -1,558 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
4
|
-
import { type NotificationRouteOverrides } from './config';
|
|
5
|
-
import { OpenClawGatewayRpcClient } from './gateway-rpc-client';
|
|
6
|
-
import { Logger } from './logger';
|
|
7
|
-
import {
|
|
8
|
-
resolveNotificationRoute,
|
|
9
|
-
type NotificationRouteConfig,
|
|
10
|
-
type ResolvedNotificationRoute,
|
|
11
|
-
} from './notification-route-resolver';
|
|
12
|
-
import { writeStoredNotificationRoute } from './session-route-memory';
|
|
13
|
-
|
|
14
|
-
const COMMAND_TIMEOUT_MS = 15000;
|
|
15
|
-
const HEARTBEAT_REASON = 'plugin:eigenflux';
|
|
16
|
-
|
|
17
|
-
export type EigenFluxNotifierConfig = {
|
|
18
|
-
gatewayUrl: string;
|
|
19
|
-
gatewayToken?: string;
|
|
20
|
-
workdir?: string;
|
|
21
|
-
sessionKey: string;
|
|
22
|
-
agentId: string;
|
|
23
|
-
replyChannel?: string;
|
|
24
|
-
replyTo?: string;
|
|
25
|
-
replyAccountId?: string;
|
|
26
|
-
openclawCliBin: string;
|
|
27
|
-
sessionStorePath?: string;
|
|
28
|
-
routeOverrides?: NotificationRouteOverrides;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
type GatewayRpcClientLike = {
|
|
32
|
-
sendAgentMessage(message: string): Promise<{ sessionKey: string; runId: string }>;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
type CreateGatewayRpcClient = (
|
|
36
|
-
config: EigenFluxNotifierConfig & ResolvedNotificationRoute,
|
|
37
|
-
logger: Logger
|
|
38
|
-
) => GatewayRpcClientLike;
|
|
39
|
-
|
|
40
|
-
type CommandRunner = (
|
|
41
|
-
argv: string[],
|
|
42
|
-
options: { timeoutMs: number }
|
|
43
|
-
) => Promise<{ code: number | null; stdout?: string; stderr?: string }>;
|
|
44
|
-
|
|
45
|
-
type SpawnRunner = (argv: string[], timeoutMs: number) => Promise<NotifyAttemptResult>;
|
|
46
|
-
|
|
47
|
-
type NotifyAttemptResult =
|
|
48
|
-
| {
|
|
49
|
-
ok: true;
|
|
50
|
-
mode: string;
|
|
51
|
-
sessionKey?: string;
|
|
52
|
-
runId?: string;
|
|
53
|
-
detail?: string;
|
|
54
|
-
}
|
|
55
|
-
| {
|
|
56
|
-
ok: false;
|
|
57
|
-
mode: string;
|
|
58
|
-
error: string;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export class EigenFluxNotifier {
|
|
62
|
-
private readonly api: OpenClawPluginApi;
|
|
63
|
-
private readonly logger: Logger;
|
|
64
|
-
private readonly config: EigenFluxNotifierConfig;
|
|
65
|
-
private readonly createGatewayRpcClient: CreateGatewayRpcClient;
|
|
66
|
-
private readonly spawnRunner: SpawnRunner;
|
|
67
|
-
|
|
68
|
-
constructor(
|
|
69
|
-
api: OpenClawPluginApi,
|
|
70
|
-
logger: Logger,
|
|
71
|
-
config: EigenFluxNotifierConfig,
|
|
72
|
-
createGatewayRpcClient: CreateGatewayRpcClient = createDefaultGatewayRpcClient,
|
|
73
|
-
spawnRunner: SpawnRunner = runSpawnCommand
|
|
74
|
-
) {
|
|
75
|
-
this.api = api;
|
|
76
|
-
this.logger = logger;
|
|
77
|
-
this.config = config;
|
|
78
|
-
this.createGatewayRpcClient = createGatewayRpcClient;
|
|
79
|
-
this.spawnRunner = spawnRunner;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async deliver(message: string): Promise<boolean> {
|
|
83
|
-
const route = this.resolveRoute();
|
|
84
|
-
const attempts: Array<() => Promise<NotifyAttemptResult>> = [
|
|
85
|
-
() => this.tryNotifyViaRuntimeSubagent(message, route),
|
|
86
|
-
() => this.tryNotifyViaGatewayRpcAgent(message, route),
|
|
87
|
-
() => this.tryNotifyViaRuntimeCommandAgent(message, route),
|
|
88
|
-
() => this.tryNotifyViaSpawnAgent(message, route),
|
|
89
|
-
() => this.tryNotifyViaRuntimeHeartbeat(message, route),
|
|
90
|
-
() => this.tryNotifyViaRuntimeCommandHeartbeat(message),
|
|
91
|
-
() => this.tryNotifyViaSpawnHeartbeat(message),
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
const errors: string[] = [];
|
|
95
|
-
|
|
96
|
-
for (const attempt of attempts) {
|
|
97
|
-
const result = await attempt();
|
|
98
|
-
if (result.ok) {
|
|
99
|
-
this.rememberRoute({
|
|
100
|
-
sessionKey: result.sessionKey ?? route.sessionKey,
|
|
101
|
-
agentId: route.agentId,
|
|
102
|
-
replyChannel: route.replyChannel,
|
|
103
|
-
replyTo: route.replyTo,
|
|
104
|
-
replyAccountId: route.replyAccountId,
|
|
105
|
-
});
|
|
106
|
-
this.logDispatch(result);
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
errors.push(`${result.mode}: ${result.error}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
this.logger.error(`Failed to deliver notification: ${errors.join(' | ')}`);
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async deliverWithSubagent(message: string): Promise<boolean> {
|
|
117
|
-
const route = this.resolveRoute();
|
|
118
|
-
const result = await this.tryNotifyViaRuntimeSubagent(message, route);
|
|
119
|
-
if (!result.ok) {
|
|
120
|
-
this.logger.warn(`runtime.subagent test dispatch failed: ${result.error}`);
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
this.rememberRoute(route);
|
|
124
|
-
this.logDispatch(result);
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private async tryNotifyViaRuntimeSubagent(
|
|
129
|
-
message: string,
|
|
130
|
-
route: ResolvedNotificationRoute
|
|
131
|
-
): Promise<NotifyAttemptResult> {
|
|
132
|
-
const runtimeSubagent = (this.api.runtime as
|
|
133
|
-
| {
|
|
134
|
-
subagent?: {
|
|
135
|
-
run?: (params: {
|
|
136
|
-
sessionKey: string;
|
|
137
|
-
message: string;
|
|
138
|
-
deliver?: boolean;
|
|
139
|
-
idempotencyKey?: string;
|
|
140
|
-
}) => Promise<{ runId: string }>;
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
| undefined)?.subagent;
|
|
144
|
-
|
|
145
|
-
if (!runtimeSubagent || typeof runtimeSubagent.run !== 'function') {
|
|
146
|
-
return {
|
|
147
|
-
ok: false,
|
|
148
|
-
mode: 'runtime.subagent',
|
|
149
|
-
error: 'runtime.subagent.run is unavailable',
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
const result = await runtimeSubagent.run({
|
|
155
|
-
sessionKey: route.sessionKey,
|
|
156
|
-
message,
|
|
157
|
-
deliver: true,
|
|
158
|
-
idempotencyKey: randomUUID(),
|
|
159
|
-
});
|
|
160
|
-
return {
|
|
161
|
-
ok: true,
|
|
162
|
-
mode: 'runtime.subagent',
|
|
163
|
-
sessionKey: route.sessionKey,
|
|
164
|
-
runId: result.runId,
|
|
165
|
-
};
|
|
166
|
-
} catch (error) {
|
|
167
|
-
return {
|
|
168
|
-
ok: false,
|
|
169
|
-
mode: 'runtime.subagent',
|
|
170
|
-
error: formatError(error),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private async tryNotifyViaGatewayRpcAgent(
|
|
176
|
-
message: string,
|
|
177
|
-
route: ResolvedNotificationRoute
|
|
178
|
-
): Promise<NotifyAttemptResult> {
|
|
179
|
-
try {
|
|
180
|
-
const client = this.createGatewayRpcClient(
|
|
181
|
-
{
|
|
182
|
-
...this.config,
|
|
183
|
-
...route,
|
|
184
|
-
},
|
|
185
|
-
this.logger
|
|
186
|
-
);
|
|
187
|
-
const result = await client.sendAgentMessage(message);
|
|
188
|
-
return {
|
|
189
|
-
ok: true,
|
|
190
|
-
mode: 'gateway.rpc.agent',
|
|
191
|
-
sessionKey: result.sessionKey,
|
|
192
|
-
runId: result.runId,
|
|
193
|
-
};
|
|
194
|
-
} catch (error) {
|
|
195
|
-
return {
|
|
196
|
-
ok: false,
|
|
197
|
-
mode: 'gateway.rpc.agent',
|
|
198
|
-
error: formatError(error),
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private async tryNotifyViaRuntimeCommandAgent(
|
|
204
|
-
message: string,
|
|
205
|
-
route: ResolvedNotificationRoute
|
|
206
|
-
): Promise<NotifyAttemptResult> {
|
|
207
|
-
return this.runRuntimeCommand(
|
|
208
|
-
'runtime.command.agent',
|
|
209
|
-
this.buildAgentCliArgs(message, route),
|
|
210
|
-
route
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private async tryNotifyViaSpawnAgent(
|
|
215
|
-
message: string,
|
|
216
|
-
route: ResolvedNotificationRoute
|
|
217
|
-
): Promise<NotifyAttemptResult> {
|
|
218
|
-
return this.runSpawnCommand('spawn.agent', this.buildAgentCliArgs(message, route), route);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private async tryNotifyViaRuntimeHeartbeat(
|
|
222
|
-
message: string,
|
|
223
|
-
route: ResolvedNotificationRoute
|
|
224
|
-
): Promise<NotifyAttemptResult> {
|
|
225
|
-
const runtimeSystem = (this.api.runtime as
|
|
226
|
-
| {
|
|
227
|
-
system?: {
|
|
228
|
-
enqueueSystemEvent?: (
|
|
229
|
-
text: string,
|
|
230
|
-
options: {
|
|
231
|
-
sessionKey: string;
|
|
232
|
-
deliveryContext?: {
|
|
233
|
-
channel?: string;
|
|
234
|
-
to?: string;
|
|
235
|
-
accountId?: string;
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
) => boolean;
|
|
239
|
-
requestHeartbeatNow?: (options: {
|
|
240
|
-
reason?: string;
|
|
241
|
-
coalesceMs?: number;
|
|
242
|
-
agentId?: string;
|
|
243
|
-
sessionKey?: string;
|
|
244
|
-
}) => void;
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
| undefined)?.system;
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
!runtimeSystem ||
|
|
251
|
-
typeof runtimeSystem.enqueueSystemEvent !== 'function' ||
|
|
252
|
-
typeof runtimeSystem.requestHeartbeatNow !== 'function'
|
|
253
|
-
) {
|
|
254
|
-
return {
|
|
255
|
-
ok: false,
|
|
256
|
-
mode: 'runtime.system.heartbeat',
|
|
257
|
-
error: 'runtime.system heartbeat APIs are unavailable',
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
const enqueued = runtimeSystem.enqueueSystemEvent(message, {
|
|
263
|
-
sessionKey: route.sessionKey,
|
|
264
|
-
...(this.resolveHeartbeatDeliveryContext(route)
|
|
265
|
-
? { deliveryContext: this.resolveHeartbeatDeliveryContext(route) }
|
|
266
|
-
: {}),
|
|
267
|
-
});
|
|
268
|
-
runtimeSystem.requestHeartbeatNow({
|
|
269
|
-
reason: HEARTBEAT_REASON,
|
|
270
|
-
coalesceMs: 0,
|
|
271
|
-
agentId: route.agentId,
|
|
272
|
-
sessionKey: route.sessionKey,
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
ok: true,
|
|
277
|
-
mode: 'runtime.system.heartbeat',
|
|
278
|
-
sessionKey: route.sessionKey,
|
|
279
|
-
detail: enqueued ? 'enqueued' : 'duplicate-enqueued',
|
|
280
|
-
};
|
|
281
|
-
} catch (error) {
|
|
282
|
-
return {
|
|
283
|
-
ok: false,
|
|
284
|
-
mode: 'runtime.system.heartbeat',
|
|
285
|
-
error: formatError(error),
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private async tryNotifyViaRuntimeCommandHeartbeat(message: string): Promise<NotifyAttemptResult> {
|
|
291
|
-
return this.runRuntimeCommand(
|
|
292
|
-
'runtime.command.heartbeat',
|
|
293
|
-
this.buildHeartbeatCliArgs(message),
|
|
294
|
-
this.resolveRoute()
|
|
295
|
-
);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private async tryNotifyViaSpawnHeartbeat(message: string): Promise<NotifyAttemptResult> {
|
|
299
|
-
return this.runSpawnCommand(
|
|
300
|
-
'spawn.heartbeat',
|
|
301
|
-
this.buildHeartbeatCliArgs(message),
|
|
302
|
-
this.resolveRoute()
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
private async runRuntimeCommand(
|
|
307
|
-
mode: string,
|
|
308
|
-
argv: string[],
|
|
309
|
-
route: ResolvedNotificationRoute
|
|
310
|
-
): Promise<NotifyAttemptResult> {
|
|
311
|
-
const runtimeCommand = (this.api.runtime as
|
|
312
|
-
| {
|
|
313
|
-
system?: {
|
|
314
|
-
runCommandWithTimeout?: CommandRunner;
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
| undefined)?.system?.runCommandWithTimeout;
|
|
318
|
-
|
|
319
|
-
if (typeof runtimeCommand !== 'function') {
|
|
320
|
-
return {
|
|
321
|
-
ok: false,
|
|
322
|
-
mode,
|
|
323
|
-
error: 'runtime.system.runCommandWithTimeout is unavailable',
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
const result = await runtimeCommand(argv, { timeoutMs: COMMAND_TIMEOUT_MS });
|
|
329
|
-
if (result.code === 0) {
|
|
330
|
-
return {
|
|
331
|
-
ok: true,
|
|
332
|
-
mode,
|
|
333
|
-
sessionKey: route.sessionKey,
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
ok: false,
|
|
339
|
-
mode,
|
|
340
|
-
error: formatCommandFailure(result),
|
|
341
|
-
};
|
|
342
|
-
} catch (error) {
|
|
343
|
-
return {
|
|
344
|
-
ok: false,
|
|
345
|
-
mode,
|
|
346
|
-
error: formatError(error),
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
private async runSpawnCommand(
|
|
352
|
-
mode: string,
|
|
353
|
-
argv: string[],
|
|
354
|
-
route: ResolvedNotificationRoute
|
|
355
|
-
): Promise<NotifyAttemptResult> {
|
|
356
|
-
const [command, ...args] = argv;
|
|
357
|
-
if (!command) {
|
|
358
|
-
return {
|
|
359
|
-
ok: false,
|
|
360
|
-
mode,
|
|
361
|
-
error: 'missing command',
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const result = await this.spawnRunner(argv, COMMAND_TIMEOUT_MS);
|
|
366
|
-
return result.ok
|
|
367
|
-
? {
|
|
368
|
-
...result,
|
|
369
|
-
mode,
|
|
370
|
-
sessionKey: result.sessionKey ?? route.sessionKey,
|
|
371
|
-
}
|
|
372
|
-
: {
|
|
373
|
-
...result,
|
|
374
|
-
mode,
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
private buildAgentCliArgs(message: string, route: ResolvedNotificationRoute): string[] {
|
|
379
|
-
const args = [
|
|
380
|
-
this.config.openclawCliBin,
|
|
381
|
-
'agent',
|
|
382
|
-
'--message',
|
|
383
|
-
message,
|
|
384
|
-
'--agent',
|
|
385
|
-
route.agentId,
|
|
386
|
-
'--deliver',
|
|
387
|
-
];
|
|
388
|
-
|
|
389
|
-
if (route.replyChannel) {
|
|
390
|
-
args.push('--reply-channel', route.replyChannel);
|
|
391
|
-
}
|
|
392
|
-
if (route.replyTo) {
|
|
393
|
-
args.push('--reply-to', route.replyTo);
|
|
394
|
-
}
|
|
395
|
-
if (route.replyAccountId) {
|
|
396
|
-
args.push('--reply-account', route.replyAccountId);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return args;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
private buildHeartbeatCliArgs(message: string): string[] {
|
|
403
|
-
return [
|
|
404
|
-
this.config.openclawCliBin,
|
|
405
|
-
'system',
|
|
406
|
-
'event',
|
|
407
|
-
'--text',
|
|
408
|
-
message,
|
|
409
|
-
'--mode',
|
|
410
|
-
'now',
|
|
411
|
-
];
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
private resolveHeartbeatDeliveryContext(
|
|
415
|
-
route: ResolvedNotificationRoute
|
|
416
|
-
):
|
|
417
|
-
| {
|
|
418
|
-
channel?: string;
|
|
419
|
-
to?: string;
|
|
420
|
-
accountId?: string;
|
|
421
|
-
}
|
|
422
|
-
| undefined {
|
|
423
|
-
if (!route.replyChannel && !route.replyTo && !route.replyAccountId) {
|
|
424
|
-
return undefined;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return {
|
|
428
|
-
...(route.replyChannel ? { channel: route.replyChannel } : {}),
|
|
429
|
-
...(route.replyTo ? { to: route.replyTo } : {}),
|
|
430
|
-
...(route.replyAccountId ? { accountId: route.replyAccountId } : {}),
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
private resolveRoute(): ResolvedNotificationRoute {
|
|
435
|
-
return resolveNotificationRoute(this.config as NotificationRouteConfig, this.logger);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
private rememberRoute(route: ResolvedNotificationRoute): void {
|
|
439
|
-
if (!route.sessionKey || !route.agentId) {
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
writeStoredNotificationRoute(this.config.workdir, route, this.logger);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private logDispatch(result: Extract<NotifyAttemptResult, { ok: true }>): void {
|
|
446
|
-
const details = [
|
|
447
|
-
`mode=${result.mode}`,
|
|
448
|
-
result.sessionKey ? `session_key=${result.sessionKey}` : null,
|
|
449
|
-
result.runId ? `run_id=${result.runId}` : null,
|
|
450
|
-
result.detail ? `detail=${result.detail}` : null,
|
|
451
|
-
]
|
|
452
|
-
.filter(Boolean)
|
|
453
|
-
.join(', ');
|
|
454
|
-
this.logger.info(`Notification dispatched: ${details}`);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
function createDefaultGatewayRpcClient(
|
|
459
|
-
config: EigenFluxNotifierConfig & ResolvedNotificationRoute,
|
|
460
|
-
logger: Logger
|
|
461
|
-
): OpenClawGatewayRpcClient {
|
|
462
|
-
return new OpenClawGatewayRpcClient({
|
|
463
|
-
gatewayUrl: config.gatewayUrl,
|
|
464
|
-
gatewayToken: config.gatewayToken,
|
|
465
|
-
sessionKey: config.sessionKey,
|
|
466
|
-
agentId: config.agentId,
|
|
467
|
-
replyChannel: config.replyChannel,
|
|
468
|
-
replyTo: config.replyTo,
|
|
469
|
-
replyAccountId: config.replyAccountId,
|
|
470
|
-
logger,
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function formatCommandFailure(result: {
|
|
475
|
-
code: number | null;
|
|
476
|
-
stdout?: string;
|
|
477
|
-
stderr?: string;
|
|
478
|
-
}): string {
|
|
479
|
-
return (
|
|
480
|
-
result.stderr?.trim() ||
|
|
481
|
-
result.stdout?.trim() ||
|
|
482
|
-
`command exited with ${result.code ?? 'unknown'}`
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function formatError(error: unknown): string {
|
|
487
|
-
if (error instanceof Error) {
|
|
488
|
-
return `${error.name}: ${error.message}`;
|
|
489
|
-
}
|
|
490
|
-
return String(error);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
async function runSpawnCommand(argv: string[], timeoutMs: number): Promise<NotifyAttemptResult> {
|
|
494
|
-
const [command, ...args] = argv;
|
|
495
|
-
if (!command) {
|
|
496
|
-
return {
|
|
497
|
-
ok: false,
|
|
498
|
-
mode: 'spawn',
|
|
499
|
-
error: 'missing command',
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return await new Promise<NotifyAttemptResult>((resolve) => {
|
|
504
|
-
const child = spawn(command, args, {
|
|
505
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
506
|
-
});
|
|
507
|
-
let stdout = '';
|
|
508
|
-
let stderr = '';
|
|
509
|
-
let settled = false;
|
|
510
|
-
|
|
511
|
-
const finish = (result: NotifyAttemptResult) => {
|
|
512
|
-
if (settled) {
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
settled = true;
|
|
516
|
-
resolve(result);
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
const timer = setTimeout(() => {
|
|
520
|
-
child.kill();
|
|
521
|
-
finish({
|
|
522
|
-
ok: false,
|
|
523
|
-
mode: 'spawn',
|
|
524
|
-
error: `spawn timed out after ${timeoutMs}ms`,
|
|
525
|
-
});
|
|
526
|
-
}, timeoutMs);
|
|
527
|
-
|
|
528
|
-
child.stdout.on('data', (chunk) => {
|
|
529
|
-
stdout += chunk.toString();
|
|
530
|
-
});
|
|
531
|
-
child.stderr.on('data', (chunk) => {
|
|
532
|
-
stderr += chunk.toString();
|
|
533
|
-
});
|
|
534
|
-
child.on('error', (error) => {
|
|
535
|
-
clearTimeout(timer);
|
|
536
|
-
finish({
|
|
537
|
-
ok: false,
|
|
538
|
-
mode: 'spawn',
|
|
539
|
-
error: `spawn failed: ${error.message}`,
|
|
540
|
-
});
|
|
541
|
-
});
|
|
542
|
-
child.on('close', (code) => {
|
|
543
|
-
clearTimeout(timer);
|
|
544
|
-
if (code === 0) {
|
|
545
|
-
finish({
|
|
546
|
-
ok: true,
|
|
547
|
-
mode: 'spawn',
|
|
548
|
-
});
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
finish({
|
|
552
|
-
ok: false,
|
|
553
|
-
mode: 'spawn',
|
|
554
|
-
error: (stderr || stdout || `exit code ${code}`).trim(),
|
|
555
|
-
});
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
declare module 'openclaw/plugin-sdk' {
|
|
2
|
-
export interface OpenClawPluginConfigSchema {
|
|
3
|
-
type: string;
|
|
4
|
-
additionalProperties?: boolean;
|
|
5
|
-
properties?: Record<string, unknown>;
|
|
6
|
-
required?: string[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface PluginLogger {
|
|
10
|
-
info: (message: string, ...args: unknown[]) => void;
|
|
11
|
-
warn: (message: string, ...args: unknown[]) => void;
|
|
12
|
-
error: (message: string, ...args: unknown[]) => void;
|
|
13
|
-
debug: (message: string, ...args: unknown[]) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface OpenClawPluginService {
|
|
17
|
-
id: string;
|
|
18
|
-
start: () => Promise<void> | void;
|
|
19
|
-
stop: () => Promise<void> | void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface OpenClawPluginCommandContext {
|
|
23
|
-
senderId?: string;
|
|
24
|
-
channel?: string;
|
|
25
|
-
channelId?: string;
|
|
26
|
-
isAuthorizedSender?: boolean;
|
|
27
|
-
args?: string;
|
|
28
|
-
commandBody?: string;
|
|
29
|
-
config?: Record<string, unknown>;
|
|
30
|
-
from?: string;
|
|
31
|
-
to?: string;
|
|
32
|
-
accountId?: string;
|
|
33
|
-
messageThreadId?: string | number;
|
|
34
|
-
requestConversationBinding?: (params?: {
|
|
35
|
-
summary?: string;
|
|
36
|
-
detachHint?: string;
|
|
37
|
-
}) => Promise<
|
|
38
|
-
| {
|
|
39
|
-
status: 'bound';
|
|
40
|
-
binding: {
|
|
41
|
-
bindingId: string;
|
|
42
|
-
pluginId: string;
|
|
43
|
-
pluginName?: string;
|
|
44
|
-
pluginRoot: string;
|
|
45
|
-
channel: string;
|
|
46
|
-
accountId: string;
|
|
47
|
-
conversationId: string;
|
|
48
|
-
parentConversationId?: string;
|
|
49
|
-
threadId?: string | number;
|
|
50
|
-
boundAt: number;
|
|
51
|
-
summary?: string;
|
|
52
|
-
detachHint?: string;
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
| {
|
|
56
|
-
status: 'pending';
|
|
57
|
-
approvalId: string;
|
|
58
|
-
reply: {
|
|
59
|
-
text?: string;
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
| {
|
|
63
|
-
status: 'error';
|
|
64
|
-
message: string;
|
|
65
|
-
}
|
|
66
|
-
>;
|
|
67
|
-
detachConversationBinding?: () => Promise<{ removed: boolean }>;
|
|
68
|
-
getCurrentConversationBinding?: () => Promise<{
|
|
69
|
-
bindingId: string;
|
|
70
|
-
pluginId: string;
|
|
71
|
-
pluginName?: string;
|
|
72
|
-
pluginRoot: string;
|
|
73
|
-
channel: string;
|
|
74
|
-
accountId: string;
|
|
75
|
-
conversationId: string;
|
|
76
|
-
parentConversationId?: string;
|
|
77
|
-
threadId?: string | number;
|
|
78
|
-
boundAt: number;
|
|
79
|
-
summary?: string;
|
|
80
|
-
detachHint?: string;
|
|
81
|
-
} | null>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface OpenClawPluginCommandDefinition {
|
|
85
|
-
name: string;
|
|
86
|
-
description: string;
|
|
87
|
-
acceptsArgs?: boolean;
|
|
88
|
-
requireAuth?: boolean;
|
|
89
|
-
handler: (
|
|
90
|
-
ctx: OpenClawPluginCommandContext
|
|
91
|
-
) => Promise<{ text: string }> | { text: string };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export interface OpenClawPluginApi {
|
|
95
|
-
id?: string;
|
|
96
|
-
name?: string;
|
|
97
|
-
version?: string;
|
|
98
|
-
description?: string;
|
|
99
|
-
source?: string;
|
|
100
|
-
config?: Record<string, unknown>;
|
|
101
|
-
pluginConfig?: Record<string, unknown>;
|
|
102
|
-
runtime?: unknown;
|
|
103
|
-
logger: PluginLogger;
|
|
104
|
-
registerService: (service: OpenClawPluginService) => void;
|
|
105
|
-
registerCommand?: (command: OpenClawPluginCommandDefinition) => void;
|
|
106
|
-
registerHook?: (
|
|
107
|
-
event: string | string[],
|
|
108
|
-
handler: (...args: unknown[]) => Promise<unknown> | unknown,
|
|
109
|
-
metadata?: Record<string, unknown>
|
|
110
|
-
) => void;
|
|
111
|
-
on?: (
|
|
112
|
-
event: string,
|
|
113
|
-
handler: (...args: unknown[]) => Promise<unknown> | unknown,
|
|
114
|
-
options?: {
|
|
115
|
-
priority?: number;
|
|
116
|
-
}
|
|
117
|
-
) => void;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function emptyPluginConfigSchema(): OpenClawPluginConfigSchema;
|
|
121
|
-
}
|