@gholl-studio/pier-connector 0.3.18 → 0.3.20

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gholl-studio/pier-connector",
3
3
  "author": "gholl",
4
- "version": "0.3.18",
4
+ "version": "0.3.20",
5
5
  "description": "OpenClaw plugin that connects to the Pier job marketplace. Automatically fetches, executes, and reports distributed tasks for rewards.",
6
6
  "type": "module",
7
7
  "main": "src/index.ts",
package/src/inbound.ts CHANGED
@@ -3,35 +3,25 @@
3
3
  * @description Manages inbound message processing, agent routing resolution, and session context propagation.
4
4
  */
5
5
 
6
- import type { PierPluginApi, InboundMessage } from './types.js';
6
+ import type { InboundMessage } from './types.js';
7
7
  import { truncate } from './job-handler.js';
8
8
 
9
9
  export async function handleInbound(
10
- api: PierPluginApi,
10
+ runtime: any, // Using context-aware runtime from gateway
11
11
  inbound: InboundMessage,
12
12
  jobId: string,
13
- robot: any, // Avoiding circular dependency with type PierRobot
14
- pierChannel: any
13
+ robot: any,
14
+ pierPlugin: any
15
15
  ) {
16
- const logger = api.logger;
17
- if (!api.runtime?.channel?.reply) {
18
- logger.error(`[pier-connector][${robot.accountId}] SDK Error: api.runtime.channel.reply is not available.`);
16
+ const logger = runtime.log || console;
17
+
18
+ if (!runtime.reply) {
19
+ console.error(`[pier-connector][${robot.accountId}] SDK Error: runtime.reply is not available.`);
19
20
  return;
20
21
  }
21
22
 
22
- // 0. Log Online/Configured Agents
23
- const agentsList = (api.config as any)?.agents?.list || [];
24
- const agentIds = Array.isArray(agentsList) ? agentsList.map((a: any) => a.id) : Object.keys(agentsList);
25
- logger.info(`[pier-connector][${robot.accountId}] Available agents in OpenClaw: ${JSON.stringify(agentIds)}`);
26
-
27
- // 1. Resolve Account-Scoped Configuration (Flat Structure for SDK)
28
- /**
29
- * ★ Multi-Account Configuration Isolation:
30
- * SDK helpers like resolveAgentRoute expect the channel config at the top level
31
- * of cfg.channels.<channelName>. Replacing it with the robot's merged config
32
- * ensures the SDK sees the correct agentId/dmPolicy for this specific account.
33
- */
34
- const rootConfig = api.config || {};
23
+ // 1. Resolve Account-Scoped Configuration
24
+ const rootConfig = runtime.cfg || {};
35
25
  const accountScopedCfg = {
36
26
  ...rootConfig,
37
27
  channels: {
@@ -40,8 +30,8 @@ export async function handleInbound(
40
30
  }
41
31
  };
42
32
 
43
- // 2. Resolve Agent Route via SDK with Scoped Config
44
- const route = api.runtime.channel.routing.resolveAgentRoute({
33
+ // 2. Resolve Agent Route
34
+ const route = runtime.routing.resolveAgentRoute({
45
35
  cfg: accountScopedCfg,
46
36
  channel: 'pier',
47
37
  accountId: robot.accountId,
@@ -53,30 +43,6 @@ export async function handleInbound(
53
43
 
54
44
  logger.info(`[pier-connector] Routing account '${robot.accountId}' -> agent '${finalAgentId}' (Source: ${routingSource})`);
55
45
 
56
- // Debug: Check Agent Identity & Detailed Config
57
- try {
58
- const identity = (api.runtime as any).agent.resolveAgentIdentity(accountScopedCfg, finalAgentId);
59
- logger.info(`[pier-connector:debug] Identity resolution [0.3.18] for ${finalAgentId}: ${JSON.stringify(identity)}`);
60
-
61
- // Deep Config Inspection
62
- const agentsList = (accountScopedCfg as any)?.agents?.list || [];
63
- const targetedAgent = agentsList.find((a: any) => a.id === finalAgentId);
64
- if (targetedAgent) {
65
- logger.info(`[pier-connector:debug] FULL AGENT CONFIG for ${finalAgentId}: ${JSON.stringify({
66
- id: targetedAgent.id,
67
- name: targetedAgent.name,
68
- agentDir: targetedAgent.agentDir,
69
- workspace: targetedAgent.workspace,
70
- runtime: targetedAgent.runtime,
71
- model: targetedAgent.model
72
- })}`);
73
- } else {
74
- logger.warn(`[pier-connector:debug] Agent ${finalAgentId} NOT FOUND in agents.list of scoped config!`);
75
- }
76
- } catch (err: any) {
77
- logger.warn(`[pier-connector:debug] Identity resolution crash for ${finalAgentId}: ${err.message}`);
78
- }
79
-
80
46
  const dynamicSessionKey = `pier-job-${jobId}`;
81
47
  const metadata = robot.activeNodeJobs.get(jobId);
82
48
  let injectedPrompt = "";
@@ -105,19 +71,18 @@ export async function handleInbound(
105
71
  ].join('\n');
106
72
  }
107
73
 
108
- logger.info(`[pier-connector:trace] Finalized inbound context for job ${jobId}. Target Agent: ${finalAgentId}, Session: ${dynamicSessionKey}`);
109
-
110
- const ctxPayload = api.runtime.channel.reply.finalizeInboundContext({
74
+ // 3. Finalize Context
75
+ const ctxPayload = runtime.reply.finalizeInboundContext({
111
76
  Body: inbound.body,
112
77
  BodyForAgent: inbound.body,
113
78
  RawBody: inbound.body,
114
- From: inbound.senderId, // Fix: senderId already includes "pier:" prefix
79
+ From: inbound.senderId,
115
80
  To: `chat:${jobId}`,
116
81
  SessionKey: dynamicSessionKey,
117
82
  AccountId: robot.accountId,
118
83
  ChatType: 'direct',
119
84
  SenderId: inbound.senderId,
120
- SenderName: inbound.senderId, // Use senderId as name if unknown
85
+ SenderName: inbound.senderId,
121
86
  Provider: 'pier',
122
87
  Surface: 'pier',
123
88
  OriginatingChannel: 'pier',
@@ -130,27 +95,25 @@ export async function handleInbound(
130
95
  Metadata: {
131
96
  ...metadata,
132
97
  accountId: robot.accountId,
133
- agentId: finalAgentId, // Explicitly tell SDK which agent we want
98
+ agentId: finalAgentId,
134
99
  pierJobId: jobId,
135
100
  routingSource: routingSource
136
101
  }
137
102
  });
138
103
 
139
- logger.info(`[pier-connector:trace] FULL DISPATCH CONTEXT for job ${jobId}: ${JSON.stringify(ctxPayload)}`);
140
-
141
- const { dispatcher, markDispatchIdle } = api.runtime.channel.reply.createReplyDispatcherWithTyping({
104
+ // 4. Create Dispatcher
105
+ const { dispatcher, markDispatchIdle } = runtime.reply.createReplyDispatcherWithTyping({
142
106
  deliver: async (payload: any, info: any) => {
143
107
  const currentMeta = robot.activeNodeJobs.get(jobId);
144
- const rawResponder = payload.agentId || payload.btw?.agentId || payload.channelData?.agentId;
145
- const resAgent = rawResponder || finalAgentId;
108
+ const resAgent = payload.agentId || finalAgentId;
146
109
 
147
- logger.info(`[pier-connector:trace] Outbound delivery for ${jobId}. Mode: ${info?.kind}. Responder: ${resAgent} (Raw: ${rawResponder || 'none'}).`);
148
- logger.info(`[pier-connector:debug] FULL PAYLOAD: ${JSON.stringify(payload)}`);
110
+ logger.info(`[pier-connector:trace] Outbound delivery for ${jobId}. Responder: ${resAgent}.`);
149
111
 
150
112
  if (payload.text && payload.text.length > 0) {
151
- await pierChannel.outbound.sendText({
113
+ await pierPlugin.outbound.sendText({
152
114
  text: payload.text,
153
115
  to: `pier:${jobId}`,
116
+ accountId: robot.accountId,
154
117
  metadata: {
155
118
  ...currentMeta,
156
119
  accountId: robot.accountId,
@@ -162,11 +125,11 @@ export async function handleInbound(
162
125
  }
163
126
  });
164
127
 
165
- if (api.runtime.channel.session?.recordSessionMetaFromInbound) {
128
+ // 5. Session Recording
129
+ if (runtime.session?.recordSessionMetaFromInbound) {
166
130
  try {
167
- const storePath = api.runtime.channel.session.resolveStorePath(dynamicSessionKey);
168
- logger.info(`[pier-connector:trace] Recording session metadata at ${storePath}`);
169
- await api.runtime.channel.session.recordSessionMetaFromInbound({
131
+ const storePath = runtime.session.resolveStorePath(dynamicSessionKey);
132
+ await runtime.session.recordSessionMetaFromInbound({
170
133
  storePath, sessionKey: dynamicSessionKey, ctx: ctxPayload
171
134
  });
172
135
  } catch (err: any) {
@@ -174,22 +137,22 @@ export async function handleInbound(
174
137
  }
175
138
  }
176
139
 
140
+ // 6. Dispatch
177
141
  try {
178
- logger.info(`[pier-connector:trace] Dispatching reply to agent ${finalAgentId}...`);
179
- await api.runtime.channel.reply.dispatchReplyFromConfig({
142
+ await runtime.reply.dispatchReplyFromConfig({
180
143
  ctx: ctxPayload,
181
144
  cfg: accountScopedCfg,
182
145
  dispatcher,
183
146
  replyOptions: {
184
147
  onModelSelected: (mCtx: any) => {
185
- logger.info(`[pier-connector:debug] Model selected for ${jobId}: ${mCtx.provider}/${mCtx.model} (Think: ${mCtx.thinkLevel})`);
148
+ logger.info(`[pier-connector:debug] Model selected for ${jobId}: ${mCtx.provider}/${mCtx.model}`);
186
149
  }
187
150
  }
188
- } as any);
189
- logger.info(`[pier-connector:trace] dispatchReplyFromConfig completed for job ${jobId}`);
151
+ });
190
152
  } catch (err: any) {
191
153
  logger.error(`[pier-connector] ✖ Dispatch error for job ${jobId}: ${err.message}`);
192
154
  } finally {
193
- markDispatchIdle();
155
+ markDispatchIdle?.();
194
156
  }
195
157
  }
158
+
package/src/index.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * @file index.ts
3
- * @description Main entry point for the Pier Connector plugin. Orchestrates robots, tools, and channel registration.
3
+ * @description Main entry point for the Pier Connector plugin.
4
+ * Implements the standard OpenClaw ChannelPlugin interface for native multi-account support.
4
5
  */
5
6
 
6
7
  import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
8
+ import type { ChannelPlugin, OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk';
7
9
  import { protocol } from '@gholl-studio/pier-sdk';
8
10
  const { createRequestPayload } = protocol;
9
11
  import { DEFAULTS } from './config.js';
@@ -12,99 +14,191 @@ import { handleInbound } from './inbound.js';
12
14
  import { registerCli } from './cli.js';
13
15
  import type { PierAccountConfig, PierPluginApi } from './types.js';
14
16
 
15
- const register = (api: PierPluginApi) => {
16
- const logger = api.logger;
17
- const instances = new Map<string, PierRobot>();
18
- const globalStats = { received: 0, completed: 0, failed: 0 };
17
+ // Global instances map to track active robots by account ID
18
+ const instances = new Map<string, PierRobot>();
19
19
 
20
- function mergedCfgFrom(legacy: any, account: any): PierAccountConfig {
21
- const merged = { ...legacy, ...account };
22
- return {
23
- accountId: account.accountId || 'default',
24
- pierApiUrl: merged.pierApiUrl || DEFAULTS.PIER_API_URL,
25
- nodeId: merged.nodeId || DEFAULTS.NODE_ID,
26
- secretKey: merged.secretKey || DEFAULTS.SECRET_KEY,
27
- privateKey: merged.privateKey || process.env.PIER_PRIVATE_KEY || DEFAULTS.PRIVATE_KEY,
28
- natsUrl: merged.natsUrl || DEFAULTS.NATS_URL,
29
- subject: merged.subject || DEFAULTS.SUBJECT,
30
- publishSubject: merged.publishSubject || DEFAULTS.PUBLISH_SUBJECT,
31
- queueGroup: merged.queueGroup || DEFAULTS.QUEUE_GROUP,
32
- agentId: merged.agentId || DEFAULTS.AGENT_ID,
33
- walletAddress: merged.walletAddress || DEFAULTS.WALLET_ADDRESS,
34
- capabilities: merged.capabilities || ['translation', 'code-execution', 'reasoning', 'vision'],
35
- };
36
- }
37
-
38
- function resolveConfigs(): PierAccountConfig[] {
39
- const globalAccounts = (api.config as any)?.channels?.['pier']?.accounts || {};
40
- const pluginAccounts = (api.pluginConfig as any)?.accounts || {};
41
- const rawAccounts = { ...globalAccounts, ...pluginAccounts };
42
- const legacyCfg = api.pluginConfig || {};
20
+ /**
21
+ * Merges global/legacy config with account-specific overrides.
22
+ */
23
+ function mergedCfgFrom(legacy: any, account: any): PierAccountConfig {
24
+ const merged = { ...legacy, ...account };
25
+ return {
26
+ accountId: account.accountId || 'default',
27
+ pierApiUrl: merged.pierApiUrl || DEFAULTS.PIER_API_URL,
28
+ nodeId: merged.nodeId || DEFAULTS.NODE_ID,
29
+ secretKey: merged.secretKey || DEFAULTS.SECRET_KEY,
30
+ privateKey: merged.privateKey || process.env.PIER_PRIVATE_KEY || DEFAULTS.PRIVATE_KEY,
31
+ natsUrl: merged.natsUrl || DEFAULTS.NATS_URL,
32
+ subject: merged.subject || DEFAULTS.SUBJECT,
33
+ publishSubject: merged.publishSubject || DEFAULTS.PUBLISH_SUBJECT,
34
+ queueGroup: merged.queueGroup || DEFAULTS.QUEUE_GROUP,
35
+ agentId: merged.agentId || DEFAULTS.AGENT_ID,
36
+ walletAddress: merged.walletAddress || DEFAULTS.WALLET_ADDRESS,
37
+ capabilities: merged.capabilities || ['translation', 'code-execution', 'reasoning', 'vision'],
38
+ };
39
+ }
43
40
 
44
- if (Object.keys(rawAccounts).length === 0) {
45
- return [mergedCfgFrom(legacyCfg, { accountId: 'default' })];
46
- }
41
+ /**
42
+ * Resolves all configured accounts for the Pier channel.
43
+ */
44
+ function getAccountConfigs(cfg: OpenClawConfig, pluginConfig: any): PierAccountConfig[] {
45
+ const globalAccounts = (cfg as any)?.channels?.['pier']?.accounts || {};
46
+ const pluginAccounts = pluginConfig?.accounts || {};
47
+ const rawAccounts = { ...globalAccounts, ...pluginAccounts };
48
+ const legacyCfg = pluginConfig || {};
47
49
 
48
- return Object.entries(rawAccounts).map(([id, account]: [string, any]) =>
49
- mergedCfgFrom(legacyCfg, { ...account, accountId: id })
50
- );
50
+ if (Object.keys(rawAccounts).length === 0) {
51
+ return [mergedCfgFrom(legacyCfg, { accountId: 'default' })];
51
52
  }
52
53
 
53
- const pierChannel = {
54
+ return Object.entries(rawAccounts).map(([id, account]: [string, any]) =>
55
+ mergedCfgFrom(legacyCfg, { ...account, accountId: id })
56
+ );
57
+ }
58
+
59
+ const pierPlugin: ChannelPlugin<PierAccountConfig> = {
60
+ id: 'pier',
61
+
62
+ meta: {
54
63
  id: 'pier',
55
- meta: {
56
- id: 'pier',
57
- label: 'Pier',
58
- selectionLabel: 'Pier (NATS Job Marketplace)',
59
- blurb: 'Connect to the Pier distributed job marketplace.'
64
+ label: 'Pier',
65
+ selectionLabel: 'Pier (NATS Job Marketplace)',
66
+ docsPath: '/channels/pier',
67
+ blurb: 'Connect to the Pier distributed job marketplace.',
68
+ },
69
+
70
+ capabilities: {
71
+ chatTypes: ['direct'],
72
+ media: false,
73
+ reactions: false,
74
+ threads: false,
75
+ nativeCommands: true,
76
+ blockStreaming: true,
77
+ },
78
+
79
+ // -------------------------------------------------------------------------
80
+ // Config Adapter
81
+ // -------------------------------------------------------------------------
82
+ config: {
83
+ listAccountIds: (cfg) => {
84
+ const accounts = (cfg as any)?.channels?.['pier']?.accounts || {};
85
+ const ids = Object.keys(accounts);
86
+ return ids.length > 0 ? ids : ['default'];
60
87
  },
61
- outbound: {
62
- sendText: async (params: any) => {
63
- const robot = instances.get(params.metadata?.accountId || 'default') || instances.values().next().value;
64
- if (!robot || !robot.js) return;
65
-
66
- const jobId = params.metadata?.pierJobId || params.to.replace(/^pier:/, '');
67
- const subject = `chat.${jobId}`;
68
-
69
- const payload = {
70
- id: crypto.randomUUID ? crypto.randomUUID() : (Math.random().toString(36).substring(2)),
71
- job_id: jobId,
72
- sender_id: robot.config.nodeId,
73
- sender_name: robot.accountId,
74
- sender_type: 'node',
75
- content: params.text,
76
- created_at: new Date().toISOString(),
77
- auth_token: robot.config.secretKey
78
- };
88
+ resolveAccount: (cfg, accountId) => {
89
+ const accounts = (cfg as any)?.channels?.['pier']?.accounts || {};
90
+ const account = accounts[accountId || 'default'] || {};
91
+ return mergedCfgFrom((cfg as any)?.channels?.['pier'] || {}, { ...account, accountId: accountId || 'default' });
92
+ },
93
+ defaultAccountId: () => 'default'
94
+ },
95
+
96
+ // -------------------------------------------------------------------------
97
+ // Pairing Adapter
98
+ // -------------------------------------------------------------------------
99
+ pairing: {
100
+ idLabel: 'pierJobId',
101
+ normalizeAllowEntry: (entry) => entry.replace(/^(pier|job):/i, ''),
102
+ notifyApproval: async ({ cfg, id, accountId }) => {
103
+ // Signal received when 'openclaw pairing approve pier <id>' is run.
104
+ // Useful if we want to trigger specific onboarding or status updates.
105
+ console.log(`[pier-connector] Pairing approved for Job ${id} on account ${accountId}`);
106
+ }
107
+ },
79
108
 
80
- await robot.js.publish(subject, new TextEncoder().encode(JSON.stringify(payload)));
109
+ // -------------------------------------------------------------------------
110
+ // Outbound Adapter
111
+ // -------------------------------------------------------------------------
112
+ outbound: {
113
+ deliveryMode: 'direct',
114
+ sendText: async (ctx) => {
115
+ const robot = instances.get(ctx.accountId || 'default') || instances.values().next().value;
116
+ if (!robot || !robot.js) {
117
+ throw new Error('Robot not connected');
81
118
  }
119
+
120
+ const jobId = ctx.to.replace(/^pier:/, '');
121
+ const subject = `chat.${jobId}`;
122
+
123
+ const payload = {
124
+ id: (globalThis.crypto as any).randomUUID ? (globalThis.crypto as any).randomUUID() : (Math.random().toString(36).substring(2)),
125
+ job_id: jobId,
126
+ sender_id: robot.config.nodeId,
127
+ sender_name: robot.accountId,
128
+ sender_type: 'node',
129
+ content: ctx.text,
130
+ created_at: new Date().toISOString(),
131
+ auth_token: robot.config.secretKey
132
+ };
133
+
134
+ await robot.js.publish(subject, new TextEncoder().encode(JSON.stringify(payload)));
135
+
136
+ return {
137
+ channel: 'pier',
138
+ messageId: payload.id,
139
+ conversationId: jobId
140
+ };
82
141
  }
83
- };
142
+ },
84
143
 
85
- api.registerChannel(pierChannel as any);
144
+ // -------------------------------------------------------------------------
145
+ // Gateway Adapter
146
+ // -------------------------------------------------------------------------
147
+ gateway: {
148
+ startAccount: async (ctx) => {
149
+ const config = ctx.account;
150
+ const robot = new PierRobot(config, ctx.runtime, async (inbound, jobId) => {
151
+ // Pass the context-aware runtime and the plugin instance
152
+ await handleInbound(ctx.runtime, inbound, jobId, robot, pierPlugin);
153
+ });
154
+ instances.set(ctx.accountId, robot);
155
+
156
+ try {
157
+ await robot.start();
158
+ ctx.setStatus({
159
+ ...ctx.getStatus(),
160
+ running: true,
161
+ lastStartAt: Date.now()
162
+ } as any);
86
163
 
87
- api.registerService({
88
- id: 'pier-connector',
89
- start: async () => {
90
- const configs = resolveConfigs();
91
- for (const config of configs) {
92
- const robot = new PierRobot(config, api, async (inbound, jobId) => {
93
- await handleInbound(api, inbound, jobId, robot, pierChannel);
94
- globalStats.received++;
164
+ // Keep the account active until the abort signal is received
165
+ await new Promise<void>((resolve) => {
166
+ ctx.abortSignal.addEventListener('abort', () => {
167
+ resolve();
168
+ }, { once: true });
95
169
  });
96
- instances.set(config.accountId, robot);
97
- await robot.start();
170
+ } catch (err: any) {
171
+ ctx.setStatus({
172
+ ...ctx.getStatus(),
173
+ running: false,
174
+ lastError: err.message
175
+ } as any);
176
+ throw err; // Re-throw to signal failure to Gateway
98
177
  }
99
178
  },
100
- stop: async () => {
101
- for (const robot of instances.values()) {
179
+ stopAccount: async (ctx) => {
180
+ const robot = instances.get(ctx.accountId);
181
+ if (robot) {
102
182
  await robot.stop();
183
+ instances.delete(ctx.accountId);
103
184
  }
104
- instances.clear();
185
+ ctx.setStatus({
186
+ ...ctx.getStatus(),
187
+ running: false,
188
+ lastStopAt: Date.now()
189
+ } as any);
105
190
  }
106
- });
191
+ }
192
+ };
107
193
 
194
+ const register = (api: PierPluginApi) => {
195
+ const logger = api.logger;
196
+ const globalStats = { received: 0, completed: 0, failed: 0 };
197
+
198
+ // Register our new ChannelPlugin
199
+ api.registerChannel({ plugin: pierPlugin as any });
200
+
201
+ // Tools and CLI registration
108
202
  api.registerTool({
109
203
  name: 'pier_publish',
110
204
  label: 'Publish to Pier',
@@ -193,6 +287,7 @@ const register = (api: PierPluginApi) => {
193
287
  { optional: true }
194
288
  );
195
289
 
290
+ // Helper to register marketplace action tools
196
291
  const registerSystemActionTool = (name: string, label: string, description: string, action: string, extraParams: any, userRole = 'node') => {
197
292
  api.registerTool({
198
293
  name,
@@ -215,7 +310,6 @@ const register = (api: PierPluginApi) => {
215
310
  }
216
311
 
217
312
  try {
218
- const subject = `chat.${params.jobId}`;
219
313
  const { jobId: j, accountId: _, ...p } = params;
220
314
 
221
315
  if (!robot.js) {
@@ -234,7 +328,7 @@ const register = (api: PierPluginApi) => {
234
328
  action: action
235
329
  };
236
330
 
237
- await robot.js.publish(subject, new TextEncoder().encode(JSON.stringify(msgData)));
331
+ await robot.js.publish(`chat.${params.jobId}`, new TextEncoder().encode(JSON.stringify(msgData)));
238
332
  return { content: [{ type: 'text', text: `${action} executed successfully` }], details: {} };
239
333
  } catch (err: any) {
240
334
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], details: {} };
@@ -279,7 +373,7 @@ const register = (api: PierPluginApi) => {
279
373
  }
280
374
  }, { optional: true });
281
375
 
282
- // Register simple status command
376
+ // Status Command
283
377
  api.registerCommand({
284
378
  name: 'pier',
285
379
  description: 'Show Pier status',
@@ -302,3 +396,4 @@ export default definePluginEntry({
302
396
  description: 'Connects OpenClaw to the Pier job marketplace.',
303
397
  register
304
398
  });
399
+
package/src/robot.ts CHANGED
@@ -25,20 +25,20 @@ export class PierRobot {
25
25
  public connectionStatus: 'disconnected' | 'connecting' | 'connected' | 'error' = 'disconnected';
26
26
  public stats = { received: 0, completed: 0, failed: 0 };
27
27
 
28
- private api: PierPluginApi;
28
+ private runtime: any;
29
29
  private logger: any;
30
30
  private heartbeatTimer: NodeJS.Timeout | null = null;
31
31
  private onInbound: (inbound: InboundMessage, jobId: string) => Promise<void>;
32
32
 
33
33
  constructor(
34
34
  config: PierAccountConfig,
35
- api: PierPluginApi,
35
+ runtime: any,
36
36
  onInbound: (inbound: InboundMessage, jobId: string) => Promise<void>
37
37
  ) {
38
38
  this.config = config;
39
39
  this.accountId = config.accountId;
40
- this.api = api;
41
- this.logger = api.logger;
40
+ this.runtime = runtime;
41
+ this.logger = runtime.log || console;
42
42
  this.onInbound = onInbound;
43
43
  this.client = new PierClient({
44
44
  apiUrl: config.pierApiUrl,
@@ -264,7 +264,7 @@ export class PierRobot {
264
264
 
265
265
  async autoRegister() {
266
266
  if (!this.config.privateKey) return;
267
- const hostName = `${(this.api as any).getRuntimeInfo?.()?.hostname ?? 'Auto'}-${this.accountId}`;
267
+ const hostName = `${this.runtime?.hostname ?? 'Auto'}-${this.accountId}`;
268
268
  const { nodeId, secretKey } = await this.client.autoRegister(this.config.privateKey, hostName);
269
269
  this.config.nodeId = nodeId;
270
270
  this.config.secretKey = secretKey;
@@ -292,16 +292,24 @@ export class PierRobot {
292
292
  await this.setupMarketplaceConsumer(streamName, this.config.subject, durableNameMarket);
293
293
  await this.setupMarketplaceConsumer(streamName, `jobs.node.${this.config.nodeId}`, durableNameDirect);
294
294
 
295
+ if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
295
296
  this.heartbeatTimer = setInterval(() => this.heartbeat(), 60000);
296
297
  } catch (err: any) {
297
298
  this.connectionStatus = 'error';
298
299
  this.logger.error(`[pier-connector][${this.accountId}] Start failed: ${err.message}`);
300
+ throw err; // Re-throw to signal failure to Gateway
299
301
  }
300
302
  }
301
303
 
302
304
  async stop() {
303
- if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
304
- if (this.nc) await this.client.drainNats();
305
+ if (this.heartbeatTimer) {
306
+ clearInterval(this.heartbeatTimer);
307
+ this.heartbeatTimer = null;
308
+ }
309
+ if (this.nc) {
310
+ await this.client.drainNats();
311
+ this.nc = null;
312
+ }
305
313
  this.connectionStatus = 'disconnected';
306
314
  }
307
315
  }