@dmsdc-ai/aigentry-deliberation 0.0.32 → 0.0.34

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.
Files changed (3) hide show
  1. package/index.js +102 -10
  2. package/model-router.js +14 -25
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -516,6 +516,34 @@ function dedupeSpeakers(items = []) {
516
516
  return out;
517
517
  }
518
518
 
519
+ function hasExplicitBrowserParticipantSelection({ speakers, participant_types } = {}) {
520
+ const manualSpeakers = Array.isArray(speakers) ? speakers : [];
521
+ const hasBrowserSpeaker = manualSpeakers.some(speaker => {
522
+ const normalized = normalizeSpeaker(speaker);
523
+ return normalized?.startsWith("web-");
524
+ });
525
+ if (hasBrowserSpeaker) return true;
526
+
527
+ const overrides = participant_types && typeof participant_types === "object"
528
+ ? Object.entries(participant_types)
529
+ : [];
530
+
531
+ return overrides.some(([speaker, type]) => {
532
+ const normalized = normalizeSpeaker(speaker);
533
+ return normalized?.startsWith("web-") || type === "browser" || type === "browser_auto";
534
+ });
535
+ }
536
+
537
+ function resolveIncludeBrowserSpeakers({ include_browser_speakers, config, speakers, participant_types } = {}) {
538
+ if (include_browser_speakers !== undefined && include_browser_speakers !== null) {
539
+ return include_browser_speakers;
540
+ }
541
+ if (config?.include_browser_speakers !== undefined && config?.include_browser_speakers !== null) {
542
+ return config.include_browser_speakers;
543
+ }
544
+ return false;
545
+ }
546
+
519
547
  function resolveCliCandidates() {
520
548
  const fromEnv = (process.env.DELIBERATION_CLI_CANDIDATES || "")
521
549
  .split(/[,\s]+/)
@@ -2473,6 +2501,10 @@ server.tool(
2473
2501
  (v) => (typeof v === "string" ? v === "true" : v),
2474
2502
  z.boolean().optional()
2475
2503
  ).describe("Whether to auto-discover speakers when omitted (defaults to config setting)"),
2504
+ include_browser_speakers: z.preprocess(
2505
+ (v) => (typeof v === "string" ? v === "true" : v),
2506
+ z.boolean().optional()
2507
+ ).describe("Whether browser speakers are allowed to participate. Defaults to false unless explicitly enabled."),
2476
2508
  participant_types: z.preprocess(
2477
2509
  (v) => (typeof v === "string" ? JSON.parse(v) : v),
2478
2510
  z.record(z.string(), z.enum(["cli", "browser", "browser_auto", "manual"])).optional()
@@ -2486,7 +2518,7 @@ server.tool(
2486
2518
  role_preset: z.enum(["balanced", "debate", "research", "brainstorm", "review", "consensus"]).optional()
2487
2519
  .describe("Role preset (balanced/debate/research/brainstorm/review/consensus). Ignored if speaker_roles is specified"),
2488
2520
  },
2489
- safeToolHandler("deliberation_start", async ({ topic, session_id, rounds, first_speaker, speakers, speaker_instructions, require_manual_speakers, auto_discover_speakers, participant_types, ordering_strategy, speaker_roles, role_preset }) => {
2521
+ safeToolHandler("deliberation_start", async ({ topic, session_id, rounds, first_speaker, speakers, speaker_instructions, require_manual_speakers, auto_discover_speakers, include_browser_speakers, participant_types, ordering_strategy, speaker_roles, role_preset }) => {
2490
2522
  // ── First-time onboarding guard ──
2491
2523
  const config = loadDeliberationConfig();
2492
2524
  if (!config.setup_complete) {
@@ -2495,7 +2527,7 @@ server.tool(
2495
2527
  return {
2496
2528
  content: [{
2497
2529
  type: "text",
2498
- text: `🎉 **Welcome to Deliberation!**\n\nPlease configure basic settings before starting.\n\n**Currently detected speakers:**\n${candidateText}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nYou can set all options at once:\n\n\`\`\`\ndeliberation_cli_config(\n require_speaker_selection: true/false,\n default_rounds: 3,\n default_ordering: "auto"\n)\n\`\`\`\n\n**1. Speaker participation mode** (\`require_speaker_selection\`)\n - \`true\` — Select participating speakers each time\n - \`false\` — All detected CLI + browser LLMs auto-join\n\n**2. Default rounds** (\`default_rounds\`)\n - \`1\` — Quick consensus\n - \`3\` — Default (recommended)\n - \`5\` — Deep discussion\n\n**3. Ordering strategy** (\`default_ordering\`)\n - \`"auto"\` — cyclic for 2 speakers, weighted-random for 3+ (recommended)\n - \`"cyclic"\` — Fixed order\n - \`"random"\` — Random each turn\n - \`"weighted-random"\` — Less spoken speakers first`,
2530
+ text: `🎉 **Welcome to Deliberation!**\n\nPlease configure basic settings before starting.\n\n**Currently detected speakers:**\n${candidateText}\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nYou can set all options at once:\n\n\`\`\`\ndeliberation_cli_config(\n require_speaker_selection: true/false,\n include_browser_speakers: false,\n default_rounds: 3,\n default_ordering: "auto"\n)\n\`\`\`\n\n**1. Speaker participation mode** (\`require_speaker_selection\`)\n - \`true\` — Select participating speakers each time\n - \`false\` — Auto-join detected speakers\n\n**2. Browser speakers** (\`include_browser_speakers\`)\n - \`false\` — CLI only (recommended)\n - \`true\` — Include browser LLM speakers too\n\n**3. Default rounds** (\`default_rounds\`)\n - \`1\` — Quick consensus\n - \`3\` — Default (recommended)\n - \`5\` — Deep discussion\n\n**4. Ordering strategy** (\`default_ordering\`)\n - \`"auto"\` — cyclic for 2 speakers, weighted-random for 3+ (recommended)\n - \`"cyclic"\` — Fixed order\n - \`"random"\` — Random each turn\n - \`"weighted-random"\` — Less spoken speakers first`,
2499
2531
  }],
2500
2532
  };
2501
2533
  }
@@ -2507,7 +2539,26 @@ server.tool(
2507
2539
  return { content: [{ type: "text", text: `❌ Session "${session_id}" is already active. Please use a different ID or reset it first.` }] };
2508
2540
  }
2509
2541
  }
2510
- const candidateSnapshot = await collectSpeakerCandidates({ include_cli: true, include_browser: true });
2542
+ const explicitBrowserSelection = hasExplicitBrowserParticipantSelection({ speakers, participant_types });
2543
+ const includeBrowserSpeakers = resolveIncludeBrowserSpeakers({
2544
+ include_browser_speakers,
2545
+ config,
2546
+ speakers,
2547
+ participant_types,
2548
+ });
2549
+ if (explicitBrowserSelection && !includeBrowserSpeakers) {
2550
+ return {
2551
+ content: [{
2552
+ type: "text",
2553
+ text: `❌ Browser speakers are currently disabled.\n\nThis deliberation server now defaults to CLI-only participation to avoid browser timeouts blocking the session.\n\nTo include browser speakers, opt in explicitly:\n\`\`\`\ndeliberation_start(\n topic: "${topic.replace(/"/g, '\\"')}",\n speakers: ${JSON.stringify(speakers || ["claude", "codex"])},\n include_browser_speakers: true,\n require_manual_speakers: true\n)\n\`\`\`\n\nOr save it in config:\n\`deliberation_cli_config(include_browser_speakers: true)\``,
2554
+ }],
2555
+ };
2556
+ }
2557
+
2558
+ const candidateSnapshot = await collectSpeakerCandidates({
2559
+ include_cli: true,
2560
+ include_browser: includeBrowserSpeakers,
2561
+ });
2511
2562
 
2512
2563
  // Resolve effective settings from config
2513
2564
  const effectiveRequireManual = require_manual_speakers ?? config.require_speaker_selection ?? true;
@@ -2536,7 +2587,7 @@ server.tool(
2536
2587
  return {
2537
2588
  content: [{
2538
2589
  type: "text",
2539
- text: `Speakers must be manually selected to start a deliberation.${configNote}${llmSuggested}\n\n${candidateText}\n\nExample:\n\ndeliberation_start(\n topic: "${topic.replace(/"/g, '\\"')}",\n rounds: ${rounds},\n speakers: ["codex", "web-claude-1", "web-chatgpt-1"],\n require_manual_speakers: true,\n first_speaker: "codex"\n)\n\nFirst call deliberation_speaker_candidates to check currently available speakers.`,
2590
+ text: `Speakers must be manually selected to start a deliberation.${configNote}${llmSuggested}\n\n${candidateText}\n\nExample:\n\ndeliberation_start(\n topic: "${topic.replace(/"/g, '\\"')}",\n rounds: ${rounds},\n speakers: ["claude", "codex", "gemini"],\n require_manual_speakers: true,\n first_speaker: "codex"\n)\n\nFirst call deliberation_speaker_candidates to check currently available speakers.`,
2540
2591
  }],
2541
2592
  };
2542
2593
  }
@@ -2544,7 +2595,6 @@ server.tool(
2544
2595
  let autoDiscoveredSpeakers = [];
2545
2596
  let autoParticipantTypes = {};
2546
2597
  if (!hasManualSpeakers && effectiveAutoDiscover) {
2547
- // Include ALL candidates: CLI + browser
2548
2598
  for (const c of candidateSnapshot.candidates) {
2549
2599
  autoDiscoveredSpeakers.push(c.speaker);
2550
2600
  if (c.type === "browser" && c.cdp_available) {
@@ -2575,12 +2625,11 @@ server.tool(
2575
2625
 
2576
2626
  // Warn if only 1 speaker — deliberation requires 2+
2577
2627
  if (speakerOrder.length < 2) {
2578
- const candidateSnapshot = await collectSpeakerCandidates({ include_cli: true, include_browser: true });
2579
2628
  const candidateText = formatSpeakerCandidatesReport(candidateSnapshot);
2580
2629
  return {
2581
2630
  content: [{
2582
2631
  type: "text",
2583
- text: `⚠️ Deliberation requires at least 2 speakers. Currently only ${speakerOrder.length} specified: ${speakerOrder.join(", ")}\n\nAvailable speaker candidates:\n${candidateText}\n\nExample:\ndeliberation_start(topic: "${topic.slice(0, 50)}...", speakers: ["claude", "codex", "web-gemini-1"])`,
2632
+ text: `⚠️ Deliberation requires at least 2 speakers. Currently only ${speakerOrder.length} specified: ${speakerOrder.join(", ")}\n\nAvailable speaker candidates:\n${candidateText}\n\nExample:\ndeliberation_start(topic: "${topic.slice(0, 50)}...", speakers: ["claude", "codex", "gemini"])`,
2584
2633
  }],
2585
2634
  };
2586
2635
  }
@@ -3324,6 +3373,41 @@ server.tool(
3324
3373
  })
3325
3374
  );
3326
3375
 
3376
+ server.tool(
3377
+ "deliberation_list_remote_sessions",
3378
+ "List all active deliberation sessions on a remote machine (via Tailscale/IP) to find the correct session_id for context injection.",
3379
+ {
3380
+ remote_url: z.string().describe("The Tailscale IP or Host and port (e.g., '100.100.100.5:3847') of the remote machine."),
3381
+ },
3382
+ safeToolHandler("deliberation_list_remote_sessions", async ({ remote_url }) => {
3383
+ try {
3384
+ const baseUrl = remote_url.startsWith("http") ? remote_url : `http://${remote_url}`;
3385
+ const cleanBaseUrl = baseUrl.replace(/\/$/, "");
3386
+ const response = await fetch(`${cleanBaseUrl}/api/sessions`);
3387
+
3388
+ if (!response.ok) {
3389
+ return { content: [{ type: "text", text: `❌ Failed to fetch remote sessions (${response.status})` }] };
3390
+ }
3391
+
3392
+ const sessions = await response.json();
3393
+ if (!Array.isArray(sessions) || sessions.length === 0) {
3394
+ return { content: [{ type: "text", text: `No active deliberation sessions found on ${remote_url}.` }] };
3395
+ }
3396
+
3397
+ let result = `### Active Sessions on ${remote_url}\n\n`;
3398
+ for (const s of sessions) {
3399
+ result += `- **ID:** \`${s.id}\`\n`;
3400
+ result += ` **Topic:** ${s.topic}\n`;
3401
+ result += ` **Status:** ${s.status} (Round ${s.current_round}/${s.max_rounds})\n\n`;
3402
+ }
3403
+
3404
+ return { content: [{ type: "text", text: result }] };
3405
+ } catch (e) {
3406
+ return { content: [{ type: "text", text: `❌ Error connecting to remote machine at ${remote_url}: ${e.message}` }] };
3407
+ }
3408
+ })
3409
+ );
3410
+
3327
3411
  server.tool(
3328
3412
  "deliberation_inject_context",
3329
3413
  "Inject additional context or instructions into a specific active session. (Useful for local or remote context injection via Tailscale)",
@@ -3631,6 +3715,10 @@ server.tool(
3631
3715
  (v) => (typeof v === "string" ? v === "true" : v),
3632
3716
  z.boolean().optional()
3633
3717
  ).describe("true: user selects speakers before each start, false: all detected speakers auto-join"),
3718
+ include_browser_speakers: z.preprocess(
3719
+ (v) => (typeof v === "string" ? v === "true" : v),
3720
+ z.boolean().optional()
3721
+ ).describe("true: browser LLM speakers may join when requested or auto-discovered, false: CLI-only mode"),
3634
3722
  default_rounds: z.coerce.number().int().min(1).max(10).optional()
3635
3723
  .describe("Default number of rounds (1-10, default 3)"),
3636
3724
  default_ordering: z.enum(["auto", "cyclic", "random", "weighted-random"]).optional()
@@ -3638,7 +3726,7 @@ server.tool(
3638
3726
  chrome_profile: z.string().optional()
3639
3727
  .describe("Chrome profile directory name for CDP (e.g., \"Default\", \"Profile 1\"). Stored for auto-launch."),
3640
3728
  },
3641
- safeToolHandler("deliberation_cli_config", async ({ enabled_clis, require_speaker_selection, default_rounds, default_ordering, chrome_profile }) => {
3729
+ safeToolHandler("deliberation_cli_config", async ({ enabled_clis, require_speaker_selection, include_browser_speakers, default_rounds, default_ordering, chrome_profile }) => {
3642
3730
  const config = loadDeliberationConfig();
3643
3731
 
3644
3732
  // Handle setup config updates
@@ -3647,6 +3735,10 @@ server.tool(
3647
3735
  config.require_speaker_selection = require_speaker_selection;
3648
3736
  configChanged = true;
3649
3737
  }
3738
+ if (include_browser_speakers !== undefined && include_browser_speakers !== null) {
3739
+ config.include_browser_speakers = include_browser_speakers;
3740
+ configChanged = true;
3741
+ }
3650
3742
  if (default_rounds !== undefined && default_rounds !== null) {
3651
3743
  config.default_rounds = default_rounds;
3652
3744
  configChanged = true;
@@ -3673,7 +3765,7 @@ server.tool(
3673
3765
  return {
3674
3766
  content: [{
3675
3767
  type: "text",
3676
- text: `## Deliberation CLI Settings\n\n**Mode:** ${mode}\n**Speaker selection:** ${config.require_speaker_selection === false ? "auto (all detected speakers join)" : "manual (user selects)"}\n**Default rounds:** ${config.default_rounds || 3}\n**Ordering:** ${config.default_ordering || "auto"}\n**Chrome profile:** ${config.chrome_profile || "Default"} (env: DELIBERATION_CHROME_PROFILE)\n**Configured CLIs:** ${configured.length > 0 ? configured.join(", ") : "(none — full auto-detection)"}\n**Currently detected CLIs:** ${detected.join(", ") || "(none)"}\n**All supported CLIs:** ${DEFAULT_CLI_CANDIDATES.join(", ")}\n\nTo change:\n\`deliberation_cli_config(require_speaker_selection: false, default_rounds: 3, default_ordering: "auto")\`\n\nTo set Chrome profile for CDP:\n\`deliberation_cli_config(chrome_profile: "Profile 1")\`\n\nTo revert to full auto-detection:\n\`deliberation_cli_config(enabled_clis: [])\``,
3768
+ text: `## Deliberation CLI Settings\n\n**Mode:** ${mode}\n**Speaker selection:** ${config.require_speaker_selection === false ? "auto (detected speakers join)" : "manual (user selects)"}\n**Browser speakers:** ${config.include_browser_speakers === true ? "enabled" : "disabled (CLI-only default)"}\n**Default rounds:** ${config.default_rounds || 3}\n**Ordering:** ${config.default_ordering || "auto"}\n**Chrome profile:** ${config.chrome_profile || "Default"} (env: DELIBERATION_CHROME_PROFILE)\n**Configured CLIs:** ${configured.length > 0 ? configured.join(", ") : "(none — full auto-detection)"}\n**Currently detected CLIs:** ${detected.join(", ") || "(none)"}\n**All supported CLIs:** ${DEFAULT_CLI_CANDIDATES.join(", ")}\n\nTo change:\n\`deliberation_cli_config(require_speaker_selection: false, include_browser_speakers: false, default_rounds: 3, default_ordering: "auto")\`\n\nTo enable browser speakers:\n\`deliberation_cli_config(include_browser_speakers: true)\`\n\nTo set Chrome profile for CDP:\n\`deliberation_cli_config(chrome_profile: "Profile 1")\`\n\nTo revert to full auto-detection:\n\`deliberation_cli_config(enabled_clis: [])\``,
3677
3769
  }],
3678
3770
  };
3679
3771
  }
@@ -4478,4 +4570,4 @@ if (__entryFile && path.resolve(__currentFile) === __entryFile) {
4478
4570
  }
4479
4571
 
4480
4572
  // ── Test exports (used by vitest) ──
4481
- export { selectNextSpeaker, loadRolePrompt, inferSuggestedRole, parseVotes, ROLE_KEYWORDS, ROLE_HEADING_MARKERS, loadRolePresets, applyRolePreset, detectDegradationLevels, formatDegradationReport, DEGRADATION_TIERS, DECISION_STAGES, STAGE_TRANSITIONS, createDecisionSession, advanceStage, buildConflictMap, parseOpinionFromResponse, buildOpinionPrompt, generateConflictQuestions, buildSynthesis, buildActionPlan, loadTemplates, matchTemplate };
4573
+ export { selectNextSpeaker, loadRolePrompt, inferSuggestedRole, parseVotes, ROLE_KEYWORDS, ROLE_HEADING_MARKERS, loadRolePresets, applyRolePreset, detectDegradationLevels, formatDegradationReport, DEGRADATION_TIERS, DECISION_STAGES, STAGE_TRANSITIONS, createDecisionSession, advanceStage, buildConflictMap, parseOpinionFromResponse, buildOpinionPrompt, generateConflictQuestions, buildSynthesis, buildActionPlan, loadTemplates, matchTemplate, hasExplicitBrowserParticipantSelection, resolveIncludeBrowserSpeakers };
package/model-router.js CHANGED
@@ -137,61 +137,50 @@ export function selectModelForProvider(provider, category, complexity) {
137
137
 
138
138
  switch (provider) {
139
139
  case 'chatgpt': {
140
- if (isHighReasoning) return { model: 'o3', reason: 'High-complexity reasoning task' };
141
- if (isCoding) return { model: 'o4-mini', reason: 'Coding/implementation task' };
142
- if (isSimple) return { model: 'gpt-4o-mini', reason: 'Simple task, cost-efficient model' };
143
- return { model: 'gpt-4o', reason: 'Creative or medium-complexity task' };
140
+ if (isHighReasoning || isCoding) return { model: 'gpt-5.4', reason: 'High-complexity or coding task' };
141
+ return { model: 'gpt-5.4', reason: 'General or simple task' };
144
142
  }
145
143
 
146
144
  case 'claude': {
147
- if (isHighReasoning) return { model: 'opus', reason: 'High-complexity reasoning task' };
148
- if (isSimple) return { model: 'haiku', reason: 'Simple task, fast and cost-efficient' };
149
- return { model: 'sonnet', reason: 'Coding or medium-complexity task' };
145
+ if (isSimple) return { model: 'sonnet', reason: 'Simple task' };
146
+ return { model: 'opus', reason: 'Medium or High-complexity task' };
150
147
  }
151
148
 
152
149
  case 'gemini': {
153
- if (isHighReasoning || complexity === 'high') return { model: '2.5 Pro', reason: 'High-complexity or reasoning task' };
154
- if (isSimple) return { model: '2.0 Flash', reason: 'Simple task, fast response' };
155
- return { model: '2.5 Flash', reason: 'Medium-complexity task' };
150
+ return { model: '3.1 Pro Preview', reason: 'Always use highest-end model' };
156
151
  }
157
152
 
158
153
  case 'deepseek': {
159
- if (category === 'reasoning') return { model: 'DeepSeek-R1', reason: 'Reasoning-focused task' };
160
- return { model: 'DeepSeek-V3', reason: 'General task' };
154
+ return { model: 'DeepSeek-R1', reason: 'High-end reasoning model' };
161
155
  }
162
156
 
163
157
  case 'grok': {
164
- if (complexity === 'high' || isHighReasoning) return { model: 'grok-3', reason: 'High-complexity task' };
165
- return { model: 'grok-3-mini', reason: 'Simple task' };
158
+ return { model: 'grok-3', reason: 'Always use highest-end model' };
166
159
  }
167
160
 
168
161
  case 'mistral': {
169
- if (complexity === 'high' || isHighReasoning) return { model: 'Mistral Large', reason: 'High-complexity task' };
170
- return { model: 'Mistral Small', reason: 'Simple task' };
162
+ return { model: 'Mistral Large', reason: 'Always use highest-end model' };
171
163
  }
172
164
 
173
165
  case 'poe': {
174
- if (complexity === 'high' || isHighReasoning) return { model: 'Claude-3.5-Sonnet', reason: 'High-complexity task' };
175
- if (isSimple) return { model: 'Claude-3-Haiku', reason: 'Simple task' };
176
- return { model: 'GPT-4o', reason: 'Medium-complexity task' };
166
+ if (isSimple) return { model: 'Claude-3.5-Sonnet', reason: 'Simple task' };
167
+ return { model: 'Claude-3.5-Opus', reason: 'Medium or High-complexity task' };
177
168
  }
178
169
 
179
170
  case 'qwen': {
180
- if (complexity === 'high' || isHighReasoning) return { model: 'Qwen-Max', reason: 'High-complexity task' };
181
- return { model: 'Qwen-Plus', reason: 'Simple task' };
171
+ return { model: 'Qwen-Max', reason: 'Always use highest-end model' };
182
172
  }
183
173
 
184
174
  case 'huggingchat': {
185
- if (complexity === 'high' || isHighReasoning) return { model: 'Qwen/QwQ-32B', reason: 'High-complexity task' };
186
- return { model: 'meta-llama/Llama-3.3-70B', reason: 'Simple task' };
175
+ return { model: 'Qwen/QwQ-32B', reason: 'Always use highest-end model' };
187
176
  }
188
177
 
189
178
  case 'copilot': {
190
- return { model: 'GPT-4o', reason: 'Copilot uses GPT-4o (no model selection)' };
179
+ return { model: 'GPT-4.5', reason: 'Copilot using latest high-end model' };
191
180
  }
192
181
 
193
182
  case 'perplexity': {
194
- return { model: 'default', reason: 'Perplexity uses default model (no model selection)' };
183
+ return { model: 'sonar-pro', reason: 'Perplexity high-end model' };
195
184
  }
196
185
 
197
186
  default: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-deliberation",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "description": "MCP Deliberation Server — Multi-session AI deliberation with smart speaker ordering and persona roles",
5
5
  "type": "module",
6
6
  "license": "MIT",