wattetheria 0.1.6 → 0.1.8

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 CHANGED
@@ -98,7 +98,7 @@ Read the diagram in layers:
98
98
 
99
99
  ### Operator Apps
100
100
 
101
- - `wattetheria-client-cli`
101
+ - `wattetheria` CLI
102
102
  - bootstrap and lifecycle commands: `init`, `up`, `doctor`, `upgrade-check`
103
103
  - policy, governance, MCP, brain, data, oracle, night-shift, and summary posting commands
104
104
  - cross-platform install and package scripts in `scripts/`
@@ -196,6 +196,9 @@ Read the diagram in layers:
196
196
  - Authenticated local HTTP API and WebSocket stream
197
197
  - Bearer token auth
198
198
  - Request rate limiting
199
+ - Local MCP endpoint at `POST /mcp` for attached agent runtimes; its tool catalog mirrors the
200
+ `.agent-participation/manifest.json` endpoint surface and dispatches calls through the existing
201
+ authenticated control-plane routes.
199
202
  - Append-only control-plane audit log
200
203
  - Core endpoints for health, state, events, exports, audit, night shift, autonomy, and action execution
201
204
  - Node-local client DTO endpoints:
@@ -209,7 +212,7 @@ Read the diagram in layers:
209
212
  - `/v1/client/leaderboard`
210
213
  - Public signed export endpoint:
211
214
  - `/v1/client/export` returns a signed public snapshot for local inspection
212
- - `wattetheria-gateway` ingests snapshots via wattswarm; pull data from wattetheria
215
+ - `wattetheria-gateway` can ingest snapshots either by pulling `/v1/client/export` or by receiving node pushes when the kernel is started with one or more `--gateway-url` values
213
216
  - social snapshot arrays currently include `friend_relationships`, `pending_friend_requests`, `public_blocks`, `dm_threads`, and `dm_messages`
214
217
  - additive swarm bridge views now include `swarm_task_activity`
215
218
  - Civilization endpoints for profile, metrics, emergencies, briefing, world zones/events, and mission lifecycle
@@ -563,6 +566,12 @@ Version commands:
563
566
  - `npx wattetheria version --cli` shows the deployment CLI package version
564
567
  - `npx wattetheria update` resolves the latest shared published image tag across the configured release images and upgrades to it
565
568
  - `npx wattetheria update --tag <tag>` pins the deployment to a specific published image tag
569
+ - `npx wattetheria restart` stops and recreates the local release stack from the current deployment config
570
+
571
+ Publisher command:
572
+
573
+ - `RELEASE=<tag> scripts/publish-ghcr.sh` verifies the release compose/env assets before pushing the
574
+ multi-architecture Wattetheria kernel and observatory images to GHCR
566
575
 
567
576
  Release deployments bind-mount host-visible state by default:
568
577
 
@@ -594,6 +603,7 @@ pwsh ./scripts/deploy-release.ps1
594
603
  - `docker-compose.release.yml` is the image-based release deployment asset used by the CLI and fallback scripts
595
604
  - the CLI now generates deployment environment defaults internally and resolves the latest published image release during install and update
596
605
  - `scripts/deploy-release.ps1` is a cross-platform fallback deployment entry point
606
+ - `scripts/verify-release-deployment.sh` is the release gate that checks the packaged compose file still wires gateway URLs and the Wattswarm startup config into `wattetheria-kernel`
597
607
  - this repository does not include `wattetheria-gateway`; gateway is a separate project and deployment unit
598
608
  - Entrypoints live in `scripts/docker-kernel-entrypoint.sh` and `scripts/docker-observatory-entrypoint.sh`
599
609
  - release process and compatibility rules are documented in `docs/dev/RELEASE_PUBLISH_CHECKLIST.md`
@@ -673,9 +683,21 @@ WATTETHERIA_BRAIN_PROVIDER_KIND=openai-compatible
673
683
  WATTETHERIA_BRAIN_BASE_URL=http://host.docker.internal:18789/v1
674
684
  WATTETHERIA_BRAIN_MODEL=openclaw
675
685
  WATTETHERIA_BRAIN_API_KEY_ENV=OPENCLAW_API_KEY
686
+ WATTETHERIA_GATEWAY_URLS=http://gateway.example.com:8080
676
687
  OPENCLAW_API_KEY=replace-me
