clawaxis 1.0.4 → 1.2.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.
package/index.ts CHANGED
@@ -1,32 +1,25 @@
1
1
  import { LOG_PREFIX, PLUGIN_VERSION, loadConfig } from "./src/config.js";
2
2
  import { setRuntime } from "./src/runtime.js";
3
- import { setUtilsConfig } from "./src/utils.js";
4
3
  import { clawaxisChannel } from "./src/channel.js";
5
4
  import { registerGovernanceHooks } from "./src/governance/hooks.js";
6
5
  import { registerTools } from "./src/tools/index.js";
7
6
 
8
7
  function register(api: any) {
9
8
  console.log(`${LOG_PREFIX} ==========================================`);
10
- console.log(`${LOG_PREFIX} \uD83E\uDD9E ClawAxis Plugin v${PLUGIN_VERSION}`);
9
+ console.log(`${LOG_PREFIX} ClawAxis Plugin v${PLUGIN_VERSION} (Unix Socket)`);
11
10
  console.log(`${LOG_PREFIX} ==========================================`);
12
11
 
13
12
  setRuntime(api.runtime);
14
13
 
15
14
  const config = loadConfig(api.config);
16
- if (!config) {
17
- console.warn(`${LOG_PREFIX} No channels.clawaxis config found (or missing relayUrl/agentToken). Plugin disabled.`);
18
- return;
19
- }
20
-
21
- setUtilsConfig(config);
22
15
 
23
- console.log(`${LOG_PREFIX} \u2713 Config loaded`);
24
- if (config.mediaDomain) console.log(`${LOG_PREFIX} \u2022 Media domain: ${config.mediaDomain}`);
25
- if (config.agentName) console.log(`${LOG_PREFIX} \u2022 Agent name: ${config.agentName}`);
16
+ if (config.mediaDomain) console.log(`${LOG_PREFIX} Media domain: ${config.mediaDomain}`);
17
+ if (config.agentName) console.log(`${LOG_PREFIX} Agent name: ${config.agentName}`);
18
+ console.log(`${LOG_PREFIX} Socket: ${config.socketPath}`);
26
19
 
27
20
  if (typeof api.registerChannel === "function") {
28
21
  api.registerChannel({ plugin: clawaxisChannel });
29
- console.log(`${LOG_PREFIX} \u2713 Channel 'clawaxis' registered (WebSocket starts via gateway.startAccount)`);
22
+ console.log(`${LOG_PREFIX} Channel 'clawaxis' registered (socket starts via gateway.startAccount)`);
30
23
  } else {
31
24
  console.warn(`${LOG_PREFIX} api.registerChannel not available — channel not registered`);
32
25
  }
@@ -34,14 +27,14 @@ function register(api: any) {
34
27
  registerGovernanceHooks(api, config);
35
28
 
36
29
  if (typeof api.registerTool === "function") {
37
- registerTools(api, config.agentToken);
38
- console.log(`${LOG_PREFIX} \u2705 All 10 tools registered successfully`);
30
+ registerTools(api);
31
+ console.log(`${LOG_PREFIX} All 9 tools registered`);
39
32
  } else {
40
33
  console.warn(`${LOG_PREFIX} api.registerTool not available — tools not registered`);
41
34
  }
42
35
 
43
36
  console.log(`${LOG_PREFIX} ==========================================`);
44
- console.log(`${LOG_PREFIX} \u2713 ClawAxis plugin loaded`);
37
+ console.log(`${LOG_PREFIX} ClawAxis plugin loaded`);
45
38
  console.log(`${LOG_PREFIX} ==========================================`);
46
39
  }
47
40
 
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "id": "clawaxis",
3
3
  "name": "ClawAxis",
4
- "version": "1.0.0",
5
- "description": "Real-time bridge between OpenClaw agents and the ClawAxis iOS app. Channel plugin with governance, content management, and document/routine sync.",
4
+ "version": "2.1.0",
5
+ "description": "Real-time bridge between OpenClaw agents and the ClawAxis iOS app via the Connect daemon. Channel plugin with governance, content management, and document/routine sync.",
6
6
  "channels": ["clawaxis"],
7
7
  "configSchema": {
8
8
  "type": "object",
9
9
  "additionalProperties": false,
10
10
  "properties": {
11
- "relayUrl": {
11
+ "socketPath": {
12
12
  "type": "string",
13
- "description": "ClawAxis WebSocket relay endpoint URL",
14
- "default": "https://fqwpwypyzcbmdeajlkzt.supabase.co/functions/v1/ws-relay"
13
+ "description": "Path to the Connect Unix socket",
14
+ "default": "~/.clawaxis/connect.sock"
15
15
  },
16
- "agentToken": {
16
+ "tokenPath": {
17
17
  "type": "string",
18
- "description": "Agent authentication token (oc_tk_...)",
19
- "default": "oc_tk_placeholder_replace_me"
18
+ "description": "Path to the local auth token file",
19
+ "default": "~/.clawaxis/local-token"
20
20
  },
21
21
  "mediaDomain": {
22
22
  "type": "string",
@@ -80,8 +80,8 @@
80
80
  "required": []
81
81
  },
82
82
  "uiHints": {
83
- "relayUrl": { "label": "Relay URL", "placeholder": "https://your-relay.supabase.co/functions/v1/ws-relay" },
84
- "agentToken": { "label": "Agent Token", "sensitive": true, "placeholder": "oc_tk_..." },
83
+ "socketPath": { "label": "Socket Path", "placeholder": "~/.clawaxis/connect.sock" },
84
+ "tokenPath": { "label": "Token Path", "placeholder": "~/.clawaxis/local-token" },
85
85
  "mediaDomain": { "label": "Media Domain", "placeholder": "pub-xxxxx.r2.dev" },
86
86
  "agentName": { "label": "Agent Name" }
87
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawaxis",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "files": [
@@ -13,7 +13,7 @@
13
13
  "openclaw",
14
14
  "openclaw-plugin",
15
15
  "clawaxis",
16
- "websocket",
16
+ "unix-socket",
17
17
  "mobile",
18
18
  "channel"
19
19
  ],
@@ -29,7 +29,6 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@sinclair/typebox": "^0.34.48",
33
- "ws": "^8.19.0"
32
+ "@sinclair/typebox": "^0.34.48"
34
33
  }
35
34
  }
package/src/channel.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { LOG_PREFIX, loadConfig } from "./config.js";
2
- import { setUtilsConfig, relayFetch } from "./utils.js";
3
- import { createWebSocketService } from "./websocket.js";
2
+ import { relaySend } from "./socket.js";
3
+ import { createSocketService } from "./socket.js";
4
4
 
5
5
  export const clawaxisChannel = {
6
6
  id: "clawaxis",
@@ -26,25 +26,16 @@ export const clawaxisChannel = {
26
26
  deliveryMode: "direct",
27
27
  sendText: async () => ({ ok: true }),
28
28
  sendMedia: async (ctx: any) => {
29
- const result = await relayFetch("POST", "message", {
29
+ const sent = relaySend({
30
+ type: "message",
30
31
  content: ctx.caption || "[media]",
31
32
  message_type: "image",
32
33
  });
33
- return { ok: !!result };
34
+ return { ok: sent };
34
35
  },
35
36
  },
36
37
 
37
38
  gateway: {
38
- /**
39
- * OpenClaw calls startAccount when the channel activates.
40
- * This is the correct lifecycle hook for channel plugins —
41
- * it replaces manual api.registerService() + wsService.start().
42
- *
43
- * ctx.account.config is pre-scoped to the channel config
44
- * (relayUrl/agentToken already at top level).
45
- *
46
- * Returns a cleanup function that OpenClaw calls on shutdown.
47
- */
48
39
  startAccount: async (ctx: any) => {
49
40
  const accountId = ctx.account?.accountId || "default";
50
41
  console.log(`${LOG_PREFIX} gateway.startAccount called for account: ${accountId}`);
@@ -52,26 +43,19 @@ export const clawaxisChannel = {
52
43
  const accountConfig = ctx.account?.config || ctx.account || ctx.config;
53
44
  const config = loadConfig(accountConfig);
54
45
 
55
- if (!config) {
56
- console.warn(`${LOG_PREFIX} No valid config for account ${accountId}, skipping WebSocket start`);
57
- return () => {};
58
- }
46
+ console.log(`${LOG_PREFIX} Config loaded for account ${accountId}`);
47
+ if (config.mediaDomain) console.log(`${LOG_PREFIX} Media domain: ${config.mediaDomain}`);
48
+ if (config.agentName) console.log(`${LOG_PREFIX} Agent name: ${config.agentName}`);
59
49
 
60
- setUtilsConfig(config);
50
+ const socketService = createSocketService(config);
51
+ await socketService.start();
61
52
 
62
- console.log(`${LOG_PREFIX} \u2713 Config loaded for account ${accountId}`);
63
- if (config.mediaDomain) console.log(`${LOG_PREFIX} \u2022 Media domain: ${config.mediaDomain}`);
64
- if (config.agentName) console.log(`${LOG_PREFIX} \u2022 Agent name: ${config.agentName}`);
65
-
66
- const wsService = createWebSocketService(config);
67
- await wsService.start();
68
-
69
- console.log(`${LOG_PREFIX} \u2713 Account ${accountId} started (WebSocket active)`);
53
+ console.log(`${LOG_PREFIX} Account ${accountId} started (socket active)`);
70
54
 
71
55
  return async () => {
72
56
  console.log(`${LOG_PREFIX} Stopping account ${accountId}...`);
73
- await wsService.stop();
74
- console.log(`${LOG_PREFIX} \u2713 Account ${accountId} stopped`);
57
+ await socketService.stop();
58
+ console.log(`${LOG_PREFIX} Account ${accountId} stopped`);
75
59
  };
76
60
  },
77
61
  },
package/src/config.ts CHANGED
@@ -1,14 +1,12 @@
1
+ import path from "node:path";
2
+ import { homedir } from "node:os";
1
3
  import type { GovernanceConfig, ClawAxisConfig } from "./types.js";
2
4
 
3
- export const HEARTBEAT_MS = 60000;
4
- export const PING_MS = 30000;
5
- export const REAUTH_BUFFER_MS = 5000;
6
- export const MAX_RECONNECT_DELAY = 60000;
7
5
  export const LOG_PREFIX = "\u{1F99E}";
8
- export const PLUGIN_VERSION = "2.0.0";
6
+ export const PLUGIN_VERSION = "2.1.0";
9
7
 
10
- // Supabase anon key (public by design — RLS enforces security, not key secrecy)
11
- export const ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZxd3B3eXB5emNibWRlYWpsa3p0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA2NDUxOTIsImV4cCI6MjA4NjIyMTE5Mn0.Dc_gve3L8tMXPMqWZQYq-gxCCdPnm7Z5W6A9fg0pLis";
8
+ export const DEFAULT_SOCKET_PATH = path.join(homedir(), ".clawaxis", "connect.sock");
9
+ export const DEFAULT_TOKEN_PATH = path.join(homedir(), ".clawaxis", "local-token");
12
10
 
13
11
  export const DEFAULT_GOVERNANCE: GovernanceConfig = {
14
12
  imageTools: [],
@@ -44,35 +42,37 @@ export const DEFAULT_GOVERNANCE: GovernanceConfig = {
44
42
  /**
45
43
  * Load and merge config from OpenClaw's config hierarchy.
46
44
  *
47
- * Supports multiple config shapes:
48
- * 1. Pre-scoped config (from gateway.startAccount ctx.account.config) —
49
- * already has relayUrl/agentToken at top level
50
- * 2. channels.clawaxis.relayUrl (flat structure)
51
- * 3. channels.clawaxis.accounts.default.relayUrl (accounts structure)
52
- * 4. plugins.entries.clawaxis-plugin.config — backward compat
53
- * 5. plugins.entries.clawaxis.config — npm package name
45
+ * With the Unix socket transport, no secrets are required in plugin config.
46
+ * Auth is handled via the local token file generated by Connect.
47
+ *
48
+ * Config sources (in priority order):
49
+ * 1. Pre-scoped config (from gateway.startAccount)
50
+ * 2. channels.clawaxis (flat)
51
+ * 3. channels.clawaxis.accounts.default
52
+ * 4. plugins.entries.clawaxis-plugin.config
53
+ * 5. plugins.entries.clawaxis.config
54
54
  */
55
- export function loadConfig(apiConfig: any): ClawAxisConfig | null {
56
- let cfg: any;
55
+ export function loadConfig(apiConfig: any): ClawAxisConfig {
56
+ let cfg: any = {};
57
57
 
58
- if (apiConfig?.relayUrl && apiConfig?.agentToken) {
58
+ if (apiConfig?.socketPath || apiConfig?.governance) {
59
59
  cfg = apiConfig;
60
60
  } else {
61
61
  const channelConfig = apiConfig?.channels?.clawaxis;
62
62
 
63
- if (channelConfig?.relayUrl && channelConfig?.agentToken) {
64
- cfg = channelConfig;
65
- } else if (channelConfig?.accounts?.default) {
66
- cfg = channelConfig.accounts.default;
63
+ if (channelConfig && typeof channelConfig === "object") {
64
+ if (channelConfig.accounts?.default) {
65
+ cfg = channelConfig.accounts.default;
66
+ } else {
67
+ cfg = channelConfig;
68
+ }
67
69
  } else {
68
70
  cfg = apiConfig?.plugins?.entries?.["clawaxis-plugin"]?.config
69
- ?? apiConfig?.plugins?.entries?.["clawaxis"]?.config;
71
+ ?? apiConfig?.plugins?.entries?.["clawaxis"]?.config
72
+ ?? {};
70
73
  }
71
74
  }
72
75
 
73
- if (!cfg) return null;
74
- if (!cfg.relayUrl || !cfg.agentToken) return null;
75
-
76
76
  const governance: GovernanceConfig = {
77
77
  imageTools: cfg.governance?.imageTools || DEFAULT_GOVERNANCE.imageTools,
78
78
  contentTools: cfg.governance?.contentTools || DEFAULT_GOVERNANCE.contentTools,
@@ -83,8 +83,8 @@ export function loadConfig(apiConfig: any): ClawAxisConfig | null {
83
83
  };
84
84
 
85
85
  return {
86
- relayUrl: cfg.relayUrl,
87
- agentToken: cfg.agentToken,
86
+ socketPath: cfg.socketPath || DEFAULT_SOCKET_PATH,
87
+ tokenPath: cfg.tokenPath || DEFAULT_TOKEN_PATH,
88
88
  mediaDomain: cfg.mediaDomain || null,
89
89
  agentName: apiConfig?.agents?.defaults?.name || cfg.agentName || null,
90
90
  governance,
@@ -72,9 +72,9 @@ export function registerGovernanceHooks(api: any, config: ClawAxisConfig) {
72
72
  if (ft) {
73
73
  clearTimeout(ft);
74
74
  setFlushTimeout(null);
75
- flushLogQueue().catch((err) => {
75
+ try { flushLogQueue(); } catch (err) {
76
76
  console.error(`${LOG_PREFIX} Log flush failed:`, err);
77
- });
77
+ }
78
78
  }
79
79
 
80
80
  return context;
@@ -1,27 +1,20 @@
1
- import WS from "ws";
2
1
  import { LOG_PREFIX } from "./config.js";
3
2
  import {
4
3
  getRuntime,
5
- getWs,
6
4
  getIsAuthenticated,
7
5
  getAuthenticatedAgentId,
8
- getLastTokenCount,
9
- setLastTokenCount,
10
- addSessionTokens,
11
6
  hasProcessedMessage,
12
7
  markMessageProcessed,
13
8
  } from "./runtime.js";
14
- import { relayFetch, relayLog, getModelName, getSessionTokens } from "./utils.js";
9
+ import { relaySend, socketWrite } from "./socket.js";
10
+ import { relayLog, getModelName, getSessionTokens } from "./utils.js";
15
11
 
16
12
  export function sendStatusUpdate(messageId: string, status: string): void {
17
- const ws = getWs();
18
- if (ws?.readyState === WS.OPEN && getIsAuthenticated()) {
19
- ws.send(JSON.stringify({
20
- type: "status",
21
- message_id: messageId,
22
- status,
23
- }));
24
- }
13
+ relaySend({
14
+ type: "status",
15
+ message_id: messageId,
16
+ status,
17
+ });
25
18
  }
26
19
 
27
20
  export async function dispatchSystemMessage(
@@ -61,16 +54,13 @@ export async function dispatchSystemMessage(
61
54
  dispatcherOptions: {
62
55
  deliver: async (payload: any) => {
63
56
  if (payload.text) {
64
- const ws = getWs();
65
- if (ws?.readyState === WS.OPEN && getIsAuthenticated()) {
66
- ws.send(JSON.stringify({
67
- type: "reply",
68
- ref_id: refId,
69
- content: payload.text,
70
- model_used: getModelName(),
71
- tokens_used: 0,
72
- }));
73
- }
57
+ relaySend({
58
+ type: "reply",
59
+ ref_id: refId,
60
+ content: payload.text,
61
+ model_used: getModelName(),
62
+ tokens_used: 0,
63
+ });
74
64
  }
75
65
  },
76
66
  onError: (err: Error) => {
@@ -82,11 +72,7 @@ export async function dispatchSystemMessage(
82
72
  }
83
73
 
84
74
  export async function handleMessage(msg: any): Promise<void> {
85
- const ws = getWs();
86
-
87
- if (ws?.readyState === WS.OPEN) {
88
- ws.send(JSON.stringify({ type: "ack", id: msg.id }));
89
- }
75
+ relaySend({ type: "ack", id: msg.id });
90
76
 
91
77
  if (hasProcessedMessage(msg.id)) {
92
78
  console.log(`${LOG_PREFIX} Skipping duplicate message: ${msg.id}`);
@@ -138,7 +124,7 @@ export async function handleMessage(msg: any): Promise<void> {
138
124
  return;
139
125
  }
140
126
 
141
- const tokensBefore = getSessionTokens(runtime) || getLastTokenCount();
127
+ const tokensBefore = getSessionTokens(runtime);
142
128
 
143
129
  await dispatchReply({
144
130
  ctx: ctxPayload,
@@ -149,28 +135,15 @@ export async function handleMessage(msg: any): Promise<void> {
149
135
  const tokensAfter = getSessionTokens(runtime);
150
136
  const tokensDelta = tokensAfter > tokensBefore ? tokensAfter - tokensBefore : 0;
151
137
 
152
- if (tokensAfter > 0) setLastTokenCount(tokensAfter);
153
- if (tokensDelta > 0) addSessionTokens(tokensDelta);
154
-
155
- const currentWs = getWs();
156
- if (currentWs?.readyState === WS.OPEN && getIsAuthenticated()) {
157
- currentWs.send(JSON.stringify({
158
- type: "reply",
159
- ref_id: msg.id,
160
- content: payload.text,
161
- model_used: getModelName(),
162
- tokens_used: tokensDelta || 0,
163
- processing_time_ms: Date.now() - (msg.timestamp || new Date(msg.created_at).getTime())
164
- }));
165
- console.log(`${LOG_PREFIX} \u2713 Reply sent via WebSocket`);
166
- } else {
167
- await relayFetch("POST", "message", {
168
- content: payload.text,
169
- model_used: getModelName(),
170
- tokens_used: tokensDelta || 0
171
- });
172
- console.log(`${LOG_PREFIX} \u2713 Reply sent via HTTPS`);
173
- }
138
+ relaySend({
139
+ type: "reply",
140
+ ref_id: msg.id,
141
+ content: payload.text,
142
+ model_used: getModelName(),
143
+ tokens_used: tokensDelta || 0,
144
+ processing_time_ms: Date.now() - (msg.timestamp || new Date(msg.created_at).getTime())
145
+ });
146
+ console.log(`${LOG_PREFIX} Reply sent via socket`);
174
147
 
175
148
  sendStatusUpdate(msg.id, "complete");
176
149
  }
@@ -196,10 +169,7 @@ export async function handleMessage(msg: any): Promise<void> {
196
169
  }
197
170
 
198
171
  export async function handleCommand(msg: any): Promise<void> {
199
- const ws = getWs();
200
- if (ws?.readyState === WS.OPEN) {
201
- ws.send(JSON.stringify({ type: "ack", id: msg.id }));
202
- }
172
+ relaySend({ type: "ack", id: msg.id });
203
173
 
204
174
  console.log(`${LOG_PREFIX} Received command: ${msg.action}`);
205
175
 
package/src/runtime.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Socket } from "node:net";
1
2
  import type { MemoryCacheEntry, LogEntry } from "./types.js";
2
3
 
3
4
  let pluginRuntime: any = null;
@@ -5,54 +6,24 @@ let pluginRuntime: any = null;
5
6
  export function getRuntime(): any { return pluginRuntime; }
6
7
  export function setRuntime(r: any) { pluginRuntime = r; }
7
8
 
8
- // WebSocket connection state
9
- let ws: any = null;
10
- let reconnectAttempts = 0;
11
- let isAuthenticated = false;
9
+ // Unix socket connection state
10
+ let socket: Socket | null = null;
11
+ let authenticated = false;
12
12
  let authenticatedAgentId: string | null = null;
13
- let pingInterval: NodeJS.Timeout | null = null;
14
- let heartbeatInterval: NodeJS.Timeout | null = null;
15
- let running = true;
13
+ let running = false;
16
14
 
17
- export function getWs() { return ws; }
18
- export function setWs(socket: any) { ws = socket; }
15
+ export function getSocket() { return socket; }
16
+ export function setSocket(s: Socket | null) { socket = s; }
19
17
 
20
- export function getReconnectAttempts() { return reconnectAttempts; }
21
- export function setReconnectAttempts(n: number) { reconnectAttempts = n; }
22
- export function incrementReconnectAttempts() { reconnectAttempts++; }
23
-
24
- export function getIsAuthenticated() { return isAuthenticated; }
25
- export function setIsAuthenticated(v: boolean) { isAuthenticated = v; }
18
+ export function getIsAuthenticated() { return authenticated; }
19
+ export function setIsAuthenticated(v: boolean) { authenticated = v; }
26
20
 
27
21
  export function getAuthenticatedAgentId() { return authenticatedAgentId; }
28
22
  export function setAuthenticatedAgentId(id: string | null) { authenticatedAgentId = id; }
29
23
 
30
- export function getPingInterval() { return pingInterval; }
31
- export function setPingInterval(t: NodeJS.Timeout | null) { pingInterval = t; }
32
-
33
- export function getHeartbeatInterval() { return heartbeatInterval; }
34
- export function setHeartbeatInterval(t: NodeJS.Timeout | null) { heartbeatInterval = t; }
35
-
36
24
  export function isRunning() { return running; }
37
25
  export function setRunning(v: boolean) { running = v; }
38
26
 
39
- // Token tracking
40
- let lastTokenCount = 0;
41
- let sessionTokensAccumulated = 0;
42
- let sessionCostAccumulated = 0;
43
- let lastHeartbeatTokens = 0;
44
-
45
- export function getLastTokenCount() { return lastTokenCount; }
46
- export function setLastTokenCount(n: number) { lastTokenCount = n; }
47
-
48
- export function getSessionTokensAccumulated() { return sessionTokensAccumulated; }
49
- export function addSessionTokens(delta: number) { sessionTokensAccumulated += delta; }
50
-
51
- export function getSessionCostAccumulated() { return sessionCostAccumulated; }
52
-
53
- export function getLastHeartbeatTokens() { return lastHeartbeatTokens; }
54
- export function setLastHeartbeatTokens(n: number) { lastHeartbeatTokens = n; }
55
-
56
27
  // Message deduplication with lazy cleanup
57
28
  const processedMessageIds = new Set<string>();
58
29
  const MESSAGE_ID_HARD_CAP = 2000;