@link-assistant/hive-mind 2.12.4 → 2.13.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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/agent.prompts.lib.mjs +12 -0
- package/src/claude.lib.mjs +52 -1
- package/src/claude.prompts.lib.mjs +9 -0
- package/src/codex.prompts.lib.mjs +9 -0
- package/src/formal-ai-prompt.lib.mjs +29 -0
- package/src/formal-ai.lib.mjs +30 -0
- package/src/gemini.prompts.lib.mjs +9 -0
- package/src/github-url-parser.lib.mjs +8 -0
- package/src/github.lib.mjs +3 -2
- package/src/hive.mjs +39 -0
- package/src/limits-i18n.lib.mjs +8 -0
- package/src/locales/en.lino +9 -0
- package/src/locales/hi.lino +9 -0
- package/src/locales/ru.lino +9 -0
- package/src/locales/zh.lino +9 -0
- package/src/opencode.prompts.lib.mjs +9 -0
- package/src/qwen.prompts.lib.mjs +9 -0
- package/src/session-monitor.lib.mjs +45 -1
- package/src/solve.mjs +46 -5
- package/src/solve.restart-shared.lib.mjs +6 -0
- package/src/subscription-block-telegram.lib.mjs +115 -0
- package/src/subscription-error.lib.mjs +328 -0
- package/src/tool-retry.lib.mjs +29 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 2.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- aaf809a: Recognize subscription/account access blocks from every supported CLI (Claude, Codex, Qwen, Gemini, opencode) as their own error class: stop the run instead of retrying or switching model, auto-commit and push the in-flight work first, report what happened and what to do in the terminal, in the `/solve` exit message and in the Telegram completion message (en/ru/zh/hi), and stop the `/hive` queue so the fleet no longer rediscovers the block once per issue.
|
|
8
|
+
|
|
9
|
+
## 2.12.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 77ed9bc: Keep Formal AI repository requests bounded, target the discovered pull request URL, and stop retrying explicit `planned_not_executed` results as successful work.
|
|
14
|
+
|
|
3
15
|
## 2.12.4
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -9,6 +9,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
|
|
|
9
9
|
import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
|
|
10
10
|
import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
|
|
11
11
|
import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
12
|
+
import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
|
|
13
|
+
import { isFormalAiModel } from './formal-ai-model.lib.mjs';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Build the user prompt for Agent
|
|
@@ -16,6 +18,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
|
16
18
|
* @returns {string} The formatted user prompt
|
|
17
19
|
*/
|
|
18
20
|
export const buildUserPrompt = params => {
|
|
21
|
+
const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
|
|
22
|
+
if (formalAiPrompt !== null) return formalAiPrompt;
|
|
23
|
+
|
|
19
24
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
20
25
|
|
|
21
26
|
const promptLines = [];
|
|
@@ -92,6 +97,13 @@ export const buildUserPrompt = params => {
|
|
|
92
97
|
export const buildSystemPrompt = params => {
|
|
93
98
|
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
|
|
94
99
|
|
|
100
|
+
// Issue #2158: Formal AI's deterministic intent router considers the whole
|
|
101
|
+
// provider request, including this caller-owned workflow prompt. Command
|
|
102
|
+
// examples such as `sudo` and `pwd` were consequently executed as if the
|
|
103
|
+
// issue requested them. The compact user prompt remains the repository
|
|
104
|
+
// objective; Formal AI supplies its own execution policy.
|
|
105
|
+
if (isFormalAiModel(argv?.model)) return '';
|
|
106
|
+
|
|
95
107
|
// When in fork mode, screenshots are pushed to the fork, not the original repo
|
|
96
108
|
const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
|
|
97
109
|
|
package/src/claude.lib.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { isENOSPC, buildToolErrorMessage } from './lib.mjs';
|
|
|
9
9
|
import { reportError } from './sentry.lib.mjs';
|
|
10
10
|
import { timeouts, retryLimits, claudeCode, getClaudeEnv, getMaxOutputTokensForModel } from './config.lib.mjs';
|
|
11
11
|
import { detectUsageLimit, formatUsageLimitMessage, isUsageLimitError } from './usage-limit.lib.mjs';
|
|
12
|
+
import { detectSubscriptionError, SUBSCRIPTION_BLOCKED_MARKER } from './subscription-error.lib.mjs'; // Issue #2161
|
|
12
13
|
import { createInteractiveHandler } from './interactive-mode.lib.mjs';
|
|
13
14
|
import { setupBidirectionalHandler, finalizeBidirectionalHandler, validateBidirectionalModeConfig, attachStreamingInput } from './bidirectional-interactive.lib.mjs';
|
|
14
15
|
import { initProgressMonitoring } from './solve.progress-monitoring.lib.mjs';
|
|
@@ -376,6 +377,10 @@ export const executeClaudeCommand = async params => {
|
|
|
376
377
|
let isInternalServerError = false;
|
|
377
378
|
let isRequestTimeout = false;
|
|
378
379
|
let isRateLimitError = false; // Issue #1924: server-side 429 temporary rate limiting
|
|
380
|
+
// Issue #2161: account/subscription-level block (e.g. oauth_org_not_allowed).
|
|
381
|
+
// Terminal — never retried, never model-switched; carried out to the caller
|
|
382
|
+
// so /solve can stop with a specific diagnosis instead of a generic failure.
|
|
383
|
+
let subscriptionError = null;
|
|
379
384
|
let apiMarkedNotRetryable = false;
|
|
380
385
|
let resultNumTurns = 0;
|
|
381
386
|
let stderrErrors = [];
|
|
@@ -731,6 +736,24 @@ export const executeClaudeCommand = async params => {
|
|
|
731
736
|
isRateLimitError = true;
|
|
732
737
|
await log(`⚠️ Detected server-side rate limiting (429) from Claude CLI (will retry with --resume). request_id=${data.request_id || 'unknown'}`, { verbose: true });
|
|
733
738
|
}
|
|
739
|
+
// Issue #2161: account/subscription block. `data.error` carries the
|
|
740
|
+
// machine-readable code ("oauth_org_not_allowed" for the reported
|
|
741
|
+
// case) alongside api_error_status 403 — a far stronger signal than
|
|
742
|
+
// the rendered sentence, so it is passed to the detector first.
|
|
743
|
+
if (!subscriptionError) {
|
|
744
|
+
subscriptionError = detectSubscriptionError({
|
|
745
|
+
message: lastMessage,
|
|
746
|
+
tool: 'claude',
|
|
747
|
+
errorCode: typeof data.error === 'string' ? data.error : null,
|
|
748
|
+
apiErrorStatus: data.api_error_status,
|
|
749
|
+
terminalReason: data.terminal_reason,
|
|
750
|
+
});
|
|
751
|
+
if (subscriptionError) {
|
|
752
|
+
// Not verbose: this is the reason the whole run is about to end.
|
|
753
|
+
await log(`${SUBSCRIPTION_BLOCKED_MARKER} — ${subscriptionError.label}`);
|
|
754
|
+
await log(` code=${subscriptionError.code || 'n/a'} http=${data.api_error_status || 'n/a'} terminal_reason=${data.terminal_reason || 'n/a'} request_id=${data.request_id || 'unknown'}`, { verbose: true });
|
|
755
|
+
}
|
|
756
|
+
}
|
|
734
757
|
// Issue #1834: Detect corrupted extended-thinking-block 400 (un-resumable session).
|
|
735
758
|
// Capture diagnostics (request id, content path) to aid debugging and upstream reports.
|
|
736
759
|
if ((lastMessage.includes('thinking') || lastMessage.includes('redacted_thinking')) && lastMessage.includes('cannot be modified')) {
|
|
@@ -765,6 +788,26 @@ export const executeClaudeCommand = async params => {
|
|
|
765
788
|
await log(`🤖 Sub-agent "${callEntry.description || 'unknown'}" completed: ${data.usage.total_tokens} total tokens`, { verbose: true });
|
|
766
789
|
}
|
|
767
790
|
}
|
|
791
|
+
// Issue #2161: Claude Code injects API failures as synthetic assistant
|
|
792
|
+
// messages flagged `is_api_error_message` and carrying the error code.
|
|
793
|
+
// In the reported run this arrived ~40s before the terminal result
|
|
794
|
+
// event, so detecting it here surfaces the diagnosis earlier.
|
|
795
|
+
if (data.type === 'assistant' && data.is_api_error_message === true && !subscriptionError) {
|
|
796
|
+
const apiErrorText = getClaudeMessageContent(data)
|
|
797
|
+
.filter(item => item.type === 'text' && item.text)
|
|
798
|
+
.map(item => item.text)
|
|
799
|
+
.join('\n');
|
|
800
|
+
subscriptionError = detectSubscriptionError({
|
|
801
|
+
message: apiErrorText,
|
|
802
|
+
tool: 'claude',
|
|
803
|
+
errorCode: typeof data.error === 'string' ? data.error : null,
|
|
804
|
+
});
|
|
805
|
+
if (subscriptionError) {
|
|
806
|
+
if (apiErrorText) lastMessage = apiErrorText;
|
|
807
|
+
await log(`${SUBSCRIPTION_BLOCKED_MARKER} — ${subscriptionError.label}`);
|
|
808
|
+
await log(` code=${subscriptionError.code || 'n/a'} request_id=${data.request_id || 'unknown'} uuid=${data.uuid || 'unknown'}`, { verbose: true });
|
|
809
|
+
}
|
|
810
|
+
}
|
|
768
811
|
if (data.type === 'assistant' && data.message && data.message.content) {
|
|
769
812
|
const content = getClaudeMessageContent(data);
|
|
770
813
|
for (const item of content) {
|
|
@@ -977,7 +1020,12 @@ export const executeClaudeCommand = async params => {
|
|
|
977
1020
|
}
|
|
978
1021
|
// Issues #1331, #1353, #1472/#1475: Unified transient error retry (exponential backoff, session preservation)
|
|
979
1022
|
const isTransientError = isStartupTimeout || isActivityTimeout || isOverloadError || isInternalServerError || is503Error || isRequestTimeout || isRateLimitError || retryableLastError.isRetryable || (lastMessage.includes('API Error: 500') && (lastMessage.includes('Overloaded') || lastMessage.includes('Internal server error'))) || (lastMessage.includes('API Error: 529') && (lastMessage.includes('overloaded_error') || lastMessage.includes('Overloaded'))) || (lastMessage.includes('api_error') && lastMessage.includes('Overloaded')) || (lastMessage.includes('overloaded_error') && lastMessage.includes('Overloaded')) || lastMessage.includes('API Error: 503') || (lastMessage.includes('503') && (lastMessage.includes('upstream connect error') || lastMessage.includes('remote connection failure'))) || lastMessage === 'Request timed out' || lastMessage.includes('Request timed out');
|
|
980
|
-
|
|
1023
|
+
// Issue #2161: an account/subscription block short-circuits every retry
|
|
1024
|
+
// path. Stale transient flags from earlier in the run (an overload at hour
|
|
1025
|
+
// one, say) must not schedule a retry that is guaranteed to fail the same
|
|
1026
|
+
// way — and each retry would burn another full startup against a provider
|
|
1027
|
+
// that has already refused the credentials.
|
|
1028
|
+
if ((commandFailed || isTransientError) && isTransientError && !subscriptionError) {
|
|
981
1029
|
// Issue #1472/#1475: Startup/activity timeout → 30s–2min backoff; #1353: Request timeout → 5min–1hr; general → 2min–30min
|
|
982
1030
|
const isTimeoutRetry = isStartupTimeout || isActivityTimeout;
|
|
983
1031
|
const maxRetries = isTimeoutRetry ? retryLimits.maxTransientErrorRetries : isRequestTimeout ? retryLimits.maxRequestTimeoutRetries : retryLimits.maxTransientErrorRetries;
|
|
@@ -1005,6 +1053,7 @@ export const executeClaudeCommand = async params => {
|
|
|
1005
1053
|
resultSummary,
|
|
1006
1054
|
// Issue #1845/#1941: surface the actual error, rejecting meaningless fragments (e.g. a lone "}")
|
|
1007
1055
|
errorInfo: { message: buildToolErrorMessage({ lastMessage, exitCode, fallback: 'API explicitly marked error as not retryable', toolLabel: 'Claude' }), exitCode },
|
|
1056
|
+
subscriptionError, // Issue #2161
|
|
1008
1057
|
queuedFeedback, // Issue #817: Bidirectional mode feedback
|
|
1009
1058
|
};
|
|
1010
1059
|
}
|
|
@@ -1055,6 +1104,7 @@ export const executeClaudeCommand = async params => {
|
|
|
1055
1104
|
resultSummary, // Issue #1263: Include result summary
|
|
1056
1105
|
// Issue #1845/#1941: surface the actual error, rejecting meaningless fragments (e.g. a lone "}")
|
|
1057
1106
|
errorInfo: { message: buildToolErrorMessage({ lastMessage, exitCode, fallback: `Transient API error persisted after ${maxRetries} retries`, toolLabel: 'Claude' }), exitCode },
|
|
1107
|
+
subscriptionError, // Issue #2161
|
|
1058
1108
|
queuedFeedback, // Issue #817: Bidirectional mode feedback
|
|
1059
1109
|
};
|
|
1060
1110
|
}
|
|
@@ -1123,6 +1173,7 @@ export const executeClaudeCommand = async params => {
|
|
|
1123
1173
|
// Issue #1845: surface the core error (e.g. "API Error: Output blocked by content filtering policy").
|
|
1124
1174
|
// Issue #1941: a lone "}" fragment at interrupt time must not become "CLAUDE execution failed with }".
|
|
1125
1175
|
errorInfo: { message: buildToolErrorMessage({ lastMessage, exitCode, fallback: `Claude command failed with exit code ${exitCode}`, toolLabel: 'Claude' }), exitCode },
|
|
1176
|
+
subscriptionError, // Issue #2161: terminal account block — /solve stops and preserves the work
|
|
1126
1177
|
queuedFeedback, // Issue #817: Bidirectional mode feedback
|
|
1127
1178
|
};
|
|
1128
1179
|
}
|
|
@@ -11,6 +11,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
|
|
|
11
11
|
import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
|
|
12
12
|
import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
|
|
13
13
|
import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
14
|
+
import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
|
|
15
|
+
import { isFormalAiModel } from './formal-ai-model.lib.mjs';
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Build the user prompt for Claude
|
|
@@ -18,6 +20,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
|
18
20
|
* @returns {string} The formatted user prompt
|
|
19
21
|
*/
|
|
20
22
|
export const buildUserPrompt = params => {
|
|
23
|
+
const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
|
|
24
|
+
if (formalAiPrompt !== null) return formalAiPrompt;
|
|
25
|
+
|
|
21
26
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, owner, repo, argv, contributingGuidelines, claudeVersion } = params;
|
|
22
27
|
|
|
23
28
|
if (argv?.minimalRestartContext && argv.resume) {
|
|
@@ -105,6 +110,10 @@ export const buildUserPrompt = params => {
|
|
|
105
110
|
export const buildSystemPrompt = params => {
|
|
106
111
|
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
|
|
107
112
|
|
|
113
|
+
// Issue #2158: keep caller workflow instructions out of Formal AI's task
|
|
114
|
+
// classifier. Formal AI provides its own execution policy.
|
|
115
|
+
if (isFormalAiModel(argv?.model)) return '';
|
|
116
|
+
|
|
108
117
|
if (argv?.minimalRestartContext && argv.resume) {
|
|
109
118
|
return '';
|
|
110
119
|
}
|
|
@@ -10,6 +10,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
|
|
|
10
10
|
import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
|
|
11
11
|
import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
|
|
12
12
|
import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
13
|
+
import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
|
|
14
|
+
import { isFormalAiModel } from './formal-ai-model.lib.mjs';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Build the user prompt for Codex
|
|
@@ -17,6 +19,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
|
17
19
|
* @returns {string} The formatted user prompt
|
|
18
20
|
*/
|
|
19
21
|
export const buildUserPrompt = params => {
|
|
22
|
+
const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
|
|
23
|
+
if (formalAiPrompt !== null) return formalAiPrompt;
|
|
24
|
+
|
|
20
25
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
21
26
|
|
|
22
27
|
const promptLines = [];
|
|
@@ -93,6 +98,10 @@ export const buildUserPrompt = params => {
|
|
|
93
98
|
export const buildSystemPrompt = params => {
|
|
94
99
|
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
|
|
95
100
|
|
|
101
|
+
// Issue #2158: keep caller workflow instructions out of Formal AI's task
|
|
102
|
+
// classifier. Formal AI provides its own execution policy.
|
|
103
|
+
if (isFormalAiModel(argv?.model)) return '';
|
|
104
|
+
|
|
96
105
|
// When in fork mode, screenshots are pushed to the fork, not the original repo
|
|
97
106
|
const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
|
|
98
107
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the small repository objective sent through native CLIs to Formal AI.
|
|
3
|
+
*
|
|
4
|
+
* Formal AI owns the agent policy for its model. Repeating Hive Mind's native
|
|
5
|
+
* provider policy in the request both wastes context and exposes incidental
|
|
6
|
+
* shell-language cues to Formal AI's deterministic intent router (#2158).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isFormalAiModel } from './formal-ai-model.lib.mjs';
|
|
10
|
+
|
|
11
|
+
export const buildFormalAiRepositoryPrompt = params => {
|
|
12
|
+
if (!isFormalAiModel(params?.argv?.model)) return null;
|
|
13
|
+
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, isContinueMode, feedbackLines, owner, repo } = params;
|
|
15
|
+
const issueReference = isContinueMode && issueNumber && owner && repo ? `https://github.com/${owner}/${repo}/issues/${issueNumber}` : issueUrl || `the issue linked to pull request ${prNumber}`;
|
|
16
|
+
const lines = [`Resolve the GitHub issue at ${issueReference} in this repository.`];
|
|
17
|
+
|
|
18
|
+
if (branchName) lines.push(`Keep the solution on branch ${branchName}.`);
|
|
19
|
+
if (prUrl) lines.push(`Update the pull request at ${prUrl}.`);
|
|
20
|
+
// The review text is caller-controlled and can contain command snippets.
|
|
21
|
+
// Point Formal AI to the canonical PR instead of copying those snippets into
|
|
22
|
+
// the intent-classification request.
|
|
23
|
+
if (feedbackLines?.length && prUrl) lines.push('Review and address all feedback recorded on that pull request.');
|
|
24
|
+
|
|
25
|
+
lines.push('', 'Implement and verify the solution before reporting completion.', isContinueMode ? 'Continue.' : 'Proceed.');
|
|
26
|
+
return `${lines.join('\n')}\n`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default { buildFormalAiRepositoryPrompt };
|
package/src/formal-ai.lib.mjs
CHANGED
|
@@ -217,6 +217,36 @@ export const createPreparedToolResult = preparedCommand => ({
|
|
|
217
217
|
errorDuringExecution: false,
|
|
218
218
|
});
|
|
219
219
|
|
|
220
|
+
const FORMAL_AI_NON_EXECUTION_PATTERNS = [/^\s*planned,\s*not executed\b/im, /^\s*planned_not_executed\s*$/im, /^\s*terminal_state\s+["']?planned_not_executed\b/im];
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Turn Formal AI's explicit non-execution terminal state into a failed tool
|
|
224
|
+
* result (issue #2158).
|
|
225
|
+
*
|
|
226
|
+
* Formal AI 0.339.1 truthfully reports repository work as
|
|
227
|
+
* `planned_not_executed`, but each native CLI exits zero. Treating that process
|
|
228
|
+
* exit as a successful solve made `--auto-restart-until-mergeable` repeat the
|
|
229
|
+
* same deterministic plan five times. Keep the model's summary for evidence,
|
|
230
|
+
* while giving Hive Mind an actionable terminal failure.
|
|
231
|
+
*/
|
|
232
|
+
export const classifyFormalAiToolResult = ({ model, toolResult } = {}) => {
|
|
233
|
+
if (!toolResult || !isFormalAiModel(model) || toolResult.success === false) return toolResult;
|
|
234
|
+
|
|
235
|
+
const evidence = [toolResult.resultSummary, toolResult.result, toolResult.lastMessage, toolResult.output].filter(value => typeof value === 'string').join('\n');
|
|
236
|
+
if (!FORMAL_AI_NON_EXECUTION_PATTERNS.some(pattern => pattern.test(evidence))) return toolResult;
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
...toolResult,
|
|
240
|
+
success: false,
|
|
241
|
+
errorDuringExecution: true,
|
|
242
|
+
formalAiNonExecution: true,
|
|
243
|
+
errorInfo: {
|
|
244
|
+
code: 'FORMAL_AI_PLANNED_NOT_EXECUTED',
|
|
245
|
+
message: "Formal AI did not execute repository work; it returned the terminal state planned_not_executed. Fix or upgrade Formal AI's repository-work executor before retrying.",
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
|
|
220
250
|
export const logPreparedToolCommand = async ({ argv, fullCommand, log, formatAligned }) => {
|
|
221
251
|
await log(`\n${formatAligned('📝', 'Raw command:', '')}`);
|
|
222
252
|
await log(fullCommand);
|
|
@@ -9,6 +9,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
|
|
|
9
9
|
import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
|
|
10
10
|
import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
|
|
11
11
|
import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
12
|
+
import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
|
|
13
|
+
import { isFormalAiModel } from './formal-ai-model.lib.mjs';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Build the user prompt for Gemini
|
|
@@ -16,6 +18,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
|
16
18
|
* @returns {string} The formatted user prompt
|
|
17
19
|
*/
|
|
18
20
|
export const buildUserPrompt = params => {
|
|
21
|
+
const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
|
|
22
|
+
if (formalAiPrompt !== null) return formalAiPrompt;
|
|
23
|
+
|
|
19
24
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
20
25
|
|
|
21
26
|
const promptLines = [];
|
|
@@ -81,6 +86,10 @@ export const buildUserPrompt = params => {
|
|
|
81
86
|
export const buildSystemPrompt = params => {
|
|
82
87
|
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
|
|
83
88
|
|
|
89
|
+
// Issue #2158: keep caller workflow instructions out of Formal AI's task
|
|
90
|
+
// classifier. Formal AI provides its own execution policy.
|
|
91
|
+
if (isFormalAiModel(argv?.model)) return '';
|
|
92
|
+
|
|
84
93
|
const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
|
|
85
94
|
|
|
86
95
|
let workspaceInstructions = '';
|
|
@@ -241,6 +241,14 @@ export function normalizeGitHubUrl(url) {
|
|
|
241
241
|
const parsed = parseGitHubUrl(url);
|
|
242
242
|
return parsed.valid ? parsed.normalized : null;
|
|
243
243
|
}
|
|
244
|
+
|
|
245
|
+
/** Build the canonical web URL for a pull request already identified by GitHub. */
|
|
246
|
+
export function buildGitHubPullRequestUrl({ owner, repo, number } = {}) {
|
|
247
|
+
if (!owner || !repo || !Number.isInteger(Number(number)) || Number(number) <= 0) {
|
|
248
|
+
throw new TypeError('A GitHub pull request URL requires owner, repo, and a positive integer number');
|
|
249
|
+
}
|
|
250
|
+
return `https://github.com/${owner}/${repo}/pull/${Number(number)}`;
|
|
251
|
+
}
|
|
244
252
|
/**
|
|
245
253
|
* Check if a URL is a valid GitHub URL of a specific type
|
|
246
254
|
* @param {string} url - The URL to check
|
package/src/github.lib.mjs
CHANGED
|
@@ -20,8 +20,8 @@ export { buildCostInfoString };
|
|
|
20
20
|
// #1756: route gh exec calls through transient + rate-limit retry wrapper
|
|
21
21
|
import { execGhWithRetry } from './github-rate-limit.lib.mjs';
|
|
22
22
|
import { QUIET_PROBE } from './quiet-probe.lib.mjs'; // issues #2130, #2135: keep read-only probe payloads out of the attached log
|
|
23
|
-
import { isGitHubUrlType, normalizeGitHubUrl, parseGitHubUrl } from './github-url-parser.lib.mjs';
|
|
24
|
-
export { isGitHubUrlType, normalizeGitHubUrl, parseGitHubUrl };
|
|
23
|
+
import { buildGitHubPullRequestUrl, isGitHubUrlType, normalizeGitHubUrl, parseGitHubUrl } from './github-url-parser.lib.mjs';
|
|
24
|
+
export { buildGitHubPullRequestUrl, isGitHubUrlType, normalizeGitHubUrl, parseGitHubUrl };
|
|
25
25
|
// Issue #1625: Named marker constants (single source of truth) + in-memory tracking for tool-posted comments. See tool-comments.lib.mjs for design.
|
|
26
26
|
import { SOLUTION_DRAFT_LOG_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, postTrackedComment, postTrackedCommentFromFile } from './tool-comments.lib.mjs';
|
|
27
27
|
export const maskGitHubToken = maskToken; // Alias for backward compatibility
|
|
@@ -1168,6 +1168,7 @@ export default {
|
|
|
1168
1168
|
fetchProjectIssues,
|
|
1169
1169
|
isRateLimitError,
|
|
1170
1170
|
batchCheckPullRequestsForIssues,
|
|
1171
|
+
buildGitHubPullRequestUrl,
|
|
1171
1172
|
parseGitHubUrl,
|
|
1172
1173
|
normalizeGitHubUrl,
|
|
1173
1174
|
isGitHubUrlType,
|
package/src/hive.mjs
CHANGED
|
@@ -36,6 +36,7 @@ if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
|
|
|
36
36
|
}
|
|
37
37
|
export { createYargsConfig } from './hive.config.lib.mjs';
|
|
38
38
|
import { attachChildExitHandlers } from './child-exit.lib.mjs';
|
|
39
|
+
import { SUBSCRIPTION_BLOCKED_MARKER } from './subscription-error.lib.mjs'; // Issue #2161
|
|
39
40
|
import { isDirectExecution, withTimeout } from './hive.bootstrap.lib.mjs';
|
|
40
41
|
import { createShutdownManager } from './hive.shutdown.lib.mjs';
|
|
41
42
|
const isRunningDirectly = isDirectExecution(process.argv[1], import.meta.url);
|
|
@@ -653,6 +654,22 @@ if (isRunningDirectly) {
|
|
|
653
654
|
// controlled SIGTERM to each (they run in their own detached process group, so the
|
|
654
655
|
// terminal's SIGINT never reaches them); a *second* interrupt force-kills the groups.
|
|
655
656
|
const activeSolveChildren = new Set();
|
|
657
|
+
// Issue #2161: an account/subscription block is hive-wide, not per-issue. The
|
|
658
|
+
// credentials every worker shares have been refused, so each remaining issue
|
|
659
|
+
// would spin up a full solve run only to die the same way — burning clones,
|
|
660
|
+
// containers and PR comments while the queue drains into "failed". The first
|
|
661
|
+
// worker to see the marker in its child's output records it here and stops the
|
|
662
|
+
// queue; the rest exit as soon as their current child returns.
|
|
663
|
+
let subscriptionBlock = null;
|
|
664
|
+
const noteSubscriptionBlock = (workerId, line) => {
|
|
665
|
+
if (subscriptionBlock) return;
|
|
666
|
+
subscriptionBlock = { workerId, line: line.trim() };
|
|
667
|
+
log(`\n${SUBSCRIPTION_BLOCKED_MARKER} — worker ${workerId} reported that the account can no longer use the tool:`, { level: 'error' }).catch(() => {});
|
|
668
|
+
log(` ${subscriptionBlock.line}`, { level: 'error' }).catch(() => {});
|
|
669
|
+
log(' Stopping the hive: every remaining issue would fail the same way until access is restored.', { level: 'error' }).catch(() => {});
|
|
670
|
+
log(' In-flight workers finish (and auto-commit their work) before the run ends.', { level: 'error' }).catch(() => {});
|
|
671
|
+
issueQueue.stop();
|
|
672
|
+
};
|
|
656
673
|
// Worker function to process issues from queue
|
|
657
674
|
async function worker(workerId) {
|
|
658
675
|
await log(`🔧 Worker ${workerId} started`, { verbose: true });
|
|
@@ -758,6 +775,9 @@ if (isRunningDirectly) {
|
|
|
758
775
|
const lines = data.toString().split('\n');
|
|
759
776
|
for (const line of lines) {
|
|
760
777
|
if (line.trim()) {
|
|
778
|
+
// Issue #2161: solve prints SUBSCRIPTION_BLOCKED_MARKER on a terminal
|
|
779
|
+
// account block. Seen here, it stops the whole hive (see noteSubscriptionBlock).
|
|
780
|
+
if (line.includes(SUBSCRIPTION_BLOCKED_MARKER)) noteSubscriptionBlock(workerId, line);
|
|
761
781
|
log(` [${solveCommand} worker-${workerId}] ${line}`).catch(logError => {
|
|
762
782
|
reportError(logError, {
|
|
763
783
|
context: 'worker_stdout_log',
|
|
@@ -777,6 +797,7 @@ if (isRunningDirectly) {
|
|
|
777
797
|
const lines = data.toString().split('\n');
|
|
778
798
|
for (const line of lines) {
|
|
779
799
|
if (line.trim()) {
|
|
800
|
+
if (line.includes(SUBSCRIPTION_BLOCKED_MARKER)) noteSubscriptionBlock(workerId, line); // Issue #2161
|
|
780
801
|
log(` [${solveCommand} worker-${workerId} stderr] ${line}`).catch(logError => {
|
|
781
802
|
reportError(logError, {
|
|
782
803
|
context: 'worker_stderr_log',
|
|
@@ -813,6 +834,14 @@ if (isRunningDirectly) {
|
|
|
813
834
|
await log(` 🛑 Worker ${workerId} stopped gracefully during shutdown on ${issueUrl} (exit ${exitCode}, ${duration}s)`);
|
|
814
835
|
gracefulStop = true;
|
|
815
836
|
break; // stop processing more PRs for this issue
|
|
837
|
+
} else if (subscriptionBlock) {
|
|
838
|
+
// Issue #2161: the run did not fail because of this issue — the account
|
|
839
|
+
// lost access mid-flight. Report the real reason and stop; solve has
|
|
840
|
+
// already auto-committed whatever work existed.
|
|
841
|
+
await log(` ${SUBSCRIPTION_BLOCKED_MARKER} Worker ${workerId} stopped on ${issueUrl} after ${duration}s: the tool account can no longer be used (exit ${exitCode}).`, { level: 'error' });
|
|
842
|
+
await log(` Restore access, then re-run the hive — this issue stays queued, not failed.`, { level: 'error' });
|
|
843
|
+
gracefulStop = true;
|
|
844
|
+
break;
|
|
816
845
|
} else {
|
|
817
846
|
throw new Error(`${solveCommand} exited with code ${exitCode}`);
|
|
818
847
|
}
|
|
@@ -1260,6 +1289,16 @@ if (isRunningDirectly) {
|
|
|
1260
1289
|
}
|
|
1261
1290
|
await log('\n👋 Hive Mind monitoring stopped');
|
|
1262
1291
|
await log(` 📁 Full log file: ${absoluteLogPath}`);
|
|
1292
|
+
// Issue #2161: the hive did not simply "finish" — it was cut short because the
|
|
1293
|
+
// account lost access. Say so last (that is what a human scrolls to) and exit
|
|
1294
|
+
// non-zero so supervisors and the Telegram monitor report a failure, not a
|
|
1295
|
+
// clean completion.
|
|
1296
|
+
if (subscriptionBlock) {
|
|
1297
|
+
await log(`\n${SUBSCRIPTION_BLOCKED_MARKER} Hive stopped early: the tool account can no longer be used.`, { level: 'error' });
|
|
1298
|
+
await log(` Reported by worker ${subscriptionBlock.workerId}: ${subscriptionBlock.line}`, { level: 'error' });
|
|
1299
|
+
await log(' Restore subscription/account access, then start the hive again.', { level: 'error' });
|
|
1300
|
+
await safeExit(1, 'Subscription/account access blocked');
|
|
1301
|
+
}
|
|
1263
1302
|
}
|
|
1264
1303
|
// Issue #1823: Graceful-shutdown + force-kill logic lives in hive.shutdown.lib.mjs.
|
|
1265
1304
|
// gracefulShutdown waits (uncapped) for in-flight solve workers to finish on the first
|
package/src/limits-i18n.lib.mjs
CHANGED
|
@@ -85,6 +85,14 @@ const ENGLISH_LIMITS = {
|
|
|
85
85
|
subscription_detail_trial_ends: 'trial ends {{time}}',
|
|
86
86
|
subscription_detail_trial_ends_in: 'trial ends in {{duration}}; {{time}}',
|
|
87
87
|
subscription_status: 'Subscription: {{status}}',
|
|
88
|
+
subscription_blocked_title: 'Subscription/account access blocked',
|
|
89
|
+
subscription_blocked_provider: 'Provider said',
|
|
90
|
+
subscription_blocked_code: 'Error code',
|
|
91
|
+
subscription_blocked_reason: 'Why the run stopped',
|
|
92
|
+
subscription_blocked_note: 'This is not a usage limit: waiting, retrying or switching model cannot fix it.',
|
|
93
|
+
subscription_blocked_steps: 'What to do',
|
|
94
|
+
subscription_blocked_preserved: 'Uncommitted work was auto-committed before stopping.',
|
|
95
|
+
subscription_blocked_resume: 'Resume after access is restored',
|
|
88
96
|
telegram_api: 'Telegram Bot API',
|
|
89
97
|
telegram_flood_control: 'flood control',
|
|
90
98
|
telegram_last_rate_limit: 'Last 429: {{method}}',
|
package/src/locales/en.lino
CHANGED
|
@@ -322,6 +322,15 @@ en
|
|
|
322
322
|
session "session"
|
|
323
323
|
start "Start"
|
|
324
324
|
subscription
|
|
325
|
+
blocked
|
|
326
|
+
title "Subscription/account access blocked"
|
|
327
|
+
provider "Provider said"
|
|
328
|
+
code "Error code"
|
|
329
|
+
reason "Why the run stopped"
|
|
330
|
+
note "This is not a usage limit: waiting, retrying or switching model cannot fix it."
|
|
331
|
+
steps "What to do"
|
|
332
|
+
preserved "Uncommitted work was auto-committed before stopping."
|
|
333
|
+
resume "Resume after access is restored"
|
|
325
334
|
detail
|
|
326
335
|
ends
|
|
327
336
|
label "ends {{time}}"
|
package/src/locales/hi.lino
CHANGED
|
@@ -322,6 +322,15 @@ hi
|
|
|
322
322
|
session "सत्र"
|
|
323
323
|
start "शुरुआत"
|
|
324
324
|
subscription
|
|
325
|
+
blocked
|
|
326
|
+
title "सदस्यता/खाता पहुँच अवरुद्ध"
|
|
327
|
+
provider "प्रदाता ने कहा"
|
|
328
|
+
code "त्रुटि कोड"
|
|
329
|
+
reason "रन क्यों रुका"
|
|
330
|
+
note "यह उपयोग सीमा नहीं है: प्रतीक्षा, पुनः प्रयास या मॉडल बदलना इसे ठीक नहीं करेगा।"
|
|
331
|
+
steps "क्या करें"
|
|
332
|
+
preserved "रुकने से पहले बिना कमिट किए बदलाव स्वतः कमिट कर दिए गए।"
|
|
333
|
+
resume "पहुँच बहाल होने पर फिर से शुरू करें"
|
|
325
334
|
detail
|
|
326
335
|
ends
|
|
327
336
|
label "{{time}} को समाप्त होगी"
|
package/src/locales/ru.lino
CHANGED
|
@@ -322,6 +322,15 @@ ru
|
|
|
322
322
|
session "сеанс"
|
|
323
323
|
start "Начало"
|
|
324
324
|
subscription
|
|
325
|
+
blocked
|
|
326
|
+
title "Доступ по подписке/аккаунту заблокирован"
|
|
327
|
+
provider "Ответ провайдера"
|
|
328
|
+
code "Код ошибки"
|
|
329
|
+
reason "Почему запуск остановлен"
|
|
330
|
+
note "Это не лимит использования: ожидание, повтор или смена модели не помогут."
|
|
331
|
+
steps "Что делать"
|
|
332
|
+
preserved "Незакоммиченные изменения были автоматически закоммичены перед остановкой."
|
|
333
|
+
resume "Продолжить после восстановления доступа"
|
|
325
334
|
detail
|
|
326
335
|
ends
|
|
327
336
|
label "заканчивается {{time}}"
|
package/src/locales/zh.lino
CHANGED
|
@@ -322,6 +322,15 @@ zh
|
|
|
322
322
|
session "会话"
|
|
323
323
|
start "开始"
|
|
324
324
|
subscription
|
|
325
|
+
blocked
|
|
326
|
+
title "订阅/账号访问被阻止"
|
|
327
|
+
provider "服务方提示"
|
|
328
|
+
code "错误代码"
|
|
329
|
+
reason "运行停止的原因"
|
|
330
|
+
note "这不是用量限制:等待、重试或切换模型都无法解决。"
|
|
331
|
+
steps "如何处理"
|
|
332
|
+
preserved "停止前已自动提交未提交的改动。"
|
|
333
|
+
resume "恢复访问后继续"
|
|
325
334
|
detail
|
|
326
335
|
ends
|
|
327
336
|
label "结束于 {{time}}"
|
|
@@ -9,6 +9,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
|
|
|
9
9
|
import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
|
|
10
10
|
import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
|
|
11
11
|
import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
12
|
+
import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
|
|
13
|
+
import { isFormalAiModel } from './formal-ai-model.lib.mjs';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Build the user prompt for OpenCode
|
|
@@ -16,6 +18,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
|
16
18
|
* @returns {string} The formatted user prompt
|
|
17
19
|
*/
|
|
18
20
|
export const buildUserPrompt = params => {
|
|
21
|
+
const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
|
|
22
|
+
if (formalAiPrompt !== null) return formalAiPrompt;
|
|
23
|
+
|
|
19
24
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
20
25
|
|
|
21
26
|
const promptLines = [];
|
|
@@ -92,6 +97,10 @@ export const buildUserPrompt = params => {
|
|
|
92
97
|
export const buildSystemPrompt = params => {
|
|
93
98
|
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
|
|
94
99
|
|
|
100
|
+
// Issue #2158: keep caller workflow instructions out of Formal AI's task
|
|
101
|
+
// classifier. Formal AI provides its own execution policy.
|
|
102
|
+
if (isFormalAiModel(argv?.model)) return '';
|
|
103
|
+
|
|
95
104
|
// When in fork mode, screenshots are pushed to the fork, not the original repo
|
|
96
105
|
const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
|
|
97
106
|
|
package/src/qwen.prompts.lib.mjs
CHANGED
|
@@ -9,6 +9,8 @@ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
|
|
|
9
9
|
import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
|
|
10
10
|
import { buildRequestedBaseBranchDirective } from './solve-option-contract.prompts.lib.mjs';
|
|
11
11
|
import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
12
|
+
import { buildFormalAiRepositoryPrompt } from './formal-ai-prompt.lib.mjs';
|
|
13
|
+
import { isFormalAiModel } from './formal-ai-model.lib.mjs';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Build the user prompt for Qwen Code
|
|
@@ -16,6 +18,9 @@ import { buildIssueResearchPrompt } from './deep-analysis.lib.mjs';
|
|
|
16
18
|
* @returns {string} The formatted user prompt
|
|
17
19
|
*/
|
|
18
20
|
export const buildUserPrompt = params => {
|
|
21
|
+
const formalAiPrompt = buildFormalAiRepositoryPrompt(params);
|
|
22
|
+
if (formalAiPrompt !== null) return formalAiPrompt;
|
|
23
|
+
|
|
19
24
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, tool = 'qwen' } = params;
|
|
20
25
|
|
|
21
26
|
const promptLines = [];
|
|
@@ -81,6 +86,10 @@ export const buildUserPrompt = params => {
|
|
|
81
86
|
export const buildSystemPrompt = params => {
|
|
82
87
|
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
|
|
83
88
|
|
|
89
|
+
// Issue #2158: keep caller workflow instructions out of Formal AI's task
|
|
90
|
+
// classifier. Formal AI provides its own execution policy.
|
|
91
|
+
if (isFormalAiModel(argv?.model)) return '';
|
|
92
|
+
|
|
84
93
|
const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
|
|
85
94
|
|
|
86
95
|
let workspaceInstructions = '';
|