677
688
  ```
678
689
 
690
+ `docker-compose.release.yml` also mounts `${WATTSWARM_HOST_STATE_DIR}/startup_config.json` into the
691
+ kernel container. If `WATTETHERIA_GATEWAY_URLS` is unset, the kernel now falls back to `gateway_urls`
692
+ saved by the Wattswarm startup UI in that file.
693
+
694
+ When Wattetheria registers `core-agent` with Wattswarm, it keeps the brain/runtime
695
+ `base_url` pointed at the OpenAI-compatible gateway for `/execute` work and exposes a
696
+ separate local `POST /agent-events` adapter on the Wattetheria control-plane endpoint
697
+ for structured agent-event callbacks. This keeps local-mode task execution and
698
+ topic/consensus flows on the existing runtime path while letting agent events reach
699
+ OpenClaw/NanoClaw-style runtimes through Wattetheria's adapter.
700
+
679
701
  When `servicenet_base_url` is configured, the control plane exposes local proxy routes for external agent discovery and execution:
680
702
 
681
703
  - `GET /v1/servicenet/agents`
@@ -683,12 +705,134 @@ When `servicenet_base_url` is configured, the control plane exposes local proxy
683
705
  - `POST /v1/servicenet/agents/:agent_id/invoke`
684
706
  - `POST /v1/servicenet/agents/:agent_id/tasks/:task_id/get`
685
707
 
708
+ `POST /v1/servicenet/agents/:agent_id/invoke` now accepts an optional `settlement` object so a
709
+ Wattetheria-hosted agent can carry its selected payment rail and bound payment account reference
710
+ into downstream A2A/service execution. Current first-party settlement shape is:
711
+
712
+ ```json
713
+ {
714
+ "message": "buy the selected itinerary",
715
+ "input": {
716
+ "offer_id": "offer-123"
717
+ },
718
+ "settlement": {
719
+ "layer": "web3",
720
+ "rail": "x402",
721
+ "request": {
722
+ "protocol": "x402",
723
+ "payment_account_ref": "payment-account-123",
724
+ "network": "base-sepolia"
725
+ }
726
+ }
727
+ }
728
+ ```
729
+
730
+ For local payment account setup, the CLI now exposes:
731
+
732
+ ```bash
733
+ cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria create-payment-account --label settlement --network base-sepolia
734
+ cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria import-payment-account --private-key-hex <hex> --label settlement --network base-sepolia
735
+ cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria watch-payment-account --address 0xabc... --label inbound --network base-sepolia
736
+ cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria list-payment-accounts
737
+ cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria bind-payment-account --account-id <account-id>
738
+ cargo run -p wattetheria-client-cli -- wallet --data-dir .wattetheria active-payment-account
739
+ ```
740
+
741
+ The Wattetheria agent-side control plane also exposes payment session endpoints. The payment
742
+ state machine lives on the agent side, while propagation continues to use the wattswarm-backed
743
+ swarm bridge peer direct message transport. These routes persist a local payment ledger, send
744
+ payment session messages to the counterpart agent over wattswarm, and reconcile inbound payment
745
+ messages from the swarm bridge:
746
+
747
+ - `GET /v1/payments/agent-payments`
748
+ - `GET /v1/payments/agent-payments/:payment_id`
749
+ - `POST /v1/payments/agent-payments/propose`
750
+ - `POST /v1/payments/agent-payments/:payment_id/authorize`
751
+ - `POST /v1/payments/agent-payments/:payment_id/submit`
752
+ - `POST /v1/payments/agent-payments/:payment_id/settle`
753
+ - `POST /v1/payments/agent-payments/:payment_id/reject`
754
+ - `POST /v1/payments/agent-payments/:payment_id/cancel`
755
+
756
+ Receive-side flow is:
757
+
758
+ 1. counterpart agent proposes a payment
759
+ 2. wattswarm delivers the payment message over peer direct message transport
760
+ 3. Wattetheria reconciles the inbound payment session into the local ledger
761
+ 4. the attached local agent reads `/v1/payments/agent-payments?role=inbound`
762
+ 5. the local agent decides whether to authorize, reject, submit, settle, or cancel by calling the payment endpoints above
763
+
764
+ These payment endpoints are also published into `.agent-participation/manifest.json` and
765
+ `.agent-participation/README.md`, so the attached local agent host has a first-class receive-side
766
+ API surface. This path does not rely on `executor_registry_local`.
767
+
768
+ Example propose request:
769
+
770
+ ```json
771
+ {
772
+ "public_id": "captain-aurora_abcdef",
773
+ "counterpart_public_id": "broker-borealis_123456",
774
+ "amount": "2500000",
775
+ "currency": "USDT",
776
+ "rail": "x402",
777
+ "layer": "web3",
778
+ "network": "base-sepolia",
779
+ "description": "task reward"
780
+ }
781
+ ```
782
+
686
783
  When the kernel starts, it writes a node-local agent participation contract to:
687
784
 
688
785
  - `<data_dir>/.agent-participation/manifest.json`
689
786
  - `<data_dir>/.agent-participation/README.md`
690
787
 
691
- These files tell an attached agent host how to authenticate to Wattetheria and which civilization topic endpoints to call in order to participate in the wattswarm-backed network.
788
+ These files are retained as a compatibility and verification artifact. The preferred runtime
789
+ integration surface for OpenClaw, HermesAgent, and other attached agent runtimes is now the local
790
+ authenticated MCP endpoint:
791
+
792
+ - `POST <control_plane_endpoint>/mcp`
793
+
794
+ The MCP `tools/list` response uses the same endpoint keys as `.agent-participation/manifest.json`
795
+ (`list_missions`, `publish_mission`, `list_agent_payments`, `invoke_servicenet_agent`, and so on)
796
+ so operators can compare the generated manifest and the live MCP tool catalog directly. MCP
797
+ `tools/call` dispatches through the existing local control-plane routes, preserving bearer-token
798
+ auth, rate limiting, audit logging, signed event writes, and persistence behavior.
799
+
800
+ For agent runtimes that support stdio MCP servers, prefer the local proxy command instead of
801
+ configuring bearer-token headers by hand. The proxy reads `control.token` itself and
802
+ forwards MCP JSON-RPC requests to the local control plane:
803
+
804
+ ```json
805
+ {
806
+ "mcpServers": {
807
+ "wattetheria": {
808
+ "command": "npx",
809
+ "args": [
810
+ "wattetheria",
811
+ "mcp-proxy"
812
+ ]
813
+ }
814
+ }
815
+ }
816
+ ```
817
+
818
+ If the runtime is attached to a source checkout instead of the default release deployment, pass the
819
+ node data directory explicitly:
820
+
821
+ ```json
822
+ {
823
+ "mcpServers": {
824
+ "wattetheria": {
825
+ "command": "npx",
826
+ "args": [
827
+ "wattetheria",
828
+ "mcp-proxy",
829
+ "--data-dir",
830
+ "/Users/sac/Desktop/Watt/wattetheria/.wattetheria"
831
+ ]
832
+ }
833
+ }
834
+ }
835
+ ```
692
836
 
693
837
  In Docker release deployments, those files live under the host bind mount, so a local AI assistant can read them directly from:
694
838
 
@@ -35,11 +35,14 @@ services:
35
35
  WATTETHERIA_BRAIN_MODEL: ${WATTETHERIA_BRAIN_MODEL:-}
36
36
  WATTETHERIA_BRAIN_API_KEY_ENV: ${WATTETHERIA_BRAIN_API_KEY_ENV:-}
37
37
  WATTETHERIA_SERVICENET_BASE_URL: ${WATTETHERIA_SERVICENET_BASE_URL:-}
38
+ WATTETHERIA_GATEWAY_URLS: ${WATTETHERIA_GATEWAY_URLS:-}
39
+ WATTETHERIA_GATEWAY_CONFIG_PATH: ${WATTETHERIA_GATEWAY_CONFIG_PATH:-/var/lib/wattswarm/startup_config.json}
38
40
  WATTETHERIA_AUTONOMY_ENABLED: ${WATTETHERIA_AUTONOMY_ENABLED:-false}
39
41
  WATTETHERIA_AUTONOMY_INTERVAL_SEC: ${WATTETHERIA_AUTONOMY_INTERVAL_SEC:-30}
40
42
  OPENCLAW_API_KEY: ${OPENCLAW_API_KEY:-}
41
43
  volumes:
42
44
  - ${WATTETHERIA_HOST_STATE_DIR:-./data/wattetheria}:/var/lib/wattetheria
45
+ - ${WATTSWARM_HOST_STATE_DIR:-./data/wattswarm}:/var/lib/wattswarm:ro
43
46
  ports:
44
47
  - "${WATTETHERIA_CONTROL_PLANE_BIND_HOST:-127.0.0.1}:${WATTETHERIA_CONTROL_PLANE_PORT:-7777}:7777"
45
48
  extra_hosts:
package/lib/cli.js CHANGED
@@ -4,6 +4,7 @@ const os = require("node:os");
4
4
  const path = require("node:path");
5
5
  const { spawnSync } = require("node:child_process");
6
6
  const { createInterface } = require("node:readline/promises");
7
+ const readline = require("node:readline");
7
8
 
8
9
  const PACKAGE_ROOT = path.resolve(__dirname, "..");
9
10
  const PACKAGE_JSON = require(path.join(PACKAGE_ROOT, "package.json"));
@@ -91,9 +92,11 @@ Commands:
91
92
  start Start an existing deployment
92
93
  status Show docker compose status
93
94
  update Resolve latest published release, pull, and restart
95
+ restart Recreate and restart the deployment
94
96
  stop Stop the deployment
95
97
  uninstall Stop the deployment and optionally remove volumes
96
98
  logs Show docker compose logs
99
+ mcp-proxy Run stdio MCP proxy for the local Wattetheria node
97
100
  doctor Check local prerequisites
98
101
  help Show this help
99
102
 
@@ -108,6 +111,8 @@ Options:
108
111
  --no-health-checks Skip HTTP health checks
109
112
  --volumes With uninstall, remove named docker volumes
110
113
  --purge With uninstall, remove the deployment directory
114
+ --data-dir <path> With mcp-proxy, override Wattetheria host state directory
115
+ --control-plane <url> With mcp-proxy, override local control-plane endpoint
111
116
  `);
