@clawcrony/claw-crony 1.2.4 → 1.3.0

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.
@@ -1,5 +1,7 @@
1
1
  import type { GatewayConfig } from "./types.js";
2
- export declare function issueEphemeralInboundToken(config: GatewayConfig, matchId: number, peerAgentId: number, ttlMs?: number): {
2
+ export declare const EPHEMERAL_INBOUND_TOKEN_LENGTH = 48;
3
+ export declare function isValidEphemeralInboundToken(value: unknown): value is string;
4
+ export declare function issueEphemeralInboundToken(config: GatewayConfig, _matchId: number, _peerAgentId: number, ttlMs?: number): {
3
5
  token: string;
4
6
  expiresAt: string;
5
7
  };
@@ -1,6 +1,11 @@
1
1
  import crypto from "node:crypto";
2
- export function issueEphemeralInboundToken(config, matchId, peerAgentId, ttlMs = 5 * 60_000) {
3
- const token = `match-${matchId}-peer-${peerAgentId}-${crypto.randomBytes(18).toString("hex")}`;
2
+ export const EPHEMERAL_INBOUND_TOKEN_LENGTH = 48;
3
+ const EPHEMERAL_INBOUND_TOKEN_PATTERN = /^[0-9a-f]{48}$/;
4
+ export function isValidEphemeralInboundToken(value) {
5
+ return typeof value === "string" && EPHEMERAL_INBOUND_TOKEN_PATTERN.test(value);
6
+ }
7
+ export function issueEphemeralInboundToken(config, _matchId, _peerAgentId, ttlMs = 5 * 60_000) {
8
+ const token = crypto.randomBytes(EPHEMERAL_INBOUND_TOKEN_LENGTH / 2).toString("hex");
4
9
  config.security.validTokens.add(token);
5
10
  setTimeout(() => {
6
11
  config.security.validTokens.delete(token);
@@ -0,0 +1,44 @@
1
+ export type RequestHistoryType = "match.created" | "match.failed" | "handshake.offer_sent" | "handshake.offer_received" | "handshake.answer_sent" | "handshake.answer_received" | "handshake.failed" | "peer.upserted" | "send.started" | "send.completed" | "send.failed" | "send_file.started" | "send_file.completed" | "send_file.failed" | "task.inbound_completed" | "task.inbound_failed";
2
+ export type RequestHistoryStatus = "started" | "success" | "failure" | "ignored";
3
+ export type RequestHistoryDirection = "inbound" | "outbound" | "local";
4
+ export interface RequestHistoryEntry {
5
+ ts: string;
6
+ type: RequestHistoryType;
7
+ status: RequestHistoryStatus;
8
+ direction?: RequestHistoryDirection;
9
+ matchId?: number;
10
+ messageId?: number;
11
+ peer?: string;
12
+ durationMs?: number;
13
+ detail?: Record<string, unknown>;
14
+ }
15
+ export interface RequestHistoryFilter {
16
+ count?: number;
17
+ type?: string;
18
+ status?: string;
19
+ direction?: string;
20
+ matchId?: number;
21
+ peer?: string;
22
+ }
23
+ export interface RequestHistoryOptions {
24
+ enabled: boolean;
25
+ includeEncryptedPayloads: boolean;
26
+ }
27
+ /**
28
+ * Append-only request history store for operator-facing troubleshooting.
29
+ * Unlike the audit log, this captures Hub match/handshake milestones and
30
+ * gateway calls. Sensitive fields are redacted before persistence.
31
+ */
32
+ export declare class RequestHistoryStore {
33
+ private readonly filePath;
34
+ private readonly options;
35
+ private dirEnsured;
36
+ constructor(filePath: string, options?: Partial<RequestHistoryOptions>);
37
+ record(entry: Omit<RequestHistoryEntry, "ts"> & {
38
+ ts?: string;
39
+ }): void;
40
+ tail(filter?: RequestHistoryFilter): Promise<RequestHistoryEntry[]>;
41
+ close(): void;
42
+ private ensureDir;
43
+ private write;
44
+ }
@@ -0,0 +1,119 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import readline from "node:readline";
4
+ const SECRET_KEY_NAMES = ["token", "secret", "password", "authorization", "ciphertext"];
5
+ function redactValue(key, value, includeEncryptedPayloads) {
6
+ const normalizedKey = key.toLowerCase();
7
+ if (SECRET_KEY_NAMES.some((name) => normalizedKey.includes(name))) {
8
+ if (normalizedKey.includes("ciphertext") && includeEncryptedPayloads) {
9
+ return value;
10
+ }
11
+ return "[redacted]";
12
+ }
13
+ if (Array.isArray(value)) {
14
+ return value.map((entry) => redactUnknown(entry, includeEncryptedPayloads));
15
+ }
16
+ if (value && typeof value === "object") {
17
+ return redactObject(value, includeEncryptedPayloads);
18
+ }
19
+ return value;
20
+ }
21
+ function redactUnknown(value, includeEncryptedPayloads) {
22
+ if (Array.isArray(value)) {
23
+ return value.map((entry) => redactUnknown(entry, includeEncryptedPayloads));
24
+ }
25
+ if (value && typeof value === "object") {
26
+ return redactObject(value, includeEncryptedPayloads);
27
+ }
28
+ return value;
29
+ }
30
+ function redactObject(value, includeEncryptedPayloads) {
31
+ const next = {};
32
+ for (const [key, entry] of Object.entries(value)) {
33
+ next[key] = redactValue(key, entry, includeEncryptedPayloads);
34
+ }
35
+ return next;
36
+ }
37
+ function matchesFilter(entry, filter) {
38
+ if (filter.type && entry.type !== filter.type)
39
+ return false;
40
+ if (filter.status && entry.status !== filter.status)
41
+ return false;
42
+ if (filter.direction && entry.direction !== filter.direction)
43
+ return false;
44
+ if (filter.matchId != null && entry.matchId !== filter.matchId)
45
+ return false;
46
+ if (filter.peer && entry.peer !== filter.peer)
47
+ return false;
48
+ return true;
49
+ }
50
+ /**
51
+ * Append-only request history store for operator-facing troubleshooting.
52
+ * Unlike the audit log, this captures Hub match/handshake milestones and
53
+ * gateway calls. Sensitive fields are redacted before persistence.
54
+ */
55
+ export class RequestHistoryStore {
56
+ filePath;
57
+ options;
58
+ dirEnsured = false;
59
+ constructor(filePath, options = {}) {
60
+ this.filePath = filePath;
61
+ this.options = {
62
+ enabled: options.enabled ?? true,
63
+ includeEncryptedPayloads: options.includeEncryptedPayloads ?? false,
64
+ };
65
+ }
66
+ record(entry) {
67
+ if (!this.options.enabled) {
68
+ return;
69
+ }
70
+ const detail = entry.detail
71
+ ? redactObject(entry.detail, this.options.includeEncryptedPayloads)
72
+ : undefined;
73
+ this.write({
74
+ ...entry,
75
+ ts: entry.ts ?? new Date().toISOString(),
76
+ ...(detail ? { detail } : {}),
77
+ });
78
+ }
79
+ async tail(filter = {}) {
80
+ if (!fs.existsSync(this.filePath))
81
+ return [];
82
+ const count = Math.min(Math.max(1, Math.floor(filter.count ?? 50)), 500);
83
+ const entries = [];
84
+ const input = fs.createReadStream(this.filePath, { encoding: "utf-8" });
85
+ const rl = readline.createInterface({ input, crlfDelay: Infinity });
86
+ for await (const line of rl) {
87
+ if (!line.trim())
88
+ continue;
89
+ try {
90
+ const entry = JSON.parse(line);
91
+ if (matchesFilter(entry, filter)) {
92
+ entries.push(entry);
93
+ }
94
+ }
95
+ catch {
96
+ // Skip malformed lines.
97
+ }
98
+ }
99
+ return entries.slice(-count).reverse();
100
+ }
101
+ close() {
102
+ // No persistent handles.
103
+ }
104
+ ensureDir() {
105
+ if (this.dirEnsured)
106
+ return;
107
+ this.dirEnsured = true;
108
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
109
+ }
110
+ write(entry) {
111
+ try {
112
+ this.ensureDir();
113
+ fs.appendFileSync(this.filePath, JSON.stringify(entry) + "\n");
114
+ }
115
+ catch {
116
+ // History is diagnostic only; never crash the gateway.
117
+ }
118
+ }
119
+ }
@@ -51,7 +51,7 @@ export class HubMatchClient {
51
51
  async getPendingMatches() {
52
52
  return this.request(`/api/matches/pending?agentId=${this.registration.agentId}`);
53
53
  }
54
- async updatePresence(presenceStatus, clientVersion = "claw-crony/1.2.4") {
54
+ async updatePresence(presenceStatus, clientVersion = "claw-crony/1.3.0") {
55
55
  return this.request(`/api/agents/${this.registration.agentId}/presence`, {
56
56
  method: "PUT",
57
57
  body: JSON.stringify({
@@ -127,7 +127,7 @@ export async function runHubRegistration(api, config, hubConfig, registrationCon
127
127
  clientId: identity.clientId,
128
128
  publicKey: identity.publicKey,
129
129
  keyVersion: identity.keyVersion,
130
- clientVersion: "claw-crony/1.2.4",
130
+ clientVersion: "claw-crony/1.3.0",
131
131
  username,
132
132
  email,
133
133
  };
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * These types support the A2A v0.3.0 protocol integration via @a2a-js/sdk.
5
5
  */
6
- export type { OpenClawPluginApi, PluginLogger, OpenClawConfig } from "openclaw/plugin-sdk";
6
+ export type { OpenClawPluginApi, PluginLogger, OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
7
7
  export type InboundAuth = "none" | "bearer";
8
8
  export type PeerAuthType = "bearer" | "apiKey";
9
9
  export interface PeerAuthConfig {
@@ -70,6 +70,9 @@ export interface GatewayConfig {
70
70
  metricsPath: string;
71
71
  metricsAuth: "none" | "bearer";
72
72
  auditLogPath: string;
73
+ historyEnabled: boolean;
74
+ historyLogPath: string;
75
+ historyIncludeEncryptedPayloads: boolean;
73
76
  };
74
77
  timeouts?: {
75
78
  /**
@@ -1,9 +1,18 @@
1
1
  {
2
- "id": "claw-crony",
3
- "name": "Claw Crony",
4
- "description": "OpenClaw A2A v0.3.0 gateway with Agent Card, JSON-RPC, REST, routing rules, transport fallback, and Hub matchmaking",
5
- "version": "1.2.3",
6
- "defaultConfig": {
2
+ "id": "claw-crony",
3
+ "name": "Claw Crony",
4
+ "description": "OpenClaw A2A v0.3.0 gateway with Agent Card, JSON-RPC, REST, routing rules, transport fallback, and Hub matchmaking",
5
+ "version": "1.3.0",
6
+ "activation": {
7
+ "onStartup": true
8
+ },
9
+ "contracts": {
10
+ "tools": [
11
+ "a2a_send_file",
12
+ "a2a_match_request"
13
+ ]
14
+ },
15
+ "defaultConfig": {
7
16
  "agentCard": {
8
17
  "name": "OpenClaw A2A Gateway",
9
18
  "description": "A2A bridge for OpenClaw agents",
@@ -58,12 +67,16 @@
58
67
  "registration": {
59
68
  "type": "object",
60
69
  "additionalProperties": false,
61
- "properties": {
62
- "username": { "type": "string" },
63
- "email": { "type": "string" },
64
- "password": { "type": "string" }
65
- }
66
- },
70
+ "properties": {
71
+ "username": { "type": "string" },
72
+ "email": { "type": "string" },
73
+ "password": { "type": "string" },
74
+ "clientId": {
75
+ "type": "string",
76
+ "description": "Optional stable client id for Hub registration. If omitted, claw-crony generates and persists one locally."
77
+ }
78
+ }
79
+ },
67
80
  "server": {
68
81
  "type": "object",
69
82
  "additionalProperties": false,
@@ -206,13 +219,28 @@
206
219
  "default": "none",
207
220
  "description": "Authentication for the metrics endpoint. When set to 'bearer', reuses security.token/tokens."
208
221
  },
209
- "auditLogPath": {
210
- "type": "string",
211
- "default": "~/.openclaw/a2a-audit.jsonl",
212
- "description": "Path for the JSONL audit log file (separate from structured logs)."
213
- }
214
- }
215
- },
222
+ "auditLogPath": {
223
+ "type": "string",
224
+ "default": "~/.openclaw/a2a-audit.jsonl",
225
+ "description": "Path for the JSONL audit log file (separate from structured logs)."
226
+ },
227
+ "historyEnabled": {
228
+ "type": "boolean",
229
+ "default": true,
230
+ "description": "Enable operator-facing JSONL request history for match, handshake, peer, send, and file-send events."
231
+ },
232
+ "historyLogPath": {
233
+ "type": "string",
234
+ "default": "~/.openclaw/a2a-history.jsonl",
235
+ "description": "Path for the JSONL request history file."
236
+ },
237
+ "historyIncludeEncryptedPayloads": {
238
+ "type": "boolean",
239
+ "default": false,
240
+ "description": "Include encrypted handshake payload fields in request history. Tokens and secrets remain redacted."
241
+ }
242
+ }
243
+ },
216
244
  "timeouts": {
217
245
  "type": "object",
218
246
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawcrony/claw-crony",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw A2A gateway plugin implementing the A2A v0.3.0 protocol surface",
6
6
  "main": "dist/index.js",
@@ -11,11 +11,16 @@
11
11
  "types": "./dist/index.d.ts"
12
12
  }
13
13
  },
14
- "files": [
15
- "dist",
16
- "openclaw.plugin.json",
17
- "skill"
18
- ],
14
+ "files": [
15
+ "dist",
16
+ "openclaw.plugin.json",
17
+ "skill",
18
+ "scripts",
19
+ "README.md",
20
+ "CONFIG.md",
21
+ "CHANGELOG.md",
22
+ "LICENSE"
23
+ ],
19
24
  "scripts": {
20
25
  "build": "tsc",
21
26
  "test": "node --import tsx --test tests/*.test.ts"
@@ -29,27 +34,38 @@
29
34
  ],
30
35
  "license": "MIT",
31
36
  "devDependencies": {
32
- "@types/express": "^5.0.6",
33
- "@types/node": "^22.0.0",
34
- "@types/supertest": "^2.0.16",
35
- "@types/uuid": "^10.0.0",
36
- "openclaw": "^2026.3.2",
37
- "supertest": "^7.1.4",
38
- "tsx": "^4.19.0",
39
- "typescript": "^5.9.3"
40
- },
41
- "dependencies": {
42
- "@a2a-js/sdk": "^0.3.0",
43
- "@bufbuild/protobuf": "^2.11.0",
44
- "@grpc/grpc-js": "^1.14.3",
45
- "express": "^4.21.2",
46
- "uuid": "^9.0.1"
47
- },
48
- "openclaw": {
49
- "extensions": [
50
- "./index.ts"
51
- ]
52
- },
37
+ "@types/express": "5.0.6",
38
+ "@types/node": "25.5.0",
39
+ "@types/supertest": "2.0.16",
40
+ "@types/uuid": "10.0.0",
41
+ "openclaw": "2026.5.2",
42
+ "supertest": "7.2.2",
43
+ "tsx": "4.21.0",
44
+ "typescript": "5.9.3"
45
+ },
46
+ "dependencies": {
47
+ "@a2a-js/sdk": "0.3.13",
48
+ "@bufbuild/protobuf": "2.11.0",
49
+ "@grpc/grpc-js": "1.14.3",
50
+ "express": "4.22.1",
51
+ "uuid": "9.0.1"
52
+ },
53
+ "openclaw": {
54
+ "extensions": [
55
+ "./dist/index.js"
56
+ ],
57
+ "compat": {
58
+ "pluginApi": ">=2026.5.2",
59
+ "minGatewayVersion": "2026.5.2"
60
+ },
61
+ "build": {
62
+ "openclawVersion": "2026.5.2",
63
+ "pluginSdkVersion": "2026.5.2"
64
+ }
65
+ },
66
+ "overrides": {
67
+ "axios": "1.16.0"
68
+ },
53
69
  "engines": {
54
70
  "node": ">=22"
55
71
  }
@@ -0,0 +1,15 @@
1
+ Write-Host "== OpenClaw Gateway =="
2
+ openclaw gateway status
3
+
4
+ Write-Host "`n== Plugin =="
5
+ openclaw plugins inspect claw-crony
6
+ openclaw plugins inspect claw-crony --runtime
7
+
8
+ Write-Host "`n== Peers =="
9
+ openclaw gateway call a2a.peers --params "{}"
10
+
11
+ Write-Host "`n== Metrics =="
12
+ openclaw gateway call a2a.metrics --params "{}"
13
+
14
+ Write-Host "`n== Recent History =="
15
+ openclaw gateway call a2a.history --params "{`"count`":20}"
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ echo "== OpenClaw Gateway =="
5
+ openclaw gateway status
6
+
7
+ echo
8
+ echo "== Plugin =="
9
+ openclaw plugins inspect claw-crony
10
+ openclaw plugins inspect claw-crony --runtime
11
+
12
+ echo
13
+ echo "== Peers =="
14
+ openclaw gateway call a2a.peers --params "{}"
15
+
16
+ echo
17
+ echo "== Metrics =="
18
+ openclaw gateway call a2a.metrics --params "{}"
19
+
20
+ echo
21
+ echo "== Recent History =="
22
+ openclaw gateway call a2a.history --params '{"count":20}'
@@ -0,0 +1,21 @@
1
+ param(
2
+ [int] $Count = 50,
3
+ [string] $Type = "",
4
+ [string] $Status = "",
5
+ [string] $Direction = "",
6
+ [int] $MatchId = 0,
7
+ [string] $Peer = ""
8
+ )
9
+
10
+ $payload = @{
11
+ count = $Count
12
+ }
13
+
14
+ if ($Type) { $payload.type = $Type }
15
+ if ($Status) { $payload.status = $Status }
16
+ if ($Direction) { $payload.direction = $Direction }
17
+ if ($MatchId -gt 0) { $payload.matchId = $MatchId }
18
+ if ($Peer) { $payload.peer = $Peer }
19
+
20
+ $json = $payload | ConvertTo-Json -Depth 20 -Compress
21
+ openclaw gateway call a2a.history --params $json
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ count="${1:-50}"
5
+ type="${2:-}"
6
+ match_id="${3:-}"
7
+
8
+ params="$(node -e 'const payload={count:Number(process.argv[1] || 50)}; if (process.argv[2]) payload.type=process.argv[2]; if (process.argv[3]) payload.matchId=Number(process.argv[3]); process.stdout.write(JSON.stringify(payload));' "$count" "$type" "$match_id")"
9
+ openclaw gateway call a2a.history --params "$params"
@@ -0,0 +1,16 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)]
3
+ [string[]] $Skills,
4
+ [string] $Description = ""
5
+ )
6
+
7
+ $payload = @{
8
+ skills = $Skills
9
+ }
10
+
11
+ if ($Description) {
12
+ $payload.description = $Description
13
+ }
14
+
15
+ $json = $payload | ConvertTo-Json -Depth 20 -Compress
16
+ openclaw gateway call a2a.match --params $json
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ skills="${1:-}"
5
+ description="${2:-}"
6
+
7
+ if [[ -z "$skills" ]]; then
8
+ echo "usage: $0 chat,code_review [description]" >&2
9
+ exit 2
10
+ fi
11
+
12
+ params="$(node -e 'const skills=process.argv[1].split(",").map((s)=>s.trim()).filter(Boolean); const description=process.argv[2] || ""; const payload={skills}; if (description) payload.description=description; process.stdout.write(JSON.stringify(payload));' "$skills" "$description")"
13
+ openclaw gateway call a2a.match --params "$params"
@@ -0,0 +1 @@
1
+ openclaw gateway call a2a.peers --params "{}"
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ openclaw gateway call a2a.peers --params "{}"
@@ -0,0 +1,23 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)]
3
+ [string] $Peer,
4
+ [Parameter(Mandatory = $true)]
5
+ [string] $Text,
6
+ [string] $AgentId = ""
7
+ )
8
+
9
+ $message = @{
10
+ text = $Text
11
+ }
12
+
13
+ if ($AgentId) {
14
+ $message.agentId = $AgentId
15
+ }
16
+
17
+ $payload = @{
18
+ peer = $Peer
19
+ message = $message
20
+ }
21
+
22
+ $json = $payload | ConvertTo-Json -Depth 20 -Compress
23
+ openclaw gateway call a2a.send --params $json
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ peer="${1:-}"
5
+ text="${2:-}"
6
+ agent_id="${3:-}"
7
+
8
+ if [[ -z "$peer" || -z "$text" ]]; then
9
+ echo "usage: $0 <peer> <text> [agentId]" >&2
10
+ exit 2
11
+ fi
12
+
13
+ params="$(node -e 'const peer=process.argv[1]; const text=process.argv[2]; const agentId=process.argv[3] || ""; const message={text}; if (agentId) message.agentId=agentId; process.stdout.write(JSON.stringify({peer,message}));' "$peer" "$text" "$agent_id")"
14
+ openclaw gateway call a2a.send --params "$params"
@@ -0,0 +1,3 @@
1
+ openclaw plugins update claw-crony
2
+ openclaw gateway restart
3
+ openclaw plugins inspect claw-crony --runtime
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ openclaw plugins update claw-crony
5
+ openclaw gateway restart
6
+ openclaw plugins inspect claw-crony --runtime