wattetheria 0.3.1 → 0.3.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/.env.release +1 -1
- package/README.md +7 -10
- package/docker-compose.release.yml +0 -1
- package/lib/cli.js +188 -0
- package/package.json +1 -1
package/.env.release
CHANGED
package/README.md
CHANGED
|
@@ -685,8 +685,7 @@ pwsh ./scripts/deploy-release.ps1
|
|
|
685
685
|
],
|
|
686
686
|
"brain_provider": {
|
|
687
687
|
"kind": "rules"
|
|
688
|
-
}
|
|
689
|
-
"servicenet_base_url": "http://127.0.0.1:8042"
|
|
688
|
+
}
|
|
690
689
|
}
|
|
691
690
|
```
|
|
692
691
|
|
|
@@ -701,7 +700,6 @@ Recommended config for autonomous loop in daemon (`.wattetheria/config.json`):
|
|
|
701
700
|
"base_url": "http://127.0.0.1:11434",
|
|
702
701
|
"model": "qwen2.5:7b-instruct"
|
|
703
702
|
},
|
|
704
|
-
"servicenet_base_url": "http://127.0.0.1:8042",
|
|
705
703
|
"autonomy_enabled": true,
|
|
706
704
|
"autonomy_interval_sec": 30
|
|
707
705
|
}
|
|
@@ -730,7 +728,6 @@ Example OpenClaw/OpenAI-compatible config:
|
|
|
730
728
|
"api_key_env": "WATTETHERIA_BRAIN_API_KEY"
|
|
731
729
|
},
|
|
732
730
|
"wattswarm_ui_base_url": "http://127.0.0.1:7788",
|
|
733
|
-
"servicenet_base_url": "http://127.0.0.1:8042",
|
|
734
731
|
"autonomy_enabled": true,
|
|
735
732
|
"autonomy_interval_sec": 30
|
|
736
733
|
}
|
|
@@ -764,11 +761,11 @@ The supervision runtime page asks for the concrete API key value. Saving that fo
|
|
|
764
761
|
|
|
765
762
|
`docker-compose.release.yml` also mounts `${WATTSWARM_HOST_STATE_DIR}/startup_config.json` into the
|
|
766
763
|
kernel container as read-only. If `WATTETHERIA_GATEWAY_URLS` is unset, the kernel falls back to
|
|
767
|
-
`gateway_urls` saved by Wattswarm in that file.
|
|
768
|
-
`
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
764
|
+
`gateway_urls` saved by Wattswarm in that file. ServiceNet uses the fixed official registry
|
|
765
|
+
endpoint `https://servicenet.wattetheria.com`; it is not read from `.wattetheria/config.json`,
|
|
766
|
+
environment variables, or Wattswarm startup config. Wattetheria resolves coarse node geo location
|
|
767
|
+
at startup and sends the resulting `latitude` / `longitude` to Wattswarm over the local sync gRPC
|
|
768
|
+
bridge, so Wattswarm remains the writer for its own startup config.
|
|
772
769
|
|
|
773
770
|
When Wattetheria registers `core-agent` with Wattswarm, it keeps the brain/runtime
|
|
774
771
|
`base_url` pointed at the OpenAI-compatible gateway for `/execute` work and exposes a
|
|
@@ -777,7 +774,7 @@ for structured agent-event callbacks. This keeps local-mode task execution and
|
|
|
777
774
|
topic/consensus flows on the existing runtime path while letting agent events reach
|
|
778
775
|
OpenClaw/NanoClaw-style runtimes through Wattetheria's adapter.
|
|
779
776
|
|
|
780
|
-
|
|
777
|
+
The control plane uses the fixed ServiceNet registry endpoint and exposes local proxy routes for external agent discovery and execution:
|
|
781
778
|
|
|
782
779
|
- `GET /v1/wattetheria/servicenet/agents`
|
|
783
780
|
- `GET /v1/wattetheria/servicenet/agents/:agent_id`
|
|
@@ -37,7 +37,6 @@ services:
|
|
|
37
37
|
WATTETHERIA_BRAIN_MODEL: ${WATTETHERIA_BRAIN_MODEL:-}
|
|
38
38
|
WATTETHERIA_BRAIN_API_KEY_ENV: ${WATTETHERIA_BRAIN_API_KEY_ENV:-}
|
|
39
39
|
WATTETHERIA_BRAIN_API_KEY: ${WATTETHERIA_BRAIN_API_KEY:-}
|
|
40
|
-
WATTETHERIA_SERVICENET_BASE_URL: ${WATTETHERIA_SERVICENET_BASE_URL:-}
|
|
41
40
|
WATTETHERIA_GATEWAY_URLS: ${WATTETHERIA_GATEWAY_URLS:-}
|
|
42
41
|
WATTETHERIA_GATEWAY_CONFIG_PATH: ${WATTETHERIA_GATEWAY_CONFIG_PATH:-/var/lib/wattswarm/startup_config.json}
|
|
43
42
|
WATTETHERIA_AUTONOMY_ENABLED: ${WATTETHERIA_AUTONOMY_ENABLED:-false}
|
package/lib/cli.js
CHANGED
|
@@ -1315,6 +1315,159 @@ function forwardedArgsForInstalledNode(commandName, rawArgv) {
|
|
|
1315
1315
|
return args;
|
|
1316
1316
|
}
|
|
1317
1317
|
|
|
1318
|
+
function isServicenetAgentCardInit(commandName, rawArgv) {
|
|
1319
|
+
return commandName === "servicenet"
|
|
1320
|
+
&& rawArgv[1] === "agent-card"
|
|
1321
|
+
&& rawArgv[2] === "init";
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function isServicenetProviderRegister(commandName, rawArgv) {
|
|
1325
|
+
return commandName === "servicenet"
|
|
1326
|
+
&& rawArgv[1] === "provider"
|
|
1327
|
+
&& rawArgv[2] === "register";
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
function isServicenetPublish(commandName, rawArgv) {
|
|
1331
|
+
return commandName === "servicenet"
|
|
1332
|
+
&& rawArgv[1] === "publish";
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function usesWorkspaceMount(commandName, rawArgv) {
|
|
1336
|
+
return isServicenetAgentCardInit(commandName, rawArgv)
|
|
1337
|
+
|| isServicenetProviderRegister(commandName, rawArgv)
|
|
1338
|
+
|| isServicenetPublish(commandName, rawArgv);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function findExistingAncestor(targetPath) {
|
|
1342
|
+
let cursor = path.resolve(targetPath);
|
|
1343
|
+
while (!fs.existsSync(cursor)) {
|
|
1344
|
+
const parent = path.dirname(cursor);
|
|
1345
|
+
if (parent === cursor) {
|
|
1346
|
+
throw new Error(`No existing parent directory found for ${targetPath}`);
|
|
1347
|
+
}
|
|
1348
|
+
cursor = parent;
|
|
1349
|
+
}
|
|
1350
|
+
return cursor;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function toContainerPath(relativePath) {
|
|
1354
|
+
if (!relativePath || relativePath === ".") {
|
|
1355
|
+
return "/workspace";
|
|
1356
|
+
}
|
|
1357
|
+
return `/workspace/${relativePath.split(path.sep).join("/")}`;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function parsePathOption(rawArgv, optionName, startIndex) {
|
|
1361
|
+
const equalsPrefix = `${optionName}=`;
|
|
1362
|
+
for (let index = startIndex; index < rawArgv.length; index += 1) {
|
|
1363
|
+
const arg = rawArgv[index];
|
|
1364
|
+
if (arg === optionName) {
|
|
1365
|
+
return { index, value: rawArgv[index + 1], style: "separate" };
|
|
1366
|
+
}
|
|
1367
|
+
if (arg.startsWith(equalsPrefix)) {
|
|
1368
|
+
return { index, value: arg.slice(equalsPrefix.length), style: "equals" };
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function pathMountFor(targetPath) {
|
|
1375
|
+
const requestedPath = path.resolve(targetPath);
|
|
1376
|
+
const existingPath = fs.existsSync(requestedPath) ? requestedPath : "";
|
|
1377
|
+
const mountSource = existingPath
|
|
1378
|
+
? (
|
|
1379
|
+
fs.statSync(existingPath).isDirectory()
|
|
1380
|
+
? existingPath
|
|
1381
|
+
: path.dirname(existingPath)
|
|
1382
|
+
)
|
|
1383
|
+
: findExistingAncestor(requestedPath);
|
|
1384
|
+
return {
|
|
1385
|
+
mountSource,
|
|
1386
|
+
containerPath: toContainerPath(path.relative(mountSource, requestedPath))
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function rewritePathOption(rawArgv, parsedOption, containerPath) {
|
|
1391
|
+
const rewrittenArgv = [...rawArgv];
|
|
1392
|
+
if (parsedOption.style === "separate") {
|
|
1393
|
+
rewrittenArgv[parsedOption.index + 1] = containerPath;
|
|
1394
|
+
} else {
|
|
1395
|
+
rewrittenArgv[parsedOption.index] = `${rewrittenArgv[parsedOption.index].split("=")[0]}=${containerPath}`;
|
|
1396
|
+
}
|
|
1397
|
+
return rewrittenArgv;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
function dockerWorkspaceInvocation(commandName, rawArgv) {
|
|
1401
|
+
if (isServicenetAgentCardInit(commandName, rawArgv)) {
|
|
1402
|
+
const outputArg = parsePathOption(rawArgv, "--out", 3);
|
|
1403
|
+
const outputPath = outputArg?.value || process.cwd();
|
|
1404
|
+
if (outputArg && !outputArg.value) {
|
|
1405
|
+
throw new Error("Missing value for --out");
|
|
1406
|
+
}
|
|
1407
|
+
const outputMount = pathMountFor(outputPath);
|
|
1408
|
+
const rewrittenArgv = outputArg
|
|
1409
|
+
? rewritePathOption(rawArgv, outputArg, outputMount.containerPath)
|
|
1410
|
+
: rawArgv;
|
|
1411
|
+
return {
|
|
1412
|
+
args: outputArg
|
|
1413
|
+
? forwardedArgsForInstalledNode(commandName, rewrittenArgv)
|
|
1414
|
+
: [...forwardedArgsForInstalledNode(commandName, rawArgv), "--out", outputMount.containerPath],
|
|
1415
|
+
mountSource: outputMount.mountSource
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if (isServicenetProviderRegister(commandName, rawArgv)) {
|
|
1420
|
+
const cardArg = parsePathOption(rawArgv, "--card", 3);
|
|
1421
|
+
if (!cardArg) {
|
|
1422
|
+
return {
|
|
1423
|
+
args: forwardedArgsForInstalledNode(commandName, rawArgv),
|
|
1424
|
+
mountSource: process.cwd()
|
|
1425
|
+
};
|
|
1426
|
+
}
|
|
1427
|
+
if (!cardArg.value) {
|
|
1428
|
+
throw new Error("Missing value for --card");
|
|
1429
|
+
}
|
|
1430
|
+
const cardMount = pathMountFor(cardArg.value);
|
|
1431
|
+
return {
|
|
1432
|
+
args: forwardedArgsForInstalledNode(
|
|
1433
|
+
commandName,
|
|
1434
|
+
rewritePathOption(rawArgv, cardArg, cardMount.containerPath)
|
|
1435
|
+
),
|
|
1436
|
+
mountSource: cardMount.mountSource
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
return {
|
|
1441
|
+
args: forwardedArgsForInstalledNode(commandName, rawArgv),
|
|
1442
|
+
mountSource: process.cwd()
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function hostPathFromWorkspace(containerPath, mountSource) {
|
|
1447
|
+
if (containerPath === "/workspace") {
|
|
1448
|
+
return mountSource;
|
|
1449
|
+
}
|
|
1450
|
+
if (!containerPath.startsWith("/workspace/")) {
|
|
1451
|
+
return containerPath;
|
|
1452
|
+
}
|
|
1453
|
+
return path.join(mountSource, containerPath.slice("/workspace/".length));
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function rewriteWorkspaceJsonStdout(stdout, mountSource) {
|
|
1457
|
+
if (!stdout.trim()) {
|
|
1458
|
+
return stdout;
|
|
1459
|
+
}
|
|
1460
|
+
try {
|
|
1461
|
+
const payload = JSON.parse(stdout);
|
|
1462
|
+
if (typeof payload.card === "string") {
|
|
1463
|
+
payload.card = hostPathFromWorkspace(payload.card, mountSource);
|
|
1464
|
+
}
|
|
1465
|
+
return `${JSON.stringify(payload, null, 2)}\n`;
|
|
1466
|
+
} catch (_error) {
|
|
1467
|
+
return stdout;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1318
1471
|
function runInstalledNodeCli(commandName, rawArgv, deployment) {
|
|
1319
1472
|
const dockerCommand = resolveDockerCommand();
|
|
1320
1473
|
if (!dockerCommand) {
|
|
@@ -1323,6 +1476,41 @@ function runInstalledNodeCli(commandName, rawArgv, deployment) {
|
|
|
1323
1476
|
`to run '${commandName}' inside the installed node. Start Docker Desktop and retry.`
|
|
1324
1477
|
);
|
|
1325
1478
|
}
|
|
1479
|
+
if (usesWorkspaceMount(commandName, rawArgv)) {
|
|
1480
|
+
const invocation = dockerWorkspaceInvocation(commandName, rawArgv);
|
|
1481
|
+
const result = spawnSync(
|
|
1482
|
+
dockerCommand,
|
|
1483
|
+
[
|
|
1484
|
+
"compose",
|
|
1485
|
+
"--project-name",
|
|
1486
|
+
DEFAULT_PROJECT_NAME,
|
|
1487
|
+
"--env-file",
|
|
1488
|
+
deployment.envPath,
|
|
1489
|
+
"-f",
|
|
1490
|
+
deployment.composePath,
|
|
1491
|
+
"run",
|
|
1492
|
+
"--rm",
|
|
1493
|
+
"--no-deps",
|
|
1494
|
+
"-T",
|
|
1495
|
+
"--entrypoint",
|
|
1496
|
+
"/app/target/release/wattetheria-client-cli",
|
|
1497
|
+
"-v",
|
|
1498
|
+
`${invocation.mountSource}:/workspace`,
|
|
1499
|
+
"-w",
|
|
1500
|
+
"/workspace",
|
|
1501
|
+
"kernel",
|
|
1502
|
+
...invocation.args
|
|
1503
|
+
],
|
|
1504
|
+
{ stdio: ["inherit", "pipe", "inherit"], encoding: "utf8" }
|
|
1505
|
+
);
|
|
1506
|
+
if (result.stdout) {
|
|
1507
|
+
process.stdout.write(rewriteWorkspaceJsonStdout(result.stdout, invocation.mountSource));
|
|
1508
|
+
}
|
|
1509
|
+
if (typeof result.status === "number") {
|
|
1510
|
+
process.exit(result.status);
|
|
1511
|
+
}
|
|
1512
|
+
throw result.error ?? new Error(`Failed to run '${commandName}' inside the installed node`);
|
|
1513
|
+
}
|
|
1326
1514
|
const result = spawnSync(
|
|
1327
1515
|
dockerCommand,
|
|
1328
1516
|
[
|