a2acalling 0.1.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/AGENTS.md +66 -0
- package/CLAUDE.md +52 -0
- package/README.md +307 -0
- package/SKILL.md +122 -0
- package/bin/cli.js +908 -0
- package/docs/protocol.md +241 -0
- package/package.json +44 -0
- package/scripts/install-openclaw.js +291 -0
- package/src/index.js +61 -0
- package/src/lib/call-monitor.js +143 -0
- package/src/lib/client.js +208 -0
- package/src/lib/config.js +173 -0
- package/src/lib/conversations.js +470 -0
- package/src/lib/openclaw-integration.js +329 -0
- package/src/lib/summarizer.js +137 -0
- package/src/lib/tokens.js +448 -0
- package/src/routes/federation.js +463 -0
- package/src/server.js +56 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call Monitor - Auto-concludes idle conversations
|
|
3
|
+
*
|
|
4
|
+
* Monitors active conversations and triggers summarization when:
|
|
5
|
+
* - No messages for 60 seconds (configurable)
|
|
6
|
+
* - Explicit end signal received
|
|
7
|
+
* - Max duration exceeded
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class CallMonitor {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.convStore = options.convStore;
|
|
13
|
+
this.summarizer = options.summarizer;
|
|
14
|
+
this.notifyOwner = options.notifyOwner || (() => {});
|
|
15
|
+
this.ownerContext = options.ownerContext || {};
|
|
16
|
+
|
|
17
|
+
// Timing config
|
|
18
|
+
this.idleTimeoutMs = options.idleTimeoutMs || 60000; // 60s idle
|
|
19
|
+
this.maxDurationMs = options.maxDurationMs || 300000; // 5min max
|
|
20
|
+
this.checkIntervalMs = options.checkIntervalMs || 10000; // Check every 10s
|
|
21
|
+
|
|
22
|
+
this.intervalId = null;
|
|
23
|
+
this.activeConversations = new Map(); // conversationId -> { startTime, lastActivity, callerInfo }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Start monitoring
|
|
28
|
+
*/
|
|
29
|
+
start() {
|
|
30
|
+
if (this.intervalId) return;
|
|
31
|
+
|
|
32
|
+
this.intervalId = setInterval(() => {
|
|
33
|
+
this._checkIdleConversations();
|
|
34
|
+
}, this.checkIntervalMs);
|
|
35
|
+
|
|
36
|
+
console.log('[a2a] Call monitor started');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Stop monitoring
|
|
41
|
+
*/
|
|
42
|
+
stop() {
|
|
43
|
+
if (this.intervalId) {
|
|
44
|
+
clearInterval(this.intervalId);
|
|
45
|
+
this.intervalId = null;
|
|
46
|
+
console.log('[a2a] Call monitor stopped');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Track a new or continuing conversation
|
|
52
|
+
*/
|
|
53
|
+
trackActivity(conversationId, callerInfo = {}) {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const existing = this.activeConversations.get(conversationId);
|
|
56
|
+
|
|
57
|
+
if (existing) {
|
|
58
|
+
existing.lastActivity = now;
|
|
59
|
+
} else {
|
|
60
|
+
this.activeConversations.set(conversationId, {
|
|
61
|
+
startTime: now,
|
|
62
|
+
lastActivity: now,
|
|
63
|
+
callerInfo
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Explicitly end a conversation
|
|
70
|
+
*/
|
|
71
|
+
async endConversation(conversationId, reason = 'explicit') {
|
|
72
|
+
const convData = this.activeConversations.get(conversationId);
|
|
73
|
+
this.activeConversations.delete(conversationId);
|
|
74
|
+
|
|
75
|
+
if (!this.convStore) return { success: false, error: 'no_store' };
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const result = await this.convStore.concludeConversation(conversationId, {
|
|
79
|
+
summarizer: this.summarizer,
|
|
80
|
+
ownerContext: this.ownerContext
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (result.success) {
|
|
84
|
+
// Notify owner
|
|
85
|
+
const context = this.convStore.getConversationContext(conversationId);
|
|
86
|
+
this.notifyOwner({
|
|
87
|
+
type: 'conversation_concluded',
|
|
88
|
+
reason,
|
|
89
|
+
conversation: context,
|
|
90
|
+
callerInfo: convData?.callerInfo
|
|
91
|
+
}).catch(err => {
|
|
92
|
+
console.error('[a2a] Failed to notify owner:', err.message);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error('[a2a] Failed to conclude conversation:', err.message);
|
|
99
|
+
return { success: false, error: err.message };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check for idle conversations
|
|
105
|
+
*/
|
|
106
|
+
async _checkIdleConversations() {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
|
|
109
|
+
for (const [convId, data] of this.activeConversations) {
|
|
110
|
+
const idleTime = now - data.lastActivity;
|
|
111
|
+
const duration = now - data.startTime;
|
|
112
|
+
|
|
113
|
+
// Check max duration
|
|
114
|
+
if (duration > this.maxDurationMs) {
|
|
115
|
+
console.log(`[a2a] Conversation ${convId} exceeded max duration, concluding...`);
|
|
116
|
+
await this.endConversation(convId, 'max_duration');
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check idle timeout
|
|
121
|
+
if (idleTime > this.idleTimeoutMs) {
|
|
122
|
+
console.log(`[a2a] Conversation ${convId} idle for ${Math.round(idleTime / 1000)}s, concluding...`);
|
|
123
|
+
await this.endConversation(convId, 'idle_timeout');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get active conversation count
|
|
130
|
+
*/
|
|
131
|
+
getActiveCount() {
|
|
132
|
+
return this.activeConversations.size;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get all active conversation IDs
|
|
137
|
+
*/
|
|
138
|
+
getActiveConversations() {
|
|
139
|
+
return Array.from(this.activeConversations.keys());
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { CallMonitor };
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Client - Make calls to remote agents
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const http = require('http');
|
|
7
|
+
|
|
8
|
+
class A2AClient {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.timeout = options.timeout || 60000;
|
|
11
|
+
this.caller = options.caller || {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse an a2a:// URL
|
|
16
|
+
*/
|
|
17
|
+
static parseInvite(inviteUrl) {
|
|
18
|
+
// Support both a2a:// and legacy oclaw:// schemes
|
|
19
|
+
const match = inviteUrl.match(/^(?:a2a|oclaw):\/\/([^/]+)\/(.+)$/);
|
|
20
|
+
if (!match) {
|
|
21
|
+
throw new Error(`Invalid invite URL: ${inviteUrl}`);
|
|
22
|
+
}
|
|
23
|
+
return { host: match[1], token: match[2] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Call a remote agent
|
|
28
|
+
*
|
|
29
|
+
* @param {string} endpoint - a2a:// URL or {host, token}
|
|
30
|
+
* @param {string} message - Message to send
|
|
31
|
+
* @param {object} options - Additional options
|
|
32
|
+
* @returns {Promise<object>} Response from remote agent
|
|
33
|
+
*/
|
|
34
|
+
async call(endpoint, message, options = {}) {
|
|
35
|
+
let host, token;
|
|
36
|
+
|
|
37
|
+
if (typeof endpoint === 'string') {
|
|
38
|
+
({ host, token } = A2AClient.parseInvite(endpoint));
|
|
39
|
+
} else {
|
|
40
|
+
({ host, token } = endpoint);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { conversationId, context, timeoutSeconds } = options;
|
|
44
|
+
|
|
45
|
+
const body = JSON.stringify({
|
|
46
|
+
message,
|
|
47
|
+
conversation_id: conversationId,
|
|
48
|
+
caller: this.caller,
|
|
49
|
+
context,
|
|
50
|
+
timeout_seconds: timeoutSeconds || 60
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const isLocalhost = host === 'localhost' || host.startsWith('localhost:') || host.startsWith('127.');
|
|
54
|
+
const hasExplicitPort = host.includes(':');
|
|
55
|
+
const port = hasExplicitPort ? parseInt(host.split(':')[1]) : (isLocalhost ? 80 : 443);
|
|
56
|
+
// Use HTTP for localhost or explicit non-443 ports, HTTPS otherwise
|
|
57
|
+
const useHttp = isLocalhost || (hasExplicitPort && port !== 443);
|
|
58
|
+
const protocol = useHttp ? http : https;
|
|
59
|
+
const hostname = host.split(':')[0];
|
|
60
|
+
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const req = protocol.request({
|
|
63
|
+
hostname,
|
|
64
|
+
port,
|
|
65
|
+
path: '/api/federation/invoke',
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Authorization': `Bearer ${token}`,
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
'Content-Length': Buffer.byteLength(body)
|
|
71
|
+
},
|
|
72
|
+
timeout: this.timeout
|
|
73
|
+
}, (res) => {
|
|
74
|
+
let data = '';
|
|
75
|
+
res.on('data', chunk => data += chunk);
|
|
76
|
+
res.on('end', () => {
|
|
77
|
+
try {
|
|
78
|
+
const json = JSON.parse(data);
|
|
79
|
+
if (res.statusCode >= 400) {
|
|
80
|
+
reject(new A2AError(json.error || 'request_failed', json.message || data, res.statusCode));
|
|
81
|
+
} else {
|
|
82
|
+
resolve(json);
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
reject(new A2AError('parse_error', `Failed to parse response: ${data}`, res.statusCode));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
req.on('error', (e) => {
|
|
91
|
+
reject(new A2AError('network_error', e.message));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
req.on('timeout', () => {
|
|
95
|
+
req.destroy();
|
|
96
|
+
reject(new A2AError('timeout', 'Request timed out'));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
req.write(body);
|
|
100
|
+
req.end();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if a remote agent is available
|
|
106
|
+
*/
|
|
107
|
+
async ping(endpoint) {
|
|
108
|
+
let host;
|
|
109
|
+
|
|
110
|
+
if (typeof endpoint === 'string') {
|
|
111
|
+
({ host } = A2AClient.parseInvite(endpoint));
|
|
112
|
+
} else {
|
|
113
|
+
({ host } = endpoint);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const isLocalhost = host === 'localhost' || host.startsWith('localhost:') || host.startsWith('127.');
|
|
117
|
+
const hasExplicitPort = host.includes(':');
|
|
118
|
+
const port = hasExplicitPort ? parseInt(host.split(':')[1]) : (isLocalhost ? 80 : 443);
|
|
119
|
+
const useHttp = isLocalhost || (hasExplicitPort && port !== 443);
|
|
120
|
+
const protocol = useHttp ? http : https;
|
|
121
|
+
const hostname = host.split(':')[0];
|
|
122
|
+
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const req = protocol.request({
|
|
125
|
+
hostname,
|
|
126
|
+
port,
|
|
127
|
+
path: '/api/federation/ping',
|
|
128
|
+
method: 'GET',
|
|
129
|
+
timeout: 5000
|
|
130
|
+
}, (res) => {
|
|
131
|
+
let data = '';
|
|
132
|
+
res.on('data', chunk => data += chunk);
|
|
133
|
+
res.on('end', () => {
|
|
134
|
+
try {
|
|
135
|
+
resolve(JSON.parse(data));
|
|
136
|
+
} catch {
|
|
137
|
+
resolve({ pong: res.statusCode === 200 });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
req.on('error', () => resolve({ pong: false }));
|
|
143
|
+
req.on('timeout', () => {
|
|
144
|
+
req.destroy();
|
|
145
|
+
resolve({ pong: false });
|
|
146
|
+
});
|
|
147
|
+
req.end();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get federation status of a remote
|
|
153
|
+
*/
|
|
154
|
+
async status(endpoint) {
|
|
155
|
+
let host;
|
|
156
|
+
|
|
157
|
+
if (typeof endpoint === 'string') {
|
|
158
|
+
({ host } = A2AClient.parseInvite(endpoint));
|
|
159
|
+
} else {
|
|
160
|
+
({ host } = endpoint);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const isLocalhost = host === 'localhost' || host.startsWith('localhost:') || host.startsWith('127.');
|
|
164
|
+
const hasExplicitPort = host.includes(':');
|
|
165
|
+
const port = hasExplicitPort ? parseInt(host.split(':')[1]) : (isLocalhost ? 80 : 443);
|
|
166
|
+
const useHttp = isLocalhost || (hasExplicitPort && port !== 443);
|
|
167
|
+
const protocol = useHttp ? http : https;
|
|
168
|
+
const hostname = host.split(':')[0];
|
|
169
|
+
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const req = protocol.request({
|
|
172
|
+
hostname,
|
|
173
|
+
port,
|
|
174
|
+
path: '/api/federation/status',
|
|
175
|
+
method: 'GET',
|
|
176
|
+
timeout: 5000
|
|
177
|
+
}, (res) => {
|
|
178
|
+
let data = '';
|
|
179
|
+
res.on('data', chunk => data += chunk);
|
|
180
|
+
res.on('end', () => {
|
|
181
|
+
try {
|
|
182
|
+
resolve(JSON.parse(data));
|
|
183
|
+
} catch {
|
|
184
|
+
reject(new A2AError('parse_error', 'Invalid status response'));
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
req.on('error', (e) => reject(new A2AError('network_error', e.message)));
|
|
190
|
+
req.on('timeout', () => {
|
|
191
|
+
req.destroy();
|
|
192
|
+
reject(new A2AError('timeout', 'Request timed out'));
|
|
193
|
+
});
|
|
194
|
+
req.end();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
class A2AError extends Error {
|
|
200
|
+
constructor(code, message, statusCode = null) {
|
|
201
|
+
super(message);
|
|
202
|
+
this.name = 'A2AError';
|
|
203
|
+
this.code = code;
|
|
204
|
+
this.statusCode = statusCode;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = { A2AClient, A2AError };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Configuration Management
|
|
3
|
+
*
|
|
4
|
+
* Stores permission tiers, default settings, and user preferences.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const CONFIG_DIR = process.env.A2A_CONFIG_DIR ||
|
|
11
|
+
process.env.OPENCLAW_CONFIG_DIR ||
|
|
12
|
+
path.join(process.env.HOME || '/tmp', '.config', 'openclaw');
|
|
13
|
+
|
|
14
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'a2a-config.json');
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
// Has the user completed onboarding?
|
|
18
|
+
onboardingComplete: false,
|
|
19
|
+
|
|
20
|
+
// Permission tiers
|
|
21
|
+
tiers: {
|
|
22
|
+
public: {
|
|
23
|
+
name: 'Public',
|
|
24
|
+
description: 'Basic networking - safe for anyone',
|
|
25
|
+
capabilities: [],
|
|
26
|
+
disclosure: 'minimal',
|
|
27
|
+
examples: ['calendar availability', 'public social posts', 'general questions']
|
|
28
|
+
},
|
|
29
|
+
friends: {
|
|
30
|
+
name: 'Friends',
|
|
31
|
+
description: 'Most capabilities, no sensitive financial data',
|
|
32
|
+
capabilities: [],
|
|
33
|
+
disclosure: 'public',
|
|
34
|
+
examples: ['email summaries', 'schedule meetings', 'project discussions']
|
|
35
|
+
},
|
|
36
|
+
private: {
|
|
37
|
+
name: 'Private',
|
|
38
|
+
description: 'Full access - only for you',
|
|
39
|
+
capabilities: [],
|
|
40
|
+
disclosure: 'public',
|
|
41
|
+
examples: ['financial data', 'personal notes', 'private conversations']
|
|
42
|
+
},
|
|
43
|
+
custom: {
|
|
44
|
+
name: 'Custom',
|
|
45
|
+
description: 'User-defined permissions',
|
|
46
|
+
capabilities: [],
|
|
47
|
+
disclosure: 'minimal',
|
|
48
|
+
examples: []
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Default token settings
|
|
53
|
+
defaults: {
|
|
54
|
+
expiration: 'never', // never, 1d, 7d, 30d
|
|
55
|
+
maxCalls: 100, // per token
|
|
56
|
+
rateLimit: {
|
|
57
|
+
perMinute: 10,
|
|
58
|
+
perHour: 100,
|
|
59
|
+
perDay: 1000
|
|
60
|
+
},
|
|
61
|
+
maxPendingRequests: 5 // max connection requests per hour
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// Agent info
|
|
65
|
+
agent: {
|
|
66
|
+
name: '',
|
|
67
|
+
description: '',
|
|
68
|
+
hostname: ''
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// Timestamps
|
|
72
|
+
createdAt: null,
|
|
73
|
+
updatedAt: null
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
class A2AConfig {
|
|
77
|
+
constructor() {
|
|
78
|
+
this._ensureDir();
|
|
79
|
+
this.config = this._load();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_ensureDir() {
|
|
83
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
84
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_load() {
|
|
89
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
90
|
+
try {
|
|
91
|
+
const saved = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
92
|
+
return { ...DEFAULT_CONFIG, ...saved };
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error('[a2a] Config corrupted, using defaults');
|
|
95
|
+
return { ...DEFAULT_CONFIG };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { ...DEFAULT_CONFIG };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_save() {
|
|
102
|
+
this.config.updatedAt = new Date().toISOString();
|
|
103
|
+
if (!this.config.createdAt) {
|
|
104
|
+
this.config.createdAt = this.config.updatedAt;
|
|
105
|
+
}
|
|
106
|
+
const tmpPath = `${CONFIG_FILE}.tmp`;
|
|
107
|
+
fs.writeFileSync(tmpPath, JSON.stringify(this.config, null, 2));
|
|
108
|
+
fs.renameSync(tmpPath, CONFIG_FILE);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if onboarding is complete
|
|
112
|
+
isOnboarded() {
|
|
113
|
+
return this.config.onboardingComplete === true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Mark onboarding complete
|
|
117
|
+
completeOnboarding() {
|
|
118
|
+
this.config.onboardingComplete = true;
|
|
119
|
+
this._save();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Reset to run onboarding again
|
|
123
|
+
resetOnboarding() {
|
|
124
|
+
this.config.onboardingComplete = false;
|
|
125
|
+
this._save();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Get/set tiers
|
|
129
|
+
getTiers() {
|
|
130
|
+
return this.config.tiers;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setTier(tierName, tierConfig) {
|
|
134
|
+
this.config.tiers[tierName] = { ...this.config.tiers[tierName], ...tierConfig };
|
|
135
|
+
this._save();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get/set defaults
|
|
139
|
+
getDefaults() {
|
|
140
|
+
return this.config.defaults;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setDefaults(defaults) {
|
|
144
|
+
this.config.defaults = { ...this.config.defaults, ...defaults };
|
|
145
|
+
this._save();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Get/set agent info
|
|
149
|
+
getAgent() {
|
|
150
|
+
return this.config.agent;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
setAgent(agent) {
|
|
154
|
+
this.config.agent = { ...this.config.agent, ...agent };
|
|
155
|
+
this._save();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Get full config
|
|
159
|
+
getAll() {
|
|
160
|
+
return this.config;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Export for sharing
|
|
164
|
+
export() {
|
|
165
|
+
return {
|
|
166
|
+
tiers: this.config.tiers,
|
|
167
|
+
defaults: this.config.defaults,
|
|
168
|
+
agent: this.config.agent
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { A2AConfig, DEFAULT_CONFIG, CONFIG_FILE };
|