a2acalling 0.6.1 → 0.6.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 +33 -9
- package/SKILL.md +67 -5
- package/bin/cli.js +468 -151
- package/docs/protocol.md +24 -14
- package/package.json +1 -1
- package/scripts/install-openclaw.js +64 -64
- package/src/dashboard/public/app.js +765 -28
- package/src/dashboard/public/index.html +57 -13
- package/src/dashboard/public/style.css +16 -0
- package/src/lib/callbook.js +358 -0
- package/src/lib/client.js +1 -2
- package/src/lib/config.js +67 -15
- package/src/lib/external-ip.js +18 -7
- package/src/lib/invite-host.js +24 -21
- package/src/lib/logger.js +26 -14
- package/src/lib/tokens.js +314 -113
- package/src/routes/a2a.js +11 -2
- package/src/routes/callbook.js +142 -0
- package/src/routes/dashboard.js +557 -25
- package/src/server.js +6 -0
package/src/lib/external-ip.js
CHANGED
|
@@ -22,6 +22,7 @@ const CONFIG_DIR = process.env.A2A_CONFIG_DIR ||
|
|
|
22
22
|
const EXTERNAL_IP_CACHE_FILE = path.join(CONFIG_DIR, 'a2a-external-ip.json');
|
|
23
23
|
|
|
24
24
|
const DEFAULT_SERVICES = [
|
|
25
|
+
'https://ifconfig.me/ip',
|
|
25
26
|
'https://api.ipify.org',
|
|
26
27
|
'https://checkip.amazonaws.com/',
|
|
27
28
|
'https://icanhazip.com/'
|
|
@@ -113,6 +114,7 @@ async function fetchExternalIp(options = {}) {
|
|
|
113
114
|
? options.services
|
|
114
115
|
: DEFAULT_SERVICES;
|
|
115
116
|
|
|
117
|
+
const attempts = [];
|
|
116
118
|
let lastError = null;
|
|
117
119
|
for (const serviceUrl of services) {
|
|
118
120
|
try {
|
|
@@ -124,14 +126,18 @@ async function fetchExternalIp(options = {}) {
|
|
|
124
126
|
if (!ip) {
|
|
125
127
|
throw new Error('invalid_ip');
|
|
126
128
|
}
|
|
127
|
-
|
|
129
|
+
attempts.push({ service: serviceUrl, ok: true, statusCode: res.statusCode, ip });
|
|
130
|
+
return { ip, source: serviceUrl, attempts };
|
|
128
131
|
} catch (err) {
|
|
129
132
|
lastError = err;
|
|
133
|
+
attempts.push({ service: serviceUrl, ok: false, error: err && err.message ? err.message : 'request_failed' });
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
const msg = lastError ? lastError.message : 'unavailable';
|
|
134
|
-
|
|
138
|
+
const failure = new Error(`external_ip_unavailable:${msg}`);
|
|
139
|
+
failure.attempts = attempts;
|
|
140
|
+
throw failure;
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
/**
|
|
@@ -168,13 +174,13 @@ async function getExternalIp(options = {}) {
|
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
try {
|
|
171
|
-
const { ip, source } = await fetchExternalIp({
|
|
177
|
+
const { ip, source, attempts } = await fetchExternalIp({
|
|
172
178
|
timeoutMs: options.timeoutMs,
|
|
173
179
|
services: options.services
|
|
174
180
|
});
|
|
175
181
|
const checkedAt = new Date(nowMs).toISOString();
|
|
176
182
|
atomicWriteJson(cacheFile, { ip, checked_at: checkedAt, source });
|
|
177
|
-
return { ip, checkedAt, source, fromCache: false, stale: false };
|
|
183
|
+
return { ip, checkedAt, source, fromCache: false, stale: false, attempts: Array.isArray(attempts) ? attempts : null };
|
|
178
184
|
} catch (err) {
|
|
179
185
|
if (cached && cached.ip) {
|
|
180
186
|
const cachedIp = parseIp(cached.ip);
|
|
@@ -184,11 +190,17 @@ async function getExternalIp(options = {}) {
|
|
|
184
190
|
checkedAt: cached.checked_at || null,
|
|
185
191
|
source: cached.source || 'cache',
|
|
186
192
|
fromCache: true,
|
|
187
|
-
stale: true
|
|
193
|
+
stale: true,
|
|
194
|
+
error: err && err.message ? err.message : 'external_ip_unavailable',
|
|
195
|
+
attempts: err && Array.isArray(err.attempts) ? err.attempts : null
|
|
188
196
|
};
|
|
189
197
|
}
|
|
190
198
|
}
|
|
191
|
-
return {
|
|
199
|
+
return {
|
|
200
|
+
ip: null,
|
|
201
|
+
error: err && err.message ? err.message : 'external_ip_unavailable',
|
|
202
|
+
attempts: err && Array.isArray(err.attempts) ? err.attempts : null
|
|
203
|
+
};
|
|
192
204
|
}
|
|
193
205
|
}
|
|
194
206
|
|
|
@@ -197,4 +209,3 @@ module.exports = {
|
|
|
197
209
|
fetchExternalIp,
|
|
198
210
|
getExternalIp
|
|
199
211
|
};
|
|
200
|
-
|
package/src/lib/invite-host.js
CHANGED
|
@@ -166,24 +166,23 @@ async function resolveInviteHost(options = {}) {
|
|
|
166
166
|
options.refreshExternalIp && isPublicIpHostname(parsed.hostname)
|
|
167
167
|
);
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (external && external.ip) {
|
|
169
|
+
const alwaysLookupExternalIp = Boolean(options.alwaysLookupExternalIp);
|
|
170
|
+
const wantsExternalIp = shouldReplaceWithExternalIp || alwaysLookupExternalIp;
|
|
171
|
+
const warnOnExternalIpFailure = options.warnOnExternalIpFailure !== undefined
|
|
172
|
+
? Boolean(options.warnOnExternalIpFailure)
|
|
173
|
+
: shouldReplaceWithExternalIp;
|
|
174
|
+
|
|
175
|
+
const external = wantsExternalIp
|
|
176
|
+
? await getExternalIp({
|
|
177
|
+
ttlMs,
|
|
178
|
+
timeoutMs: options.externalIpTimeoutMs,
|
|
179
|
+
services: options.externalIpServices,
|
|
180
|
+
cacheFile: options.externalIpCacheFile,
|
|
181
|
+
forceRefresh: Boolean(options.forceRefreshExternalIp)
|
|
182
|
+
})
|
|
183
|
+
: null;
|
|
184
|
+
|
|
185
|
+
if (shouldReplaceWithExternalIp && external && external.ip) {
|
|
187
186
|
const finalHost = formatHostPort(external.ip, desiredPort);
|
|
188
187
|
if (finalHost !== candidateHostWithPort) {
|
|
189
188
|
warnings.push(
|
|
@@ -195,17 +194,21 @@ async function resolveInviteHost(options = {}) {
|
|
|
195
194
|
source: 'external_ip',
|
|
196
195
|
originalHost: candidateHostWithPort,
|
|
197
196
|
externalIp: external.ip,
|
|
197
|
+
externalIpInfo: external,
|
|
198
198
|
warnings
|
|
199
199
|
};
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
if (wantsExternalIp && (!external || !external.ip) && warnOnExternalIpFailure) {
|
|
203
|
+
warnings.push(
|
|
204
|
+
`Invite host "${candidateHostWithPort}" may not be reachable from other machines, and external IP lookup failed. Set A2A_HOSTNAME="your-public-host:port".`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
205
207
|
return {
|
|
206
208
|
host: candidateHostWithPort,
|
|
207
209
|
source: candidateSource,
|
|
208
210
|
originalHost: candidateHostWithPort,
|
|
211
|
+
externalIpInfo: external,
|
|
209
212
|
warnings
|
|
210
213
|
};
|
|
211
214
|
}
|
package/src/lib/logger.js
CHANGED
|
@@ -129,7 +129,28 @@ class LogStore {
|
|
|
129
129
|
} catch (err) {
|
|
130
130
|
// best effort
|
|
131
131
|
}
|
|
132
|
-
this.
|
|
132
|
+
const ok = this._ensureSchema();
|
|
133
|
+
if (!ok) {
|
|
134
|
+
// Prototyping mode: do not attempt DB migrations; keep the old file and start fresh.
|
|
135
|
+
const backupPath = `${this.dbPath}.legacy.${Date.now()}`;
|
|
136
|
+
try {
|
|
137
|
+
this.db.close();
|
|
138
|
+
} catch (err) {
|
|
139
|
+
// ignore
|
|
140
|
+
}
|
|
141
|
+
fs.renameSync(this.dbPath, backupPath);
|
|
142
|
+
this.db = new Database(this.dbPath);
|
|
143
|
+
try {
|
|
144
|
+
fs.chmodSync(this.dbPath, 0o600);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
// best effort
|
|
147
|
+
}
|
|
148
|
+
const ok2 = this._ensureSchema();
|
|
149
|
+
if (!ok2) {
|
|
150
|
+
this._dbError = 'failed_to_initialize_log_db_schema';
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
133
154
|
this._prepareStatements();
|
|
134
155
|
return this.db;
|
|
135
156
|
} catch (err) {
|
|
@@ -138,7 +159,7 @@ class LogStore {
|
|
|
138
159
|
}
|
|
139
160
|
}
|
|
140
161
|
|
|
141
|
-
|
|
162
|
+
_ensureSchema() {
|
|
142
163
|
this.db.exec(`
|
|
143
164
|
CREATE TABLE IF NOT EXISTS logs (
|
|
144
165
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -167,18 +188,9 @@ class LogStore {
|
|
|
167
188
|
`);
|
|
168
189
|
|
|
169
190
|
const columns = this.db.prepare(`PRAGMA table_info(logs)`).all();
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
if (!hasErrorCode) {
|
|
174
|
-
this.db.exec(`ALTER TABLE logs ADD COLUMN error_code TEXT`);
|
|
175
|
-
}
|
|
176
|
-
if (!hasStatusCode) {
|
|
177
|
-
this.db.exec(`ALTER TABLE logs ADD COLUMN status_code INTEGER`);
|
|
178
|
-
}
|
|
179
|
-
if (!hasHint) {
|
|
180
|
-
this.db.exec(`ALTER TABLE logs ADD COLUMN hint TEXT`);
|
|
181
|
-
}
|
|
191
|
+
const names = new Set(columns.map(c => c && c.name).filter(Boolean));
|
|
192
|
+
const required = ['timestamp', 'level', 'component', 'message', 'error_code', 'status_code', 'hint', 'data'];
|
|
193
|
+
return required.every((name) => names.has(name));
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
_prepareStatements() {
|