@gholl-studio/pier-connector 0.2.7 → 0.2.10
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/index.js +77 -5
- package/src/protocol.js +9 -6
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gholl-studio/pier-connector",
|
|
3
3
|
"author": "gholl",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.10",
|
|
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.js",
|
package/src/index.js
CHANGED
|
@@ -46,6 +46,12 @@ export default function register(api) {
|
|
|
46
46
|
const jobStopTimeouts = new Map();
|
|
47
47
|
const activeNodeJobs = new Map();
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Anti-Interference Flag (BUG-34)
|
|
51
|
+
* When true, the robot will ignore new marketplace jobs.
|
|
52
|
+
*/
|
|
53
|
+
let isBusy = false;
|
|
54
|
+
|
|
49
55
|
let jobsReceived = 0;
|
|
50
56
|
let jobsCompleted = 0;
|
|
51
57
|
let jobsFailed = 0;
|
|
@@ -127,6 +133,39 @@ export default function register(api) {
|
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Atomically claims a job from the backend before starting execution.
|
|
138
|
+
* Ensures "one-job-at-a-time" exclusivity.
|
|
139
|
+
*/
|
|
140
|
+
async function claimJob(jobId) {
|
|
141
|
+
const config = getActiveConfig();
|
|
142
|
+
try {
|
|
143
|
+
const resp = await fetch(`${config.pierApiUrl}/jobs/${jobId}/claim`, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: {
|
|
146
|
+
'Content-Type': 'application/json',
|
|
147
|
+
'Authorization': `Bearer ${config.secretKey}`,
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (!resp.ok) {
|
|
152
|
+
if (resp.status === 409) {
|
|
153
|
+
logger.warn(`[pier-connector] 🚫 Job ${jobId} was already claimed by someone else.`);
|
|
154
|
+
} else {
|
|
155
|
+
const errData = await resp.json().catch(() => ({}));
|
|
156
|
+
logger.error(`[pier-connector] ✖ Failed to claim job ${jobId}: ${errData.error || resp.statusText}`);
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
logger.info(`[pier-connector] ✅ Successfully claimed job ${jobId}`);
|
|
162
|
+
return true;
|
|
163
|
+
} catch (err) {
|
|
164
|
+
logger.error(`[pier-connector] ✖ Network error claiming job ${jobId}: ${err.message}`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
130
169
|
/**
|
|
131
170
|
* Routes an incoming message from Pier to the OpenClaw agent.
|
|
132
171
|
* Replaces the non-existent api.runtime.sendIncoming.
|
|
@@ -764,6 +803,13 @@ export default function register(api) {
|
|
|
764
803
|
return;
|
|
765
804
|
}
|
|
766
805
|
|
|
806
|
+
// Anti-Interference: If busy, ignore new marketplace jobs (BUG-34)
|
|
807
|
+
if (isBusy) {
|
|
808
|
+
// NAK so others in the queue group can take it
|
|
809
|
+
msg.nak();
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
767
813
|
// V1.1 FEATURE: Task Poisoning / Poaching Protection
|
|
768
814
|
if (payload.assigned_node_id && payload.assigned_node_id !== config.nodeId) {
|
|
769
815
|
// Not for us, nak it so others can take it
|
|
@@ -771,17 +817,30 @@ export default function register(api) {
|
|
|
771
817
|
return;
|
|
772
818
|
}
|
|
773
819
|
|
|
774
|
-
//
|
|
820
|
+
// For Marketplace jobs (those without assigned_node_id, or even those with it),
|
|
821
|
+
// we must ATOMICALLY CLAIM it from the backend before setting isBusy.
|
|
822
|
+
const jobIdToClaim = payload.id;
|
|
823
|
+
if (jobIdToClaim) {
|
|
824
|
+
const success = await claimJob(jobIdToClaim);
|
|
825
|
+
if (!success) {
|
|
826
|
+
// Already taken or error, NAK it just in case someone else can try
|
|
827
|
+
msg.nak();
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Claimed successfully! Set busy state
|
|
833
|
+
isBusy = true;
|
|
775
834
|
msg.ack();
|
|
776
835
|
|
|
777
836
|
jobsReceived++;
|
|
778
837
|
const parsed = parseJob(msg, logger);
|
|
779
838
|
if (!parsed.ok) {
|
|
780
839
|
jobsFailed++;
|
|
840
|
+
isBusy = false; // Reset busy state on parse error
|
|
781
841
|
logger.error(`[pier-connector] Job parse error: ${parsed.error}`);
|
|
782
|
-
// Since we already acked, we must send an error result back to Pier
|
|
783
842
|
safeRespond(msg, createErrorPayload({
|
|
784
|
-
id: 'unknown',
|
|
843
|
+
id: jobIdToClaim || 'unknown',
|
|
785
844
|
errorCode: 'PARSE_ERROR',
|
|
786
845
|
errorMessage: parsed.error,
|
|
787
846
|
workerId: config.nodeId,
|
|
@@ -810,13 +869,21 @@ export default function register(api) {
|
|
|
810
869
|
text: job.task,
|
|
811
870
|
};
|
|
812
871
|
|
|
813
|
-
// SUBSCRIBE to job-specific messages FIRST so we don't miss follow-ups
|
|
814
|
-
// This handles the user's "can only receive one message" issue
|
|
872
|
+
// SUBSCRIBE to job-specific messages FIRST so we don't miss follow-ups
|
|
815
873
|
subscribeToJobMessages(job.id);
|
|
816
874
|
|
|
817
875
|
// Trigger agent run
|
|
818
876
|
await receiveIncoming(inbound, job.id);
|
|
877
|
+
|
|
878
|
+
// NOTE: isBusy is reset when the agent completes.
|
|
879
|
+
// Because receiveIncoming (via OpenClaw core) might be async or callback-based,
|
|
880
|
+
// we need to be careful. However, for standard MCP tools/agents,
|
|
881
|
+
// we'll assume isBusy should stay true until the job lifecycle is managed.
|
|
882
|
+
// For now, simple reset after agent returns or in error.
|
|
883
|
+
isBusy = false;
|
|
884
|
+
|
|
819
885
|
} catch (err) {
|
|
886
|
+
isBusy = false;
|
|
820
887
|
jobsFailed++;
|
|
821
888
|
safeRespond(msg, createErrorPayload({
|
|
822
889
|
id: (job && job.id) || 'unknown',
|
|
@@ -884,6 +951,10 @@ export default function register(api) {
|
|
|
884
951
|
type: 'number',
|
|
885
952
|
description: 'Timeout in milliseconds to wait for a result (default 60000)',
|
|
886
953
|
},
|
|
954
|
+
parentJobId: {
|
|
955
|
+
type: 'string',
|
|
956
|
+
description: 'Optional ID of the parent job triggering this sub-task (for delegation)',
|
|
957
|
+
},
|
|
887
958
|
},
|
|
888
959
|
required: ['task'],
|
|
889
960
|
},
|
|
@@ -910,6 +981,7 @@ export default function register(api) {
|
|
|
910
981
|
task: params.task,
|
|
911
982
|
timeoutMs: timeout,
|
|
912
983
|
meta: params.meta,
|
|
984
|
+
parentJobId: params.parentJobId,
|
|
913
985
|
});
|
|
914
986
|
|
|
915
987
|
try {
|
package/src/protocol.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Standardizes outbound and inbound JSON boundaries for NATS.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export const PROTOCOL_VERSION = '1.
|
|
7
|
+
export const PROTOCOL_VERSION = '1.1';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Creates a standard task request payload to send to Pier.
|
|
@@ -15,18 +15,20 @@ export const PROTOCOL_VERSION = '1.0';
|
|
|
15
15
|
* @param {number} [params.timeoutMs]
|
|
16
16
|
* @param {object} [params.meta]
|
|
17
17
|
* @param {string} [params.targetNodeId]
|
|
18
|
+
* @param {string} [params.parentJobId]
|
|
18
19
|
* @returns {object}
|
|
19
20
|
*/
|
|
20
|
-
export function createRequestPayload({ task, systemPrompt, timeoutMs, meta, targetNodeId }) {
|
|
21
|
+
export function createRequestPayload({ task, systemPrompt, timeoutMs, meta, targetNodeId, parentJobId }) {
|
|
21
22
|
return {
|
|
22
23
|
version: PROTOCOL_VERSION,
|
|
23
24
|
id: crypto.randomUUID(),
|
|
24
|
-
type: '
|
|
25
|
+
type: 'task', // Changed from task_request to task to align with backend models
|
|
25
26
|
task,
|
|
26
27
|
systemPrompt,
|
|
27
28
|
timeoutMs: timeoutMs || 60000,
|
|
28
29
|
meta: meta || {},
|
|
29
30
|
targetNodeId: targetNodeId || null,
|
|
31
|
+
parentJobId: parentJobId || null,
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -91,10 +93,10 @@ export function createErrorPayload({ id, errorCode, errorMessage, workerId, wall
|
|
|
91
93
|
* @returns {{ ok: true, job: object } | { ok: false, error: string }}
|
|
92
94
|
*/
|
|
93
95
|
export function normalizeInboundPayload(payload) {
|
|
94
|
-
// If it's a strict v1.
|
|
95
|
-
if (payload.type === 'task_request') {
|
|
96
|
+
// If it's a strict v1.1+ protocol request
|
|
97
|
+
if (payload.type === 'task' || payload.type === 'task_request') {
|
|
96
98
|
if (!payload.task) {
|
|
97
|
-
return { ok: false, error: 'Missing "task" field in
|
|
99
|
+
return { ok: false, error: 'Missing "task" field in task payload' };
|
|
98
100
|
}
|
|
99
101
|
return {
|
|
100
102
|
ok: true,
|
|
@@ -104,6 +106,7 @@ export function normalizeInboundPayload(payload) {
|
|
|
104
106
|
systemPrompt: payload.systemPrompt,
|
|
105
107
|
meta: payload.meta || {},
|
|
106
108
|
targetNodeId: payload.targetNodeId || null,
|
|
109
|
+
parentJobId: payload.parentJobId || null,
|
|
107
110
|
},
|
|
108
111
|
};
|
|
109
112
|
}
|