@phronesis-io/openclaw-eigenflux 0.0.1 → 0.0.3
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 +16 -3
- package/dist/config.d.ts +12 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +47 -10
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -7
- 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/dist/pm-polling-client.d.ts +1 -0
- package/dist/pm-polling-client.d.ts.map +1 -1
- package/dist/pm-polling-client.js +64 -57
- package/dist/pm-polling-client.js.map +1 -1
- package/dist/polling-client.d.ts +1 -0
- package/dist/polling-client.d.ts.map +1 -1
- package/dist/polling-client.js +65 -58
- package/dist/polling-client.js.map +1 -1
- package/openclaw.plugin.json +8 -6
- package/package.json +2 -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
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as os from 'os';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
|
|
5
|
-
import { CredentialsLoader } from './credentials-loader';
|
|
6
|
-
import { Logger } from './logger';
|
|
7
|
-
|
|
8
|
-
function createLogger(): Logger {
|
|
9
|
-
return new Logger({
|
|
10
|
-
info: jest.fn(),
|
|
11
|
-
warn: jest.fn(),
|
|
12
|
-
error: jest.fn(),
|
|
13
|
-
debug: jest.fn(),
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe('CredentialsLoader', () => {
|
|
18
|
-
let workdir: string;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
workdir = fs.mkdtempSync(path.join(os.tmpdir(), 'eigenflux-workdir-'));
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
fs.rmSync(workdir, { recursive: true, force: true });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('loads access token from credentials.json', () => {
|
|
29
|
-
fs.writeFileSync(
|
|
30
|
-
path.join(workdir, 'credentials.json'),
|
|
31
|
-
JSON.stringify({ access_token: 'at_file_token' }),
|
|
32
|
-
'utf-8'
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const loader = new CredentialsLoader(createLogger(), workdir);
|
|
36
|
-
expect(loader.loadAccessToken()).toBe('at_file_token');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('returns null when credentials file is missing', () => {
|
|
40
|
-
const loader = new CredentialsLoader(createLogger(), workdir);
|
|
41
|
-
expect(loader.loadAccessToken()).toBeNull();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test('returns expired auth state when credentials file token is stale', () => {
|
|
45
|
-
fs.writeFileSync(
|
|
46
|
-
path.join(workdir, 'credentials.json'),
|
|
47
|
-
JSON.stringify({
|
|
48
|
-
access_token: 'at_expired_token',
|
|
49
|
-
expires_at: Date.now() - 1_000,
|
|
50
|
-
}),
|
|
51
|
-
'utf-8'
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
const loader = new CredentialsLoader(createLogger(), workdir);
|
|
55
|
-
expect(loader.loadAuthState()).toEqual(
|
|
56
|
-
expect.objectContaining({
|
|
57
|
-
status: 'expired',
|
|
58
|
-
source: 'file',
|
|
59
|
-
})
|
|
60
|
-
);
|
|
61
|
-
expect(loader.loadAccessToken()).toBeNull();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('saveAccessToken creates the workdir and writes credentials.json', () => {
|
|
65
|
-
const nestedWorkdir = path.join(workdir, 'nested/eigenflux');
|
|
66
|
-
const loader = new CredentialsLoader(createLogger(), nestedWorkdir);
|
|
67
|
-
|
|
68
|
-
loader.saveAccessToken('at_saved_token', 'bot@example.com', 1_760_000_000_000);
|
|
69
|
-
|
|
70
|
-
const credentialsPath = path.join(nestedWorkdir, 'credentials.json');
|
|
71
|
-
expect(fs.existsSync(credentialsPath)).toBe(true);
|
|
72
|
-
expect(JSON.parse(fs.readFileSync(credentialsPath, 'utf-8'))).toEqual({
|
|
73
|
-
access_token: 'at_saved_token',
|
|
74
|
-
email: 'bot@example.com',
|
|
75
|
-
expires_at: 1_760_000_000_000,
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credentials loader for the EigenFlux auth token.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import { Logger } from './logger';
|
|
8
|
-
import { PLUGIN_CONFIG } from './config';
|
|
9
|
-
|
|
10
|
-
interface EigenFluxCredentials {
|
|
11
|
-
access_token: string;
|
|
12
|
-
email?: string;
|
|
13
|
-
expires_at?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type AuthState =
|
|
17
|
-
| {
|
|
18
|
-
status: 'available';
|
|
19
|
-
accessToken: string;
|
|
20
|
-
source: 'file';
|
|
21
|
-
credentialsPath: string;
|
|
22
|
-
expiresAt?: number;
|
|
23
|
-
email?: string;
|
|
24
|
-
}
|
|
25
|
-
| {
|
|
26
|
-
status: 'missing' | 'expired';
|
|
27
|
-
source?: 'file';
|
|
28
|
-
credentialsPath: string;
|
|
29
|
-
expiresAt?: number;
|
|
30
|
-
email?: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export class CredentialsLoader {
|
|
34
|
-
private readonly logger: Logger;
|
|
35
|
-
private readonly workdir: string;
|
|
36
|
-
|
|
37
|
-
constructor(logger: Logger, workdir: string) {
|
|
38
|
-
this.logger = logger;
|
|
39
|
-
this.workdir = workdir;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
loadAccessToken(): string | null {
|
|
43
|
-
const authState = this.loadAuthState();
|
|
44
|
-
if (authState.status !== 'available') {
|
|
45
|
-
if (authState.status === 'missing') {
|
|
46
|
-
this.logger.error(`No access token found in ${authState.credentialsPath}`);
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
return authState.accessToken;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
loadAuthState(): AuthState {
|
|
54
|
-
const credentialsPath = this.resolveCredentialsPath();
|
|
55
|
-
|
|
56
|
-
if (fs.existsSync(credentialsPath)) {
|
|
57
|
-
try {
|
|
58
|
-
const content = fs.readFileSync(credentialsPath, 'utf-8');
|
|
59
|
-
const credentials: EigenFluxCredentials = JSON.parse(content);
|
|
60
|
-
|
|
61
|
-
if (credentials.access_token) {
|
|
62
|
-
if (credentials.expires_at) {
|
|
63
|
-
const now = Date.now();
|
|
64
|
-
if (now >= credentials.expires_at) {
|
|
65
|
-
this.logger.warn('Access token has expired');
|
|
66
|
-
return {
|
|
67
|
-
status: 'expired',
|
|
68
|
-
source: 'file',
|
|
69
|
-
credentialsPath,
|
|
70
|
-
expiresAt: credentials.expires_at,
|
|
71
|
-
email: credentials.email,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
this.logger.info(`Loaded access token from ${credentialsPath}`);
|
|
77
|
-
return {
|
|
78
|
-
status: 'available',
|
|
79
|
-
accessToken: credentials.access_token,
|
|
80
|
-
source: 'file',
|
|
81
|
-
credentialsPath,
|
|
82
|
-
expiresAt: credentials.expires_at,
|
|
83
|
-
email: credentials.email,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
} catch (error) {
|
|
87
|
-
this.logger.error(`Failed to read credentials file: ${credentialsPath}`, error);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
status: 'missing',
|
|
93
|
-
credentialsPath,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
saveAccessToken(token: string, email?: string, expiresAt?: number): void {
|
|
98
|
-
const credentialsPath = this.resolveCredentialsPath();
|
|
99
|
-
|
|
100
|
-
if (!fs.existsSync(this.workdir)) {
|
|
101
|
-
fs.mkdirSync(this.workdir, { recursive: true });
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const credentials: EigenFluxCredentials = {
|
|
105
|
-
access_token: token,
|
|
106
|
-
email,
|
|
107
|
-
expires_at: expiresAt,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
fs.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), 'utf-8');
|
|
112
|
-
this.logger.info(`Saved access token to ${credentialsPath}`);
|
|
113
|
-
} catch (error) {
|
|
114
|
-
this.logger.error('Failed to save credentials file', error);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private resolveCredentialsPath(): string {
|
|
119
|
-
return path.join(this.workdir, PLUGIN_CONFIG.CREDENTIALS_FILE);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import http from 'http';
|
|
2
|
-
import { WebSocketServer } from 'ws';
|
|
3
|
-
import { OpenClawGatewayRpcClient } from './gateway-rpc-client';
|
|
4
|
-
import { Logger } from './logger';
|
|
5
|
-
|
|
6
|
-
describe('OpenClawGatewayRpcClient', () => {
|
|
7
|
-
let server: http.Server;
|
|
8
|
-
let wss: WebSocketServer;
|
|
9
|
-
let port: number;
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
server = http.createServer();
|
|
13
|
-
wss = new WebSocketServer({ server });
|
|
14
|
-
await new Promise<void>((resolve) => {
|
|
15
|
-
server.listen(0, '127.0.0.1', () => {
|
|
16
|
-
port = (server.address() as any).port;
|
|
17
|
-
resolve();
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(async () => {
|
|
23
|
-
await new Promise<void>((resolve) => wss.close(() => resolve()));
|
|
24
|
-
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('uses configured session key and sends agent directly', async () => {
|
|
28
|
-
const methods: string[] = [];
|
|
29
|
-
let agentParams: any = null;
|
|
30
|
-
|
|
31
|
-
wss.on('connection', (socket) => {
|
|
32
|
-
socket.send(
|
|
33
|
-
JSON.stringify({
|
|
34
|
-
type: 'event',
|
|
35
|
-
event: 'connect.challenge',
|
|
36
|
-
payload: { nonce: 'nonce-test-1' },
|
|
37
|
-
})
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
socket.on('message', (raw) => {
|
|
41
|
-
const frame = JSON.parse(raw.toString());
|
|
42
|
-
methods.push(String(frame.method || ''));
|
|
43
|
-
|
|
44
|
-
if (frame.method === 'connect') {
|
|
45
|
-
socket.send(
|
|
46
|
-
JSON.stringify({
|
|
47
|
-
type: 'res',
|
|
48
|
-
id: frame.id,
|
|
49
|
-
ok: true,
|
|
50
|
-
payload: {
|
|
51
|
-
protocol: 3,
|
|
52
|
-
server: { version: 'test', connId: 'conn-1' },
|
|
53
|
-
features: { methods: ['agent'], events: [] },
|
|
54
|
-
snapshot: { ts: Date.now() },
|
|
55
|
-
policy: { maxPayload: 1000000, maxBufferedBytes: 1000000, tickIntervalMs: 30000 },
|
|
56
|
-
},
|
|
57
|
-
})
|
|
58
|
-
);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (frame.method === 'agent') {
|
|
63
|
-
agentParams = frame.params;
|
|
64
|
-
socket.send(
|
|
65
|
-
JSON.stringify({
|
|
66
|
-
type: 'res',
|
|
67
|
-
id: frame.id,
|
|
68
|
-
ok: true,
|
|
69
|
-
payload: { status: 'started', runId: 'run-1' },
|
|
70
|
-
})
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const client = new OpenClawGatewayRpcClient({
|
|
77
|
-
gatewayUrl: `ws://127.0.0.1:${port}`,
|
|
78
|
-
gatewayToken: 'gw_token_1',
|
|
79
|
-
sessionKey: 'agent:test:feishu:direct:ou_123',
|
|
80
|
-
agentId: 'test',
|
|
81
|
-
replyChannel: 'feishu',
|
|
82
|
-
replyTo: 'ou_123',
|
|
83
|
-
logger: new Logger({ info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() }),
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const result = await client.sendAgentMessage('[EIGENFLUX_TEST] first payload');
|
|
87
|
-
|
|
88
|
-
expect(result).toEqual({
|
|
89
|
-
sessionKey: 'agent:test:feishu:direct:ou_123',
|
|
90
|
-
runId: 'run-1',
|
|
91
|
-
});
|
|
92
|
-
expect(methods).toEqual(['connect', 'agent']);
|
|
93
|
-
expect(agentParams).toEqual(
|
|
94
|
-
expect.objectContaining({
|
|
95
|
-
sessionKey: 'agent:test:feishu:direct:ou_123',
|
|
96
|
-
agentId: 'test',
|
|
97
|
-
message: '[EIGENFLUX_TEST] first payload',
|
|
98
|
-
deliver: true,
|
|
99
|
-
replyChannel: 'feishu',
|
|
100
|
-
replyTo: 'ou_123',
|
|
101
|
-
})
|
|
102
|
-
);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('resolves session key from sessions.list when not configured', async () => {
|
|
106
|
-
const methods: string[] = [];
|
|
107
|
-
let agentParams: any = null;
|
|
108
|
-
|
|
109
|
-
wss.on('connection', (socket) => {
|
|
110
|
-
socket.send(
|
|
111
|
-
JSON.stringify({
|
|
112
|
-
type: 'event',
|
|
113
|
-
event: 'connect.challenge',
|
|
114
|
-
payload: { nonce: 'nonce-test-2' },
|
|
115
|
-
})
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
socket.on('message', (raw) => {
|
|
119
|
-
const frame = JSON.parse(raw.toString());
|
|
120
|
-
methods.push(String(frame.method || ''));
|
|
121
|
-
|
|
122
|
-
if (frame.method === 'connect') {
|
|
123
|
-
socket.send(
|
|
124
|
-
JSON.stringify({
|
|
125
|
-
type: 'res',
|
|
126
|
-
id: frame.id,
|
|
127
|
-
ok: true,
|
|
128
|
-
payload: {
|
|
129
|
-
protocol: 3,
|
|
130
|
-
server: { version: 'test', connId: 'conn-2' },
|
|
131
|
-
features: { methods: ['sessions.list', 'agent'], events: [] },
|
|
132
|
-
snapshot: { ts: Date.now() },
|
|
133
|
-
policy: { maxPayload: 1000000, maxBufferedBytes: 1000000, tickIntervalMs: 30000 },
|
|
134
|
-
},
|
|
135
|
-
})
|
|
136
|
-
);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (frame.method === 'sessions.list') {
|
|
141
|
-
socket.send(
|
|
142
|
-
JSON.stringify({
|
|
143
|
-
type: 'res',
|
|
144
|
-
id: frame.id,
|
|
145
|
-
ok: true,
|
|
146
|
-
payload: {
|
|
147
|
-
sessions: [{ key: 'agent:foo:main' }, { key: 'agent:foo:feishu:direct:ou_999', active: true }],
|
|
148
|
-
},
|
|
149
|
-
})
|
|
150
|
-
);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (frame.method === 'agent') {
|
|
155
|
-
agentParams = frame.params;
|
|
156
|
-
socket.send(
|
|
157
|
-
JSON.stringify({
|
|
158
|
-
type: 'res',
|
|
159
|
-
id: frame.id,
|
|
160
|
-
ok: true,
|
|
161
|
-
payload: { status: 'started', runId: 'run-2' },
|
|
162
|
-
})
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const client = new OpenClawGatewayRpcClient({
|
|
169
|
-
gatewayUrl: `ws://127.0.0.1:${port}`,
|
|
170
|
-
gatewayToken: 'gw_token_2',
|
|
171
|
-
logger: new Logger({ info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() }),
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const result = await client.sendAgentMessage('[EIGENFLUX_TEST] second payload');
|
|
175
|
-
|
|
176
|
-
expect(result).toEqual({
|
|
177
|
-
sessionKey: 'agent:foo:feishu:direct:ou_999',
|
|
178
|
-
runId: 'run-2',
|
|
179
|
-
});
|
|
180
|
-
expect(methods).toEqual(['connect', 'sessions.list', 'agent']);
|
|
181
|
-
expect(agentParams).toEqual(
|
|
182
|
-
expect.objectContaining({
|
|
183
|
-
sessionKey: 'agent:foo:feishu:direct:ou_999',
|
|
184
|
-
agentId: 'foo',
|
|
185
|
-
message: '[EIGENFLUX_TEST] second payload',
|
|
186
|
-
deliver: true,
|
|
187
|
-
})
|
|
188
|
-
);
|
|
189
|
-
});
|
|
190
|
-
});
|