@link-assistant/hive-mind 2.0.23 → 2.0.25

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 (39) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +16 -16
  3. package/src/agent-commander.lib.mjs +1 -1
  4. package/src/auto-language.lib.mjs +1 -1
  5. package/src/claude-quiet-config.lib.mjs +1 -1
  6. package/src/claude.lib.mjs +1 -1
  7. package/src/cleanup.mjs +1 -1
  8. package/src/cleanup.os.lib.mjs +1 -1
  9. package/src/codex-health.lib.mjs +191 -0
  10. package/src/codex.lib.mjs +57 -102
  11. package/src/gemini.lib.mjs +47 -0
  12. package/src/git.lib.mjs +2 -2
  13. package/src/github-repository-names.lib.mjs +1 -1
  14. package/src/github.lib.mjs +1 -1
  15. package/src/handoff-skill.lib.mjs +1 -1
  16. package/src/hive-screens.lib.mjs +1 -1
  17. package/src/i18n.lib.mjs +1 -1
  18. package/src/instrument.mjs +1 -1
  19. package/src/interactive-mode.lib.mjs +3 -3
  20. package/src/isolation-runner.lib.mjs +100 -3
  21. package/src/qwen.lib.mjs +43 -0
  22. package/src/session-monitor.lib.mjs +57 -15
  23. package/src/session-store.lib.mjs +1 -1
  24. package/src/solve.auto-pr.lib.mjs +2 -2
  25. package/src/solve.disk-diagnostics.lib.mjs +50 -29
  26. package/src/solve.escalate.lib.mjs +0 -3
  27. package/src/solve.keep-working.lib.mjs +1 -1
  28. package/src/solve.repository.lib.mjs +1 -1
  29. package/src/solve.validation.lib.mjs +1 -2
  30. package/src/telegram-bot-launcher.lib.mjs +2 -6
  31. package/src/telegram-command-execution.lib.mjs +4 -0
  32. package/src/telegram-isolation.lib.mjs +1 -0
  33. package/src/telegram-tokens-command.lib.mjs +1 -1
  34. package/src/telegram-top-command.lib.mjs +1 -1
  35. package/src/tool-run-health.lib.mjs +64 -0
  36. package/src/use-m-bootstrap.lib.mjs +1 -1
  37. package/src/useless-tools.lib.mjs +1 -1
  38. package/src/youtrack/solve.youtrack.lib.mjs +2 -2
  39. package/src/youtrack/youtrack.lib.mjs +1 -1
