@mediagato/modelreins-channel 4.11.1 → 4.11.3
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 +116 -2
- package/package.json +2 -8
package/modelreins-channel.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* @module modelreins-channel
|
|
4
|
-
* @version 4.
|
|
4
|
+
* @version 4.11.3
|
|
5
5
|
*
|
|
6
6
|
* ModelReins MCP Channel
|
|
7
7
|
* ======================
|
|
@@ -134,6 +134,12 @@
|
|
|
134
134
|
* you can do locally, delegate the rest to a headless
|
|
135
135
|
* worker with more model capacity.
|
|
136
136
|
*
|
|
137
|
+
* modelreins_result — Read back a job's actual output: full metadata plus
|
|
138
|
+
* the recorded output stream. This is the other half
|
|
139
|
+
* of dispatch — without it, dispatched work is a one
|
|
140
|
+
* way street. Works on running jobs too (partial
|
|
141
|
+
* output so far).
|
|
142
|
+
*
|
|
137
143
|
* modelreins_status — Snapshot of the entire fleet: workers, recent jobs,
|
|
138
144
|
* active schedules. Useful for situational awareness
|
|
139
145
|
* before dispatching ("is worker-01 free right now?").
|
|
@@ -161,6 +167,10 @@
|
|
|
161
167
|
* Channel polls → pulls job → notification → Claude executes →
|
|
162
168
|
* Claude calls modelreins_complete → POST /jobs/{id}/output + PUT /jobs/{id}
|
|
163
169
|
* → dashboard updates in real time.
|
|
170
|
+
*
|
|
171
|
+
* Readback flow (the return trip):
|
|
172
|
+
* Claude calls modelreins_result → GET /jobs/{id} + GET /jobs/{id}/output
|
|
173
|
+
* → job metadata + recorded output rendered back into the session.
|
|
164
174
|
*/
|
|
165
175
|
|
|
166
176
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
@@ -273,7 +283,7 @@ const STUCK_JOB_MS = 30 * 60 * 1000; // 30 min — generous Claude completion w
|
|
|
273
283
|
// without user interaction.
|
|
274
284
|
|
|
275
285
|
const mcp = new Server(
|
|
276
|
-
{ name: 'modelreins', version: '4.
|
|
286
|
+
{ name: 'modelreins', version: '4.11.3' },
|
|
277
287
|
{
|
|
278
288
|
capabilities: {
|
|
279
289
|
// Enable the Claude-specific channel notification method.
|
|
@@ -306,6 +316,11 @@ const mcp = new Server(
|
|
|
306
316
|
'DISPATCHING TO OTHER WORKERS:',
|
|
307
317
|
'Use modelreins_dispatch to hand off subtasks. This is powerful: you can split a large job,',
|
|
308
318
|
'keep the local/interactive parts for yourself, and delegate batch work to headless workers.',
|
|
319
|
+
'',
|
|
320
|
+
'READING RESULTS BACK:',
|
|
321
|
+
'Use modelreins_result to read a job\'s actual output — dispatch alone is a one way street.',
|
|
322
|
+
'After a dispatched job completes (check with modelreins_status), fetch its real result',
|
|
323
|
+
'before reporting on it or building on it. Works mid-run too for partial output.',
|
|
309
324
|
].join('\n'),
|
|
310
325
|
}
|
|
311
326
|
);
|
|
@@ -397,6 +412,37 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
397
412
|
},
|
|
398
413
|
},
|
|
399
414
|
|
|
415
|
+
// ── modelreins_result ─────────────────────────────────────────────────
|
|
416
|
+
{
|
|
417
|
+
name: 'modelreins_result',
|
|
418
|
+
description: [
|
|
419
|
+
'Read back a job\'s actual result: full metadata (status, worker, timing, exit code)',
|
|
420
|
+
'plus the recorded output stream. This is the other half of modelreins_dispatch —',
|
|
421
|
+
'without it, dispatched work is a one way street.',
|
|
422
|
+
'',
|
|
423
|
+
'Use after modelreins_status shows a dispatched job as done/failed, to get the real',
|
|
424
|
+
'output instead of just a status flag. Also works on running jobs — returns whatever',
|
|
425
|
+
'partial output the worker has streamed so far.',
|
|
426
|
+
'',
|
|
427
|
+
'Output is tail-truncated at max_chars (the end of a job\'s output carries the',
|
|
428
|
+
'summary/conclusion). Truncation is always reported explicitly, never silent.',
|
|
429
|
+
].join('\n'),
|
|
430
|
+
inputSchema: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
properties: {
|
|
433
|
+
job_id: {
|
|
434
|
+
type: 'string',
|
|
435
|
+
description: 'The job ID to read back (from modelreins_dispatch or modelreins_status).',
|
|
436
|
+
},
|
|
437
|
+
max_chars: {
|
|
438
|
+
type: 'number',
|
|
439
|
+
description: 'Max characters of output to return (tail-biased). Default 30000, max 200000.',
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
required: ['job_id'],
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
|
|
400
446
|
// ── modelreins_status ─────────────────────────────────────────────────
|
|
401
447
|
{
|
|
402
448
|
name: 'modelreins_status',
|
|
@@ -628,6 +674,74 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
628
674
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
629
675
|
}
|
|
630
676
|
|
|
677
|
+
// ── modelreins_result ─────────────────────────────────────────────────────
|
|
678
|
+
|
|
679
|
+
if (name === 'modelreins_result') {
|
|
680
|
+
const jobId = String(args.job_id).trim();
|
|
681
|
+
const maxChars = Math.max(1000, Math.min(args.max_chars ?? 30000, 200000));
|
|
682
|
+
|
|
683
|
+
const [jobRes, outRes] = await Promise.all([
|
|
684
|
+
api('GET', `/jobs/${jobId}`),
|
|
685
|
+
api('GET', `/jobs/${jobId}/output?limit=500`),
|
|
686
|
+
]);
|
|
687
|
+
|
|
688
|
+
// A missing job and an unreachable server are NOT the same finding —
|
|
689
|
+
// same discipline as the workers block in modelreins_status.
|
|
690
|
+
if (jobRes.status === 404) {
|
|
691
|
+
return { content: [{ type: 'text', text: `Job #${jobId} not found (404). Check the ID — jobs are tenant-scoped, so a job dispatched under another token is invisible here.` }] };
|
|
692
|
+
}
|
|
693
|
+
if (jobRes.status < 200 || jobRes.status >= 300) {
|
|
694
|
+
return { content: [{ type: 'text', text: `COULD NOT READ job #${jobId} — HTTP ${jobRes.status}${jobRes.data?.error ? ` (${jobRes.data.error})` : ''}. This is a failed check, not a missing job.` }] };
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const job = jobRes.data.job || {};
|
|
698
|
+
const lines = [`## Job #${jobId} — ${job.status || 'unknown'}`];
|
|
699
|
+
const meta = [];
|
|
700
|
+
if (job.assigned_to) meta.push(`worker: ${job.assigned_to}`);
|
|
701
|
+
if (job.model_preference) meta.push(`model: ${job.model_preference}`);
|
|
702
|
+
if (job.exit_code !== null && job.exit_code !== undefined) meta.push(`exit: ${job.exit_code}`);
|
|
703
|
+
if (job.created_at) meta.push(`created: ${job.created_at}`);
|
|
704
|
+
if (job.completed_at) meta.push(`completed: ${job.completed_at}`);
|
|
705
|
+
if (meta.length) lines.push(meta.join(' · '));
|
|
706
|
+
if (job.error) lines.push(`error: ${job.error}`);
|
|
707
|
+
if (job.prompt) {
|
|
708
|
+
const p = String(job.prompt);
|
|
709
|
+
lines.push('', `### Prompt${p.length > 300 ? ' (first 300 chars)' : ''}`, p.slice(0, 300));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (outRes.status < 200 || outRes.status >= 300) {
|
|
713
|
+
lines.push('', `### Output — COULD NOT READ (HTTP ${outRes.status}${outRes.data?.error ? `: ${outRes.data.error}` : ''})`);
|
|
714
|
+
lines.push('This is a failed read, not an empty result.');
|
|
715
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const rows = outRes.data?.output || [];
|
|
719
|
+
const systemRows = rows.filter(r => r.stream === 'system');
|
|
720
|
+
const bodyRows = rows.filter(r => r.stream !== 'system');
|
|
721
|
+
// Join with '' — workers stream token/text DELTAS that carry their own
|
|
722
|
+
// whitespace (verified against job output from both the Node worker
|
|
723
|
+
// daemon and LlamaStash). Joining with '\n' renders one token per line.
|
|
724
|
+
const body = bodyRows.map(r => r.content).join('');
|
|
725
|
+
|
|
726
|
+
if (rows.length === 0) {
|
|
727
|
+
lines.push('', '### Output', 'No output rows recorded for this job. The worker never posted output — for a done job that usually means it completed via a bare status update without a summary.');
|
|
728
|
+
} else {
|
|
729
|
+
let shown = body;
|
|
730
|
+
let truncNote = '';
|
|
731
|
+
if (body.length > maxChars) {
|
|
732
|
+
shown = body.slice(-maxChars);
|
|
733
|
+
truncNote = ` — TRUNCATED, showing last ${maxChars} of ${body.length} chars (raise max_chars for more)`;
|
|
734
|
+
}
|
|
735
|
+
lines.push('', `### Output (${bodyRows.length} chunk${bodyRows.length === 1 ? '' : 's'}, ${body.length} chars${truncNote})`, shown);
|
|
736
|
+
if (systemRows.length) {
|
|
737
|
+
lines.push('', `### System notes (${systemRows.length})`);
|
|
738
|
+
for (const r of systemRows.slice(-10)) lines.push(` ${r.content}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
743
|
+
}
|
|
744
|
+
|
|
631
745
|
// ── modelreins_status ─────────────────────────────────────────────────────
|
|
632
746
|
|
|
633
747
|
if (name === 'modelreins_status') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mediagato/modelreins-channel",
|
|
3
|
-
"version": "4.11.
|
|
3
|
+
"version": "4.11.3",
|
|
4
4
|
"description": "ModelReins MCP Channel — turn any Claude Code session into a ModelReins worker",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "modelreins-channel.mjs",
|
|
@@ -10,13 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
12
12
|
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"modelreins",
|
|
15
|
-
"mcp",
|
|
16
|
-
"claude-code",
|
|
17
|
-
"channel",
|
|
18
|
-
"ai-orchestration"
|
|
19
|
-
],
|
|
13
|
+
"keywords": ["modelreins", "mcp", "claude-code", "channel", "ai-orchestration"],
|
|
20
14
|
"license": "MIT",
|
|
21
15
|
"homepage": "https://modelreins.com"
|
|
22
16
|
}
|