@link-assistant/hive-mind 2.5.4 → 2.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.5.6
4
+
5
+ ### Patch Changes
6
+
7
+ - e501237: Warn when malformed or safety-clamped numeric environment settings fall back to a different effective value.
8
+ - 10ddbca: Document same-session live input support and the Codex app-server integration plan for issue #2057.
9
+
10
+ ## 2.5.5
11
+
12
+ ### Patch Changes
13
+
14
+ - e6ccdc1: Allow operators to lower the queue startup interval safety floor with `HIVE_MIND_MIN_START_INTERVAL_FLOOR_MS` while preserving the 10-minute default.
15
+
3
16
  ## 2.5.4
4
17
 
5
18
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.5.4",
3
+ "version": "2.5.6",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -30,23 +30,13 @@ const getenv = typeof getenvModule === 'function' ? getenvModule : getenvModule.
30
30
  // Use semver package for version comparison (see issue #1146)
31
31
  import semver from 'semver';
32
32
  import { buildClaudeQuietEnv } from './claude-quiet-config.lib.mjs';
33
+ import { clampEnvValue, parseIntegerEnv, parseNumberEnv } from './env-config.lib.mjs';
33
34
 
34
35
  // Import lino for parsing Links Notation format
35
36
  const { lino } = await import('./lino.lib.mjs');
36
37
 
37
- // Helper function to safely parse integers with fallback
38
- const parseIntWithDefault = (envVar, defaultValue) => {
39
- const value = getenv(envVar, defaultValue.toString());
40
- const parsed = parseInt(value);
41
- return isNaN(parsed) ? defaultValue : parsed;
42
- };
43
-
44
- // Helper function to safely parse floats with fallback
45
- const parseFloatWithDefault = (envVar, defaultValue) => {
46
- const value = getenv(envVar, defaultValue.toString());
47
- const parsed = parseFloat(value);
48
- return isNaN(parsed) ? defaultValue : parsed;
49
- };
38
+ const parseIntWithDefault = (envVar, defaultValue) => parseIntegerEnv(envVar, defaultValue);
39
+ const parseFloatWithDefault = (envVar, defaultValue) => parseNumberEnv(envVar, defaultValue);
50
40
 
51
41
  // Timeout configurations (in milliseconds)