@@ -0,0 +1,64 @@
1
+ // Tool-agnostic run-health analysis (Issue #1990).
2
+ //
3
+ // Background: under docker isolation two long-running `solve --tool codex` tasks
4
+ // reported SUCCESS (Exit Code: 0) while their containers had run out of disk —
5
+ // the AI session was cut off mid-run, no commits were produced, yet the process
6
+ // exited 0. Reporting that as success also discarded the container filesystem we
7
+ // needed to inspect and retry from.
8
+ //
9
+ // codex.lib.mjs gets a bespoke gate (paired turn.started/turn.completed lifecycle
10
+ // — see codex-health.lib.mjs) and claude.lib.mjs already requires its final
11
+ // `result` event (shouldFailClaudeStreamWithoutResult). This module provides the
12
+ // equivalent gate for the tools whose stream-json output (adopted from the Claude
13
+ // Agent SDK schema) ends with a single terminal `result` event: gemini-cli and
14
+ // qwen-code. An exit-0 run that clearly began work but never emitted that
15
+ // terminal event was interrupted and must NOT be reported as success.
16
+ //
17
+ // opencode is deliberately NOT gated here. Its `run --format json` output has no
18
+ // single terminal completion event we have verified is always emitted before a
19
+ // clean exit — opencode.lib.mjs treats several event types ('text', 'assistant',
20
+ // 'message', 'result', 'step_finish') as best-effort and decides success purely
21
+ // on the exit code. Gating opencode on a terminal event without first confirming
22
+ // upstream that it is reliably flushed would risk converting genuine successes
23
+ // into failures, so it is left as follow-up. See docs/case-studies/issue-1990.
24
+ //
25
+ // Disk-exhaustion strings ("No space left on device", ENOSPC) are surfaced only
26
+ // as supporting *diagnostics* — never an independent failure gate — to avoid the
27
+ // issue #1955 class of false positive where a tool echoes a command's stdout that
28
+ // merely mentions the phrase.
29
+
30
+ import { isENOSPC } from './lib.mjs';
31
+
32
+ export const getTerminalEventCompletionHealth = ({ eventCounts = {}, terminalEventTypes = ['result'], hadActivity = false, diskEvidenceTexts = [] } = {}) => {
33
+ const terminalCount = terminalEventTypes.reduce((sum, type) => sum + (eventCounts[type] || 0), 0);
34
+
35
+ // Only flag a run that did work but never reached its terminal event. A run
36
+ // with no activity at all is handled separately by each tool (e.g. gemini's
37
+ // emittedNoEvents check) and must not be double-counted here.
38
+ const incompleteSession = hadActivity && terminalCount === 0;
39
+
40
+ const diskEvidence = [];
41
+ for (const { source, text } of diskEvidenceTexts) {
42
+ if (text && isENOSPC(text)) {
43
+ diskEvidence.push({ source, text: String(text).replace(/\s+/g, ' ').trim().slice(0, 300) });
44
+ }
45
+ }
46
+ const diskPressureDetected = diskEvidence.length > 0;
47
+
48
+ const reasons = [];
49
+ if (incompleteSession) {
50
+ reasons.push(`The tool exited 0 but never emitted its terminal completion event (${terminalEventTypes.join('/')}); the session was cut off mid-run.`);
51
+ if (diskPressureDetected) {
52
+ reasons.push(`Disk-exhaustion signals were present in ${diskEvidence.length} location(s) (e.g. "No space left on device") — the likely cause of the interrupted session.`);
53
+ }
54
+ }
55
+
56
+ return {
57
+ healthy: !incompleteSession,
58
+ incompleteSession,
59
+ diskPressureDetected,
60
+ diskEvidence,
61
+ terminalCount,
62
+ reasons,
63
+ };
64
+ };
@@ -24,7 +24,7 @@ export const fetchUseMCodeFromCdn = async ({ fetcher = fetch } = {}) => {
24
24
  try {
25
25
  return await fetchUseMCodeFromUrl(USE_M_BOOTSTRAP_FALLBACK_URL, fetcher);
26
26
  } catch (fallbackError) {
27
- throw new Error(`Failed to load use-m bootstrap from primary and fallback URLs: ${primaryError.message}; ${fallbackError.message}`);
27
+ throw new Error(`Failed to load use-m bootstrap from primary and fallback URLs: ${primaryError.message}; ${fallbackError.message}`, { cause: fallbackError });
28
28
  }
29
29
  };
30
30
 
@@ -154,7 +154,7 @@ export const resolveClaudeSessionToolFlags = async ({ argv, log, fallbackBuildMc
154
154
  export const ensureDisallowedToolsInSettings = async ({ settingsPath, log } = {}) => {
155
155
  const resolvedPath = settingsPath || path.join(os.homedir(), '.claude', 'settings.json');
156
156
  const toBlock = buildDisallowedToolsList();
157
- let settings = {};
157
+ let settings;
158
158
  try {
159
159
  const content = await fs.readFile(resolvedPath, 'utf-8');
160
160
  settings = JSON.parse(content);
@@ -15,8 +15,8 @@ const { parseYouTrackIssueId, updateYouTrackIssueStage, addYouTrackComment, crea
15
15
  * @returns {Object} Validation result with YouTrack info
16
16
  */
17
17
  export async function validateYouTrackUrl(issueUrl) {
18
- let isYouTrackUrl = null;
19
- let youTrackIssueId = null;
18
+ let isYouTrackUrl;
19
+ let youTrackIssueId;
20
20
  let youTrackConfig = null;
21
21
 
22
22
  if (!issueUrl) {
@@ -128,7 +128,7 @@ async function makeYouTrackRequest(endpoint, config, options = {}) {
128
128
  return await response.json();
129
129
  } catch (error) {
130
130
  if (error.message.includes('fetch')) {
131
- throw new Error(`Failed to connect to YouTrack at ${config.url}: ${error.message}`);
131
+ throw new Error(`Failed to connect to YouTrack at ${config.url}: ${error.message}`, { cause: error });
132
132
  }
133
133
  throw error;
134
134
  }