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 CHANGED
@@ -1,5 +1,5 @@
1
1
  # Coordinated release image set
2
- RELEASE_TAG=1.0.3
2
+ RELEASE_TAG=1.0.4
3
3
 
4
4
  WATTETHERIA_KERNEL_IMAGE=ghcr.io/wattetheria/wattetheria-kernel:${RELEASE_TAG}
5
5
  WATTSWARM_KERNEL_IMAGE=ghcr.io/wattetheria/wattswarm-kernel:${RELEASE_TAG}
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. For ServiceNet, the kernel uses the first
768
- `servicenet_urls` entry from the same file as the release path; `WATTETHERIA_SERVICENET_BASE_URL`
769
- is only a local override when no startup config URL is available. Wattetheria resolves coarse node
770
- geo location at startup and sends the resulting `latitude` / `longitude` to Wattswarm over the
771
- local sync gRPC bridge, so Wattswarm remains the writer for its own startup config.
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
- When `servicenet_base_url` is configured, the control plane exposes local proxy routes for external agent discovery and execution:
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
  [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wattetheria",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Wattetheria deployment CLI",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",