@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 +1 -1
- package/src/inbound.ts +33 -70
- package/src/index.ts +173 -78
- package/src/robot.ts +15 -7
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.
|
|
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 {
|
|
6
|
+
import type { InboundMessage } from './types.js';
|
|
7
7
|
import { truncate } from './job-handler.js';
|
|
8
8
|
|
|
9
9
|
export async function handleInbound(
|
|
10
|
-
|
|
10
|
+
runtime: any, // Using context-aware runtime from gateway
|
|
11
11
|
inbound: InboundMessage,
|
|
12
12
|
jobId: string,
|
|
13
|
-
robot: any,
|
|
14
|
-
|
|
13
|
+
robot: any,
|
|
14
|
+
pierPlugin: any
|
|
15
15
|
) {
|
|
16
|
-
const logger =
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
//
|
|
23
|
-
const
|
|
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
|
|
44
|
-
const route =
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
98
|
+
agentId: finalAgentId,
|
|
134
99
|
pierJobId: jobId,
|
|
135
100
|
routingSource: routingSource
|
|
136
101
|
}
|
|
137
102
|
});
|
|
138
103
|
|
|
139
|
-
|
|
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
|
|
145
|
-
const resAgent = rawResponder || finalAgentId;
|
|
108
|
+
const resAgent = payload.agentId || finalAgentId;
|
|
146
109
|
|
|
147
|
-
logger.info(`[pier-connector:trace] Outbound delivery for ${jobId}.
|
|
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
|
|
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
|
-
|
|
128
|
+
// 5. Session Recording
|
|
129
|
+
if (runtime.session?.recordSessionMetaFromInbound) {
|
|
166
130
|
try {
|
|
167
|
-
const storePath =
|
|
168
|
-
|
|
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
|
-
|
|
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}
|
|
148
|
+
logger.info(`[pier-connector:debug] Model selected for ${jobId}: ${mCtx.provider}/${mCtx.model}`);
|
|
186
149
|
}
|
|
187
150
|
}
|
|
188
|
-
}
|
|
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.
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
50
|
+
if (Object.keys(rawAccounts).length === 0) {
|
|
51
|
+
return [mergedCfgFrom(legacyCfg, { accountId: 'default' })];
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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.
|
|
41
|
-
this.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 = `${
|
|
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)
|
|
304
|
-
|
|
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
|
}
|