52
42
  export const timeouts = {
@@ -737,7 +727,7 @@ export const cacheTtl = {
737
727
  usageApi: parseIntWithDefault('HIVE_MIND_USAGE_API_CACHE_TTL_MS', 13 * 60 * 1000), // 13 minutes
738
728
  // System metrics cache TTL (RAM, CPU, disk). Issue #2015 caps this at
739
729
  // 1 minute so queue decisions do not use stale host pressure data.
740
- system: Math.min(parseIntWithDefault('HIVE_MIND_SYSTEM_CACHE_TTL_MS', 60 * 1000), 60 * 1000), // max 1 minute
730
+ system: clampEnvValue('HIVE_MIND_SYSTEM_CACHE_TTL_MS', parseIntWithDefault('HIVE_MIND_SYSTEM_CACHE_TTL_MS', 60 * 1000), { maximum: 60 * 1000 }), // max 1 minute
741
731
  };
742
732
 
743
733
  // File and path configurations
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Parse numeric environment configuration without silently accepting malformed
3
+ * suffixes or discarding explicit operator input.
4
+ */
5
+
6
+ const emittedWarnings = new Set();
7
+
8
+ function warnOnce(key, message) {
9
+ if (emittedWarnings.has(key)) return;
10
+ emittedWarnings.add(key);
11
+ console.warn(message);
12
+ }
13
+
14
+ function readExplicitEnv(envVar) {
15
+ const rawValue = process.env[envVar];
16
+ return rawValue === undefined ? null : rawValue;
17
+ }
18
+
19
+ function warnInvalid(envVar, rawValue, type, defaultValue, scope) {
20
+ warnOnce(`invalid:${envVar}`, `[${scope}] ${envVar}=${JSON.stringify(rawValue)} is not a valid ${type}; using default ${defaultValue}.`);
21
+ }
22
+
23
+ /**
24
+ * Read a base-10 integer environment variable.
25
+ *
26
+ * @param {string} envVar
27
+ * @param {number} defaultValue
28
+ * @param {{scope?: string}} [options]
29
+ * @returns {number}
30
+ */
31
+ export function parseIntegerEnv(envVar, defaultValue, { scope = 'config' } = {}) {
32
+ const rawValue = readExplicitEnv(envVar);
33
+ if (rawValue === null) return defaultValue;
34
+
35
+ const normalized = rawValue.trim();
36
+ if (!/^[+-]?\d+$/.test(normalized)) {
37
+ warnInvalid(envVar, rawValue, 'integer', defaultValue, scope);
38
+ return defaultValue;
39
+ }
40
+
41
+ const parsed = Number(normalized);
42
+ if (!Number.isSafeInteger(parsed)) {
43
+ warnInvalid(envVar, rawValue, 'integer', defaultValue, scope);
44
+ return defaultValue;
45
+ }
46
+
47
+ return parsed;
48
+ }
49
+
50
+ /**
51
+ * Read a finite decimal environment variable.
52
+ *
53
+ * @param {string} envVar
54
+ * @param {number} defaultValue
55
+ * @param {{scope?: string}} [options]
56
+ * @returns {number}
57
+ */
58
+ export function parseNumberEnv(envVar, defaultValue, { scope = 'config' } = {}) {
59
+ const rawValue = readExplicitEnv(envVar);
60
+ if (rawValue === null) return defaultValue;
61
+
62
+ const normalized = rawValue.trim();
63
+ const isDecimal = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(normalized);
64
+ const parsed = Number(normalized);
65
+ if (!isDecimal || !Number.isFinite(parsed)) {
66
+ warnInvalid(envVar, rawValue, 'number', defaultValue, scope);
67
+ return defaultValue;
68
+ }
69
+
70
+ return parsed;
71
+ }
72
+
73
+ /**
74
+ * Apply a safety bound and explain when it changes an explicitly configured
75
+ * environment value.
76
+ *
77
+ * @param {string} envVar
78
+ * @param {number} value
79
+ * @param {{minimum?: number, maximum?: number, scope?: string, hint?: string}} options
80
+ * @returns {number}
81
+ */
82
+ export function clampEnvValue(envVar, value, { minimum, maximum, scope = 'config', hint = '' }) {
83
+ if (minimum !== undefined && value < minimum) {
84
+ if (readExplicitEnv(envVar) !== null) {
85
+ warnOnce(`minimum:${envVar}`, `[${scope}] ${envVar}=${value} is below the minimum (${minimum}); using ${minimum}.${hint ? ` ${hint}` : ''}`);
86
+ }
87
+ return minimum;
88
+ }
89
+
90
+ if (maximum !== undefined && value > maximum) {
91
+ if (readExplicitEnv(envVar) !== null) {
92
+ warnOnce(`maximum:${envVar}`, `[${scope}] ${envVar}=${value} exceeds the maximum (${maximum}); using ${maximum}.${hint ? ` ${hint}` : ''}`);
93
+ }
94
+ return maximum;
95
+ }
96
+
97
+ return value;
98
+ }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { ensureUseM } from './use-m-bootstrap.lib.mjs';
3
+ import { clampEnvValue, parseIntegerEnv, parseNumberEnv } from './env-config.lib.mjs';
3
4
 
4
5
  /**
5
6
  * Queue Configuration Module
@@ -178,21 +179,11 @@ export function parseQueueConfig(linoConfig) {
178
179
  }
179
180
  }
180
181
 
181
- // Helper function to safely parse floats with fallback
182
- const parseFloatWithDefault = (envVar, defaultValue) => {
183
- const value = getenv(envVar, defaultValue.toString());
184
- const parsed = parseFloat(value);
185
- return isNaN(parsed) ? defaultValue : parsed;
186
- };
187
-
188
- // Helper function to safely parse integers with fallback
189
- const parseIntWithDefault = (envVar, defaultValue) => {
190
- const value = getenv(envVar, defaultValue.toString());
191
- const parsed = parseInt(value);
192
- return isNaN(parsed) ? defaultValue : parsed;
193
- };
182
+ const parseFloatWithDefault = (envVar, defaultValue) => parseNumberEnv(envVar, defaultValue, { scope: 'queue-config' });
183
+ const parseIntWithDefault = (envVar, defaultValue) => parseIntegerEnv(envVar, defaultValue, { scope: 'queue-config' });
194
184
 
195
- const MINIMUM_START_INTERVAL_MS = 10 * 60 * 1000;
185
+ const DEFAULT_MINIMUM_START_INTERVAL_MS = 10 * 60 * 1000;
186
+ const minimumStartIntervalMs = parseIntWithDefault('HIVE_MIND_MIN_START_INTERVAL_FLOOR_MS', DEFAULT_MINIMUM_START_INTERVAL_MS);
196
187
 
197
188
  // Parse links notation config from environment variable (if provided)
198
189
  const linoConfig = parseQueueConfig(getenv('HIVE_MIND_QUEUE_CONFIG', ''));
@@ -276,7 +267,13 @@ export const QUEUE_CONFIG = {
276
267
  // MIN_START_INTERVAL_MS: Minimum global spacing between task startups.
277
268
  // Issue #2015: after resource thresholds clear, starting a backlog in a burst
278
269
  // can kill the next batch before host metrics have time to settle.
279
- MIN_START_INTERVAL_MS: Math.max(parseIntWithDefault('HIVE_MIND_MIN_START_INTERVAL_MS', MINIMUM_START_INTERVAL_MS), MINIMUM_START_INTERVAL_MS), // at least 10 minutes between starts
270
+ // Issue #2053: operators can explicitly lower the safety floor on hosts where
271
+ // resource metrics settle sooner. The default remains 10 minutes.
272
+ MIN_START_INTERVAL_MS: clampEnvValue('HIVE_MIND_MIN_START_INTERVAL_MS', parseIntWithDefault('HIVE_MIND_MIN_START_INTERVAL_MS', DEFAULT_MINIMUM_START_INTERVAL_MS), {
273
+ minimum: minimumStartIntervalMs,
274
+ scope: 'queue-config',
275
+ hint: 'Set HIVE_MIND_MIN_START_INTERVAL_FLOOR_MS to lower the minimum.',
276
+ }),
280
277
  CONSUMER_POLL_INTERVAL_MS: parseIntWithDefault('HIVE_MIND_CONSUMER_POLL_INTERVAL_MS', 60000), // 1 minute between queue checks
281
278
  MESSAGE_UPDATE_INTERVAL_MS: parseIntWithDefault('HIVE_MIND_MESSAGE_UPDATE_INTERVAL_MS', 60000), // 1 minute between status message updates
282
279