@mediagato/modelreins-channel 4.12.4 → 4.12.6
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 +95 -22
- package/package.json +1 -1
package/modelreins-channel.mjs
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* ModelReins MCP Channel
|
|
7
7
|
* ======================
|
|
8
8
|
* Bridges any Claude Code session — CLI or VSCode extension — to a ModelReins
|
|
9
|
-
* fleet. Once installed, this session becomes a named worker:
|
|
10
|
-
* from the dashboard, executes them with full local access
|
|
11
|
-
* env vars, browser), and reports results back in real
|
|
9
|
+
* fleet. Once installed, this session becomes a named worker: by default it
|
|
10
|
+
* receives jobs from the dashboard, executes them with full local access
|
|
11
|
+
* (files, git, shell, env vars, browser), and reports results back in real
|
|
12
|
+
* time. Set MODELREINS_RECEIVE_JOBS=false for outbound-only — dispatch/status
|
|
13
|
+
* stay available, nothing here ever executes a dispatched job (see below).
|
|
12
14
|
*
|
|
13
15
|
* How it works
|
|
14
16
|
* ------------
|
|
@@ -51,6 +53,9 @@
|
|
|
51
53
|
* -e MODELREINS_WORKER=my-laptop \
|
|
52
54
|
* -- npx -y @mediagato/modelreins-channel
|
|
53
55
|
*
|
|
56
|
+
* Outbound-only (this machine can dispatch to the fleet, the fleet can never
|
|
57
|
+
* dispatch to it): add -e MODELREINS_RECEIVE_JOBS=false to either option above.
|
|
58
|
+
*
|
|
54
59
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
55
60
|
* QUICK START — VSCode (Claude Code extension)
|
|
56
61
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -91,7 +96,7 @@
|
|
|
91
96
|
* Required:
|
|
92
97
|
* MODELREINS_URL — Dashboard URL. Examples:
|
|
93
98
|
* https://app.modelreins.com (hosted)
|
|
94
|
-
* http://
|
|
99
|
+
* http://your-server:8484 (self-hosted)
|
|
95
100
|
* http://localhost:8484 (local dev)
|
|
96
101
|
*
|
|
97
102
|
* MODELREINS_TOKEN — Worker auth token. Find it in the dashboard under
|
|
@@ -121,6 +126,19 @@
|
|
|
121
126
|
* Lower = more responsive, higher = less network.
|
|
122
127
|
* Minimum recommended: 2000.
|
|
123
128
|
*
|
|
129
|
+
* MODELREINS_RECEIVE_JOBS — Whether this session accepts dispatched jobs.
|
|
130
|
+
* Default: "true" (unchanged from every prior
|
|
131
|
+
* version — existing installs need no changes).
|
|
132
|
+
* Set to "false" for an outbound-only session:
|
|
133
|
+
* modelreins_dispatch/status/recruit/complete/
|
|
134
|
+
* result stay available so you can still hand
|
|
135
|
+
* work OUT to the fleet, but job polling never
|
|
136
|
+
* starts, so nothing ever arrives as a <channel>
|
|
137
|
+
* tag and nothing here executes unattended.
|
|
138
|
+
* Use this for a machine you're actively driving
|
|
139
|
+
* interactively that should never become a
|
|
140
|
+
* dispatch target itself.
|
|
141
|
+
*
|
|
124
142
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
125
143
|
* AVAILABLE TOOLS (visible to Claude in every session)
|
|
126
144
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -222,6 +240,22 @@ const WORKER_MODEL = process.env.MODELREINS_WORKER_MODEL || 'claude-session';
|
|
|
222
240
|
*/
|
|
223
241
|
const WORKER_TAGS = process.env.MODELREINS_WORKER_TAGS || 'local,interactive,full-access';
|
|
224
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Whether this session accepts dispatched jobs from the dashboard at all.
|
|
245
|
+
* Default 'true' preserves the channel's original all-or-nothing behavior —
|
|
246
|
+
* every existing install keeps working exactly as before with no env changes.
|
|
247
|
+
* Set MODELREINS_RECEIVE_JOBS=false for an outbound-only session: the
|
|
248
|
+
* modelreins_dispatch/status/recruit/complete/result tools stay available (a
|
|
249
|
+
* session can still hand work OUT to the fleet), but pollJobs() never starts,
|
|
250
|
+
* so nothing ever arrives as a <channel> tag and nothing here ever executes
|
|
251
|
+
* unattended. Added 2026-08-30 after a real setup mistake: this channel was
|
|
252
|
+
* described to a second Claude Code seat as "outbound-only," which the
|
|
253
|
+
* channel as shipped has never actually supported — every install has always
|
|
254
|
+
* been fully bidirectional, receive included, with no way to opt out. This
|
|
255
|
+
* flag is the actual missing feature, not a workaround.
|
|
256
|
+
*/
|
|
257
|
+
const RECEIVE_JOBS = (process.env.MODELREINS_RECEIVE_JOBS || 'true').toLowerCase() !== 'false';
|
|
258
|
+
|
|
225
259
|
// ── HTTP Client ───────────────────────────────────────────────────────────────
|
|
226
260
|
//
|
|
227
261
|
// Thin wrapper around fetch — attaches auth, serializes body, swallows
|
|
@@ -294,24 +328,34 @@ const mcp = new Server(
|
|
|
294
328
|
|
|
295
329
|
// System-level instructions Claude receives when the channel is connected.
|
|
296
330
|
// These prime Claude to handle incoming jobs correctly without the user
|
|
297
|
-
// having to explain the setup in every session.
|
|
331
|
+
// having to explain the setup in every session. Built conditionally on
|
|
332
|
+
// RECEIVE_JOBS so the session is told the truth about what it actually
|
|
333
|
+
// does — an outbound-only session should never be primed to expect
|
|
334
|
+
// <channel> tags that pollJobs() (gated below) will never produce.
|
|
298
335
|
instructions: [
|
|
299
336
|
`You are connected to ModelReins (${BASE_URL}) as worker "${WORKER_NAME}".`,
|
|
300
337
|
`Your capability tags: ${WORKER_TAGS}`,
|
|
301
338
|
'',
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
339
|
+
...(RECEIVE_JOBS ? [
|
|
340
|
+
'RECEIVING JOBS:',
|
|
341
|
+
'Jobs from the ModelReins dashboard arrive as <channel source="modelreins" job_id="..." priority="..."> tags.',
|
|
342
|
+
'When a job arrives, read the prompt inside the tag and execute it using your full local capabilities.',
|
|
343
|
+
'Treat dispatched jobs with the same care as user requests — use your tools, check your work.',
|
|
344
|
+
'',
|
|
345
|
+
'COMPLETING JOBS:',
|
|
346
|
+
'Always call modelreins_complete when you finish, whether the task succeeded or failed.',
|
|
347
|
+
'Never leave a job in "running" state — it blocks the queue and confuses the dashboard.',
|
|
348
|
+
'A good summary is 1-3 sentences: what you did, what changed, any caveats.',
|
|
349
|
+
'',
|
|
350
|
+
'BETWEEN JOBS:',
|
|
351
|
+
'You remain a fully normal Claude Code session. The user can still talk to you, open files,',
|
|
352
|
+
'run commands, etc. The ModelReins channel is invisible until work arrives.',
|
|
353
|
+
] : [
|
|
354
|
+
'OUTBOUND-ONLY MODE (MODELREINS_RECEIVE_JOBS=false):',
|
|
355
|
+
'This session does NOT receive dispatched jobs. No polling ever runs, no <channel> tag will',
|
|
356
|
+
'ever appear here, and nothing on the ModelReins dashboard can execute anything on this',
|
|
357
|
+
'machine. You can dispatch work OUT to the fleet (below) but nothing dispatches IN.',
|
|
358
|
+
]),
|
|
315
359
|
'',
|
|
316
360
|
'DISPATCHING TO OTHER WORKERS:',
|
|
317
361
|
'Use modelreins_dispatch to hand off subtasks. This is powerful: you can split a large job,',
|
|
@@ -1218,14 +1262,37 @@ await mcp.connect(new StdioServerTransport());
|
|
|
1218
1262
|
* full worker context without a separate lookup.
|
|
1219
1263
|
*/
|
|
1220
1264
|
async function heartbeat() {
|
|
1265
|
+
// An outbound-only session must NOT advertise itself as available work.
|
|
1266
|
+
// It used to report 'active' with details 'idle, polling' while pollJobs()
|
|
1267
|
+
// had never started -- both false. Cost: a job dispatched to such a seat
|
|
1268
|
+
// sat "pending" for 6.4 hours while the dashboard showed the worker
|
|
1269
|
+
// "active" the whole time, and nothing anywhere could tell the difference
|
|
1270
|
+
// (2026-09-03). The control that proved it was a real finding: the same
|
|
1271
|
+
// dispatch to a consuming worker was claimed and completed in 18 seconds.
|
|
1272
|
+
//
|
|
1273
|
+
// Worse than the manual case: app.py's routing selects
|
|
1274
|
+
// `status NOT IN ('offline','maintenance')`, so an outbound-only worker
|
|
1275
|
+
// reporting 'active' is a valid auto-route target -- `assigned_to: "auto"`
|
|
1276
|
+
// could silently park a job on a seat that will never claim it.
|
|
1277
|
+
//
|
|
1278
|
+
// 'offline' is the correct report and the only safe one here: it is in the
|
|
1279
|
+
// routing exclusion list, and unlike 'maintenance' and 'retired' it is NOT
|
|
1280
|
+
// sticky server-side (app.py:4104,4107), so flipping MODELREINS_RECEIVE_JOBS
|
|
1281
|
+
// back to true restores 'active' on the very next heartbeat with no manual
|
|
1282
|
+
// unpark call. The heartbeat itself keeps flowing either way, so
|
|
1283
|
+
// last_heartbeat still proves the process is alive.
|
|
1284
|
+
const idleStatus = RECEIVE_JOBS ? 'active' : 'offline';
|
|
1285
|
+
const idleDetails = RECEIVE_JOBS
|
|
1286
|
+
? 'idle, polling'
|
|
1287
|
+
: 'outbound-only (MODELREINS_RECEIVE_JOBS=false) — never claims dispatched jobs';
|
|
1221
1288
|
await api('PUT', '/presence', {
|
|
1222
1289
|
instance: WORKER_NAME,
|
|
1223
|
-
status: currentJobId ? 'busy' :
|
|
1290
|
+
status: currentJobId ? 'busy' : idleStatus,
|
|
1224
1291
|
project: 'channel',
|
|
1225
1292
|
worker_type: 'channel',
|
|
1226
1293
|
model: WORKER_MODEL,
|
|
1227
1294
|
tags: WORKER_TAGS,
|
|
1228
|
-
details: currentJobId ? `working on job #${currentJobId}` :
|
|
1295
|
+
details: currentJobId ? `working on job #${currentJobId}` : idleDetails,
|
|
1229
1296
|
});
|
|
1230
1297
|
}
|
|
1231
1298
|
|
|
@@ -1354,9 +1421,15 @@ async function stuckJobWatchdog() {
|
|
|
1354
1421
|
}
|
|
1355
1422
|
}
|
|
1356
1423
|
|
|
1357
|
-
// Kick off the background loops.
|
|
1424
|
+
// Kick off the background loops. Heartbeat always runs (presence/status display
|
|
1425
|
+
// on the dashboard); pollJobs is what actually claims and executes dispatched
|
|
1426
|
+
// work, so it's the one RECEIVE_JOBS gates.
|
|
1358
1427
|
setInterval(heartbeat, 30_000);
|
|
1359
|
-
|
|
1428
|
+
if (RECEIVE_JOBS) {
|
|
1429
|
+
setInterval(pollJobs, POLL_MS);
|
|
1430
|
+
} else {
|
|
1431
|
+
console.error('[channel] MODELREINS_RECEIVE_JOBS=false — outbound-only, job polling disabled');
|
|
1432
|
+
}
|
|
1360
1433
|
setInterval(stuckJobWatchdog, 60_000);
|
|
1361
1434
|
|
|
1362
1435
|
// Initial heartbeat fires immediately so the worker shows as online right away.
|
package/package.json
CHANGED