clawdentity 0.0.4 → 0.0.5
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/dist/bin.js +524 -614
- package/dist/index.js +524 -614
- package/dist/postinstall.js +0 -0
- package/package.json +17 -16
- package/skill-bundle/openclaw-skill/dist/relay-to-peer.mjs +165 -18
- package/skill-bundle/openclaw-skill/skill/SKILL.md +15 -8
- package/skill-bundle/openclaw-skill/skill/references/clawdentity-protocol.md +18 -16
package/dist/postinstall.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawdentity",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -21,6 +21,17 @@
|
|
|
21
21
|
"postinstall.mjs",
|
|
22
22
|
"skill-bundle"
|
|
23
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "pnpm -F @clawdentity/openclaw-skill build && pnpm run sync:skill-bundle && pnpm run verify:skill-bundle && tsup",
|
|
26
|
+
"format": "biome format .",
|
|
27
|
+
"lint": "biome lint .",
|
|
28
|
+
"prepack": "pnpm run build",
|
|
29
|
+
"postinstall": "node ./postinstall.mjs",
|
|
30
|
+
"sync:skill-bundle": "node ./scripts/sync-skill-bundle.mjs",
|
|
31
|
+
"verify:skill-bundle": "node ./scripts/verify-skill-bundle.mjs",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"typecheck": "tsc --noEmit"
|
|
34
|
+
},
|
|
24
35
|
"dependencies": {
|
|
25
36
|
"commander": "^13.1.0",
|
|
26
37
|
"jsqr": "^1.4.0",
|
|
@@ -29,21 +40,11 @@
|
|
|
29
40
|
"ws": "^8.19.0"
|
|
30
41
|
},
|
|
31
42
|
"devDependencies": {
|
|
43
|
+
"@clawdentity/connector": "workspace:*",
|
|
44
|
+
"@clawdentity/protocol": "workspace:*",
|
|
45
|
+
"@clawdentity/sdk": "workspace:*",
|
|
32
46
|
"@types/node": "^22.18.11",
|
|
33
47
|
"@types/pngjs": "^6.0.5",
|
|
34
|
-
"@types/qrcode": "^1.5.6"
|
|
35
|
-
"@clawdentity/connector": "0.0.0",
|
|
36
|
-
"@clawdentity/protocol": "0.0.0",
|
|
37
|
-
"@clawdentity/sdk": "0.0.0"
|
|
38
|
-
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "pnpm -F @clawdentity/openclaw-skill build && pnpm run sync:skill-bundle && pnpm run verify:skill-bundle && tsup",
|
|
41
|
-
"format": "biome format .",
|
|
42
|
-
"lint": "biome lint .",
|
|
43
|
-
"postinstall": "node ./postinstall.mjs",
|
|
44
|
-
"sync:skill-bundle": "node ./scripts/sync-skill-bundle.mjs",
|
|
45
|
-
"verify:skill-bundle": "node ./scripts/verify-skill-bundle.mjs",
|
|
46
|
-
"test": "vitest run",
|
|
47
|
-
"typecheck": "tsc --noEmit"
|
|
48
|
+
"@types/qrcode": "^1.5.6"
|
|
48
49
|
}
|
|
49
|
-
}
|
|
50
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/transforms/relay-to-peer.ts
|
|
2
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
3
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
1
6
|
// src/transforms/peers-config.ts
|
|
2
7
|
import { chmod, mkdir, readFile, writeFile } from "fs/promises";
|
|
3
8
|
import { homedir } from "os";
|
|
@@ -131,9 +136,17 @@ async function loadPeersConfig(options = {}) {
|
|
|
131
136
|
// src/transforms/relay-to-peer.ts
|
|
132
137
|
var DEFAULT_CONNECTOR_BASE_URL = "http://127.0.0.1:19400";
|
|
133
138
|
var DEFAULT_CONNECTOR_OUTBOUND_PATH = "/v1/outbound";
|
|
139
|
+
var RELAY_RUNTIME_FILE_NAME = "clawdentity-relay.json";
|
|
140
|
+
var RELAY_PEERS_FILE_NAME = "clawdentity-peers.json";
|
|
134
141
|
function isRecord2(value) {
|
|
135
142
|
return typeof value === "object" && value !== null;
|
|
136
143
|
}
|
|
144
|
+
function getErrorCode2(error) {
|
|
145
|
+
if (!isRecord2(error)) {
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
return typeof error.code === "string" ? error.code : void 0;
|
|
149
|
+
}
|
|
137
150
|
function parseRequiredString(value) {
|
|
138
151
|
if (typeof value !== "string") {
|
|
139
152
|
throw new Error("Input value must be a string");
|
|
@@ -182,12 +195,114 @@ function normalizeConnectorPath(value) {
|
|
|
182
195
|
}
|
|
183
196
|
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
184
197
|
}
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
198
|
+
function resolveTransformsDir() {
|
|
199
|
+
return dirname2(fileURLToPath(import.meta.url));
|
|
200
|
+
}
|
|
201
|
+
async function readJson(filePath) {
|
|
202
|
+
let raw;
|
|
203
|
+
try {
|
|
204
|
+
raw = await readFile2(filePath, "utf8");
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (getErrorCode2(error) === "ENOENT") {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
return JSON.parse(raw);
|
|
213
|
+
} catch {
|
|
214
|
+
throw new Error(`Relay runtime config at ${filePath} is not valid JSON`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function parseRelayRuntimeConfig(value) {
|
|
218
|
+
if (!isRecord2(value)) {
|
|
219
|
+
throw new Error("Relay runtime config must be an object");
|
|
220
|
+
}
|
|
221
|
+
const connectorBaseUrl = typeof value.connectorBaseUrl === "string" && value.connectorBaseUrl.trim().length > 0 ? parseConnectorBaseUrl(value.connectorBaseUrl.trim()) : void 0;
|
|
222
|
+
const connectorPath = typeof value.connectorPath === "string" && value.connectorPath.trim().length > 0 ? normalizeConnectorPath(value.connectorPath) : void 0;
|
|
223
|
+
const peersConfigPath = typeof value.peersConfigPath === "string" && value.peersConfigPath.trim().length > 0 ? value.peersConfigPath.trim() : void 0;
|
|
224
|
+
const connectorBaseUrls = Array.isArray(value.connectorBaseUrls) ? value.connectorBaseUrls.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0).map(parseConnectorBaseUrl) : void 0;
|
|
225
|
+
return {
|
|
226
|
+
connectorBaseUrl,
|
|
227
|
+
connectorBaseUrls,
|
|
228
|
+
connectorPath,
|
|
229
|
+
peersConfigPath
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
async function loadRelayRuntimeConfig() {
|
|
233
|
+
const runtimePath = join2(resolveTransformsDir(), RELAY_RUNTIME_FILE_NAME);
|
|
234
|
+
const parsed = await readJson(runtimePath);
|
|
235
|
+
if (parsed === void 0) {
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
return parseRelayRuntimeConfig(parsed);
|
|
239
|
+
}
|
|
240
|
+
function parseGatewayHexToIpv4(value) {
|
|
241
|
+
if (!/^[0-9A-Fa-f]{8}$/.test(value)) {
|
|
242
|
+
return void 0;
|
|
243
|
+
}
|
|
244
|
+
const octets = [0, 2, 4, 6].map(
|
|
245
|
+
(index) => Number.parseInt(value.slice(index, index + 2), 16)
|
|
246
|
+
);
|
|
247
|
+
return `${octets[3]}.${octets[2]}.${octets[1]}.${octets[0]}`;
|
|
248
|
+
}
|
|
249
|
+
async function resolveLinuxDockerGatewayHost() {
|
|
250
|
+
let raw;
|
|
251
|
+
try {
|
|
252
|
+
raw = await readFile2("/proc/net/route", "utf8");
|
|
253
|
+
} catch {
|
|
254
|
+
return void 0;
|
|
255
|
+
}
|
|
256
|
+
const lines = raw.split("\n");
|
|
257
|
+
for (const line of lines.slice(1)) {
|
|
258
|
+
const parts = line.trim().split(/\s+/);
|
|
259
|
+
if (parts.length < 4) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const destination = parts[1];
|
|
263
|
+
const gateway = parts[2];
|
|
264
|
+
const flags = Number.parseInt(parts[3], 16);
|
|
265
|
+
if (destination === "00000000" && Number.isFinite(flags) && (flags & 2) === 2) {
|
|
266
|
+
return parseGatewayHexToIpv4(gateway);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return void 0;
|
|
270
|
+
}
|
|
271
|
+
async function resolveConnectorEndpoints(options) {
|
|
272
|
+
const runtimeConfig = await loadRelayRuntimeConfig();
|
|
273
|
+
const pathInput = options.connectorPath ?? runtimeConfig.connectorPath ?? process.env.CLAWDENTITY_CONNECTOR_OUTBOUND_PATH ?? DEFAULT_CONNECTOR_OUTBOUND_PATH;
|
|
189
274
|
const path = normalizeConnectorPath(pathInput.trim());
|
|
190
|
-
|
|
275
|
+
const candidates = [];
|
|
276
|
+
if (options.connectorBaseUrl) {
|
|
277
|
+
candidates.push(parseConnectorBaseUrl(options.connectorBaseUrl.trim()));
|
|
278
|
+
}
|
|
279
|
+
if (runtimeConfig.connectorBaseUrls) {
|
|
280
|
+
candidates.push(...runtimeConfig.connectorBaseUrls);
|
|
281
|
+
}
|
|
282
|
+
if (runtimeConfig.connectorBaseUrl) {
|
|
283
|
+
candidates.push(runtimeConfig.connectorBaseUrl);
|
|
284
|
+
}
|
|
285
|
+
if (typeof process.env.CLAWDENTITY_CONNECTOR_BASE_URL === "string" && process.env.CLAWDENTITY_CONNECTOR_BASE_URL.trim().length > 0) {
|
|
286
|
+
candidates.push(
|
|
287
|
+
parseConnectorBaseUrl(process.env.CLAWDENTITY_CONNECTOR_BASE_URL.trim())
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
candidates.push(DEFAULT_CONNECTOR_BASE_URL);
|
|
291
|
+
const linuxGatewayHost = await resolveLinuxDockerGatewayHost();
|
|
292
|
+
if (linuxGatewayHost) {
|
|
293
|
+
for (const candidate of [...candidates]) {
|
|
294
|
+
try {
|
|
295
|
+
const parsed = new URL(candidate);
|
|
296
|
+
if (parsed.hostname === "host.docker.internal" || parsed.hostname === "gateway.docker.internal" || parsed.hostname === "172.17.0.1") {
|
|
297
|
+
parsed.hostname = linuxGatewayHost;
|
|
298
|
+
candidates.push(parsed.toString());
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const deduped = Array.from(new Set(candidates.map((candidate) => candidate)));
|
|
305
|
+
return deduped.map((baseUrl) => new URL(path, baseUrl).toString());
|
|
191
306
|
}
|
|
192
307
|
function mapConnectorFailure(status) {
|
|
193
308
|
if (status === 404) {
|
|
@@ -218,6 +333,26 @@ async function postToConnector(endpoint, payload, fetchImpl) {
|
|
|
218
333
|
throw mapConnectorFailure(response.status);
|
|
219
334
|
}
|
|
220
335
|
}
|
|
336
|
+
function shouldTryNextConnectorEndpoint(error) {
|
|
337
|
+
if (!(error instanceof Error)) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
return error.message === "Local connector outbound relay request failed" || error.message === "Local connector outbound endpoint is unavailable";
|
|
341
|
+
}
|
|
342
|
+
async function resolvePeersConfigPathOptions(options) {
|
|
343
|
+
if (options.configPath !== void 0 || options.configDir !== void 0 || options.homeDir !== void 0) {
|
|
344
|
+
return options;
|
|
345
|
+
}
|
|
346
|
+
const runtimeConfig = await loadRelayRuntimeConfig();
|
|
347
|
+
if (runtimeConfig.peersConfigPath) {
|
|
348
|
+
return {
|
|
349
|
+
configPath: join2(resolveTransformsDir(), runtimeConfig.peersConfigPath)
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
configPath: join2(resolveTransformsDir(), RELAY_PEERS_FILE_NAME)
|
|
354
|
+
};
|
|
355
|
+
}
|
|
221
356
|
async function relayPayloadToPeer(payload, options = {}) {
|
|
222
357
|
if (!isRecord2(payload)) {
|
|
223
358
|
return payload;
|
|
@@ -227,25 +362,37 @@ async function relayPayloadToPeer(payload, options = {}) {
|
|
|
227
362
|
return payload;
|
|
228
363
|
}
|
|
229
364
|
const peerAlias = parseRequiredString(peerAliasValue);
|
|
230
|
-
const
|
|
365
|
+
const peersConfigPathOptions = await resolvePeersConfigPathOptions(options);
|
|
366
|
+
const peersConfig = await loadPeersConfig(peersConfigPathOptions);
|
|
231
367
|
const peerEntry = peersConfig.peers[peerAlias];
|
|
232
368
|
if (!peerEntry) {
|
|
233
369
|
throw new Error("Peer alias is not configured");
|
|
234
370
|
}
|
|
235
|
-
const
|
|
371
|
+
const connectorEndpoints = await resolveConnectorEndpoints(options);
|
|
236
372
|
const fetchImpl = resolveRelayFetch(options.fetchImpl);
|
|
237
373
|
const outboundPayload = removePeerField(payload);
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
374
|
+
const relayPayload = {
|
|
375
|
+
peer: peerAlias,
|
|
376
|
+
peerDid: peerEntry.did,
|
|
377
|
+
peerProxyUrl: peerEntry.proxyUrl,
|
|
378
|
+
payload: outboundPayload
|
|
379
|
+
};
|
|
380
|
+
let lastError;
|
|
381
|
+
for (const endpoint of connectorEndpoints) {
|
|
382
|
+
try {
|
|
383
|
+
await postToConnector(endpoint, relayPayload, fetchImpl);
|
|
384
|
+
return null;
|
|
385
|
+
} catch (error) {
|
|
386
|
+
lastError = error;
|
|
387
|
+
if (!shouldTryNextConnectorEndpoint(error)) {
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (lastError instanceof Error) {
|
|
393
|
+
throw lastError;
|
|
394
|
+
}
|
|
395
|
+
throw new Error("Local connector outbound relay request failed");
|
|
249
396
|
}
|
|
250
397
|
async function relayToPeer(ctx) {
|
|
251
398
|
return relayPayloadToPeer(ctx?.payload);
|
|
@@ -19,9 +19,14 @@ Use this skill when any of the following are requested:
|
|
|
19
19
|
## Filesystem Truth (must be used exactly)
|
|
20
20
|
|
|
21
21
|
### OpenClaw state files
|
|
22
|
-
- OpenClaw state root (default): `~/.openclaw`
|
|
23
|
-
- OpenClaw config:
|
|
22
|
+
- OpenClaw state root (default): `~/.openclaw` (legacy fallback dirs may exist: `~/.clawdbot`, `~/.moldbot`, `~/.moltbot`)
|
|
23
|
+
- OpenClaw config: `<resolved-openclaw-state>/openclaw.json` (legacy names may exist: `clawdbot.json`, `moldbot.json`, `moltbot.json`)
|
|
24
|
+
- OpenClaw config env overrides: `OPENCLAW_CONFIG_PATH`, legacy `CLAWDBOT_CONFIG_PATH`
|
|
25
|
+
- OpenClaw state env overrides: `OPENCLAW_STATE_DIR`, legacy `CLAWDBOT_STATE_DIR`
|
|
26
|
+
- OpenClaw home override (used when config/state overrides are unset): `OPENCLAW_HOME`
|
|
24
27
|
- Transform target path: `~/.openclaw/hooks/transforms/relay-to-peer.mjs`
|
|
28
|
+
- Transform runtime snapshot: `~/.openclaw/hooks/transforms/clawdentity-relay.json`
|
|
29
|
+
- Transform peers snapshot: `~/.openclaw/hooks/transforms/clawdentity-peers.json`
|
|
25
30
|
- Workspace skill location: `~/.openclaw/workspace/skills/clawdentity-openclaw-relay/SKILL.md`
|
|
26
31
|
- Default transform source expected by CLI setup:
|
|
27
32
|
`~/.openclaw/workspace/skills/clawdentity-openclaw-relay/relay-to-peer.mjs`
|
|
@@ -35,6 +40,7 @@ Use this skill when any of the following are requested:
|
|
|
35
40
|
- Peer map: `~/.clawdentity/peers.json`
|
|
36
41
|
- Local selected agent marker: `~/.clawdentity/openclaw-agent-name`
|
|
37
42
|
- Relay runtime config: `~/.clawdentity/openclaw-relay.json`
|
|
43
|
+
- Connector assignment map: `~/.clawdentity/openclaw-connectors.json`
|
|
38
44
|
|
|
39
45
|
## Invite Input Assumption
|
|
40
46
|
|
|
@@ -133,11 +139,10 @@ Successful confirm establishes mutual trust for the two agent DIDs. After confir
|
|
|
133
139
|
- Use `--openclaw-dir <path>` when state directory is non-default.
|
|
134
140
|
- Use `--openclaw-base-url <url>` when local OpenClaw HTTP endpoint is non-default.
|
|
135
141
|
- Use `--peer-alias <alias>` only when alias override is required.
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
140
|
-
- `--echo-max-length <number>`
|
|
142
|
+
- Setup now provisions OpenClaw hook auth automatically:
|
|
143
|
+
- ensures `hooks.enabled=true`
|
|
144
|
+
- preserves existing `hooks.token` or generates one when missing
|
|
145
|
+
- mirrors token into `~/.clawdentity/openclaw-relay.json` as `openclawHookToken`
|
|
141
146
|
|
|
142
147
|
7. Verify setup outputs.
|
|
143
148
|
- Confirm setup reports:
|
|
@@ -146,12 +151,13 @@ Successful confirm establishes mutual trust for the two agent DIDs. After confir
|
|
|
146
151
|
- updated OpenClaw config path
|
|
147
152
|
- installed transform path
|
|
148
153
|
- OpenClaw base URL
|
|
149
|
-
- relay echo config (enabled/channel/target/max length)
|
|
150
154
|
- relay runtime config path
|
|
151
155
|
- Confirm `~/.clawdentity/openclaw-agent-name` is set to the local agent name.
|
|
152
156
|
|
|
153
157
|
8. Start connector runtime for local relay handoff.
|
|
154
158
|
- Run `clawdentity connector start <agent-name>`.
|
|
159
|
+
- Connector startup auto-loads `openclawHookToken` from `~/.clawdentity/openclaw-relay.json` when `--openclaw-hook-token` is not provided.
|
|
160
|
+
- Connector startup auto-resolves outbound bind URL from `~/.clawdentity/openclaw-connectors.json` when `CLAWDENTITY_CONNECTOR_BASE_URL` is not set.
|
|
155
161
|
- Optional: run `clawdentity connector service install <agent-name>` for persistent autostart.
|
|
156
162
|
|
|
157
163
|
9. Complete trust pairing bootstrap.
|
|
@@ -186,6 +192,7 @@ If setup or relay fails:
|
|
|
186
192
|
- Report precise missing file/path/value.
|
|
187
193
|
- Fix only the failing config/input.
|
|
188
194
|
- Ensure connector runtime is active (`clawdentity connector start <agent-name>`).
|
|
195
|
+
- If relay failures show `405 Method Not Allowed`, rerun setup and restart OpenClaw so hook config is reloaded.
|
|
189
196
|
- Re-run `clawdentity openclaw doctor`.
|
|
190
197
|
- Re-run `clawdentity openclaw relay test --peer <alias>`.
|
|
191
198
|
- Re-run the same user-style flow from step 6 onward only after health checks pass.
|
|
@@ -7,9 +7,15 @@ Define the exact runtime contract used by `relay-to-peer.mjs`.
|
|
|
7
7
|
## Filesystem Paths
|
|
8
8
|
|
|
9
9
|
### OpenClaw files
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
10
|
+
- `<resolved-openclaw-state>/openclaw.json` (legacy filenames may exist: `clawdbot.json`, `moldbot.json`, `moltbot.json`)
|
|
11
|
+
- `<resolved-openclaw-state>/hooks/transforms/relay-to-peer.mjs`
|
|
12
|
+
- `<resolved-openclaw-state>/hooks/transforms/clawdentity-relay.json`
|
|
13
|
+
- `<resolved-openclaw-state>/hooks/transforms/clawdentity-peers.json`
|
|
14
|
+
- `<resolved-openclaw-state>/workspace/skills/clawdentity-openclaw-relay/SKILL.md`
|
|
15
|
+
- env overrides:
|
|
16
|
+
- `OPENCLAW_CONFIG_PATH`, `CLAWDBOT_CONFIG_PATH`
|
|
17
|
+
- `OPENCLAW_STATE_DIR`, `CLAWDBOT_STATE_DIR`
|
|
18
|
+
- `OPENCLAW_HOME` (used when explicit config/state overrides are unset)
|
|
13
19
|
|
|
14
20
|
### Clawdentity files
|
|
15
21
|
- `~/.clawdentity/config.json`
|
|
@@ -18,6 +24,7 @@ Define the exact runtime contract used by `relay-to-peer.mjs`.
|
|
|
18
24
|
- `~/.clawdentity/peers.json`
|
|
19
25
|
- `~/.clawdentity/openclaw-agent-name`
|
|
20
26
|
- `~/.clawdentity/openclaw-relay.json`
|
|
27
|
+
- `~/.clawdentity/openclaw-connectors.json`
|
|
21
28
|
|
|
22
29
|
## Invite Code Contract
|
|
23
30
|
|
|
@@ -124,36 +131,31 @@ Relay resolves local agent name in this order:
|
|
|
124
131
|
|
|
125
132
|
## Local OpenClaw Base URL Contract
|
|
126
133
|
|
|
127
|
-
`~/.clawdentity/openclaw-relay.json` stores OpenClaw
|
|
134
|
+
`~/.clawdentity/openclaw-relay.json` stores the OpenClaw upstream base URL used by local proxy runtime fallback:
|
|
128
135
|
|
|
129
136
|
```json
|
|
130
137
|
{
|
|
131
138
|
"openclawBaseUrl": "http://127.0.0.1:18789",
|
|
132
|
-
"
|
|
133
|
-
"enabled": true,
|
|
134
|
-
"channel": "last",
|
|
135
|
-
"maxLength": 500
|
|
136
|
-
},
|
|
139
|
+
"openclawHookToken": "<auto-provisioned-token>",
|
|
137
140
|
"updatedAt": "2026-02-15T20:00:00.000Z"
|
|
138
141
|
}
|
|
139
142
|
```
|
|
140
143
|
|
|
141
144
|
Rules:
|
|
142
145
|
- `openclawBaseUrl` must be absolute `http` or `https`.
|
|
143
|
-
- `
|
|
144
|
-
- `echo.channel` defaults to `last`.
|
|
145
|
-
- `echo.to` is optional; when omitted OpenClaw uses last-route delivery target.
|
|
146
|
-
- `echo.maxLength` must be an integer from `50` to `2000` (default `500`).
|
|
146
|
+
- `openclawHookToken` is optional in schema but should be present after `clawdentity openclaw setup`; connector runtime uses it for `/hooks/*` auth when no explicit hook token option/env is provided.
|
|
147
147
|
- `updatedAt` is ISO-8601 UTC timestamp.
|
|
148
|
-
-
|
|
148
|
+
- Proxy runtime precedence is: `OPENCLAW_BASE_URL` env first, then `openclaw-relay.json`, then built-in default.
|
|
149
149
|
|
|
150
150
|
## Connector Handoff Contract
|
|
151
151
|
|
|
152
152
|
The transform does not send directly to the peer proxy. It posts to the local connector runtime:
|
|
153
|
-
-
|
|
154
|
-
-
|
|
153
|
+
- Endpoint candidates are loaded from OpenClaw-local `hooks/transforms/clawdentity-relay.json` (generated by `openclaw setup`) and attempted in order.
|
|
154
|
+
- Default fallback endpoint remains `http://127.0.0.1:19400/v1/outbound`.
|
|
155
|
+
- Runtime may also use:
|
|
155
156
|
- `CLAWDENTITY_CONNECTOR_BASE_URL`
|
|
156
157
|
- `CLAWDENTITY_CONNECTOR_OUTBOUND_PATH`
|
|
158
|
+
- `connector start <agentName>` resolves bind URL from `~/.clawdentity/openclaw-connectors.json` when explicit env override is absent.
|
|
157
159
|
|
|
158
160
|
Outbound JSON body sent by transform:
|
|
159
161
|
|