@link-assistant/hive-mind 2.0.21 → 2.0.23

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.0.23
4
+
5
+ ### Patch Changes
6
+
7
+ - 4778963: Add `--sub-agent-model` for Claude Code subagents and agent teams. The option is accepted by solve, hive, and Telegram command parsing, validates Claude aliases/full IDs plus `inherit`, and maps to `CLAUDE_CODE_SUBAGENT_MODEL` only when explicitly provided so Claude Code defaults remain unchanged.
8
+
9
+ ## 2.0.22
10
+
11
+ ### Patch Changes
12
+
13
+ - 4d65c05: Make disk admission safer by default: the disk usage queue gate now waits at 80%, the absolute free-space default is 10240 MB, and isolation defaults to Docker.
14
+
3
15
  ## 2.0.21
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.0.21",
3
+ "version": "2.0.23",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { resolveCodexReasoningEffort } from './codex.options.lib.mjs';
11
- import { mapModelForTool } from './models/index.mjs';
11
+ import { mapClaudeSubAgentModelToEnvValue, mapModelForTool } from './models/index.mjs';
12
12
  import { buildCodexDisable1mContextConfigArgs, buildCodexSubSessionSizeConfigArgs, parseSubSessionSize } from './sub-session-size.lib.mjs';
13
13
  import { detectUsageLimit } from './usage-limit.lib.mjs';
14
14
  import { getCacheReadTokenCount, getCumulativeContextInputTokens, getOutputTokenCount } from './context-fill.lib.mjs';
