@dp-pcs/ogp 0.4.2 → 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 +45 -15
- package/dist/cli/federation.d.ts +14 -0
- package/dist/cli/federation.d.ts.map +1 -1
- package/dist/cli/federation.js +165 -17
- 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 +4 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +57 -4
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli.js +11 -4
- package/dist/cli.js.map +1 -1
- package/dist/daemon/agent-comms.js +1 -1
- 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 +12 -6
- 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 +119 -7
- package/dist/daemon/keypair.js.map +1 -1
- package/dist/daemon/message-handler.js +23 -16
- package/dist/daemon/message-handler.js.map +1 -1
- package/dist/daemon/notify.d.ts.map +1 -1
- package/dist/daemon/notify.js +47 -0
- package/dist/daemon/notify.js.map +1 -1
- package/dist/daemon/peers.d.ts +13 -0
- package/dist/daemon/peers.d.ts.map +1 -1
- package/dist/daemon/peers.js +77 -6
- 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 +23 -16
- package/dist/daemon/projects.js.map +1 -1
- package/dist/daemon/server.d.ts +1 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +54 -44
- package/dist/daemon/server.js.map +1 -1
- package/dist/shared/help.js +2 -1
- package/dist/shared/help.js.map +1 -1
- package/docs/CLI-REFERENCE.md +1 -0
- package/docs/GETTING-STARTED.md +12 -1
- package/docs/cloudflare-named-tunnel-setup.md +126 -0
- package/docs/federation-flow.md +6 -6
- package/docs/project-intent-testing.md +97 -0
- package/docs/quickstart.md +12 -4
- package/docs/scopes.md +13 -13
- package/package.json +4 -4
- package/scripts/install-skills.js +19 -1
- package/scripts/test-project-intents.mjs +614 -0
- package/skills/ogp/SKILL.md +1 -1
- package/skills/ogp-agent-comms/SKILL.md +1 -1
- package/skills/ogp-expose/SKILL.md +103 -8
- package/skills/ogp-project/SKILL.md +47 -33
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, cpSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, cpSync, readdirSync, rmSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -55,6 +55,13 @@ const availableSkills = readdirSync(skillsSrc, { withFileTypes: true })
|
|
|
55
55
|
.filter(d => d.isDirectory() && existsSync(join(skillsSrc, d.name, 'SKILL.md')))
|
|
56
56
|
.map(d => d.name);
|
|
57
57
|
|
|
58
|
+
function getSkillVersion(skill) {
|
|
59
|
+
const skillFile = join(skillsSrc, skill, 'SKILL.md');
|
|
60
|
+
const content = readFileSync(skillFile, 'utf8');
|
|
61
|
+
const match = content.match(/^version:\s*(.+)$/m);
|
|
62
|
+
return match?.[1]?.trim() ?? 'unknown';
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
if (availableSkills.length === 0) {
|
|
59
66
|
console.log('No skills found in the skills/ directory.');
|
|
60
67
|
process.exit(0);
|
|
@@ -176,6 +183,9 @@ async function main() {
|
|
|
176
183
|
const src = join(skillsSrc, skill);
|
|
177
184
|
const dest = join(platform.skillsDir, skill);
|
|
178
185
|
try {
|
|
186
|
+
// Replace the installed skill directory wholesale so stale files from
|
|
187
|
+
// previous package versions do not survive upgrades.
|
|
188
|
+
rmSync(dest, { recursive: true, force: true });
|
|
179
189
|
cpSync(src, dest, { recursive: true });
|
|
180
190
|
console.log(` ✓ ${skill}`);
|
|
181
191
|
installed++;
|
|
@@ -192,6 +202,14 @@ async function main() {
|
|
|
192
202
|
console.log(`\n✅ Successfully installed ${totalInstalled} skill(s) to ${selectedPlatforms.length} platform(s)`);
|
|
193
203
|
console.log('');
|
|
194
204
|
|
|
205
|
+
const installedVersions = selectedSkills
|
|
206
|
+
.map(skill => `${skill}@${getSkillVersion(skill)}`)
|
|
207
|
+
.join(', ');
|
|
208
|
+
console.log(`Installed skill versions: ${installedVersions}`);
|
|
209
|
+
console.log('Verify installed copies with:');
|
|
210
|
+
console.log(" rg -n '^version:' ~/.openclaw/skills/ogp*/SKILL.md ~/.claude/skills/ogp*/SKILL.md 2>/dev/null");
|
|
211
|
+
console.log('');
|
|
212
|
+
|
|
195
213
|
// Platform-specific restart instructions
|
|
196
214
|
const platformNames = selectedPlatforms.map(p => p.name);
|
|
197
215
|
if (platformNames.includes('OpenClaw')) {
|
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import fsp from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
13
|
+
const cliPath = path.join(repoRoot, 'dist', 'cli.js');
|
|
14
|
+
|
|
15
|
+
function timestamp() {
|
|
16
|
+
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseArgs(argv) {
|
|
20
|
+
const args = {
|
|
21
|
+
root: path.join(repoRoot, '.tmp', 'project-intent-test', timestamp()),
|
|
22
|
+
projectId: 'project-intent-smoke',
|
|
23
|
+
projectName: 'Project Intent Smoke Test',
|
|
24
|
+
skipBuild: false,
|
|
25
|
+
keepState: false,
|
|
26
|
+
ports: {
|
|
27
|
+
alpha: 18890,
|
|
28
|
+
beta: 18891
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
33
|
+
const arg = argv[i];
|
|
34
|
+
switch (arg) {
|
|
35
|
+
case '--root':
|
|
36
|
+
args.root = path.resolve(argv[++i]);
|
|
37
|
+
break;
|
|
38
|
+
case '--project-id':
|
|
39
|
+
args.projectId = argv[++i];
|
|
40
|
+
break;
|
|
41
|
+
case '--project-name':
|
|
42
|
+
args.projectName = argv[++i];
|
|
43
|
+
break;
|
|
44
|
+
case '--alpha-port':
|
|
45
|
+
args.ports.alpha = Number.parseInt(argv[++i], 10);
|
|
46
|
+
break;
|
|
47
|
+
case '--beta-port':
|
|
48
|
+
args.ports.beta = Number.parseInt(argv[++i], 10);
|
|
49
|
+
break;
|
|
50
|
+
case '--skip-build':
|
|
51
|
+
args.skipBuild = true;
|
|
52
|
+
break;
|
|
53
|
+
case '--keep-state':
|
|
54
|
+
args.keepState = true;
|
|
55
|
+
break;
|
|
56
|
+
case '--help':
|
|
57
|
+
case '-h':
|
|
58
|
+
printHelp();
|
|
59
|
+
process.exit(0);
|
|
60
|
+
default:
|
|
61
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!Number.isInteger(args.ports.alpha) || !Number.isInteger(args.ports.beta)) {
|
|
66
|
+
throw new Error('Ports must be integers');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (args.ports.alpha === args.ports.beta) {
|
|
70
|
+
throw new Error('Alpha and beta ports must differ');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return args;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function printHelp() {
|
|
77
|
+
console.log(`Project-intent end-to-end harness
|
|
78
|
+
|
|
79
|
+
Usage:
|
|
80
|
+
node scripts/test-project-intents.mjs [options]
|
|
81
|
+
|
|
82
|
+
Options:
|
|
83
|
+
--root <dir> State root (default: repo-local .tmp directory)
|
|
84
|
+
--project-id <id> Project ID to use
|
|
85
|
+
--project-name <name> Project name to use
|
|
86
|
+
--alpha-port <port> Alpha daemon port (default: 18890)
|
|
87
|
+
--beta-port <port> Beta daemon port (default: 18891)
|
|
88
|
+
--skip-build Skip npm run build
|
|
89
|
+
--keep-state Keep test state/logs after success
|
|
90
|
+
`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function logSection(title) {
|
|
94
|
+
console.log(`\n=== ${title} ===`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatShellCommand(command, args = [], envAdditions = {}) {
|
|
98
|
+
const envPrefix = Object.entries(envAdditions)
|
|
99
|
+
.map(([key, value]) => `${key}=${shellQuote(String(value))}`)
|
|
100
|
+
.join(' ');
|
|
101
|
+
const parts = [command, ...args].map(shellQuote);
|
|
102
|
+
return [envPrefix, ...parts].filter(Boolean).join(' ');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function shellQuote(value) {
|
|
106
|
+
if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function ensureDir(dir) {
|
|
113
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function writeJson(filePath, data) {
|
|
117
|
+
await ensureDir(path.dirname(filePath));
|
|
118
|
+
await fsp.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function readJson(filePath) {
|
|
122
|
+
const raw = await fsp.readFile(filePath, 'utf-8');
|
|
123
|
+
return JSON.parse(raw);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getPeersPath(home) {
|
|
127
|
+
return path.join(home, 'peers.json');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getProjectsPath(home) {
|
|
131
|
+
return path.join(home, 'projects.json');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getConfigPath(home) {
|
|
135
|
+
return path.join(home, 'config.json');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function assert(condition, message) {
|
|
139
|
+
if (!condition) {
|
|
140
|
+
throw new Error(message);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function runCommand(command, args, options = {}) {
|
|
145
|
+
const {
|
|
146
|
+
cwd = repoRoot,
|
|
147
|
+
env = process.env,
|
|
148
|
+
expectCode = 0,
|
|
149
|
+
label
|
|
150
|
+
} = options;
|
|
151
|
+
|
|
152
|
+
const printable = formatShellCommand(command, args, options.envAdditions || {});
|
|
153
|
+
console.log(`$ ${printable}`);
|
|
154
|
+
|
|
155
|
+
const child = spawn(command, args, {
|
|
156
|
+
cwd,
|
|
157
|
+
env,
|
|
158
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
let stdout = '';
|
|
162
|
+
let stderr = '';
|
|
163
|
+
|
|
164
|
+
child.stdout.on('data', (chunk) => {
|
|
165
|
+
stdout += chunk.toString();
|
|
166
|
+
});
|
|
167
|
+
child.stderr.on('data', (chunk) => {
|
|
168
|
+
stderr += chunk.toString();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const code = await new Promise((resolve, reject) => {
|
|
172
|
+
child.on('error', reject);
|
|
173
|
+
child.on('close', resolve);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (stdout.trim()) {
|
|
177
|
+
console.log(stdout.trimEnd());
|
|
178
|
+
}
|
|
179
|
+
if (stderr.trim()) {
|
|
180
|
+
console.error(stderr.trimEnd());
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (code !== expectCode) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`${label || command} exited with code ${code}; expected ${expectCode}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { code, stdout, stderr };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function startDaemon(home, name, logsDir) {
|
|
193
|
+
const env = {
|
|
194
|
+
...process.env,
|
|
195
|
+
OGP_HOME: home
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const stdoutLog = fs.createWriteStream(path.join(logsDir, `${name}.stdout.log`), { flags: 'a' });
|
|
199
|
+
const stderrLog = fs.createWriteStream(path.join(logsDir, `${name}.stderr.log`), { flags: 'a' });
|
|
200
|
+
|
|
201
|
+
const printable = formatShellCommand(process.execPath, [cliPath, 'start'], { OGP_HOME: home });
|
|
202
|
+
console.log(`$ ${printable}`);
|
|
203
|
+
|
|
204
|
+
const child = spawn(process.execPath, [cliPath, 'start'], {
|
|
205
|
+
cwd: repoRoot,
|
|
206
|
+
env,
|
|
207
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
let stdout = '';
|
|
211
|
+
let stderr = '';
|
|
212
|
+
|
|
213
|
+
child.stdout.on('data', (chunk) => {
|
|
214
|
+
const text = chunk.toString();
|
|
215
|
+
stdout += text;
|
|
216
|
+
stdoutLog.write(text);
|
|
217
|
+
});
|
|
218
|
+
child.stderr.on('data', (chunk) => {
|
|
219
|
+
const text = chunk.toString();
|
|
220
|
+
stderr += text;
|
|
221
|
+
stderrLog.write(text);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
child,
|
|
226
|
+
home,
|
|
227
|
+
name,
|
|
228
|
+
getStdout: () => stdout,
|
|
229
|
+
getStderr: () => stderr,
|
|
230
|
+
closeLogs: () => {
|
|
231
|
+
stdoutLog.end();
|
|
232
|
+
stderrLog.end();
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function stopDaemon(daemon) {
|
|
238
|
+
if (!daemon?.child || daemon.child.exitCode !== null) {
|
|
239
|
+
daemon?.closeLogs?.();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
daemon.child.kill('SIGINT');
|
|
244
|
+
await Promise.race([
|
|
245
|
+
new Promise((resolve) => daemon.child.once('close', resolve)),
|
|
246
|
+
sleep(5000).then(() => {
|
|
247
|
+
daemon.child.kill('SIGKILL');
|
|
248
|
+
})
|
|
249
|
+
]);
|
|
250
|
+
daemon.closeLogs();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function waitForJson(url, daemon, timeoutMs = 15000) {
|
|
254
|
+
const started = Date.now();
|
|
255
|
+
while (Date.now() - started < timeoutMs) {
|
|
256
|
+
if (daemon?.child?.exitCode !== null) {
|
|
257
|
+
throw new Error(`${daemon.name} daemon exited early.\nstdout:\n${daemon.getStdout()}\nstderr:\n${daemon.getStderr()}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const response = await fetch(url);
|
|
262
|
+
if (response.ok) {
|
|
263
|
+
return response.json();
|
|
264
|
+
}
|
|
265
|
+
} catch {
|
|
266
|
+
// Wait and retry while the daemon comes up.
|
|
267
|
+
}
|
|
268
|
+
await sleep(250);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
throw new Error(`Timed out waiting for ${url}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function waitForPeer(home, predicate, timeoutMs = 10000) {
|
|
275
|
+
const started = Date.now();
|
|
276
|
+
while (Date.now() - started < timeoutMs) {
|
|
277
|
+
if (fs.existsSync(getPeersPath(home))) {
|
|
278
|
+
const peers = await readJson(getPeersPath(home));
|
|
279
|
+
const match = peers.find(predicate);
|
|
280
|
+
if (match) {
|
|
281
|
+
return match;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
await sleep(200);
|
|
285
|
+
}
|
|
286
|
+
throw new Error(`Timed out waiting for peer in ${home}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function waitForProject(home, projectId, timeoutMs = 10000) {
|
|
290
|
+
const started = Date.now();
|
|
291
|
+
while (Date.now() - started < timeoutMs) {
|
|
292
|
+
if (fs.existsSync(getProjectsPath(home))) {
|
|
293
|
+
const projects = await readJson(getProjectsPath(home));
|
|
294
|
+
const match = projects.find((project) => project.id === projectId);
|
|
295
|
+
if (match) {
|
|
296
|
+
return match;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
await sleep(200);
|
|
300
|
+
}
|
|
301
|
+
throw new Error(`Timed out waiting for project ${projectId} in ${home}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function createGatewayConfig(home, gateway) {
|
|
305
|
+
const config = {
|
|
306
|
+
daemonPort: gateway.port,
|
|
307
|
+
openclawUrl: 'http://127.0.0.1:9',
|
|
308
|
+
openclawToken: 'test-token',
|
|
309
|
+
openclawHooksToken: 'test-hooks',
|
|
310
|
+
gatewayUrl: gateway.url,
|
|
311
|
+
displayName: gateway.displayName,
|
|
312
|
+
email: gateway.email,
|
|
313
|
+
stateDir: path.join(home, 'state'),
|
|
314
|
+
agentId: gateway.agentId,
|
|
315
|
+
platform: 'openclaw'
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
await ensureDir(home);
|
|
319
|
+
await ensureDir(config.stateDir);
|
|
320
|
+
await writeJson(getConfigPath(home), config);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getContributionSummaries(project) {
|
|
324
|
+
return (project.topics || []).flatMap((topic) =>
|
|
325
|
+
(topic.contributions || []).map((contribution) => contribution.summary)
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function main() {
|
|
330
|
+
const args = parseArgs(process.argv.slice(2));
|
|
331
|
+
const runRoot = args.root;
|
|
332
|
+
const logsDir = path.join(runRoot, 'logs');
|
|
333
|
+
const alphaHome = path.join(runRoot, 'alpha');
|
|
334
|
+
const betaHome = path.join(runRoot, 'beta');
|
|
335
|
+
|
|
336
|
+
const alpha = {
|
|
337
|
+
home: alphaHome,
|
|
338
|
+
displayName: 'Alpha Test Gateway',
|
|
339
|
+
email: 'alpha@example.test',
|
|
340
|
+
agentId: 'alpha',
|
|
341
|
+
port: args.ports.alpha,
|
|
342
|
+
url: `http://127.0.0.1:${args.ports.alpha}`
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const beta = {
|
|
346
|
+
home: betaHome,
|
|
347
|
+
displayName: 'Beta Test Gateway',
|
|
348
|
+
email: 'beta@example.test',
|
|
349
|
+
agentId: 'beta',
|
|
350
|
+
port: args.ports.beta,
|
|
351
|
+
url: `http://127.0.0.1:${args.ports.beta}`
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
let alphaDaemon;
|
|
355
|
+
let betaDaemon;
|
|
356
|
+
|
|
357
|
+
await ensureDir(logsDir);
|
|
358
|
+
await createGatewayConfig(alpha.home, alpha);
|
|
359
|
+
await createGatewayConfig(beta.home, beta);
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
logSection('Build');
|
|
363
|
+
if (!args.skipBuild) {
|
|
364
|
+
await runCommand('npm', ['run', 'build'], { cwd: repoRoot, label: 'build' });
|
|
365
|
+
} else {
|
|
366
|
+
console.log('Skipping build because --skip-build was provided.');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
assert(fs.existsSync(cliPath), `CLI build artifact not found: ${cliPath}`);
|
|
370
|
+
|
|
371
|
+
logSection('Start Local Gateways');
|
|
372
|
+
alphaDaemon = startDaemon(alpha.home, 'alpha', logsDir);
|
|
373
|
+
betaDaemon = startDaemon(beta.home, 'beta', logsDir);
|
|
374
|
+
|
|
375
|
+
const alphaCard = await waitForJson(`${alpha.url}/.well-known/ogp`, alphaDaemon);
|
|
376
|
+
const betaCard = await waitForJson(`${beta.url}/.well-known/ogp`, betaDaemon);
|
|
377
|
+
const betaRuntimeSenderId = betaCard.publicKey.substring(0, 32);
|
|
378
|
+
|
|
379
|
+
console.log(`Alpha online: ${alphaCard.displayName} (${alpha.url})`);
|
|
380
|
+
console.log(`Beta online: ${betaCard.displayName} (${beta.url})`);
|
|
381
|
+
|
|
382
|
+
logSection('Federate Alpha -> Beta');
|
|
383
|
+
await runCommand(
|
|
384
|
+
process.execPath,
|
|
385
|
+
[cliPath, 'federation', 'request', beta.url, 'beta-local', '--alias', 'beta-local'],
|
|
386
|
+
{
|
|
387
|
+
cwd: repoRoot,
|
|
388
|
+
env: { ...process.env, OGP_HOME: alpha.home },
|
|
389
|
+
envAdditions: { OGP_HOME: alpha.home },
|
|
390
|
+
label: 'alpha federation request'
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const alphaPeerOnBeta = await waitForPeer(
|
|
395
|
+
beta.home,
|
|
396
|
+
(peer) => peer.gatewayUrl === alpha.url && peer.status === 'pending'
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
await runCommand(
|
|
400
|
+
process.execPath,
|
|
401
|
+
[
|
|
402
|
+
cliPath,
|
|
403
|
+
'federation',
|
|
404
|
+
'approve',
|
|
405
|
+
alphaPeerOnBeta.id,
|
|
406
|
+
'--intents',
|
|
407
|
+
'message,agent-comms,project.join,project.contribute,project.query,project.status'
|
|
408
|
+
],
|
|
409
|
+
{
|
|
410
|
+
cwd: repoRoot,
|
|
411
|
+
env: { ...process.env, OGP_HOME: beta.home },
|
|
412
|
+
envAdditions: { OGP_HOME: beta.home },
|
|
413
|
+
label: 'beta federation approve'
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const betaPeerOnAlpha = await waitForPeer(
|
|
418
|
+
alpha.home,
|
|
419
|
+
(peer) => peer.gatewayUrl === beta.url && peer.status === 'approved'
|
|
420
|
+
);
|
|
421
|
+
const refreshedAlphaPeerOnBeta = await waitForPeer(
|
|
422
|
+
beta.home,
|
|
423
|
+
(peer) => peer.gatewayUrl === alpha.url && peer.status === 'approved'
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
console.log(`Alpha sees beta as approved peer: ${betaPeerOnAlpha.id}`);
|
|
427
|
+
console.log(`Beta sees alpha as approved peer: ${refreshedAlphaPeerOnBeta.id}`);
|
|
428
|
+
|
|
429
|
+
logSection('Owner-Side Local Project Smoke');
|
|
430
|
+
await runCommand(
|
|
431
|
+
process.execPath,
|
|
432
|
+
[cliPath, 'project', 'create', args.projectId, args.projectName, '--description', 'Project-intent test harness'],
|
|
433
|
+
{
|
|
434
|
+
cwd: repoRoot,
|
|
435
|
+
env: { ...process.env, OGP_HOME: alpha.home },
|
|
436
|
+
envAdditions: { OGP_HOME: alpha.home },
|
|
437
|
+
label: 'alpha project create'
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
await runCommand(
|
|
442
|
+
process.execPath,
|
|
443
|
+
[
|
|
444
|
+
cliPath,
|
|
445
|
+
'project',
|
|
446
|
+
'contribute',
|
|
447
|
+
args.projectId,
|
|
448
|
+
'progress',
|
|
449
|
+
'Alpha created the test project',
|
|
450
|
+
'--metadata',
|
|
451
|
+
JSON.stringify({ stage: 'owner-local-smoke' }),
|
|
452
|
+
'--local-only'
|
|
453
|
+
],
|
|
454
|
+
{
|
|
455
|
+
cwd: repoRoot,
|
|
456
|
+
env: { ...process.env, OGP_HOME: alpha.home },
|
|
457
|
+
envAdditions: { OGP_HOME: alpha.home },
|
|
458
|
+
label: 'alpha local contribution'
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
const alphaProjectAfterLocal = await waitForProject(alpha.home, args.projectId);
|
|
463
|
+
assert(
|
|
464
|
+
getContributionSummaries(alphaProjectAfterLocal).includes('Alpha created the test project'),
|
|
465
|
+
'Alpha local contribution did not persist'
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
logSection('Membership Isolation Before Join');
|
|
469
|
+
await runCommand(
|
|
470
|
+
process.execPath,
|
|
471
|
+
[cliPath, 'project', 'query-peer', refreshedAlphaPeerOnBeta.id, args.projectId, '--limit', '5'],
|
|
472
|
+
{
|
|
473
|
+
cwd: repoRoot,
|
|
474
|
+
env: { ...process.env, OGP_HOME: beta.home },
|
|
475
|
+
envAdditions: { OGP_HOME: beta.home },
|
|
476
|
+
expectCode: 1,
|
|
477
|
+
label: 'beta pre-join query-peer'
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
logSection('Join Flow');
|
|
482
|
+
await runCommand(
|
|
483
|
+
process.execPath,
|
|
484
|
+
[
|
|
485
|
+
cliPath,
|
|
486
|
+
'project',
|
|
487
|
+
'request-join',
|
|
488
|
+
refreshedAlphaPeerOnBeta.id,
|
|
489
|
+
args.projectId,
|
|
490
|
+
args.projectName,
|
|
491
|
+
'--description',
|
|
492
|
+
'Joined through local harness'
|
|
493
|
+
],
|
|
494
|
+
{
|
|
495
|
+
cwd: repoRoot,
|
|
496
|
+
env: { ...process.env, OGP_HOME: beta.home },
|
|
497
|
+
envAdditions: { OGP_HOME: beta.home },
|
|
498
|
+
label: 'beta request-join'
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
const alphaProjectAfterJoin = await waitForProject(alpha.home, args.projectId);
|
|
503
|
+
const betaProjectAfterJoin = await waitForProject(beta.home, args.projectId);
|
|
504
|
+
|
|
505
|
+
assert(
|
|
506
|
+
alphaProjectAfterJoin.members.includes(betaRuntimeSenderId),
|
|
507
|
+
'Alpha project did not record beta using its runtime sender identity'
|
|
508
|
+
);
|
|
509
|
+
assert(
|
|
510
|
+
betaProjectAfterJoin.members.includes(beta.email),
|
|
511
|
+
'Beta local project did not record beta email as a local member'
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
logSection('Remote Contribution And Query');
|
|
515
|
+
await runCommand(
|
|
516
|
+
process.execPath,
|
|
517
|
+
[
|
|
518
|
+
cliPath,
|
|
519
|
+
'project',
|
|
520
|
+
'send-contribution',
|
|
521
|
+
refreshedAlphaPeerOnBeta.id,
|
|
522
|
+
args.projectId,
|
|
523
|
+
'decision',
|
|
524
|
+
'Beta confirmed remote contribution path',
|
|
525
|
+
'--metadata',
|
|
526
|
+
JSON.stringify({ source: 'beta', assertion: 'remote-send' })
|
|
527
|
+
],
|
|
528
|
+
{
|
|
529
|
+
cwd: repoRoot,
|
|
530
|
+
env: { ...process.env, OGP_HOME: beta.home },
|
|
531
|
+
envAdditions: { OGP_HOME: beta.home },
|
|
532
|
+
label: 'beta send-contribution'
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
const alphaProjectAfterRemoteContribution = await waitForProject(alpha.home, args.projectId);
|
|
537
|
+
assert(
|
|
538
|
+
getContributionSummaries(alphaProjectAfterRemoteContribution).includes('Beta confirmed remote contribution path'),
|
|
539
|
+
'Alpha project did not receive beta contribution'
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
await runCommand(
|
|
543
|
+
process.execPath,
|
|
544
|
+
[cliPath, 'project', 'query-peer', refreshedAlphaPeerOnBeta.id, args.projectId, '--limit', '10'],
|
|
545
|
+
{
|
|
546
|
+
cwd: repoRoot,
|
|
547
|
+
env: { ...process.env, OGP_HOME: beta.home },
|
|
548
|
+
envAdditions: { OGP_HOME: beta.home },
|
|
549
|
+
label: 'beta query-peer post-join'
|
|
550
|
+
}
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
logSection('Status Request Path');
|
|
554
|
+
await runCommand(
|
|
555
|
+
process.execPath,
|
|
556
|
+
[cliPath, 'project', 'status-peer', refreshedAlphaPeerOnBeta.id, args.projectId],
|
|
557
|
+
{
|
|
558
|
+
cwd: repoRoot,
|
|
559
|
+
env: { ...process.env, OGP_HOME: beta.home },
|
|
560
|
+
envAdditions: { OGP_HOME: beta.home },
|
|
561
|
+
label: 'beta status-peer'
|
|
562
|
+
}
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
logSection('Validation Summary');
|
|
566
|
+
console.log('Validated:');
|
|
567
|
+
console.log('- Two isolated local gateways booted with separate OGP_HOME directories.');
|
|
568
|
+
console.log('- Federation request + approval completed.');
|
|
569
|
+
console.log('- Local owner project creation and contribution persisted.');
|
|
570
|
+
console.log('- Pre-join project query was denied as expected.');
|
|
571
|
+
console.log('- Remote project join succeeded.');
|
|
572
|
+
console.log('- Remote contribution arrived on the owner gateway.');
|
|
573
|
+
console.log('- Remote query succeeded after membership was granted.');
|
|
574
|
+
console.log('- Remote status request path completed without transport failure.');
|
|
575
|
+
|
|
576
|
+
console.log('\nManual replay commands:');
|
|
577
|
+
console.log(formatShellCommand(process.execPath, [cliPath, 'federation', 'request', beta.url, 'beta-local', '--alias', 'beta-local'], { OGP_HOME: alpha.home }));
|
|
578
|
+
console.log(formatShellCommand(process.execPath, [cliPath, 'federation', 'approve', alphaPeerOnBeta.id, '--intents', 'message,agent-comms,project.join,project.contribute,project.query,project.status'], { OGP_HOME: beta.home }));
|
|
579
|
+
console.log(formatShellCommand(process.execPath, [cliPath, 'project', 'create', args.projectId, args.projectName, '--description', 'Project-intent test harness'], { OGP_HOME: alpha.home }));
|
|
580
|
+
console.log(formatShellCommand(process.execPath, [cliPath, 'project', 'contribute', args.projectId, 'progress', 'Alpha created the test project', '--metadata', JSON.stringify({ stage: 'owner-local-smoke' }), '--local-only'], { OGP_HOME: alpha.home }));
|
|
581
|
+
console.log(formatShellCommand(process.execPath, [cliPath, 'project', 'request-join', refreshedAlphaPeerOnBeta.id, args.projectId, args.projectName, '--description', 'Joined through local harness'], { OGP_HOME: beta.home }));
|
|
582
|
+
console.log(formatShellCommand(process.execPath, [cliPath, 'project', 'send-contribution', refreshedAlphaPeerOnBeta.id, args.projectId, 'decision', 'Beta confirmed remote contribution path', '--metadata', JSON.stringify({ source: 'beta', assertion: 'remote-send' })], { OGP_HOME: beta.home }));
|
|
583
|
+
console.log(formatShellCommand(process.execPath, [cliPath, 'project', 'query-peer', refreshedAlphaPeerOnBeta.id, args.projectId, '--limit', '10'], { OGP_HOME: beta.home }));
|
|
584
|
+
console.log(formatShellCommand(process.execPath, [cliPath, 'project', 'status-peer', refreshedAlphaPeerOnBeta.id, args.projectId], { OGP_HOME: beta.home }));
|
|
585
|
+
|
|
586
|
+
console.log(`\nState root: ${runRoot}`);
|
|
587
|
+
console.log(`Alpha logs: ${path.join(logsDir, 'alpha.stdout.log')} / ${path.join(logsDir, 'alpha.stderr.log')}`);
|
|
588
|
+
console.log(`Beta logs: ${path.join(logsDir, 'beta.stdout.log')} / ${path.join(logsDir, 'beta.stderr.log')}`);
|
|
589
|
+
|
|
590
|
+
if (!args.keepState) {
|
|
591
|
+
await stopDaemon(alphaDaemon);
|
|
592
|
+
await stopDaemon(betaDaemon);
|
|
593
|
+
await fsp.rm(runRoot, { recursive: true, force: true });
|
|
594
|
+
console.log('\nCleaned up temporary state. Re-run with --keep-state to inspect files.');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
await stopDaemon(alphaDaemon);
|
|
599
|
+
await stopDaemon(betaDaemon);
|
|
600
|
+
console.log('\nKept state on disk because --keep-state was provided.');
|
|
601
|
+
} catch (error) {
|
|
602
|
+
console.error(`\nProject-intent harness failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
603
|
+
console.error(`State root retained at: ${runRoot}`);
|
|
604
|
+
process.exitCode = 1;
|
|
605
|
+
} finally {
|
|
606
|
+
await stopDaemon(alphaDaemon);
|
|
607
|
+
await stopDaemon(betaDaemon);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
main().catch((error) => {
|
|
612
|
+
console.error(error);
|
|
613
|
+
process.exit(1);
|
|
614
|
+
});
|
package/skills/ogp/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
skill_name: ogp-agent-comms
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.6.0
|
|
4
4
|
description: Interactive wizard to configure agent-to-agent communication policies (updated for multi-framework `--for` workflows, OGP 0.2.24+ peer identity, and 0.2.28+ multi-agent routing)
|
|
5
5
|
trigger: Use when the user wants to configure how their agent responds to incoming agent-comms messages from federated peers
|
|
6
6
|
---
|