@mediagato/modelreins-channel 4.10.0 → 4.11.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Kingsley / MEDiAGATO LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * @module modelreins-channel
4
- * @version 4.5.0
4
+ * @version 4.6.0
5
5
  *
6
6
  * ModelReins MCP Channel
7
7
  * ======================
@@ -273,7 +273,7 @@ const STUCK_JOB_MS = 30 * 60 * 1000; // 30 min — generous Claude completion w
273
273
  // without user interaction.
274
274
 
275
275
  const mcp = new Server(
276
- { name: 'modelreins', version: '4.5.0' },
276
+ { name: 'modelreins', version: '4.6.0' },
277
277
  {
278
278
  capabilities: {
279
279
  // Enable the Claude-specific channel notification method.
@@ -463,6 +463,40 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
463
463
  },
464
464
  },
465
465
 
466
+ // ── modelreins_plan ───────────────────────────────────────────────────
467
+ {
468
+ name: 'modelreins_plan',
469
+ description: [
470
+ 'Turn a free-form goal into a multi-step plan and, by default, run it across the fleet.',
471
+ '',
472
+ 'This is the Director: it plans a sequence of skills (dispatch_inference, web_search,',
473
+ 'worker_list, worker_load_model, etc — see GET /director/skills on the API) from your',
474
+ 'request, then executes each step in order and returns the results. Reach for this instead',
475
+ 'of modelreins_dispatch when a goal needs more than one action, or you want the fleet',
476
+ 'itself to decide the steps rather than picking a single worker by hand.',
477
+ '',
478
+ 'Set auto_execute: false to preview the plan without running it, so you can inspect the',
479
+ 'proposed steps before committing the fleet to them.',
480
+ '',
481
+ 'Caveat: the web_search skill is currently a stub (returns no results) pending the',
482
+ 'Neutron-as-worker integration — a plan that includes it will run but find nothing yet.',
483
+ ].join('\n'),
484
+ inputSchema: {
485
+ type: 'object',
486
+ properties: {
487
+ request: {
488
+ type: 'string',
489
+ description: 'The goal, in plain language. The planner only sees this text — be as specific as a good dispatch prompt.',
490
+ },
491
+ auto_execute: {
492
+ type: 'boolean',
493
+ description: 'Execute the plan immediately after planning it. Default true. Set false to preview only.',
494
+ },
495
+ },
496
+ required: ['request'],
497
+ },
498
+ },
499
+
466
500
  ],
467
501
  }));
468
502
 
@@ -531,11 +565,74 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
531
565
  };
532
566
  }
533
567
 
568
+ // ── modelreins_plan ────────────────────────────────────────────────────────
569
+
570
+ if (name === 'modelreins_plan') {
571
+ const { request, auto_execute } = args;
572
+ const shouldExecute = auto_execute !== false;
573
+
574
+ const planRes = await api('POST', '/director/plan', {
575
+ request,
576
+ auto_execute: shouldExecute,
577
+ });
578
+
579
+ if (!planRes.data?.success) {
580
+ return {
581
+ content: [{
582
+ type: 'text',
583
+ text: `Planning failed (HTTP ${planRes.status}): ${JSON.stringify(planRes.data)}`,
584
+ }],
585
+ };
586
+ }
587
+
588
+ const { plan_id, plan } = planRes.data;
589
+ const lines = [`## Director Plan #${plan_id}`, ''];
590
+ lines.push(`Rationale: ${plan.rationale}`);
591
+ lines.push(`Risk tier: ${plan.risk_tier}`);
592
+ lines.push(`Steps (${plan.step_count}):`);
593
+ plan.steps.forEach((s, i) => {
594
+ lines.push(` ${i + 1}. ${s.tool_name} ${JSON.stringify(s.arguments)}`);
595
+ if (s.rationale) lines.push(` -> ${s.rationale}`);
596
+ });
597
+
598
+ if (!shouldExecute) {
599
+ lines.push('', 'Not executed (auto_execute=false). Call modelreins_plan again with auto_execute:true to run it.');
600
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
601
+ }
602
+
603
+ const execRes = await api('POST', '/director/execute', { plan_id });
604
+
605
+ // api() always returns a truthy data object (even on network failure),
606
+ // so failure must be detected via the HTTP status / success flag.
607
+ if (execRes.status < 200 || execRes.status >= 300 || execRes.data.success === false) {
608
+ lines.push('', `## Execution FAILED (HTTP ${execRes.status})`);
609
+ lines.push(`Error: ${execRes.data.error || execRes.data.detail || JSON.stringify(execRes.data)}`);
610
+ lines.push(`Plan #${plan_id} was created but not executed. Call modelreins_plan again (or POST /director/execute with this plan_id) to run it.`);
611
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
612
+ }
613
+
614
+ const { status: finalStatus, results } = execRes.data;
615
+ lines.push('', `## Execution — ${finalStatus || 'unknown'}`);
616
+ for (const r of (results || [])) {
617
+ const mark = r.status === 'done' ? 'done' : r.status === 'failed' ? 'FAILED' : (r.status || 'unknown');
618
+ lines.push(` [${r.step_index + 1}] ${r.tool_name.padEnd(20)} ${mark}`);
619
+ if (r.status === 'done' && r.result !== undefined) {
620
+ const preview = typeof r.result === 'string' ? r.result : JSON.stringify(r.result);
621
+ lines.push(` ${preview.slice(0, 400)}`);
622
+ }
623
+ if (r.status === 'failed' && r.error) {
624
+ lines.push(` error: ${r.error}`);
625
+ }
626
+ }
627
+
628
+ return { content: [{ type: 'text', text: lines.join('\n') }] };
629
+ }
630
+
534
631
  // ── modelreins_status ─────────────────────────────────────────────────────
535
632
 
536
633
  if (name === 'modelreins_status') {
537
634
  const [workers, jobs, schedules] = await Promise.all([
538
- api('GET', '/workers'),
635
+ api('GET', '/fleet/workers'),
539
636
  api('GET', '/jobs?limit=5'),
540
637
  api('GET', '/schedules'),
541
638
  ]);
@@ -544,8 +641,15 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
544
641
 
545
642
  // Workers
546
643
  const wList = workers.data?.workers || [];
547
- lines.push(`### Workers (${wList.length} known)`);
548
- if (wList.length === 0) {
644
+ // An empty list and a failed request are NOT the same finding -- collapsing
645
+ // them into "No workers registered" is what let 12 live workers read as
646
+ // zero for as long as that line existed (see 71be396). Say which happened.
647
+ const wFailed = workers.status !== 200;
648
+ lines.push(`### Workers (${wFailed ? 'unreadable' : `${wList.length} known`})`);
649
+ if (wFailed) {
650
+ lines.push(` COULD NOT READ /fleet/workers — HTTP ${workers.status}${workers.data?.error ? ` (${workers.data.error})` : ''}.`);
651
+ lines.push(' This is a failed check, not an empty fleet.');
652
+ } else if (wList.length === 0) {
549
653
  lines.push(' No workers registered.');
550
654
  }
551
655
  for (const w of wList) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mediagato/modelreins-channel",
3
- "version": "4.10.0",
3
+ "version": "4.11.0",
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",
@@ -17,6 +17,6 @@
17
17
  "channel",
18
18
  "ai-orchestration"
19
19
  ],
20
- "license": "BSL-1.1",
20
+ "license": "MIT",
21
21
  "homepage": "https://modelreins.com"
22
22
  }