@@ -54,6 +54,7 @@ const buildClaudeToolOptions = (argv = {}) => {
54
54
  if (argv.disable1mContext) extraEnv.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
55
55
  if (argv.showThinkingContent) extraEnv.CLAUDE_CODE_SHOW_THINKING = '1';
56
56
  if (argv.planModel) extraEnv.ANTHROPIC_DEFAULT_OPUS_MODEL = argv.planModel;
57
+ if (argv.subAgentModel) extraEnv.CLAUDE_CODE_SUBAGENT_MODEL = mapClaudeSubAgentModelToEnvValue(argv.subAgentModel);
57
58
  appendExtraEnv(options, extraEnv);
58
59
 
59
60
  return options;
@@ -21,7 +21,7 @@ import { seedCumulativeAnthropicCost, addAnthropicRunCost } from './anthropic-co
21
21
  import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs'; // Issue #942
22
22
  import { SESSION_FORCE_KILLED_MARKER, postTrackedComment } from './tool-comments.lib.mjs'; // Issue #1625
23
23
  import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
24
- import { CLAUDE_MODELS as availableModels } from './models/index.mjs'; // Issue #1221
24
+ import { CLAUDE_MODELS as availableModels, mapClaudeSubAgentModelToEnvValue } from './models/index.mjs'; // Issue #1221, #1978
25
25
  import { buildMcpConfigWithoutPlaywright, ensureClaudePlaywrightMcpServer } from './playwright-mcp.lib.mjs';
26
26
  import { resolveClaudeSessionToolFlags } from './useless-tools.lib.mjs';
27
27
  import { ensureClaudeQuietConfig } from './claude-quiet-config.lib.mjs';
@@ -658,6 +658,7 @@ export const executeClaudeCommand = async params => {
658
658
  let execCommand;
659
659
  const mappedModel = mapModelToId(argv.model);
660
660
  const resolvedPlanModel = argv.planModel ? mapModelToId(argv.planModel) : undefined; // Issue #1223
661
+ const resolvedSubAgentModel = argv.subAgentModel ? mapClaudeSubAgentModelToEnvValue(argv.subAgentModel) : undefined; // Issue #1978
661
662
  const effectiveModel = resolvedPlanModel ? 'opusplan' : mappedModel;
662
663
  const resolvedExecutionModel = resolvedPlanModel ? mappedModel : undefined;
663
664
  // Issue #1949: Let Claude Code handle transient overload (529) fallback via its own
@@ -706,12 +707,13 @@ export const executeClaudeCommand = async params => {
706
707
  // Issue #1706: --sub-session-size + --disable-1m-context. Resolve here, then pass into getClaudeEnv along with the rest.
707
708
  const { parsed: parsedSubSessionSize, contextWindowTokens } = await resolveSubSessionSize({ rawValue: argv.subSessionSize, tool: 'claude', modelId: effectiveModel, fetchModelInfo, log });
708
709
  // Issue #817: streaming mode sets exitAfterStopDelayMs=60000 so the headless Claude process stays alive between NDJSON turns.
709
- const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget, model: effectiveModel, thinkLevel, maxBudget, planModel: resolvedPlanModel, executionModel: resolvedExecutionModel, showThinkingContent: argv.showThinkingContent, exitAfterStopDelayMs: streamingInput ? 60_000 : undefined, disable1mContext: !!argv.disable1mContext, subSessionSize: parsedSubSessionSize, contextWindowTokens });
710
+ const claudeEnv = getClaudeEnv({ thinkingBudget: resolvedThinkingBudget, model: effectiveModel, thinkLevel, maxBudget, planModel: resolvedPlanModel, executionModel: resolvedExecutionModel, subAgentModel: resolvedSubAgentModel, showThinkingContent: argv.showThinkingContent, exitAfterStopDelayMs: streamingInput ? 60_000 : undefined, disable1mContext: !!argv.disable1mContext, subSessionSize: parsedSubSessionSize, contextWindowTokens });
710
711
  if (argv.verbose) claudeEnv.ANTHROPIC_LOG = 'debug';
711
712
  const modelMaxOutputTokens = getMaxOutputTokensForModel(effectiveModel);
712
713
  if (argv.verbose) {
713
714
  await log(`📊 CLAUDE_CODE_MAX_OUTPUT_TOKENS: ${modelMaxOutputTokens}, MCP_TIMEOUT: ${claudeCode.mcpTimeout}ms, MCP_TOOL_TIMEOUT: ${claudeCode.mcpToolTimeout}ms, ANTHROPIC_LOG: debug`, { verbose: true });
714
715
  if (resolvedPlanModel) await log(`📊 opusplan: plan=${resolvedPlanModel}, exec=${resolvedExecutionModel}`, { verbose: true });
716
+ if (claudeEnv.CLAUDE_CODE_SUBAGENT_MODEL) await log(`📊 CLAUDE_CODE_SUBAGENT_MODEL: ${claudeEnv.CLAUDE_CODE_SUBAGENT_MODEL}`, { verbose: true });
715
717
  if (resolvedThinkingBudget !== undefined) await log(`📊 MAX_THINKING_TOKENS: ${resolvedThinkingBudget}`, { verbose: true });
716
718
  if (claudeEnv.CLAUDE_CODE_EFFORT_LEVEL) await log(`📊 CLAUDE_CODE_EFFORT_LEVEL: ${claudeEnv.CLAUDE_CODE_EFFORT_LEVEL}`, { verbose: true });
717
719
  if (claudeEnv.CLAUDE_CODE_SHOW_THINKING) await log(`📊 CLAUDE_CODE_SHOW_THINKING: ${claudeEnv.CLAUDE_CODE_SHOW_THINKING}`, { verbose: true });
@@ -111,7 +111,7 @@ export const githubLimits = {
111
111
 
112
112
  // Memory and disk configurations
113
113
  export const systemLimits = {
114
- minDiskSpaceMb: parseIntWithDefault('HIVE_MIND_MIN_DISK_SPACE_MB', 2048),
114
+ minDiskSpaceMb: parseIntWithDefault('HIVE_MIND_MIN_DISK_SPACE_MB', 10240),
115
115
  defaultPageSizeKb: parseIntWithDefault('HIVE_MIND_DEFAULT_PAGE_SIZE_KB', 16),
116
116
  };
117
117
 
@@ -543,9 +543,11 @@ export const supportsThinkingBudget = (version, minVersion = '2.1.12') => {
543
543
  // Supports planModel/executionModel for opusplan mode (Issue #1223)
544
544
  // Issue #1706: supports subSessionSize (parsed) + disable1mContext to cap
545
545
  // auto-compaction sub-session size and opt out of the 1M extended context.
546
+ // Issue #1978: supports subAgentModel for Claude Code native subagents and agent teams.
546
547
  // See: https://code.claude.com/docs/en/env-vars and https://code.claude.com/docs/en/model-config
547
548
  // ANTHROPIC_DEFAULT_OPUS_MODEL → model used in plan mode (and for 'opus' alias)
548
549
  // ANTHROPIC_DEFAULT_SONNET_MODEL → model used in execution mode (and for 'sonnet' alias)
550
+ // CLAUDE_CODE_SUBAGENT_MODEL → model used by all subagents and agent teams
549
551
  // CLAUDE_CODE_DISABLE_1M_CONTEXT, CLAUDE_CODE_AUTO_COMPACT_WINDOW, CLAUDE_AUTOCOMPACT_PCT_OVERRIDE
550
552
  export const getClaudeEnv = (options = {}) => {
551
553
  // Get max output tokens based on model (Issue #1221)
@@ -619,6 +621,12 @@ export const getClaudeEnv = (options = {}) => {
619
621
  env.ANTHROPIC_DEFAULT_SONNET_MODEL = String(options.executionModel);
620
622
  }
621
623
 
624
+ // Issue #1978: Set Claude Code native subagent/agent-team model only when
625
+ // explicitly requested. Leaving this unset preserves Claude Code defaults.
626
+ if (options.subAgentModel) {
627
+ env.CLAUDE_CODE_SUBAGENT_MODEL = String(options.subAgentModel);
628
+ }
629
+
622
630
  // Issue #1706: --disable-1m-context. Sets CLAUDE_CODE_DISABLE_1M_CONTEXT=1.
623
631
  if (options.disable1mContext) {
624
632
  env.CLAUDE_CODE_DISABLE_1M_CONTEXT = '1';
package/src/hive.mjs CHANGED
@@ -74,7 +74,7 @@ if (isRunningDirectly) {
74
74
  const { validateClaudeConnection } = claudeLib;
75
75
  // Import model validation library
76
76
  const modelValidation = await import('./models/index.mjs');
77
- const { validateAndExitOnInvalidModel, defaultModels, resolveRuntimeDefaultModel } = modelValidation;
77
+ const { validateAndExitOnInvalidClaudeSubAgentModel, validateAndExitOnInvalidModel, defaultModels, resolveRuntimeDefaultModel } = modelValidation;
78
78
  const githubLib = await import('./github.lib.mjs');
79
79
  const { checkGitHubPermissions, fetchAllIssuesWithPagination, fetchProjectIssues, isRateLimitError, batchCheckPullRequestsForIssues, parseGitHubUrl, batchCheckArchivedRepositories } = githubLib;
80
80
  // Import YouTrack-related functions
@@ -473,6 +473,9 @@ if (isRunningDirectly) {
473
473
  }
474
474
  await validateAndExitOnInvalidModel(argv.planModel, tool, safeExit);
475
475
  }
476
+ if (argv.subAgentModel) {
477
+ await validateAndExitOnInvalidClaudeSubAgentModel(argv.subAgentModel, tool, safeExit);
478
+ }
476
479
 
477
480
  // Handle -s (--skip-issues-with-prs) and --auto-continue interaction
478
481
  const hasExplicitAutoContinue = rawArgs.includes('--auto-continue');
@@ -1444,7 +1447,7 @@ if (isRunningDirectly) {
1444
1447
  } else {
1445
1448
  const systemCheck = await checkSystem(
1446
1449
  {
1447
- minDiskSpaceMB: argv.minDiskSpace || 2048,
1450
+ minDiskSpaceMB: argv.minDiskSpace || 10240,
1448
1451
  minMemoryMB: 256,
1449
1452
  exitOnFailure: true,
1450
1453
  },
@@ -26,7 +26,7 @@ const lib = await import('./lib.mjs');
26
26
  const { log: libLog, setLogFile } = lib;
27
27
 
28
28
  // Function to check available disk space
29
- export const checkDiskSpace = async (minSpaceMB = 2048, options = {}) => {
29
+ export const checkDiskSpace = async (minSpaceMB = 10240, options = {}) => {
30
30
  const log = options.log || libLog;
31
31
 
32
32
  try {
@@ -289,7 +289,7 @@ export const getResourceSnapshot = async () => {
289
289
 
290
290
  // Combined system check function
291
291
  export const checkSystem = async (requirements = {}, options = {}) => {
292
- const { minMemoryMB = 256, minDiskSpaceMB = 2048, exitOnFailure = false } = requirements;
292
+ const { minMemoryMB = 256, minDiskSpaceMB = 10240, exitOnFailure = false } = requirements;
293
293
 
294
294
  // Note: log is passed through options to checkDiskSpace and checkRAM
295
295
  const results = {
@@ -334,7 +334,7 @@ const createMemoryCheckYargsConfig = yargsInstance =>
334
334
  alias: 'd',
335
335
  type: 'number',
336
336
  description: 'Minimum required disk space in MB',
337
- default: 2048,
337
+ default: 10240,
338
338
  })
339
339
  .option('exit-on-failure', {
340
340
  alias: 'e',
@@ -742,6 +742,62 @@ export const validateModelName = (model, tool = 'claude') => {
742
742
  };
743
743
  };
744
744
 
745
+ export const CLAUDE_SUB_AGENT_MODEL_INHERIT = 'inherit';
746
+
747
+ export const normalizeClaudeSubAgentModelName = model => {
748
+ if (model === undefined || model === null) return model;
749
+ if (typeof model !== 'string') return model;
750
+
751
+ const trimmed = model.trim();
752
+ return trimmed.toLowerCase() === CLAUDE_SUB_AGENT_MODEL_INHERIT ? CLAUDE_SUB_AGENT_MODEL_INHERIT : trimmed;
753
+ };
754
+
755
+ const looksLikeClaudeProviderModelId = model => {
756
+ if (typeof model !== 'string') return false;
757
+ const normalized = model.toLowerCase();
758
+ return normalized.startsWith('claude-') || normalized.startsWith('anthropic/') || normalized.startsWith('anthropic.') || normalized.includes('.anthropic.');
759
+ };
760
+
761
+ /**
762
+ * Validate the Claude Code subagent/agent-team model override.
763
+ *
764
+ * Claude Code documents CLAUDE_CODE_SUBAGENT_MODEL as accepting full provider
765
+ * model IDs, normal Claude model aliases, and the special value "inherit".
766
+ * Keep "inherit" scoped to this option so it does not become a valid main
767
+ * session model.
768
+ *
769
+ * @param {string} model - The subagent model override value
770
+ * @returns {{ valid: boolean, message?: string, suggestions?: string[], mappedModel?: string }}
771
+ */
772
+ export const validateClaudeSubAgentModelName = model => {
773
+ const normalized = normalizeClaudeSubAgentModelName(model);
774
+
775
+ if (normalized === CLAUDE_SUB_AGENT_MODEL_INHERIT) {
776
+ return {
777
+ valid: true,
778
+ mappedModel: CLAUDE_SUB_AGENT_MODEL_INHERIT,
779
+ };
780
+ }
781
+
782
+ const validation = validateModelName(normalized, 'claude');
783
+ if (validation.valid) return validation;
784
+ if (looksLikeClaudeProviderModelId(normalized)) {
785
+ return {
786
+ valid: true,
787
+ mappedModel: normalized,
788
+ };
789
+ }
790
+
791
+ return validation;
792
+ };
793
+
794
+ export const mapClaudeSubAgentModelToEnvValue = model => {
795
+ const result = validateClaudeSubAgentModelName(model);
796
+ if (result.valid && result.mappedModel) return result.mappedModel;
797
+ const normalized = normalizeClaudeSubAgentModelName(model);
798
+ return mapModelForTool('claude', normalized);
799
+ };
800
+
745
801
  /**
746
802
  * Validate model name and exit with error if invalid
747
803
  * This is the main entry point for model validation in solve.mjs, hive.mjs, etc.
@@ -767,6 +823,46 @@ export const validateAndExitOnInvalidModel = async (model, tool = 'claude', exit
767
823
  return true;
768
824
  };
769
825
 
826
+ /**
827
+ * Validate --sub-agent-model and exit with error if invalid.
828
+ *
829
+ * This option maps to Claude Code's CLAUDE_CODE_SUBAGENT_MODEL, so it is only
830
+ * meaningful for the Claude tool even when solve/hive supports multiple tools.
831
+ *
832
+ * @param {string} model - The subagent model override value
833
+ * @param {string} tool - The selected tool
834
+ * @param {Function} exitFn - Function to call for exiting (default: process.exit)
835
+ * @returns {Promise<boolean>} True if valid, exits process if invalid
836
+ */
837
+ export const validateAndExitOnInvalidClaudeSubAgentModel = async (model, tool = 'claude', exitFn = null) => {
838
+ if (model === undefined || model === null || model === '') return true;
839
+
840
+ if (tool !== 'claude') {
841
+ await log(`❌ --sub-agent-model is only supported with --tool claude (current tool: ${tool})`, { level: 'error' });
842
+ if (exitFn) {
843
+ await exitFn(1, '--sub-agent-model requires --tool claude');
844
+ } else {
845
+ process.exit(1);
846
+ }
847
+ return false;
848
+ }
849
+
850
+ const result = validateClaudeSubAgentModelName(model);
851
+
852
+ if (!result.valid) {
853
+ await log(`❌ Invalid --sub-agent-model: ${result.message}`, { level: 'error' });
854
+
855
+ if (exitFn) {
856
+ await exitFn(1, 'Invalid sub-agent model name');
857
+ } else {
858
+ process.exit(1);
859
+ }
860
+ return false;
861
+ }
862
+
863
+ return true;
864
+ };
865
+
770
866
  /**
771
867
  * Format the list of available models for help text
772
868
  * @param {string} tool - The tool name
@@ -22,6 +22,7 @@ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
22
22
  *
23
23
  * @see https://github.com/link-assistant/hive-mind/issues/1242
24
24
  * @see https://github.com/link-assistant/hive-mind/issues/1253
25
+ * @see https://github.com/link-assistant/hive-mind/issues/1981
25
26
  */
26
27
 
27
28
  // Use use-m to dynamically import modules
@@ -236,9 +237,8 @@ function getThresholdConfig(linoKey, envVarThreshold, envVarStrategy, defaultThr
236
237
  * - 'enqueue': Block and wait in queue
237
238
  * - 'dequeue-one-at-a-time': Allow one command, block subsequent
238
239
  *
239
- * BREAKING CHANGE: Disk threshold default strategy changed from 'dequeue-one-at-a-time' to 'reject'
240
- * because the queue is lost on server restart anyway, so there's no point in queueing.
241
- * To restore old behavior: HIVE_MIND_DISK_STRATEGY=dequeue-one-at-a-time
240
+ * Issue #1981: disk now defaults to the normal wait/enqueue path at 80% used.
241
+ * Operators can still choose immediate rejection with HIVE_MIND_DISK_STRATEGY=reject.
242
242
  */
243
243
  export const QUEUE_CONFIG = {
244
244
  // Threshold configurations with value and strategy
@@ -246,10 +246,8 @@ export const QUEUE_CONFIG = {
246
246
  thresholds: {
247
247
  ram: getThresholdConfig('ram', 'HIVE_MIND_RAM_THRESHOLD', 'HIVE_MIND_RAM_STRATEGY', 0.65, 'enqueue'),
248
248
  cpu: getThresholdConfig('cpu', 'HIVE_MIND_CPU_THRESHOLD', 'HIVE_MIND_CPU_STRATEGY', 0.65, 'enqueue'),
249
- // BREAKING: disk default changed from 'dequeue-one-at-a-time' to 'reject'
250
- // Queue is in RAM and lost on restart - no point enlarging it when disk is full
251
- // See: https://github.com/link-assistant/hive-mind/issues/1253
252
- disk: getThresholdConfig('disk', 'HIVE_MIND_DISK_THRESHOLD', 'HIVE_MIND_DISK_STRATEGY', 0.9, 'reject'),
249
+ // Issue #1981: wait instead of immediately rejecting when disk crosses 80%.
250
+ disk: getThresholdConfig('disk', 'HIVE_MIND_DISK_THRESHOLD', 'HIVE_MIND_DISK_STRATEGY', 0.8, 'enqueue'),
253
251
  claude5Hour: getThresholdConfig('claude5Hour', 'HIVE_MIND_CLAUDE_5_HOUR_SESSION_THRESHOLD', 'HIVE_MIND_CLAUDE_5_HOUR_SESSION_STRATEGY', 0.65, 'dequeue-one-at-a-time'),
254
252
  claudeWeekly: getThresholdConfig('claudeWeekly', 'HIVE_MIND_CLAUDE_WEEKLY_THRESHOLD', 'HIVE_MIND_CLAUDE_WEEKLY_STRATEGY', 0.97, 'dequeue-one-at-a-time'),
255
253
  codex5Hour: getThresholdConfig('codex5Hour', 'HIVE_MIND_CODEX_5_HOUR_SESSION_THRESHOLD', 'HIVE_MIND_CODEX_5_HOUR_SESSION_STRATEGY', 0.65, 'dequeue-one-at-a-time'),
@@ -265,7 +263,7 @@ export const QUEUE_CONFIG = {
265
263
  // These are derived from thresholds.{metric}.value
266
264
  RAM_THRESHOLD: getThresholdConfig('ram', 'HIVE_MIND_RAM_THRESHOLD', 'HIVE_MIND_RAM_STRATEGY', 0.65, 'enqueue').value,
267
265
  CPU_THRESHOLD: getThresholdConfig('cpu', 'HIVE_MIND_CPU_THRESHOLD', 'HIVE_MIND_CPU_STRATEGY', 0.65, 'enqueue').value,
268
- DISK_THRESHOLD: getThresholdConfig('disk', 'HIVE_MIND_DISK_THRESHOLD', 'HIVE_MIND_DISK_STRATEGY', 0.9, 'reject').value,
266
+ DISK_THRESHOLD: getThresholdConfig('disk', 'HIVE_MIND_DISK_THRESHOLD', 'HIVE_MIND_DISK_STRATEGY', 0.8, 'enqueue').value,
269
267
  CLAUDE_5_HOUR_SESSION_THRESHOLD: getThresholdConfig('claude5Hour', 'HIVE_MIND_CLAUDE_5_HOUR_SESSION_THRESHOLD', 'HIVE_MIND_CLAUDE_5_HOUR_SESSION_STRATEGY', 0.65, 'dequeue-one-at-a-time').value,
270
268
  CLAUDE_WEEKLY_THRESHOLD: getThresholdConfig('claudeWeekly', 'HIVE_MIND_CLAUDE_WEEKLY_THRESHOLD', 'HIVE_MIND_CLAUDE_WEEKLY_STRATEGY', 0.97, 'dequeue-one-at-a-time').value,
271
269
  CODEX_5_HOUR_SESSION_THRESHOLD: getThresholdConfig('codex5Hour', 'HIVE_MIND_CODEX_5_HOUR_SESSION_THRESHOLD', 'HIVE_MIND_CODEX_5_HOUR_SESSION_STRATEGY', 0.65, 'dequeue-one-at-a-time').value,
@@ -289,8 +289,8 @@ export const SOLVE_OPTION_DEFINITIONS = {
289
289
  },
290
290
  'min-disk-space': {
291
291
  type: 'number',
292
- description: 'Minimum required disk space in MB (default: 2048)',
293
- default: 2048,
292
+ description: 'Minimum required disk space in MB (default: 10240)',
293
+ default: 10240,
294
294
  },
295
295
  'log-dir': {
296
296
  type: 'string',
@@ -333,6 +333,11 @@ export const SOLVE_OPTION_DEFINITIONS = {
333
333
  description: 'Fallback model to switch to on model capacity/overload errors (and, for Fable 5, on safety-classifier refusals). When supported, retries resume the same session with this model. Defaults: claude fable/claude-fable-5 -> opus (Opus 4.8); claude mythos-5/claude-mythos-5 -> fable; claude opus/opus-4-8 -> opus-4-7; claude opus-4-7 -> opus-4-6; codex gpt-5.5 -> gpt-5.4; all others unset.',
334
334
  default: undefined,
335
335
  },
336
+ 'sub-agent-model': {
337
+ type: 'string',
338
+ description: 'Claude Code subagent/agent-team model override. Sets CLAUDE_CODE_SUBAGENT_MODEL only when provided. Accepts Claude model aliases, full model IDs, or "inherit" to use normal Claude Code subagent model resolution. Only works with --tool claude.',
339
+ default: undefined,
340
+ },
336
341
  'show-thinking-content': {
337
342
  type: 'boolean',
338
343
  description: 'Show thinking content in Claude responses. Opus 4.7+ omits thinking content by default (applies to Opus 4.8 as well); this option opts in to receive summarized thinking blocks. Disabled by default. Only affects --tool claude.',
package/src/solve.mjs CHANGED
@@ -64,7 +64,7 @@ const { attachFinalLogIfMissing } = await import('./attach-logs-guarantee.lib.mj
64
64
  // "did the AI post anything?" check in --auto-attach-solution-summary).
65
65
  const { postTrackedComment, USAGE_LIMIT_REACHED_MARKER } = await import('./tool-comments.lib.mjs');
66
66
  const { prepareFeedbackAndTimestamps, checkUncommittedChanges, checkForkActions } = await import('./solve.preparation.lib.mjs');
67
- const { validateAndExitOnInvalidModel } = await import('./models/index.mjs');
67
+ const { validateAndExitOnInvalidClaudeSubAgentModel, validateAndExitOnInvalidModel } = await import('./models/index.mjs');
68
68
  const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs');
69
69
  const { handleAutoForkOption, handleMaintainerForkAccess } = await import('./solve.fork-detection.lib.mjs');
70
70
  // Initialize log file early (before argument parsing) to capture all output
@@ -243,12 +243,13 @@ if (argv.planModel) {
243
243
  }
244
244
  await validateAndExitOnInvalidModel(argv.planModel, tool, safeExit);
245
245
  }
246
+ if (argv.subAgentModel) await validateAndExitOnInvalidClaudeSubAgentModel(argv.subAgentModel, tool, safeExit);
246
247
 
247
248
  // Perform all system checks (skip tool connection check in dry-run or when --skip-tool-connection-check; model validation always runs)
248
249
  const skipToolConnectionCheck = argv.dryRun || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false;
249
250
  const { cascadePlaywrightMcpDisable, ensureSolvePlaywrightMcpReady } = await import('./playwright-mcp.lib.mjs');
250
251
  await cascadePlaywrightMcpDisable(argv, log);
251
- if (!(await performSystemChecks(argv.minDiskSpace || 2048, skipToolConnectionCheck, argv.model, argv))) {
252
+ if (!(await performSystemChecks(argv.minDiskSpace || 10240, skipToolConnectionCheck, argv.model, argv))) {
252
253
  await safeExit(1, 'System checks failed');
253
254
  }
254
255
  // Playwright MCP preflight is local/free and stays independent from paid tool connection checks.
@@ -54,7 +54,7 @@ const { parseResetTime: parseResetTimeToDate } = usageLimitLib;
54
54
  const { validateClaudeConnection } = claudeLib;
55
55
 
56
56
  // Wrapper function for disk space check using imported module
57
- const checkDiskSpace = async (minSpaceMB = 2048) => {
57
+ const checkDiskSpace = async (minSpaceMB = 10240) => {
58
58
  const result = await memoryCheck.checkDiskSpace(minSpaceMB, { log });
59
59
  return result.success;
60
60
  };
@@ -216,7 +216,7 @@ export const validateContinueOnlyOnFeedback = async (argv, isPrUrl, isIssueUrl)
216
216
  // Perform all system checks (disk space, memory, tool connection, GitHub permissions)
217
217
  // Note: skipToolConnection only skips the connection check, not model validation
218
218
  // Model validation should be done separately before calling this function
219
- export const performSystemChecks = async (minDiskSpace = 2048, skipToolConnection = false, model = 'sonnet', argv = {}) => {
219
+ export const performSystemChecks = async (minDiskSpace = 10240, skipToolConnection = false, model = 'sonnet', argv = {}) => {
220
220
  // Check disk space before proceeding
221
221
  const hasEnoughSpace = await checkDiskSpace(minDiskSpace);
222
222
  if (!hasEnoughSpace) {
@@ -61,7 +61,7 @@ export const createYargsConfig = yargsInstance =>
61
61
  type: 'string',
62
62
  description: 'agent-commander isolation mode',
63
63
  choices: ['screen', 'none', 'docker'],
64
- default: 'screen',
64
+ default: 'docker',
65
65
  })
66
66
  .option('screen-name', {
67
67
  type: 'string',
package/src/task.mjs CHANGED
@@ -35,7 +35,7 @@ if (earlyArgs.length === 0 || earlyArgs.includes('--help') || earlyArgs.includes
35
35
  console.log(' --split-count Number of issues to split into [default: 2]');
36
36
  console.log(' --tool AI tool for agent-commander read-only mode (claude, codex, opencode, agent, qwen, gemini) [default: claude]');
37
37
  console.log(' --model, -m Model to use');
38
- console.log(' --isolation agent-commander isolation mode [default: screen]');
38
+ console.log(' --isolation agent-commander isolation mode [default: docker]');
39
39
  console.log(' --dry-run Print split output without creating GitHub issues');
40
40
  console.log(' --verbose, -v Enable verbose logging');
41
41
  console.log(' --output-format Output format (text or json) [default: text]');
@@ -102,7 +102,7 @@ const config = yargs(hideBin(process.argv))
102
102
  .option('autoStartScreenWatchMessage', { type: 'boolean', description: 'Experimental: auto-start separate /terminal_watch messages for public /solve sessions', alias: 'auto-start-screen-watch-message', default: getenv('TELEGRAM_AUTO_START_SCREEN_WATCH_MESSAGE', getenv('TELEGRAM_AUTO_WATCH_MESSAGE', 'false')) === 'true' })
103
103
  // Issue #594: bot-owner toggle for --show-limits virtual option in /solve and /hive.
104
104
  .option('showLimits', { type: 'boolean', description: 'Experimental: allow /solve and /hive callers to use --show-limits to embed Claude/Codex usage at start, end, and delta in the completion message', alias: 'show-limits', default: getenv('TELEGRAM_SHOW_LIMITS', 'true') !== 'false' })
105
- .option('isolation', { type: 'string', description: "Isolation backend (screen/tmux/docker). Defaults to 'screen' so Telegram-bot work sessions survive bot restarts; pass --isolation '' (or set TELEGRAM_ISOLATION='') to disable.", default: getenv('TELEGRAM_ISOLATION', 'screen') })
105
+ .option('isolation', { type: 'string', description: "Isolation backend (screen/tmux/docker). Defaults to 'docker' so Telegram-bot work sessions run in Docker isolation; pass --isolation '' (or set TELEGRAM_ISOLATION='') to disable.", default: getenv('TELEGRAM_ISOLATION', 'docker') })
106
106
  .help('h')
107
107
  .alias('h', 'help')
108
108
  .parserConfiguration({
@@ -316,7 +316,7 @@ if (config.dryRun) {
316
316
  const { buildUserMention } = await import('./buildUserMention.lib.mjs');
317
317
  const { reportError, initializeSentry, addBreadcrumb } = await import('./sentry.lib.mjs');
318
318
  const { parseGitHubUrl, validateGitHubEntityExistence } = await import('./github.lib.mjs');
319
- const { validateModelName, buildModelOptionDescription } = await import('./models/index.mjs');
319
+ const { validateClaudeSubAgentModelName, validateModelName, buildModelOptionDescription } = await import('./models/index.mjs');
320
320
  const { resolveIsolation, createIsolationAwareQueueCallback } = await import('./telegram-isolation.lib.mjs');
321
321
  const limitsLib = await import('./limits.lib.mjs');
322
322
  const { formatUsageMessage, formatCodexLimitsSection, getAllCachedLimits } = limitsLib;
@@ -435,6 +435,12 @@ function validateModelInArgs(args, tool = 'claude') {
435
435
  if (!validation.valid) {
436
436
  return validation.message;
437
437
  }
438
+ } else if (args[i] === '--sub-agent-model' || args[i].startsWith('--sub-agent-model=')) {
439
+ const modelName = args[i] === '--sub-agent-model' ? args[i + 1] : args[i].substring('--sub-agent-model='.length);
440
+ if (!modelName) continue;
441
+ if (tool !== 'claude') return `--sub-agent-model is only supported with --tool claude (current tool: ${tool})`;
442
+ const validation = validateClaudeSubAgentModelName(modelName);
443
+ if (!validation.valid) return `Invalid --sub-agent-model: ${validation.message}`;
438
444
  }
439
445
  }
440
446
  return null;
@@ -724,10 +724,11 @@ export class SolveQueue {
724
724
  * Default strategies:
725
725
  * - RAM: enqueue
726
726
  * - CPU: enqueue
727
- * - DISK: reject (changed from dequeue-one-at-a-time - queue lost on restart)
727
+ * - DISK: enqueue (waits until disk drops below the threshold)
728
728
  *
729
729
  * See: https://github.com/link-assistant/hive-mind/issues/1155
730
730
  * See: https://github.com/link-assistant/hive-mind/issues/1253
731
+ * See: https://github.com/link-assistant/hive-mind/issues/1981
731
732
  *
732
733
  * @param {number} totalProcessing - Total processing count (queue + external claude processes)
733
734
  * @returns {Promise<{ok: boolean, reasons: string[], oneAtATime: boolean, rejected: boolean, rejectReason: string|null}>}
@@ -584,7 +584,7 @@ export function registerStartStopCommands(bot, options) {
584
584
  // immediately and was dispatched to a detached session) but the session
585
585
  // monitor still tracks a running isolated session for this URL, forward
586
586
  // CTRL+C to its start-command UUID. This is the common case for tasks
587
- // that begin executing right away with `--isolation screen`.
587
+ // that begin executing right away with an isolation backend.
588
588
  const queueHasTask = lookup.action === 'cancel-queued' || lookup.action === 'stop-running';
589
589
  if (!queueHasTask && runningSession?.stoppable && runningSession.sessionId) {
590
590
  VERBOSE && console.log(`[VERBOSE] /stop: forwarding CTRL+C to tracked session ${runningSession.sessionId} for ${url} (queue action=${lookup.action})`);
@@ -597,7 +597,7 @@ export function registerStartStopCommands(bot, options) {
597
597
  // running-but-non-stoppable (non-isolation) session, say so; otherwise
598
598
  // fall back to the UUID hint.
599
599
  if (runningSession) {
600
- await ctx.reply(`âš ī¸ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time you can run the command with \`--isolation screen\` to make this task interruptible via \`/stop\`.`, {
600
+ await ctx.reply(`âš ī¸ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time run it with the default isolation backend or pass \`--isolation docker\` to make this task interruptible via \`/stop\`.`, {
601
601
  parse_mode: 'Markdown',
602
602
  reply_to_message_id: message.message_id,
603
603
  });
@@ -615,13 +615,13 @@ export function registerStartStopCommands(bot, options) {
615
615
  // have forwarded CTRL+C above). If it tracked a non-isolation session,
616
616
  // explain why it can't be stopped; otherwise report not found.
617
617
  if (runningSession) {
618
- await ctx.reply(`âš ī¸ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time you can run the command with \`--isolation screen\` to make this task interruptible via \`/stop\`.`, {
618
+ await ctx.reply(`âš ī¸ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time run it with the default isolation backend or pass \`--isolation docker\` to make this task interruptible via \`/stop\`.`, {
619
619
  parse_mode: 'Markdown',
620
620
  reply_to_message_id: message.message_id,
621
621
  });
622
622
  return;
623
623
  }
624
- await ctx.reply(`â„šī¸ No queued or running task found for ${url}.\n\nIf the task is running with \`--isolation screen\`, try \`/stop <UUID>\` (the UUID is shown in the bot's session-id message).`, {
624
+ await ctx.reply(`â„šī¸ No queued or running task found for ${url}.\n\nIf the task is running with an isolation backend, try \`/stop <UUID>\` (the UUID is shown in the bot's session-id message).`, {
625
625
  parse_mode: 'Markdown',
626
626
  reply_to_message_id: message.message_id,
627
627
  });
@@ -655,7 +655,7 @@ export function registerStartStopCommands(bot, options) {
655
655
  // running-not-isolated: a started, non-isolated screen session. We
656
656
  // could shell out to `screen -X -S <name> stuff $'\003'`, but that's
657
657
  // brittle and out of scope for #1780. Tell the user how to recover.
658
- await ctx.reply(`âš ī¸ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time you can run the command with \`--isolation screen\` to make this task interruptible via \`/stop\`.`, {
658
+ await ctx.reply(`âš ī¸ Found a running task for ${url}, but it was not started with an isolation backend, so \`/stop\` cannot forward CTRL+C to it.\n\nNext time run it with the default isolation backend or pass \`--isolation docker\` to make this task interruptible via \`/stop\`.`, {
659
659
  parse_mode: 'Markdown',
660
660
  reply_to_message_id: message.message_id,
661
661
  });