@mediagato/modelreins-channel 4.5.0 → 4.6.1
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/modelreins-channel.mjs +64 -32
- package/package.json +25 -19
package/modelreins-channel.mjs
CHANGED
|
@@ -1013,50 +1013,82 @@ async function heartbeat() {
|
|
|
1013
1013
|
* 4. Claude reads it, executes the task, calls modelreins_complete when done
|
|
1014
1014
|
*/
|
|
1015
1015
|
let _pollCount = 0;
|
|
1016
|
+
let _pollInProgress = false;
|
|
1016
1017
|
|
|
1017
1018
|
async function pollJobs() {
|
|
1019
|
+
// setInterval does not await us, so a slow tick (e.g. a stalled API
|
|
1020
|
+
// call) can overlap with the next one. Guard against re-entry.
|
|
1021
|
+
if (_pollInProgress) return;
|
|
1018
1022
|
// One job at a time — don't accept new work while something is running.
|
|
1019
1023
|
if (currentJobId) return;
|
|
1024
|
+
_pollInProgress = true;
|
|
1020
1025
|
|
|
1021
|
-
//
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1026
|
+
// Track the optimistic local claim so we can roll it back if the
|
|
1027
|
+
// claim PUT or the MCP notification fails before Claude sees the job.
|
|
1028
|
+
let claimedJobId = null;
|
|
1029
|
+
|
|
1030
|
+
try {
|
|
1031
|
+
// Check killswitch every 12 polls (~60s at default POLL_MS).
|
|
1032
|
+
_pollCount++;
|
|
1033
|
+
if (_pollCount % 12 === 1) {
|
|
1025
1034
|
const ks = await api('GET', '/killswitch');
|
|
1026
|
-
if (ks.data?.active) {
|
|
1035
|
+
if (ks.status >= 200 && ks.status < 300 && ks.data?.active) {
|
|
1027
1036
|
const reason = ks.data.reason || 'killswitch active';
|
|
1028
1037
|
await api('PUT', '/presence', { instance: WORKER_NAME, status: 'offline', details: `Killswitch: ${reason}` });
|
|
1029
1038
|
process.exit(0);
|
|
1030
1039
|
}
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const res = await api('GET', `/jobs?status=pending&assigned_to=${encodeURIComponent(WORKER_NAME)}&limit=1`);
|
|
1043
|
+
if (res.status < 200 || res.status >= 300) return;
|
|
1044
|
+
const jobs = res.data?.jobs || (Array.isArray(res.data) ? res.data : []);
|
|
1045
|
+
|
|
1046
|
+
if (jobs.length === 0) return;
|
|
1047
|
+
|
|
1048
|
+
const job = jobs[0];
|
|
1049
|
+
currentJobId = job.id;
|
|
1050
|
+
claimedJobId = job.id;
|
|
1051
|
+
|
|
1052
|
+
// Claim the job immediately so no other worker picks it up. If the
|
|
1053
|
+
// claim fails, roll back so a future poll can retry — without this,
|
|
1054
|
+
// a transient 5xx leaves currentJobId stuck and the channel deadlocks.
|
|
1055
|
+
const claim = await api('PUT', `/jobs/${job.id}`, { status: 'running' });
|
|
1056
|
+
if (claim.status < 200 || claim.status >= 300) {
|
|
1057
|
+
throw new Error(`claim PUT returned ${claim.status}: ${JSON.stringify(claim.data).slice(0, 120)}`);
|
|
1058
|
+
}
|
|
1033
1059
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
await mcp.notification({
|
|
1048
|
-
method: 'notifications/claude/channel',
|
|
1049
|
-
params: {
|
|
1050
|
-
content: job.prompt,
|
|
1051
|
-
meta: {
|
|
1052
|
-
job_id: String(job.id),
|
|
1053
|
-
priority: String(job.priority ?? 0),
|
|
1054
|
-
model_preference: job.model_preference || '',
|
|
1055
|
-
working_dir: job.working_dir || '',
|
|
1056
|
-
source: 'modelreins-dashboard',
|
|
1060
|
+
// Inject the job prompt into Claude's context via the channel notification.
|
|
1061
|
+
// Claude Code intercepts this and presents it as a <channel> tag in the session.
|
|
1062
|
+
await mcp.notification({
|
|
1063
|
+
method: 'notifications/claude/channel',
|
|
1064
|
+
params: {
|
|
1065
|
+
content: job.prompt,
|
|
1066
|
+
meta: {
|
|
1067
|
+
job_id: String(job.id),
|
|
1068
|
+
priority: String(job.priority ?? 0),
|
|
1069
|
+
model_preference: job.model_preference || '',
|
|
1070
|
+
working_dir: job.working_dir || '',
|
|
1071
|
+
source: 'modelreins-dashboard',
|
|
1072
|
+
},
|
|
1057
1073
|
},
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
// Notification delivered — Claude now owns the job. modelreins_complete
|
|
1077
|
+
// will clear currentJobId when Claude reports back.
|
|
1078
|
+
claimedJobId = null;
|
|
1079
|
+
} catch (err) {
|
|
1080
|
+
// Roll back the optimistic local claim so the next poll can retry.
|
|
1081
|
+
if (claimedJobId !== null && currentJobId === claimedJobId) {
|
|
1082
|
+
currentJobId = null;
|
|
1083
|
+
// Best-effort: return the job to pending on the server too.
|
|
1084
|
+
// If this also fails, the server-side stale-running cleanup will
|
|
1085
|
+
// eventually reclaim it; we don't want to retry forever here.
|
|
1086
|
+
api('PUT', `/jobs/${claimedJobId}`, { status: 'pending' }).catch(() => {});
|
|
1087
|
+
}
|
|
1088
|
+
console.error(`[channel] pollJobs error: ${err?.message || err}`);
|
|
1089
|
+
} finally {
|
|
1090
|
+
_pollInProgress = false;
|
|
1091
|
+
}
|
|
1060
1092
|
}
|
|
1061
1093
|
|
|
1062
1094
|
// Kick off the background loops.
|
package/package.json
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@mediagato/modelreins-channel",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "ModelReins MCP Channel — turn any Claude Code session into a ModelReins worker",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "modelreins-channel.mjs",
|
|
7
|
-
"bin": {
|
|
8
|
-
"modelreins-channel": "./modelreins-channel.mjs"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
12
|
-
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@mediagato/modelreins-channel",
|
|
3
|
+
"version": "4.6.1",
|
|
4
|
+
"description": "ModelReins MCP Channel — turn any Claude Code session into a ModelReins worker",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "modelreins-channel.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"modelreins-channel": "./modelreins-channel.mjs"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"modelreins",
|
|
15
|
+
"mcp",
|
|
16
|
+
"claude-code",
|
|
17
|
+
"channel",
|
|
18
|
+
"ai-orchestration"
|
|
19
|
+
],
|
|
20
|
+
"license": "BSL-1.1",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/mediagato/modelreins"
|
|
24
|
+
}
|
|
25
|
+
}
|