@dp-pcs/ogp 0.3.3 → 0.4.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 +317 -61
- package/dist/cli/completion.d.ts +5 -0
- package/dist/cli/completion.d.ts.map +1 -0
- package/dist/cli/completion.js +148 -0
- package/dist/cli/completion.js.map +1 -0
- package/dist/cli/config.d.ts +3 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +207 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/expose.d.ts.map +1 -1
- package/dist/cli/expose.js +20 -13
- package/dist/cli/expose.js.map +1 -1
- package/dist/cli/federation.d.ts +14 -0
- package/dist/cli/federation.d.ts.map +1 -1
- package/dist/cli/federation.js +347 -23
- package/dist/cli/federation.js.map +1 -1
- package/dist/cli/project.d.ts +4 -3
- package/dist/cli/project.d.ts.map +1 -1
- package/dist/cli/project.js +34 -24
- package/dist/cli/project.js.map +1 -1
- package/dist/cli/setup.d.ts +23 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +560 -32
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli.js +358 -35
- package/dist/cli.js.map +1 -1
- package/dist/daemon/agent-comms.d.ts.map +1 -1
- package/dist/daemon/agent-comms.js +15 -10
- package/dist/daemon/agent-comms.js.map +1 -1
- package/dist/daemon/heartbeat.d.ts +22 -0
- package/dist/daemon/heartbeat.d.ts.map +1 -0
- package/dist/daemon/heartbeat.js +119 -0
- package/dist/daemon/heartbeat.js.map +1 -0
- package/dist/daemon/intent-registry.d.ts.map +1 -1
- package/dist/daemon/intent-registry.js +19 -10
- package/dist/daemon/intent-registry.js.map +1 -1
- package/dist/daemon/keypair.d.ts +1 -0
- package/dist/daemon/keypair.d.ts.map +1 -1
- package/dist/daemon/keypair.js +151 -18
- package/dist/daemon/keypair.js.map +1 -1
- package/dist/daemon/message-handler.d.ts.map +1 -1
- package/dist/daemon/message-handler.js +30 -16
- package/dist/daemon/message-handler.js.map +1 -1
- package/dist/daemon/notify.d.ts +19 -0
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +376 -73
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/openclaw-bridge.d.ts +34 -0
- package/dist/daemon/openclaw-bridge.d.ts.map +1 -0
- package/dist/daemon/openclaw-bridge.js +261 -0
- package/dist/daemon/openclaw-bridge.js.map +1 -0
- package/dist/daemon/peers.d.ts +21 -0
- package/dist/daemon/peers.d.ts.map +1 -1
- package/dist/daemon/peers.js +125 -20
- package/dist/daemon/peers.js.map +1 -1
- package/dist/daemon/projects.d.ts +9 -6
- package/dist/daemon/projects.d.ts.map +1 -1
- package/dist/daemon/projects.js +30 -20
- package/dist/daemon/projects.js.map +1 -1
- package/dist/daemon/server.d.ts +17 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +188 -77
- package/dist/daemon/server.js.map +1 -1
- package/dist/shared/config.d.ts +52 -1
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +18 -11
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/framework-detection.d.ts +31 -0
- package/dist/shared/framework-detection.d.ts.map +1 -0
- package/dist/shared/framework-detection.js +91 -0
- package/dist/shared/framework-detection.js.map +1 -0
- package/dist/shared/help.d.ts +5 -0
- package/dist/shared/help.d.ts.map +1 -0
- package/dist/shared/help.js +281 -0
- package/dist/shared/help.js.map +1 -0
- package/dist/shared/meta-config.d.ts +44 -0
- package/dist/shared/meta-config.d.ts.map +1 -0
- package/dist/shared/meta-config.js +89 -0
- package/dist/shared/meta-config.js.map +1 -0
- package/dist/shared/migration.d.ts +57 -0
- package/dist/shared/migration.d.ts.map +1 -0
- package/dist/shared/migration.js +255 -0
- package/dist/shared/migration.js.map +1 -0
- package/docs/CLI-REFERENCE.md +1361 -0
- package/docs/GETTING-STARTED.md +953 -0
- package/docs/MIGRATION.md +202 -0
- package/docs/MULTI-FRAMEWORK-DEMO.md +352 -0
- package/docs/MULTI-FRAMEWORK-DESIGN.md +378 -0
- package/docs/MULTI-FRAMEWORK-IMPL.md +197 -0
- package/docs/case-studies/CRASH_RESOLUTION_20260407.md +190 -0
- package/docs/case-studies/OpenClaw_Hermes_Status_Report_20260407.md +142 -0
- package/docs/case-studies/OpenClaw_Stability_Fix_Summary.md +209 -0
- package/docs/case-studies/README.md +40 -0
- package/docs/case-studies/crash_observations.md +250 -0
- package/docs/cloudflare-named-tunnel-setup.md +126 -0
- package/docs/federation-flow.md +27 -37
- package/docs/hermes-implementation-checklist.md +4 -0
- package/docs/project-intent-testing.md +97 -0
- package/docs/quickstart.md +12 -4
- package/docs/rendezvous.md +13 -14
- package/docs/scopes.md +13 -13
- package/package.json +12 -6
- package/scripts/completion.bash +123 -0
- package/scripts/completion.zsh +372 -0
- package/scripts/install-skills.js +19 -1
- package/scripts/test-migration-execute.js +74 -0
- package/scripts/test-migration.js +42 -0
- package/scripts/test-project-intents.mjs +614 -0
- package/skills/ogp/SKILL.md +197 -64
- package/skills/ogp-agent-comms/SKILL.md +107 -41
- package/skills/ogp-expose/SKILL.md +183 -25
- package/skills/ogp-project/SKILL.md +110 -88
package/dist/cli/federation.js
CHANGED
|
@@ -1,11 +1,94 @@
|
|
|
1
|
-
import { listPeers, loadPeers, getPeer, approvePeer, rejectPeer, updatePeerGrantedScopes } from '../daemon/peers.js';
|
|
2
|
-
import { requireConfig } from '../shared/config.js';
|
|
1
|
+
import { listPeers, loadPeers, getPeer, approvePeer, rejectPeer, updatePeer, updatePeerGrantedScopes } from '../daemon/peers.js';
|
|
2
|
+
import { requireConfig, loadConfig } from '../shared/config.js';
|
|
3
3
|
import { lookupPeer } from '../daemon/rendezvous.js';
|
|
4
4
|
import { getPublicKey, getPrivateKey, loadOrGenerateKeyPair } from '../daemon/keypair.js';
|
|
5
5
|
import { signObject, sign } from '../shared/signing.js';
|
|
6
6
|
import * as crypto from 'node:crypto';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import * as path from 'node:path';
|
|
7
9
|
import { createScopeBundle, createScopeGrant, parseRateLimit, formatRateLimit, DEFAULT_RATE_LIMIT } from '../daemon/scopes.js';
|
|
8
10
|
import { loadIntents } from '../daemon/intent-registry.js';
|
|
11
|
+
import { loadMetaConfig } from '../shared/meta-config.js';
|
|
12
|
+
import { logActivity } from '../daemon/agent-comms.js';
|
|
13
|
+
import { deliverLocalSessionText } from '../daemon/notify.js';
|
|
14
|
+
/**
|
|
15
|
+
* Expand tilde in paths
|
|
16
|
+
*/
|
|
17
|
+
function expandTilde(filePath) {
|
|
18
|
+
if (filePath.startsWith('~/') || filePath === '~') {
|
|
19
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
20
|
+
}
|
|
21
|
+
return filePath;
|
|
22
|
+
}
|
|
23
|
+
function normalizeGatewayUrl(url) {
|
|
24
|
+
const trimmed = url.trim();
|
|
25
|
+
if (!trimmed) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
// Add https:// if no protocol specified
|
|
29
|
+
const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)
|
|
30
|
+
? trimmed
|
|
31
|
+
: `https://${trimmed}`;
|
|
32
|
+
// Remove trailing slashes
|
|
33
|
+
return withProtocol.replace(/\/+$/, '');
|
|
34
|
+
}
|
|
35
|
+
export async function fetchFederationCard(gatewayUrl, fetchImpl = fetch) {
|
|
36
|
+
const requestedUrl = normalizeGatewayUrl(gatewayUrl);
|
|
37
|
+
const wellKnownUrl = `${requestedUrl}/.well-known/ogp`;
|
|
38
|
+
const response = await fetchImpl(wellKnownUrl);
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`Could not fetch ${wellKnownUrl}: ${response.status} ${response.statusText}`);
|
|
41
|
+
}
|
|
42
|
+
const card = await response.json();
|
|
43
|
+
const canonicalUrl = card.gatewayUrl ? normalizeGatewayUrl(card.gatewayUrl) : requestedUrl;
|
|
44
|
+
return { requestedUrl, canonicalUrl, card };
|
|
45
|
+
}
|
|
46
|
+
export async function ensureLocalGatewayReachable(config, actionLabel, fetchImpl = fetch) {
|
|
47
|
+
const configuredGatewayUrl = normalizeGatewayUrl(config.gatewayUrl || '');
|
|
48
|
+
if (!configuredGatewayUrl) {
|
|
49
|
+
console.error(`Error: gatewayUrl is not set. Run "ogp expose" or update your config before you ${actionLabel}.`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const { canonicalUrl } = await fetchFederationCard(configuredGatewayUrl, fetchImpl);
|
|
54
|
+
if (canonicalUrl !== configuredGatewayUrl) {
|
|
55
|
+
console.error(`Error: configured gatewayUrl is stale.`);
|
|
56
|
+
console.error(` Config: ${configuredGatewayUrl}`);
|
|
57
|
+
console.error(` Live card: ${canonicalUrl}`);
|
|
58
|
+
console.error(` Update your config before you ${actionLabel}.`);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error(`Error: your gatewayUrl is not reachable at ${configuredGatewayUrl}.`);
|
|
65
|
+
console.error(` Run "ogp expose" or fix gatewayUrl before you ${actionLabel}.`);
|
|
66
|
+
console.error(` Details: ${error.message}`);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function resolvePeerGatewayUrl(gatewayUrl, contextLabel) {
|
|
71
|
+
try {
|
|
72
|
+
const { requestedUrl, canonicalUrl, card } = await fetchFederationCard(gatewayUrl);
|
|
73
|
+
if (canonicalUrl !== requestedUrl) {
|
|
74
|
+
console.log(`ℹ ${contextLabel}: peer advertises canonical gateway URL ${canonicalUrl}; using it instead of ${requestedUrl}`);
|
|
75
|
+
}
|
|
76
|
+
return { gatewayUrl: canonicalUrl, card };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
throw new Error(`${contextLabel}: peer gateway is not reachable or missing /.well-known/ogp. ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function refreshPeerGatewayUrlForApproval(peer) {
|
|
83
|
+
const { gatewayUrl, card } = await resolvePeerGatewayUrl(peer.gatewayUrl, 'Preflight');
|
|
84
|
+
if (card.publicKey && peer.publicKey && card.publicKey !== peer.publicKey) {
|
|
85
|
+
throw new Error(`Preflight: peer gateway identity mismatch. Expected ${peer.publicKey.substring(0, 32)}, got ${card.publicKey.substring(0, 32)}.`);
|
|
86
|
+
}
|
|
87
|
+
if (gatewayUrl !== peer.gatewayUrl) {
|
|
88
|
+
updatePeer(peer.id, { gatewayUrl });
|
|
89
|
+
}
|
|
90
|
+
return gatewayUrl;
|
|
91
|
+
}
|
|
9
92
|
/**
|
|
10
93
|
* Resolve a peer identifier (alias, ID, or public key) to a peer ID.
|
|
11
94
|
* Returns the peer ID if found, or null.
|
|
@@ -25,6 +108,73 @@ function resolvePeerId(identifier) {
|
|
|
25
108
|
return null;
|
|
26
109
|
}
|
|
27
110
|
export async function federationList(status) {
|
|
111
|
+
// Check if --for all was specified
|
|
112
|
+
if (process.env.OGP_FOR_ALL === 'true') {
|
|
113
|
+
const metaConfig = loadMetaConfig();
|
|
114
|
+
const enabledFrameworks = metaConfig.frameworks.filter(f => f.enabled);
|
|
115
|
+
if (enabledFrameworks.length === 0) {
|
|
116
|
+
console.error('Error: No enabled frameworks found. Run "ogp setup" first.');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
// Print header
|
|
120
|
+
console.log('\n═══════════════════════════════════════════════════════════════');
|
|
121
|
+
console.log(`Federation Peers (All Frameworks)`);
|
|
122
|
+
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
123
|
+
let totalPeers = 0;
|
|
124
|
+
// Iterate through each framework
|
|
125
|
+
for (const framework of enabledFrameworks) {
|
|
126
|
+
const originalOgpHome = process.env.OGP_HOME;
|
|
127
|
+
process.env.OGP_HOME = expandTilde(framework.configDir);
|
|
128
|
+
try {
|
|
129
|
+
const config = loadConfig();
|
|
130
|
+
if (!config) {
|
|
131
|
+
console.log(`${framework.name} (${framework.displayName || framework.id})`);
|
|
132
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
133
|
+
console.log(' No config found - run setup');
|
|
134
|
+
console.log('');
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Load peers for this framework
|
|
138
|
+
const allPeers = loadPeers();
|
|
139
|
+
const peers = status ? allPeers.filter(p => p.status === status) : allPeers.filter(p => p.status !== 'removed');
|
|
140
|
+
// Print framework header
|
|
141
|
+
console.log(`${framework.name} (${framework.displayName || framework.id})`);
|
|
142
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
143
|
+
if (peers.length === 0) {
|
|
144
|
+
console.log(' No peers found');
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
totalPeers += peers.length;
|
|
148
|
+
peers.forEach(peer => {
|
|
149
|
+
const aliasDisplay = peer.alias || peer.displayName || '-';
|
|
150
|
+
const displayName = peer.alias ? peer.displayName : '';
|
|
151
|
+
const keyShort = peer.publicKey?.substring(0, 16) || '-';
|
|
152
|
+
const statusIcon = peer.status === 'approved' ? '✓' : peer.status === 'pending' ? '?' : '✗';
|
|
153
|
+
console.log(` ${statusIcon} ${aliasDisplay.padEnd(15)} ${(displayName || '').padEnd(25)} ${peer.status.padEnd(10)} ${peer.grantedScopes?.scopes.map(s => s.intent).join(', ') || 'none'}`);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
console.log('');
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.log(`${framework.name} (${framework.displayName || framework.id})`);
|
|
160
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
161
|
+
console.log(` Error: ${error.message}`);
|
|
162
|
+
console.log('');
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
// Restore original OGP_HOME
|
|
166
|
+
if (originalOgpHome) {
|
|
167
|
+
process.env.OGP_HOME = originalOgpHome;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
delete process.env.OGP_HOME;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
console.log(`Total: ${totalPeers} peer${totalPeers !== 1 ? 's' : ''} across ${enabledFrameworks.length} framework${enabledFrameworks.length !== 1 ? 's' : ''}`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Single framework mode (existing behavior)
|
|
28
178
|
// listPeers() doesn't filter 'removed' — load all and filter manually if needed
|
|
29
179
|
const allPeers = loadPeers();
|
|
30
180
|
const peers = status ? allPeers.filter(p => p.status === status) : allPeers.filter(p => p.status !== 'removed');
|
|
@@ -41,18 +191,133 @@ export async function federationList(status) {
|
|
|
41
191
|
const displayCol = (peer.displayName || '-').slice(0, 20).padEnd(20);
|
|
42
192
|
const keyCol = (peer.publicKey?.substring(0, 16) || '-') + '...';
|
|
43
193
|
const statusCol = peer.status;
|
|
44
|
-
|
|
194
|
+
// Health status indicator
|
|
195
|
+
let healthIcon = '';
|
|
196
|
+
if (peer.status === 'approved') {
|
|
197
|
+
if (peer.healthy === true) {
|
|
198
|
+
healthIcon = '✓';
|
|
199
|
+
}
|
|
200
|
+
else if (peer.healthy === false) {
|
|
201
|
+
healthIcon = '✗';
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
healthIcon = '?'; // Unknown health status
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
console.log(` ${healthIcon ? healthIcon + ' ' : ''}${aliasCol} ${displayCol} ${keyCol.padEnd(20)} ${statusCol}`);
|
|
45
208
|
console.log(` Gateway: ${peer.gatewayUrl}`);
|
|
46
209
|
console.log(` ID: ${peer.id}`);
|
|
210
|
+
// Show health details for approved peers
|
|
211
|
+
if (peer.status === 'approved') {
|
|
212
|
+
if (peer.lastSeenAt) {
|
|
213
|
+
const lastSeen = new Date(peer.lastSeenAt);
|
|
214
|
+
const now = new Date();
|
|
215
|
+
const minutesAgo = Math.floor((now.getTime() - lastSeen.getTime()) / 60000);
|
|
216
|
+
console.log(` Last seen: ${minutesAgo < 60 ? minutesAgo + 'm ago' : Math.floor(minutesAgo / 60) + 'h ago'}`);
|
|
217
|
+
}
|
|
218
|
+
if (peer.healthCheckFailures && peer.healthCheckFailures > 0) {
|
|
219
|
+
console.log(` Health check failures: ${peer.healthCheckFailures}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
47
222
|
console.log('');
|
|
48
223
|
});
|
|
49
224
|
}
|
|
50
225
|
export async function federationStatus() {
|
|
226
|
+
// Check if --for all was specified
|
|
227
|
+
if (process.env.OGP_FOR_ALL === 'true') {
|
|
228
|
+
const metaConfig = loadMetaConfig();
|
|
229
|
+
const enabledFrameworks = metaConfig.frameworks.filter(f => f.enabled);
|
|
230
|
+
if (enabledFrameworks.length === 0) {
|
|
231
|
+
console.error('Error: No enabled frameworks found. Run "ogp setup" first.');
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
// Print header
|
|
235
|
+
console.log('\n═══════════════════════════════════════════════════════════════');
|
|
236
|
+
console.log(`Federation Status (All Frameworks)`);
|
|
237
|
+
console.log('═══════════════════════════════════════════════════════════════\n');
|
|
238
|
+
let totalApproved = 0;
|
|
239
|
+
let totalPending = 0;
|
|
240
|
+
let totalRejected = 0;
|
|
241
|
+
let totalRemoved = 0;
|
|
242
|
+
// Iterate through each framework
|
|
243
|
+
for (const framework of enabledFrameworks) {
|
|
244
|
+
const originalOgpHome = process.env.OGP_HOME;
|
|
245
|
+
process.env.OGP_HOME = expandTilde(framework.configDir);
|
|
246
|
+
try {
|
|
247
|
+
const config = loadConfig();
|
|
248
|
+
if (!config) {
|
|
249
|
+
console.log(`${framework.name} (${framework.displayName || framework.id})`);
|
|
250
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
251
|
+
console.log(' No config found - run setup');
|
|
252
|
+
console.log('');
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
// Load peers for this framework
|
|
256
|
+
const peers = listPeers();
|
|
257
|
+
const approvedPeers = peers.filter(p => p.status === 'approved');
|
|
258
|
+
const pendingPeers = peers.filter(p => p.status === 'pending');
|
|
259
|
+
const rejectedPeers = peers.filter(p => p.status === 'rejected');
|
|
260
|
+
const removedPeers = peers.filter(p => p.status === 'removed');
|
|
261
|
+
// Update totals
|
|
262
|
+
totalApproved += approvedPeers.length;
|
|
263
|
+
totalPending += pendingPeers.length;
|
|
264
|
+
totalRejected += rejectedPeers.length;
|
|
265
|
+
totalRemoved += removedPeers.length;
|
|
266
|
+
// Print framework header
|
|
267
|
+
console.log(`${framework.name} (${framework.displayName || framework.id})`);
|
|
268
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
269
|
+
if (peers.length === 0) {
|
|
270
|
+
console.log(' No peers configured');
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
console.log(` Total: ${peers.length} | Approved: ${approvedPeers.length} | Pending: ${pendingPeers.length} | Rejected: ${rejectedPeers.length} | Removed: ${removedPeers.length}`);
|
|
274
|
+
// Show aliases for approved peers
|
|
275
|
+
if (approvedPeers.length > 0) {
|
|
276
|
+
console.log('\n Approved peers:');
|
|
277
|
+
for (const peer of approvedPeers) {
|
|
278
|
+
const aliasDisplay = peer.alias || peer.displayName || 'no alias';
|
|
279
|
+
const scopes = peer.grantedScopes?.scopes.map(s => s.intent).join(', ') || 'none';
|
|
280
|
+
console.log(` ${aliasDisplay.padEnd(20)} ${scopes}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
console.log('');
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
console.log(`${framework.name} (${framework.displayName || framework.id})`);
|
|
288
|
+
console.log('───────────────────────────────────────────────────────────────');
|
|
289
|
+
console.log(` Error: ${error.message}`);
|
|
290
|
+
console.log('');
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
// Restore original OGP_HOME
|
|
294
|
+
if (originalOgpHome) {
|
|
295
|
+
process.env.OGP_HOME = originalOgpHome;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
delete process.env.OGP_HOME;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
console.log('═══════════════════════════════════════════════════════════════');
|
|
303
|
+
console.log(`Total across all frameworks:`);
|
|
304
|
+
console.log(` Approved: ${totalApproved}`);
|
|
305
|
+
console.log(` Pending: ${totalPending}`);
|
|
306
|
+
console.log(` Rejected: ${totalRejected}`);
|
|
307
|
+
console.log(` Removed: ${totalRemoved}`);
|
|
308
|
+
console.log('');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// Single framework mode (existing behavior)
|
|
51
312
|
const peers = listPeers();
|
|
52
313
|
const approvedPeers = peers.filter(p => p.status === 'approved');
|
|
53
314
|
const pendingPeers = peers.filter(p => p.status === 'pending');
|
|
54
315
|
const rejectedPeers = peers.filter(p => p.status === 'rejected');
|
|
55
316
|
const removedPeers = peers.filter(p => p.status === 'removed');
|
|
317
|
+
// Health statistics for approved peers
|
|
318
|
+
const healthyPeers = approvedPeers.filter(p => p.healthy === true);
|
|
319
|
+
const unhealthyPeers = approvedPeers.filter(p => p.healthy === false);
|
|
320
|
+
const unknownHealthPeers = approvedPeers.filter(p => p.healthy === undefined);
|
|
56
321
|
console.log('\n📊 FEDERATION STATUS\n');
|
|
57
322
|
// Summary counts
|
|
58
323
|
console.log(`Total peers: ${peers.length}`);
|
|
@@ -61,6 +326,14 @@ export async function federationStatus() {
|
|
|
61
326
|
console.log(` Rejected: ${rejectedPeers.length}`);
|
|
62
327
|
console.log(` Removed: ${removedPeers.length}`);
|
|
63
328
|
console.log('');
|
|
329
|
+
// Health summary for approved peers
|
|
330
|
+
if (approvedPeers.length > 0) {
|
|
331
|
+
console.log('🏥 PEER HEALTH:\n');
|
|
332
|
+
console.log(` Healthy: ${healthyPeers.length} (✓)`);
|
|
333
|
+
console.log(` Unhealthy: ${unhealthyPeers.length} (✗)`);
|
|
334
|
+
console.log(` Unknown: ${unknownHealthPeers.length} (?)`);
|
|
335
|
+
console.log('');
|
|
336
|
+
}
|
|
64
337
|
// Alias → Public Key mapping section
|
|
65
338
|
if (peers.length > 0) {
|
|
66
339
|
console.log('📝 ALIAS → PUBLIC KEY MAPPING:\n');
|
|
@@ -91,8 +364,22 @@ export async function federationStatus() {
|
|
|
91
364
|
export async function federationRequest(peerUrl, peerId, alias) {
|
|
92
365
|
const config = requireConfig();
|
|
93
366
|
const keypair = loadOrGenerateKeyPair();
|
|
367
|
+
if (!await ensureLocalGatewayReachable(config, 'send federation requests')) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
94
370
|
// BUILD-111: Use public key prefix as peer ID (port-agnostic identity)
|
|
95
371
|
const ourPeerId = keypair.publicKey.substring(0, 16);
|
|
372
|
+
let resolvedPeerUrl = normalizeGatewayUrl(peerUrl);
|
|
373
|
+
let peerCard = null;
|
|
374
|
+
try {
|
|
375
|
+
const resolved = await resolvePeerGatewayUrl(resolvedPeerUrl, 'Preflight');
|
|
376
|
+
resolvedPeerUrl = resolved.gatewayUrl;
|
|
377
|
+
peerCard = resolved.card;
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
console.error(error.message);
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
96
383
|
// Build our peer info
|
|
97
384
|
const peer = {
|
|
98
385
|
id: ourPeerId, // Public key prefix, not hostname:port
|
|
@@ -112,7 +399,7 @@ export async function federationRequest(peerUrl, peerId, alias) {
|
|
|
112
399
|
const requestBody = { peer, signature, offeredIntents: ourIntents };
|
|
113
400
|
// Send request
|
|
114
401
|
try {
|
|
115
|
-
const response = await fetch(`${
|
|
402
|
+
const response = await fetch(`${resolvedPeerUrl}/federation/request`, {
|
|
116
403
|
method: 'POST',
|
|
117
404
|
headers: { 'Content-Type': 'application/json' },
|
|
118
405
|
body: JSON.stringify(requestBody)
|
|
@@ -129,18 +416,18 @@ export async function federationRequest(peerUrl, peerId, alias) {
|
|
|
129
416
|
// Store them as a pending peer so we can send intents when approved
|
|
130
417
|
try {
|
|
131
418
|
const { addPeer } = await import('../daemon/peers.js');
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
const canonicalId = card.publicKey?.substring(0,
|
|
419
|
+
const card = peerCard;
|
|
420
|
+
if (card) {
|
|
421
|
+
const peerHostname = new URL(resolvedPeerUrl).hostname;
|
|
422
|
+
const peerPort = new URL(resolvedPeerUrl).port || '18790';
|
|
423
|
+
// BUILD-111: Use a 32-char public key prefix as canonical ID to avoid
|
|
424
|
+
// duplicate short/full peer IDs across request/approve flows.
|
|
425
|
+
const canonicalId = card.publicKey?.substring(0, 32) || `${peerHostname}:${peerPort}`;
|
|
139
426
|
addPeer({
|
|
140
427
|
id: canonicalId,
|
|
141
428
|
displayName: card.displayName || peerId,
|
|
142
429
|
email: card.email || '',
|
|
143
|
-
gatewayUrl:
|
|
430
|
+
gatewayUrl: resolvedPeerUrl,
|
|
144
431
|
publicKey: card.publicKey || '',
|
|
145
432
|
status: 'pending',
|
|
146
433
|
requestedAt: new Date().toISOString(),
|
|
@@ -161,6 +448,9 @@ export async function federationRequest(peerUrl, peerId, alias) {
|
|
|
161
448
|
}
|
|
162
449
|
export async function federationApprove(peerId, options = {}) {
|
|
163
450
|
const config = requireConfig();
|
|
451
|
+
if (!await ensureLocalGatewayReachable(config, 'approve federation requests')) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
164
454
|
// Resolve peer identifier (alias, ID, or public key)
|
|
165
455
|
const resolvedId = resolvePeerId(peerId);
|
|
166
456
|
if (!resolvedId) {
|
|
@@ -177,6 +467,15 @@ export async function federationApprove(peerId, options = {}) {
|
|
|
177
467
|
console.log(`Peer ${peerId} is already approved.`);
|
|
178
468
|
return;
|
|
179
469
|
}
|
|
470
|
+
let peerGatewayUrl = peer.gatewayUrl;
|
|
471
|
+
try {
|
|
472
|
+
peerGatewayUrl = await refreshPeerGatewayUrlForApproval(peer);
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
console.error(error.message);
|
|
476
|
+
console.error('Ask the peer to fix their gatewayUrl and resend the federation request.');
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
180
479
|
// BUILD-110: Mirror peer's offered intents by default, with user confirmation
|
|
181
480
|
const DEFAULT_INTENTS = ['message', 'agent-comms', 'project.join', 'project.contribute', 'project.query', 'project.status'];
|
|
182
481
|
// If peer offered intents, use those as default (for symmetry)
|
|
@@ -225,9 +524,9 @@ export async function federationApprove(peerId, options = {}) {
|
|
|
225
524
|
approvePeer(peerId);
|
|
226
525
|
console.log(`✓ Approved peer: ${peerId}`);
|
|
227
526
|
// BUILD-102: Auto-register existing local projects as agent-comms topics for this peer
|
|
228
|
-
const {
|
|
527
|
+
const { listProjectsForPeer } = await import('../daemon/projects.js');
|
|
229
528
|
const { setPeerTopicPolicy } = await import('../daemon/peers.js');
|
|
230
|
-
const projects =
|
|
529
|
+
const projects = listProjectsForPeer(peerId);
|
|
231
530
|
if (projects.length > 0) {
|
|
232
531
|
for (const project of projects) {
|
|
233
532
|
setPeerTopicPolicy(peerId, project.id, 'summary');
|
|
@@ -245,7 +544,7 @@ export async function federationApprove(peerId, options = {}) {
|
|
|
245
544
|
const keypair = loadOrGenerateKeyPair();
|
|
246
545
|
const ourConfig = requireConfig();
|
|
247
546
|
const nonce = crypto.randomUUID();
|
|
248
|
-
await fetch(`${
|
|
547
|
+
await fetch(`${peerGatewayUrl}/federation/approve`, {
|
|
249
548
|
method: 'POST',
|
|
250
549
|
headers: { 'Content-Type': 'application/json' },
|
|
251
550
|
body: JSON.stringify({
|
|
@@ -319,7 +618,7 @@ export async function federationRemove(peerId) {
|
|
|
319
618
|
try {
|
|
320
619
|
const keypair = loadOrGenerateKeyPair();
|
|
321
620
|
// BUILD-111: Use public key prefix as our ID (not hostname:port)
|
|
322
|
-
const ourId = keypair.publicKey.substring(0,
|
|
621
|
+
const ourId = keypair.publicKey.substring(0, 32);
|
|
323
622
|
const timestamp = new Date().toISOString();
|
|
324
623
|
// Sign the removal payload
|
|
325
624
|
const payload = { peerId: ourId, timestamp };
|
|
@@ -371,7 +670,7 @@ export async function federationSend(peerId, intent, payloadJson, timeoutMs) {
|
|
|
371
670
|
const payload = JSON.parse(payloadJson);
|
|
372
671
|
const keypair = loadOrGenerateKeyPair();
|
|
373
672
|
// BUILD-111: Use public key prefix as our ID (not hostname:port)
|
|
374
|
-
const ourId = keypair.publicKey.substring(0,
|
|
673
|
+
const ourId = keypair.publicKey.substring(0, 32);
|
|
375
674
|
const message = {
|
|
376
675
|
intent,
|
|
377
676
|
from: ourId,
|
|
@@ -396,11 +695,25 @@ export async function federationSend(peerId, intent, payloadJson, timeoutMs) {
|
|
|
396
695
|
});
|
|
397
696
|
if (timeoutId)
|
|
398
697
|
clearTimeout(timeoutId);
|
|
698
|
+
let result = null;
|
|
699
|
+
try {
|
|
700
|
+
result = await response.json();
|
|
701
|
+
}
|
|
702
|
+
catch {
|
|
703
|
+
result = null;
|
|
704
|
+
}
|
|
399
705
|
if (!response.ok) {
|
|
706
|
+
if (result?.error) {
|
|
707
|
+
console.error(`Send failed: ${response.status} ${response.statusText} - ${result.error}`);
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
400
710
|
console.error(`Send failed: ${response.status} ${response.statusText}`);
|
|
401
|
-
return
|
|
711
|
+
return {
|
|
712
|
+
success: false,
|
|
713
|
+
error: `Send failed: ${response.status} ${response.statusText}`,
|
|
714
|
+
statusCode: response.status
|
|
715
|
+
};
|
|
402
716
|
}
|
|
403
|
-
const result = await response.json();
|
|
404
717
|
return result;
|
|
405
718
|
}
|
|
406
719
|
catch (error) {
|
|
@@ -431,7 +744,7 @@ export async function federationShowScopes(peerId) {
|
|
|
431
744
|
}
|
|
432
745
|
console.log(`\nSCOPES FOR ${peer.displayName} (${peerId}):\n`);
|
|
433
746
|
console.log(' Status:', peer.status);
|
|
434
|
-
console.log(' Protocol:', peer.protocolVersion || '0.1.0 (legacy)');
|
|
747
|
+
console.log(' Wire Protocol:', peer.protocolVersion || '0.1.0 (legacy)');
|
|
435
748
|
console.log('');
|
|
436
749
|
// What I grant TO this peer
|
|
437
750
|
if (peer.grantedScopes) {
|
|
@@ -544,9 +857,10 @@ export async function federationSendAgentComms(peerId, topic, messageText, optio
|
|
|
544
857
|
return;
|
|
545
858
|
}
|
|
546
859
|
const keypair = loadOrGenerateKeyPair();
|
|
547
|
-
//
|
|
548
|
-
const ourId = keypair.publicKey.substring(0,
|
|
860
|
+
// Use 32-char public key prefix as our ID (avoids Ed25519 DER header collision with 16-char)
|
|
861
|
+
const ourId = keypair.publicKey.substring(0, 32);
|
|
549
862
|
const nonce = crypto.randomUUID();
|
|
863
|
+
const conversationId = options.conversationId || nonce;
|
|
550
864
|
// Build replyTo URL if we want to receive callbacks
|
|
551
865
|
const replyTo = options.waitForReply
|
|
552
866
|
? `${config.gatewayUrl}/federation/reply/${nonce}`
|
|
@@ -558,7 +872,7 @@ export async function federationSendAgentComms(peerId, topic, messageText, optio
|
|
|
558
872
|
nonce,
|
|
559
873
|
timestamp: new Date().toISOString(),
|
|
560
874
|
replyTo,
|
|
561
|
-
conversationId
|
|
875
|
+
conversationId,
|
|
562
876
|
payload: {
|
|
563
877
|
topic,
|
|
564
878
|
message: messageText,
|
|
@@ -591,6 +905,14 @@ export async function federationSendAgentComms(peerId, topic, messageText, optio
|
|
|
591
905
|
return;
|
|
592
906
|
}
|
|
593
907
|
const result = await response.json();
|
|
908
|
+
logActivity({
|
|
909
|
+
direction: 'out',
|
|
910
|
+
peerId,
|
|
911
|
+
peerName: peer.displayName,
|
|
912
|
+
topic,
|
|
913
|
+
message: messageText
|
|
914
|
+
});
|
|
915
|
+
await deliverLocalSessionText(`[OGP Agent-Comms Sent] To ${peer.displayName} (${topic}): ${messageText}`);
|
|
594
916
|
console.log(`✓ Agent-comms sent to ${peer.displayName}`);
|
|
595
917
|
console.log(` Topic: ${topic}`);
|
|
596
918
|
console.log(` Message: ${messageText}`);
|
|
@@ -618,10 +940,12 @@ export async function federationSendAgentComms(peerId, topic, messageText, optio
|
|
|
618
940
|
}
|
|
619
941
|
}
|
|
620
942
|
console.log('\n⏱ Reply timeout - no response received');
|
|
943
|
+
process.exit(1);
|
|
621
944
|
}
|
|
622
945
|
}
|
|
623
946
|
catch (error) {
|
|
624
947
|
console.error('Failed to send agent-comms:', error);
|
|
948
|
+
process.exit(1);
|
|
625
949
|
}
|
|
626
950
|
}
|
|
627
951
|
/**
|