aquaman-plugin 0.5.1 → 0.7.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/README.md +26 -16
- package/index.ts +56 -81
- package/openclaw.plugin.json +1 -12
- package/package.json +5 -3
- package/src/commands.ts +26 -198
- package/src/config-schema.ts +2 -32
- package/src/http-interceptor.ts +30 -59
- package/src/index.ts +1 -13
- package/src/plugin.ts +16 -143
- package/src/proxy-health.ts +50 -26
- package/src/proxy-manager.ts +10 -24
- package/src/embedded.ts +0 -182
package/src/commands.ts
CHANGED
|
@@ -4,13 +4,11 @@
|
|
|
4
4
|
* Provides /aquaman commands for users to interact with the plugin.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { EmbeddedMode } from './embedded.js';
|
|
8
7
|
import type { ProxyManager } from './proxy-manager.js';
|
|
9
8
|
import type { PluginConfig } from './config-schema.js';
|
|
10
9
|
|
|
11
10
|
export interface CommandContext {
|
|
12
11
|
config: PluginConfig;
|
|
13
|
-
embeddedMode?: EmbeddedMode;
|
|
14
12
|
proxyManager?: ProxyManager;
|
|
15
13
|
}
|
|
16
14
|
|
|
@@ -37,43 +35,17 @@ export async function statusCommand(ctx: CommandContext): Promise<CommandResult>
|
|
|
37
35
|
|
|
38
36
|
lines.push('aquaman plugin status');
|
|
39
37
|
lines.push('');
|
|
40
|
-
lines.push(`Mode: ${ctx.config.mode || 'embedded'}`);
|
|
41
38
|
lines.push(`Backend: ${ctx.config.backend || 'keychain'}`);
|
|
42
39
|
lines.push(`Services: ${(ctx.config.services || []).join(', ')}`);
|
|
43
40
|
|
|
44
|
-
if (ctx.
|
|
45
|
-
|
|
46
|
-
const info = ctx.proxyManager.getConnectionInfo();
|
|
47
|
-
lines.push('');
|
|
48
|
-
lines.push('Proxy Status: Running');
|
|
49
|
-
lines.push(` URL: ${info?.baseUrl}`);
|
|
50
|
-
lines.push(` Port: ${info?.port}`);
|
|
51
|
-
lines.push(` Protocol: ${info?.protocol}`);
|
|
52
|
-
} else {
|
|
53
|
-
lines.push('');
|
|
54
|
-
lines.push('Proxy Status: Not running');
|
|
55
|
-
}
|
|
56
|
-
} else if (ctx.embeddedMode) {
|
|
57
|
-
const status = ctx.embeddedMode.getStatus();
|
|
41
|
+
if (ctx.proxyManager?.isRunning()) {
|
|
42
|
+
const info = ctx.proxyManager.getConnectionInfo();
|
|
58
43
|
lines.push('');
|
|
59
|
-
lines.push('
|
|
60
|
-
lines.push(`
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// List credentials (without values)
|
|
65
|
-
if (ctx.embeddedMode) {
|
|
66
|
-
try {
|
|
67
|
-
const creds = await ctx.embeddedMode.listCredentials();
|
|
68
|
-
lines.push('');
|
|
69
|
-
lines.push(`Stored Credentials: ${creds.length}`);
|
|
70
|
-
for (const cred of creds) {
|
|
71
|
-
lines.push(` - ${cred.service}/${cred.key}`);
|
|
72
|
-
}
|
|
73
|
-
} catch (error) {
|
|
74
|
-
lines.push('');
|
|
75
|
-
lines.push('Credentials: (unavailable)');
|
|
76
|
-
}
|
|
44
|
+
lines.push('Proxy Status: Running');
|
|
45
|
+
lines.push(` Socket: ${info?.socketPath}`);
|
|
46
|
+
} else {
|
|
47
|
+
lines.push('');
|
|
48
|
+
lines.push('Proxy Status: Not running');
|
|
77
49
|
}
|
|
78
50
|
|
|
79
51
|
return {
|
|
@@ -86,19 +58,10 @@ export async function statusCommand(ctx: CommandContext): Promise<CommandResult>
|
|
|
86
58
|
* /aquaman add <service> - Add a credential (prompts for value)
|
|
87
59
|
*/
|
|
88
60
|
export async function addCommand(
|
|
89
|
-
|
|
61
|
+
_ctx: CommandContext,
|
|
90
62
|
service: string,
|
|
91
63
|
key: string = 'api_key'
|
|
92
64
|
): Promise<CommandResult> {
|
|
93
|
-
if (!ctx.embeddedMode) {
|
|
94
|
-
return {
|
|
95
|
-
success: false,
|
|
96
|
-
message: 'Embedded mode not available. Use proxy mode for credential management.'
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Note: In a real OpenClaw plugin, this would trigger a secure input prompt
|
|
101
|
-
// For now, we return instructions
|
|
102
65
|
return {
|
|
103
66
|
success: true,
|
|
104
67
|
message: `To add a credential for ${service}/${key}:\n\n` +
|
|
@@ -111,143 +74,30 @@ export async function addCommand(
|
|
|
111
74
|
/**
|
|
112
75
|
* /aquaman list - List stored credentials
|
|
113
76
|
*/
|
|
114
|
-
export async function listCommand(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const creds = await ctx.embeddedMode.listCredentials();
|
|
124
|
-
|
|
125
|
-
if (creds.length === 0) {
|
|
126
|
-
return {
|
|
127
|
-
success: true,
|
|
128
|
-
message: 'No credentials stored.\n\nUse `aquaman credentials add <service> <key>` to add one.'
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const lines = ['Stored credentials:', ''];
|
|
133
|
-
for (const cred of creds) {
|
|
134
|
-
lines.push(` ${cred.service}/${cred.key}`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
success: true,
|
|
139
|
-
message: lines.join('\n'),
|
|
140
|
-
data: creds
|
|
141
|
-
};
|
|
142
|
-
} catch (error) {
|
|
143
|
-
return {
|
|
144
|
-
success: false,
|
|
145
|
-
message: `Failed to list credentials: ${error}`
|
|
146
|
-
};
|
|
147
|
-
}
|
|
77
|
+
export async function listCommand(_ctx: CommandContext): Promise<CommandResult> {
|
|
78
|
+
return {
|
|
79
|
+
success: true,
|
|
80
|
+
message: 'Run in your terminal:\n aquaman credentials list'
|
|
81
|
+
};
|
|
148
82
|
}
|
|
149
83
|
|
|
150
84
|
/**
|
|
151
85
|
* /aquaman logs - Show recent audit entries
|
|
152
86
|
*/
|
|
153
|
-
export async function logsCommand(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
const entries = await ctx.embeddedMode.getRecentAuditEntries(count);
|
|
163
|
-
|
|
164
|
-
if (entries.length === 0) {
|
|
165
|
-
return {
|
|
166
|
-
success: true,
|
|
167
|
-
message: 'No audit entries found.'
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const lines = [`Last ${entries.length} audit entries:`, ''];
|
|
172
|
-
for (const entry of entries) {
|
|
173
|
-
const time = new Date(entry.timestamp).toISOString();
|
|
174
|
-
const type = entry.type.toUpperCase().padEnd(16);
|
|
175
|
-
let details = '';
|
|
176
|
-
|
|
177
|
-
if (entry.type === 'credential_access') {
|
|
178
|
-
details = `${entry.data.service} ${entry.data.operation} ${entry.data.success ? 'OK' : 'FAIL'}`;
|
|
179
|
-
} else {
|
|
180
|
-
details = JSON.stringify(entry.data).slice(0, 60);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
lines.push(`${time} [${type}] ${details}`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
success: true,
|
|
188
|
-
message: lines.join('\n'),
|
|
189
|
-
data: entries
|
|
190
|
-
};
|
|
191
|
-
} catch (error) {
|
|
192
|
-
return {
|
|
193
|
-
success: false,
|
|
194
|
-
message: `Failed to get audit logs: ${error}`
|
|
195
|
-
};
|
|
196
|
-
}
|
|
87
|
+
export async function logsCommand(_ctx: CommandContext, _count: number = 10): Promise<CommandResult> {
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
message: 'Run in your terminal:\n aquaman audit tail'
|
|
91
|
+
};
|
|
197
92
|
}
|
|
198
93
|
|
|
199
94
|
/**
|
|
200
95
|
* /aquaman verify - Verify audit log integrity
|
|
201
96
|
*/
|
|
202
|
-
export async function verifyCommand(
|
|
203
|
-
if (!ctx.embeddedMode) {
|
|
204
|
-
return {
|
|
205
|
-
success: false,
|
|
206
|
-
message: 'Embedded mode not available.'
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
const result = await ctx.embeddedMode.verifyAuditIntegrity();
|
|
212
|
-
|
|
213
|
-
if (result.valid) {
|
|
214
|
-
return {
|
|
215
|
-
success: true,
|
|
216
|
-
message: 'Audit log integrity verified. No tampering detected.'
|
|
217
|
-
};
|
|
218
|
-
} else {
|
|
219
|
-
return {
|
|
220
|
-
success: false,
|
|
221
|
-
message: 'Audit log integrity FAILED!\n\n' +
|
|
222
|
-
'Errors:\n' +
|
|
223
|
-
result.errors.map(e => ` - ${e}`).join('\n')
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
} catch (error) {
|
|
227
|
-
return {
|
|
228
|
-
success: false,
|
|
229
|
-
message: `Failed to verify audit log: ${error}`
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* /aquaman mode <embedded|proxy> - Switch mode
|
|
236
|
-
*/
|
|
237
|
-
export async function modeCommand(ctx: CommandContext, mode: 'embedded' | 'proxy'): Promise<CommandResult> {
|
|
238
|
-
if (mode !== 'embedded' && mode !== 'proxy') {
|
|
239
|
-
return {
|
|
240
|
-
success: false,
|
|
241
|
-
message: 'Invalid mode. Use "embedded" or "proxy".'
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Mode switching requires configuration change
|
|
97
|
+
export async function verifyCommand(_ctx: CommandContext): Promise<CommandResult> {
|
|
246
98
|
return {
|
|
247
99
|
success: true,
|
|
248
|
-
message:
|
|
249
|
-
`{\n "plugins": {\n "aquaman": {\n "mode": "${mode}"\n }\n }\n}\n\n` +
|
|
250
|
-
`Then restart OpenClaw.`
|
|
100
|
+
message: 'Run in your terminal:\n aquaman audit verify'
|
|
251
101
|
};
|
|
252
102
|
}
|
|
253
103
|
|
|
@@ -272,36 +122,26 @@ export async function executeCommand(
|
|
|
272
122
|
case 'list':
|
|
273
123
|
return listCommand(ctx);
|
|
274
124
|
|
|
275
|
-
case 'logs':
|
|
125
|
+
case 'logs': {
|
|
276
126
|
const count = args[0] ? parseInt(args[0], 10) : 10;
|
|
277
127
|
return logsCommand(ctx, count);
|
|
128
|
+
}
|
|
278
129
|
|
|
279
130
|
case 'verify':
|
|
280
131
|
return verifyCommand(ctx);
|
|
281
132
|
|
|
282
|
-
case 'mode':
|
|
283
|
-
if (args.length < 1) {
|
|
284
|
-
return { success: false, message: 'Usage: /aquaman mode <embedded|proxy>' };
|
|
285
|
-
}
|
|
286
|
-
return modeCommand(ctx, args[0] as 'embedded' | 'proxy');
|
|
287
|
-
|
|
288
133
|
case 'help':
|
|
289
134
|
default:
|
|
290
135
|
return {
|
|
291
136
|
success: true,
|
|
292
137
|
message: `aquaman plugin commands:
|
|
293
138
|
|
|
294
|
-
/aquaman status - Show plugin status
|
|
139
|
+
/aquaman status - Show plugin status
|
|
295
140
|
/aquaman add - Add a credential (shows instructions)
|
|
296
141
|
/aquaman list - List stored credentials
|
|
297
|
-
/aquaman logs [n] - Show recent audit entries
|
|
142
|
+
/aquaman logs [n] - Show recent audit entries
|
|
298
143
|
/aquaman verify - Verify audit log integrity
|
|
299
|
-
/aquaman
|
|
300
|
-
/aquaman help - Show this help message
|
|
301
|
-
|
|
302
|
-
Mode comparison:
|
|
303
|
-
embedded: Simpler setup, credentials in Gateway memory
|
|
304
|
-
proxy: Stronger isolation, credentials in separate process`
|
|
144
|
+
/aquaman help - Show this help message`
|
|
305
145
|
};
|
|
306
146
|
}
|
|
307
147
|
}
|
|
@@ -313,7 +153,7 @@ export function getAvailableCommands(ctx: CommandContext): PluginCommand[] {
|
|
|
313
153
|
return [
|
|
314
154
|
{
|
|
315
155
|
name: 'status',
|
|
316
|
-
description: 'Show aquaman plugin status
|
|
156
|
+
description: 'Show aquaman plugin status',
|
|
317
157
|
execute: async () => {
|
|
318
158
|
const result = await statusCommand(ctx);
|
|
319
159
|
return result.message;
|
|
@@ -357,18 +197,6 @@ export function getAvailableCommands(ctx: CommandContext): PluginCommand[] {
|
|
|
357
197
|
return result.message;
|
|
358
198
|
}
|
|
359
199
|
},
|
|
360
|
-
{
|
|
361
|
-
name: 'mode',
|
|
362
|
-
description: 'Switch between embedded and proxy mode',
|
|
363
|
-
execute: async (args) => {
|
|
364
|
-
const mode = args.mode || args._?.[0];
|
|
365
|
-
if (!mode) {
|
|
366
|
-
return 'Usage: /aquaman mode <embedded|proxy>';
|
|
367
|
-
}
|
|
368
|
-
const result = await modeCommand(ctx, mode as 'embedded' | 'proxy');
|
|
369
|
-
return result.message;
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
200
|
{
|
|
373
201
|
name: 'help',
|
|
374
202
|
description: 'Show help for aquaman commands',
|
package/src/config-schema.ts
CHANGED
|
@@ -6,16 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
import { Type, type Static } from '@sinclair/typebox';
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
* Plugin operation mode
|
|
11
|
-
* - embedded: Direct vault access within OpenClaw process (simpler, less isolation)
|
|
12
|
-
* - proxy: Separate proxy process (stronger isolation, credentials never in Gateway)
|
|
13
|
-
*/
|
|
14
|
-
export const PluginMode = Type.Union([
|
|
15
|
-
Type.Literal('embedded'),
|
|
16
|
-
Type.Literal('proxy')
|
|
17
|
-
], { default: 'embedded' });
|
|
18
|
-
|
|
19
9
|
/**
|
|
20
10
|
* Credential backend type
|
|
21
11
|
*/
|
|
@@ -23,7 +13,8 @@ export const CredentialBackend = Type.Union([
|
|
|
23
13
|
Type.Literal('keychain'),
|
|
24
14
|
Type.Literal('1password'),
|
|
25
15
|
Type.Literal('vault'),
|
|
26
|
-
Type.Literal('encrypted-file')
|
|
16
|
+
Type.Literal('encrypted-file'),
|
|
17
|
+
Type.Literal('keepassxc')
|
|
27
18
|
], { default: 'keychain' });
|
|
28
19
|
|
|
29
20
|
/**
|
|
@@ -37,19 +28,12 @@ export const ProxiedServices = Type.Array(Type.String(), {
|
|
|
37
28
|
* Complete plugin configuration schema
|
|
38
29
|
*/
|
|
39
30
|
export const ConfigSchema = Type.Object({
|
|
40
|
-
// Mode selection
|
|
41
|
-
mode: Type.Optional(PluginMode),
|
|
42
|
-
|
|
43
31
|
// Credential backend
|
|
44
32
|
backend: Type.Optional(CredentialBackend),
|
|
45
33
|
|
|
46
34
|
// Services to proxy
|
|
47
35
|
services: Type.Optional(ProxiedServices),
|
|
48
36
|
|
|
49
|
-
// Proxy mode options
|
|
50
|
-
proxyPort: Type.Optional(Type.Number({ default: 8081, minimum: 1024, maximum: 65535 })),
|
|
51
|
-
proxyAutoStart: Type.Optional(Type.Boolean({ default: true })),
|
|
52
|
-
|
|
53
37
|
// 1Password options
|
|
54
38
|
onePasswordVault: Type.Optional(Type.String()),
|
|
55
39
|
onePasswordAccount: Type.Optional(Type.String()),
|
|
@@ -59,15 +43,6 @@ export const ConfigSchema = Type.Object({
|
|
|
59
43
|
vaultToken: Type.Optional(Type.String()),
|
|
60
44
|
vaultNamespace: Type.Optional(Type.String()),
|
|
61
45
|
vaultMountPath: Type.Optional(Type.String({ default: 'secret' })),
|
|
62
|
-
|
|
63
|
-
// TLS options
|
|
64
|
-
tlsEnabled: Type.Optional(Type.Boolean({ default: true })),
|
|
65
|
-
tlsCertPath: Type.Optional(Type.String()),
|
|
66
|
-
tlsKeyPath: Type.Optional(Type.String()),
|
|
67
|
-
|
|
68
|
-
// Audit options
|
|
69
|
-
auditEnabled: Type.Optional(Type.Boolean({ default: true })),
|
|
70
|
-
auditLogDir: Type.Optional(Type.String())
|
|
71
46
|
});
|
|
72
47
|
|
|
73
48
|
export type PluginConfig = Static<typeof ConfigSchema>;
|
|
@@ -76,14 +51,9 @@ export type PluginConfig = Static<typeof ConfigSchema>;
|
|
|
76
51
|
* Default configuration values
|
|
77
52
|
*/
|
|
78
53
|
export const defaultConfig: PluginConfig = {
|
|
79
|
-
mode: 'embedded',
|
|
80
54
|
backend: 'keychain',
|
|
81
55
|
services: ['anthropic', 'openai'],
|
|
82
|
-
proxyPort: 8081,
|
|
83
|
-
proxyAutoStart: true,
|
|
84
56
|
vaultMountPath: 'secret',
|
|
85
|
-
tlsEnabled: true,
|
|
86
|
-
auditEnabled: true
|
|
87
57
|
};
|
|
88
58
|
|
|
89
59
|
/**
|
package/src/http-interceptor.ts
CHANGED
|
@@ -1,47 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HTTP
|
|
2
|
+
* HTTP interceptor for channel credential isolation.
|
|
3
3
|
*
|
|
4
4
|
* Overrides globalThis.fetch to redirect requests targeting known channel API
|
|
5
|
-
* hosts through the aquaman credential proxy
|
|
6
|
-
* credentials, so the Gateway process never sees them.
|
|
5
|
+
* hosts through the aquaman credential proxy via Unix domain socket.
|
|
6
|
+
* The proxy injects the real credentials, so the Gateway process never sees them.
|
|
7
7
|
*
|
|
8
8
|
* OpenClaw channels use globalThis.fetch (backed by undici) for all HTTP calls.
|
|
9
9
|
* Many channel monitor functions also accept a `proxyFetch` parameter that
|
|
10
10
|
* falls back to globalThis.fetch, so overriding it covers both paths.
|
|
11
|
+
*
|
|
12
|
+
* Uses sentinel hostname `aquaman.local` — SDK base URLs are set to
|
|
13
|
+
* `http://aquaman.local/<service>` and the interceptor routes them through UDS.
|
|
11
14
|
*/
|
|
12
15
|
|
|
16
|
+
import { Agent } from 'undici';
|
|
17
|
+
|
|
18
|
+
/** Sentinel hostname used in SDK base URLs to route through UDS */
|
|
19
|
+
const PROXY_HOST = 'aquaman.local';
|
|
20
|
+
|
|
13
21
|
export interface HttpInterceptorOptions {
|
|
14
|
-
/**
|
|
15
|
-
|
|
22
|
+
/** Unix domain socket path for the proxy */
|
|
23
|
+
socketPath: string;
|
|
16
24
|
/** Map of hostname (or *.domain wildcard) → service name */
|
|
17
25
|
hostMap: Map<string, string>;
|
|
18
|
-
/** Client authentication token for the proxy */
|
|
19
|
-
clientToken?: string;
|
|
20
26
|
/** Optional logger */
|
|
21
27
|
log?: (msg: string) => void;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
export class HttpInterceptor {
|
|
25
|
-
private
|
|
26
|
-
private proxyHost: string;
|
|
31
|
+
private socketPath: string;
|
|
27
32
|
private hostMap: Map<string, string>;
|
|
28
|
-
private clientToken: string | null;
|
|
29
33
|
private originalFetch: typeof globalThis.fetch | null = null;
|
|
30
34
|
private active = false;
|
|
31
35
|
private log: (msg: string) => void;
|
|
36
|
+
private socketAgent: Agent | null = null;
|
|
32
37
|
|
|
33
38
|
constructor(options: HttpInterceptorOptions) {
|
|
34
|
-
this.
|
|
39
|
+
this.socketPath = options.socketPath;
|
|
35
40
|
this.hostMap = options.hostMap;
|
|
36
|
-
this.clientToken = options.clientToken || null;
|
|
37
41
|
this.log = options.log || (() => {});
|
|
38
|
-
|
|
39
|
-
// Extract proxy hostname to avoid intercepting requests to the proxy itself
|
|
40
|
-
try {
|
|
41
|
-
this.proxyHost = new URL(this.proxyBaseUrl).hostname;
|
|
42
|
-
} catch {
|
|
43
|
-
this.proxyHost = '127.0.0.1';
|
|
44
|
-
}
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
/**
|
|
@@ -51,15 +48,13 @@ export class HttpInterceptor {
|
|
|
51
48
|
if (this.active) return;
|
|
52
49
|
|
|
53
50
|
this.originalFetch = globalThis.fetch;
|
|
51
|
+
this.socketAgent = new Agent({ connect: { socketPath: this.socketPath } });
|
|
54
52
|
|
|
55
53
|
const origFetch = this.originalFetch;
|
|
56
|
-
const
|
|
57
|
-
const proxyHostname = this.proxyHost;
|
|
58
|
-
const token = this.clientToken;
|
|
54
|
+
const socketAgent = this.socketAgent;
|
|
59
55
|
const matchHost = this.matchHost.bind(this);
|
|
60
56
|
const extractUrl = this.extractUrl.bind(this);
|
|
61
57
|
const stripAuthHeaders = this.stripAuthHeaders.bind(this);
|
|
62
|
-
const injectToken = this.injectTokenHeader.bind(this);
|
|
63
58
|
const logFn = this.log;
|
|
64
59
|
|
|
65
60
|
(globalThis as any).fetch = (
|
|
@@ -71,22 +66,19 @@ export class HttpInterceptor {
|
|
|
71
66
|
return origFetch.call(globalThis, input, init);
|
|
72
67
|
}
|
|
73
68
|
|
|
74
|
-
//
|
|
75
|
-
if (url.hostname ===
|
|
76
|
-
|
|
77
|
-
const tokenInit = injectToken(init, token);
|
|
78
|
-
return origFetch.call(globalThis, input, tokenInit);
|
|
79
|
-
}
|
|
80
|
-
return origFetch.call(globalThis, input, init);
|
|
69
|
+
// SDK traffic via env vars (hostname = aquaman.local) — route through UDS
|
|
70
|
+
if (url.hostname === PROXY_HOST) {
|
|
71
|
+
return origFetch.call(globalThis, input, { ...init, dispatcher: socketAgent } as any);
|
|
81
72
|
}
|
|
82
73
|
|
|
74
|
+
// Channel traffic — match against host map
|
|
83
75
|
const service = matchHost(url.hostname);
|
|
84
76
|
if (!service) {
|
|
85
77
|
return origFetch.call(globalThis, input, init);
|
|
86
78
|
}
|
|
87
79
|
|
|
88
80
|
// Rewrite the URL to go through the proxy
|
|
89
|
-
const proxyUrl =
|
|
81
|
+
const proxyUrl = `http://${PROXY_HOST}/${service}${url.pathname}${url.search}`;
|
|
90
82
|
logFn(`[aquaman] Intercepted ${url.hostname}${url.pathname} → ${service}`);
|
|
91
83
|
|
|
92
84
|
// Strip any existing authorization headers — the proxy will inject the real ones
|
|
@@ -96,12 +88,7 @@ export class HttpInterceptor {
|
|
|
96
88
|
newInit = { ...init, headers: stripped };
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
|
|
100
|
-
if (token) {
|
|
101
|
-
newInit = injectToken(newInit, token);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return origFetch.call(globalThis, proxyUrl, { ...newInit, redirect: 'manual' });
|
|
91
|
+
return origFetch.call(globalThis, proxyUrl, { ...newInit, redirect: 'manual', dispatcher: socketAgent } as any);
|
|
105
92
|
};
|
|
106
93
|
|
|
107
94
|
this.active = true;
|
|
@@ -117,6 +104,12 @@ export class HttpInterceptor {
|
|
|
117
104
|
globalThis.fetch = this.originalFetch;
|
|
118
105
|
this.originalFetch = null;
|
|
119
106
|
this.active = false;
|
|
107
|
+
|
|
108
|
+
if (this.socketAgent) {
|
|
109
|
+
this.socketAgent.close();
|
|
110
|
+
this.socketAgent = null;
|
|
111
|
+
}
|
|
112
|
+
|
|
120
113
|
this.log('[aquaman] HTTP interceptor deactivated');
|
|
121
114
|
}
|
|
122
115
|
|
|
@@ -153,28 +146,6 @@ export class HttpInterceptor {
|
|
|
153
146
|
return null;
|
|
154
147
|
}
|
|
155
148
|
|
|
156
|
-
private injectTokenHeader(init: RequestInit | undefined, token: string): RequestInit {
|
|
157
|
-
const base = init || {};
|
|
158
|
-
const headers = base.headers;
|
|
159
|
-
|
|
160
|
-
if (!headers) {
|
|
161
|
-
return { ...base, headers: { 'x-aquaman-token': token } };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (headers instanceof Headers) {
|
|
165
|
-
const h = new Headers(headers);
|
|
166
|
-
h.set('x-aquaman-token', token);
|
|
167
|
-
return { ...base, headers: h };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (Array.isArray(headers)) {
|
|
171
|
-
return { ...base, headers: [...headers, ['x-aquaman-token', token]] };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Plain object
|
|
175
|
-
return { ...base, headers: { ...headers, 'x-aquaman-token': token } };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
149
|
private stripAuthHeaders(headers: HeadersInit): HeadersInit {
|
|
179
150
|
if (headers instanceof Headers) {
|
|
180
151
|
const h = new Headers(headers);
|
package/src/index.ts
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This package provides an OpenClaw plugin that enables:
|
|
5
5
|
* - Secure credential storage using enterprise backends (Keychain, 1Password, Vault)
|
|
6
|
-
* -
|
|
7
|
-
* - Embedded: Direct vault access (simpler, credentials in Gateway memory)
|
|
8
|
-
* - Proxy: Separate process (stronger isolation, credentials never in Gateway)
|
|
6
|
+
* - Proxy mode: Separate process, credentials never in Gateway memory
|
|
9
7
|
* - Hash-chained tamper-evident audit logs
|
|
10
8
|
* - Slash commands for credential management
|
|
11
9
|
*
|
|
@@ -16,7 +14,6 @@
|
|
|
16
14
|
* {
|
|
17
15
|
* "plugins": {
|
|
18
16
|
* "aquaman-plugin": {
|
|
19
|
-
* "mode": "embedded",
|
|
20
17
|
* "backend": "keychain",
|
|
21
18
|
* "services": ["anthropic", "openai"]
|
|
22
19
|
* }
|
|
@@ -34,7 +31,6 @@ export {
|
|
|
34
31
|
// Config Schema
|
|
35
32
|
export {
|
|
36
33
|
ConfigSchema,
|
|
37
|
-
PluginMode,
|
|
38
34
|
CredentialBackend,
|
|
39
35
|
ProxiedServices,
|
|
40
36
|
type PluginConfig,
|
|
@@ -42,13 +38,6 @@ export {
|
|
|
42
38
|
mergeConfig
|
|
43
39
|
} from './config-schema.js';
|
|
44
40
|
|
|
45
|
-
// Embedded Mode
|
|
46
|
-
export {
|
|
47
|
-
type EmbeddedModeOptions,
|
|
48
|
-
EmbeddedMode,
|
|
49
|
-
createEmbeddedMode
|
|
50
|
-
} from './embedded.js';
|
|
51
|
-
|
|
52
41
|
// Proxy Manager
|
|
53
42
|
export {
|
|
54
43
|
type ProxyConnectionInfo,
|
|
@@ -66,7 +55,6 @@ export {
|
|
|
66
55
|
listCommand,
|
|
67
56
|
logsCommand,
|
|
68
57
|
verifyCommand,
|
|
69
|
-
modeCommand,
|
|
70
58
|
executeCommand
|
|
71
59
|
} from './commands.js';
|
|
72
60
|
|