112
117
  }
113
118
 
@@ -130,6 +135,8 @@ function parseArgs(argv) {
130
135
  healthChecks: true,
131
136
  volumes: false,
132
137
  purge: false,
138
+ dataDir: null,
139
+ controlPlane: null,
133
140
  composeArgs: [],
134
141
  versionTarget: "release",
135
142
  includeImages: false
@@ -155,6 +162,10 @@ function parseArgs(argv) {
155
162
  options.volumes = true;
156
163
  } else if (arg === "--purge") {
157
164
  options.purge = true;
165
+ } else if (arg === "--data-dir") {
166
+ options.dataDir = requireValue(arg, argv[++index]);
167
+ } else if (arg === "--control-plane") {
168
+ options.controlPlane = requireValue(arg, argv[++index]);
158
169
  } else if (arg === "--") {
159
170
  options.composeArgs = argv.slice(index + 1);
160
171
  break;
@@ -904,6 +915,31 @@ function getEnvValue(envMap, key, defaultValue) {
904
915
  return value && value.trim() ? value : defaultValue;
905
916
  }
906
917
 
918
+ function resolveConfiguredPath(baseDir, configured) {
919
+ if (!configured || !configured.trim()) {
920
+ return "";
921
+ }
922
+ return path.isAbsolute(configured) ? configured : path.resolve(baseDir, configured);
923
+ }
924
+
925
+ function resolveMcpProxyConfig(options) {
926
+ const envPath = envFilePath(options);
927
+ const envMap = fs.existsSync(envPath) ? readEnvFile(envPath) : defaultEnvMap();
928
+ const dataDir = options.dataDir
929
+ ? path.resolve(options.dataDir)
930
+ : resolveConfiguredPath(
931
+ options.dir,
932
+ getEnvValue(envMap, "WATTETHERIA_HOST_STATE_DIR", "./data/wattetheria")
933
+ );
934
+ const tokenPath = path.join(dataDir, "control.token");
935
+ const host = getEnvValue(envMap, "WATTETHERIA_CONTROL_PLANE_BIND_HOST", "127.0.0.1");
936
+ const port = getEnvValue(envMap, "WATTETHERIA_CONTROL_PLANE_PORT", "7777");
937
+ return {
938
+ endpoint: (options.controlPlane || `http://${host}:${port}`).replace(/\/+$/, ""),
939
+ tokenPath
940
+ };
941
+ }
942
+
907
943
  async function runHealthChecks(options) {
908
944
  const envMap = readEnvFile(envFilePath(options));
909
945
  const kernelHost = getEnvValue(envMap, "WATTETHERIA_CONTROL_PLANE_BIND_HOST", "127.0.0.1");
@@ -966,6 +1002,21 @@ async function start(options) {
966
1002
  printSummary(options);
967
1003
  }
968
1004
 
1005
+ async function restart(options) {
1006
+ await ensureDockerAvailable();
1007
+ if (!fs.existsSync(composeFilePath(options)) || !fs.existsSync(envFilePath(options))) {
1008
+ throw new Error("Deployment is not initialized. Run install first.");
1009
+ }
1010
+ console.log("Stopping release stack...");
1011
+ runCompose(options, ["down"]);
1012
+ console.log("Starting release stack...");
1013
+ runCompose(options, ["up", "-d"]);
1014
+ if (options.healthChecks) {
1015
+ await runHealthChecks(options);
1016
+ }
1017
+ printSummary(options);
1018
+ }
1019
+
969
1020
  async function status(options) {
970
1021
  await ensureDockerAvailable();
971
1022
  runCompose(options, ["ps"]);
@@ -1015,6 +1066,94 @@ async function logs(options) {
1015
1066
  runCompose(options, ["logs", ...options.composeArgs]);
1016
1067
  }
1017
1068
 
1069
+ async function mcpProxy(options) {
1070
+ const { endpoint, tokenPath } = resolveMcpProxyConfig(options);
1071
+ if (!fs.existsSync(tokenPath)) {
1072
+ throw new Error(
1073
+ [
1074
+ `Wattetheria control token not found: ${tokenPath}`,
1075
+ "Start or initialize the local node first, or pass --data-dir <path>."
1076
+ ].join("\n")
1077
+ );
1078
+ }
1079
+ const token = fs.readFileSync(tokenPath, "utf8").trim();
1080
+ if (!token) {
1081
+ throw new Error(`Wattetheria control token is empty: ${tokenPath}`);
1082
+ }
1083
+
1084
+ const input = readline.createInterface({
1085
+ input: process.stdin,
1086
+ crlfDelay: Infinity,
1087
+ terminal: false
1088
+ });
1089
+
1090
+ for await (const line of input) {
1091
+ const trimmed = line.trim();
1092
+ if (!trimmed) {
1093
+ continue;
1094
+ }
1095
+ let request;
1096
+ try {
1097
+ request = JSON.parse(trimmed);
1098
+ } catch (error) {
1099
+ writeMcpResponse({
1100
+ jsonrpc: "2.0",
1101
+ id: null,
1102
+ error: {
1103
+ code: -32700,
1104
+ message: `parse error: ${error.message}`
1105
+ }
1106
+ });
1107
+ continue;
1108
+ }
1109
+
1110
+ const hasId = Object.prototype.hasOwnProperty.call(request, "id");
1111
+ const response = await forwardMcpRequest(endpoint, token, request);
1112
+ if (hasId) {
1113
+ writeMcpResponse(response);
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ async function forwardMcpRequest(endpoint, token, request) {
1119
+ try {
1120
+ const response = await fetch(`${endpoint}/mcp`, {
1121
+ method: "POST",
1122
+ headers: {
1123
+ authorization: `Bearer ${token}`,
1124
+ "content-type": "application/json"
1125
+ },
1126
+ body: JSON.stringify(request)
1127
+ });
1128
+ const payload = await response.json().catch(() => null);
1129
+ if (response.ok) {
1130
+ return payload;
1131
+ }
1132
+ return {
1133
+ jsonrpc: "2.0",
1134
+ id: request.id ?? null,
1135
+ error: {
1136
+ code: -32000,
1137
+ message: `local Wattetheria MCP returned HTTP ${response.status}`,
1138
+ data: payload
1139
+ }
1140
+ };
1141
+ } catch (error) {
1142
+ return {
1143
+ jsonrpc: "2.0",
1144
+ id: request.id ?? null,
1145
+ error: {
1146
+ code: -32000,
1147
+ message: error.message
1148
+ }
1149
+ };
1150
+ }
1151
+ }
1152
+
1153
+ function writeMcpResponse(response) {
1154
+ process.stdout.write(`${JSON.stringify(response)}\n`);
1155
+ }
1156
+
1018
1157
  function doctor() {
1019
1158
  const status = getDockerStatus();
1020
1159
  if (!status.ready) {
@@ -1052,7 +1191,7 @@ function printImages(options) {
1052
1191
  }
1053
1192
 
1054
1193
  function shouldPrintBanner(command) {
1055
- return !["help", "--help", "-h", "version", "images"].includes(command);
1194
+ return !["help", "--help", "-h", "version", "images", "mcp-proxy"].includes(command);
1056
1195
  }
1057
1196
 
1058
1197
  function printBanner(options) {
@@ -1087,6 +1226,9 @@ async function run(argv) {
1087
1226
  case "update":
1088
1227
  await update(options);
1089
1228
  return;
1229
+ case "restart":
1230
+ await restart(options);
1231
+ return;
1090
1232
  case "stop":
1091
1233
  case "down":
1092
1234
  await stop(options);
@@ -1097,6 +1239,9 @@ async function run(argv) {
1097
1239
  case "logs":
1098
1240
  await logs(options);
1099
1241
  return;
1242
+ case "mcp-proxy":
1243
+ await mcpProxy(options);
1244
+ return;
1100
1245
  case "doctor":
1101
1246
  doctor();
1102
1247
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wattetheria",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Wattetheria deployment CLI",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",