@link-assistant/hive-mind 2.10.4 → 2.11.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/README.hi.md +5 -0
- package/README.md +5 -0
- package/README.ru.md +5 -0
- package/README.zh.md +4 -0
- package/package.json +1 -1
- package/src/exit-handler.lib.mjs +61 -44
- package/src/fix.ci-cd-issue.lib.mjs +137 -0
- package/src/fix.mjs +6 -116
- package/src/github-pr-state.lib.mjs +56 -0
- package/src/locales/en.lino +6 -3
- package/src/locales/hi.lino +6 -3
- package/src/locales/ru.lino +6 -3
- package/src/locales/zh.lino +6 -3
- package/src/session-monitor.docker-terminal.lib.mjs +118 -0
- package/src/session-monitor.lib.mjs +71 -158
- package/src/session-monitor.stale-executing.lib.mjs +151 -0
- package/src/solve.finalize.lib.mjs +23 -8
- package/src/solve.mjs +1 -1
- package/src/telegram-task-command.lib.mjs +47 -1
- package/src/work-session-formatting.lib.mjs +12 -2
|
@@ -2,6 +2,8 @@ import { buildUserMention } from './buildUserMention.lib.mjs';
|
|
|
2
2
|
import { validateModelName } from './models/index.mjs';
|
|
3
3
|
import { getLinoYargsFactory } from './cli-arguments.lib.mjs';
|
|
4
4
|
import { createYargsConfig as createTaskYargsConfig } from './task.config.lib.mjs';
|
|
5
|
+
import { createCiCdIssue } from './fix.ci-cd-issue.lib.mjs';
|
|
6
|
+
import { parseFixRepository } from './fix.ci-cd.lib.mjs';
|
|
5
7
|
import { createTaskIssue, parseTaskIssueCreationInput, resolveTaskIssueCreationInput } from './task.issue-creation.lib.mjs';
|
|
6
8
|
import { parseTaskIssueUrl } from './task.split.lib.mjs';
|
|
7
9
|
import { escapeMarkdown } from './telegram-markdown.lib.mjs';
|
|
@@ -23,6 +25,10 @@ export function hasTaskSplitFlag(args) {
|
|
|
23
25
|
return args.includes('--split') || args.some(arg => arg.startsWith('--split='));
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
export function hasTaskCiCdFlag(args) {
|
|
29
|
+
return args.includes('--ci-cd');
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
export function applyTaskCommandDefaults(args, commandName = 'task') {
|
|
27
33
|
if (commandName !== 'split') return args;
|
|
28
34
|
const hasSplit = args.includes('--split') || args.some(arg => arg.startsWith('--split='));
|
|
@@ -66,6 +72,16 @@ export function buildTaskCommandArgs(text) {
|
|
|
66
72
|
};
|
|
67
73
|
}
|
|
68
74
|
|
|
75
|
+
export function buildTaskCiCdCommandArgs(text) {
|
|
76
|
+
const args = parseCommandArgs(text);
|
|
77
|
+
const repositoryRaw = args.find(arg => !arg.startsWith('-') && parseFixRepository(arg)) || null;
|
|
78
|
+
return {
|
|
79
|
+
args,
|
|
80
|
+
repositoryRaw,
|
|
81
|
+
repository: repositoryRaw ? parseFixRepository(repositoryRaw) : null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
69
85
|
function getReplyText(message) {
|
|
70
86
|
const reply = message?.reply_to_message;
|
|
71
87
|
if (!reply || reply.forum_topic_created) return '';
|
|
@@ -97,7 +113,7 @@ function injectLanguageIfMissing(args, locale) {
|
|
|
97
113
|
}
|
|
98
114
|
|
|
99
115
|
export function registerTaskCommands(bot, options) {
|
|
100
|
-
const { VERBOSE, taskEnabled, addBreadcrumb, isOldMessage, isForwarded, isGroupChat, isTopicAuthorized, buildAuthErrorMessage, isChatStopped, getStoppedChatRejectMessage, safeReply, executeAndUpdateMessage, createTaskIssue: createTaskIssueFn = createTaskIssue, resolveLocale = null } = options;
|
|
116
|
+
const { VERBOSE, taskEnabled, addBreadcrumb, isOldMessage, isForwarded, isGroupChat, isTopicAuthorized, buildAuthErrorMessage, isChatStopped, getStoppedChatRejectMessage, safeReply, executeAndUpdateMessage, createTaskIssue: createTaskIssueFn = createTaskIssue, createCiCdIssue: createCiCdIssueFn = createCiCdIssue, resolveLocale = null } = options;
|
|
101
117
|
|
|
102
118
|
async function handleTaskCommand(ctx) {
|
|
103
119
|
const commandName = getTaskCommandNameFromText(ctx.message?.text) || 'task';
|
|
@@ -138,6 +154,36 @@ export function registerTaskCommands(bot, options) {
|
|
|
138
154
|
|
|
139
155
|
const parsedArgs = parseCommandArgs(ctx.message.text);
|
|
140
156
|
const splitMode = commandName === 'split' || hasTaskSplitFlag(parsedArgs);
|
|
157
|
+
const ciCdMode = commandName === 'task' && hasTaskCiCdFlag(parsedArgs);
|
|
158
|
+
|
|
159
|
+
if (splitMode && ciCdMode) {
|
|
160
|
+
await safeReply(ctx, '❌ `--ci-cd` and `--split` cannot be used together.', { reply_to_message_id: ctx.message.message_id });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (ciCdMode) {
|
|
165
|
+
const built = buildTaskCiCdCommandArgs(ctx.message.text);
|
|
166
|
+
if (!built.repository) {
|
|
167
|
+
await safeReply(ctx, `❌ Missing GitHub repository URL. Usage: \`${commandDisplay} --ci-cd <github-repository-url>\`\n\nExample: \`${commandDisplay} --ci-cd https://github.com/owner/repo\``, { reply_to_message_id: ctx.message.message_id });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const statusMessage = await ctx.reply(`Collecting CI/CD context and creating a GitHub issue in ${built.repository.fullName}...`, {
|
|
172
|
+
reply_to_message_id: ctx.message.message_id,
|
|
173
|
+
disable_web_page_preview: true,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const createdIssue = await createCiCdIssueFn({
|
|
178
|
+
repository: built.repository,
|
|
179
|
+
log: message => VERBOSE && console.log(`[VERBOSE] ${message}`),
|
|
180
|
+
});
|
|
181
|
+
await editTelegramMessage(ctx, statusMessage, `Created GitHub issue:\n${createdIssue.url}\n\nReply to this message with /solve --development-log --deep-analysis --auto-merge to continue the full CI/CD remediation workflow.`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
await editTelegramMessage(ctx, statusMessage, `Error creating CI/CD issue:\n${error.message || String(error)}`);
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
141
187
|
|
|
142
188
|
if (!splitMode) {
|
|
143
189
|
const replyText = getReplyText(ctx.message);
|
|
@@ -128,7 +128,7 @@ export function appendPullRequestLine(infoBlock, pullRequestUrl, { locale = null
|
|
|
128
128
|
return [...before, prLine, ...after].join('\n');
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
export function formatSessionCompletionMessage({ sessionName, sessionInfo, statusResult = null, observedEndTime = new Date(), exitCode = null, infoBlock = '', pullRequestUrl = null, extraSections = [], locale = null } = {}) {
|
|
131
|
+
export function formatSessionCompletionMessage({ sessionName, sessionInfo, statusResult = null, observedEndTime = new Date(), exitCode = null, infoBlock = '', pullRequestUrl = null, pullRequestState = null, extraSections = [], locale = null } = {}) {
|
|
132
132
|
const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
|
|
133
133
|
const outcome = classifySessionOutcome({ exitCode: finalExitCode, status: statusResult?.status || null });
|
|
134
134
|
const { failed, killed, signal } = outcome;
|
|
@@ -142,6 +142,7 @@ export function formatSessionCompletionMessage({ sessionName, sessionInfo, statu
|
|
|
142
142
|
// is an orderly, intentional termination, so surface it as such regardless of
|
|
143
143
|
// which signal actually delivered the kill.
|
|
144
144
|
const stopRequestedByUser = Boolean(sessionInfo?.stopRequestedByUser);
|
|
145
|
+
const pullRequestMerged = pullRequestState?.merged === true || Boolean(pullRequestState?.mergedAt);
|
|
145
146
|
let statusEmojiOverride = null;
|
|
146
147
|
let statusText;
|
|
147
148
|
if (killed && stopRequestedByUser) {
|
|
@@ -158,6 +159,14 @@ export function formatSessionCompletionMessage({ sessionName, sessionInfo, statu
|
|
|
158
159
|
const exitSuffix = showCode ? ` (exit code: ${finalExitCode})` : '';
|
|
159
160
|
const reason = signal ? signal.reason : 'killed';
|
|
160
161
|
statusText = text(messageLocale, 'telegram.work_session_killed', `Work session ${reason}${exitSuffix}`, { reason, exitCode: finalExitCode ?? '', signal: signal?.signal ?? '', exitSuffix });
|
|
162
|
+
} else if (failed && pullRequestMerged) {
|
|
163
|
+
// Issue #2117: the runner's exit code is still authoritative and must not
|
|
164
|
+
// be hidden, but calling the entire session "failed" contradicts the
|
|
165
|
+
// externally verified result when its pull request has already merged.
|
|
166
|
+
// Describe both outcomes so operators know the requested goal completed
|
|
167
|
+
// and that a later orchestration failure still needs investigation.
|
|
168
|
+
statusEmojiOverride = '⚠️';
|
|
169
|
+
statusText = text(messageLocale, 'telegram.work_session_merged_but_failed', `Pull request merged, but the work session exited with code: ${finalExitCode}`, { exitCode: finalExitCode });
|
|
161
170
|
} else if (failed) {
|
|
162
171
|
statusText = text(messageLocale, 'telegram.work_session_failed', `Work session failed (exit code: ${finalExitCode})`, { exitCode: finalExitCode });
|
|
163
172
|
} else {
|
|
@@ -183,7 +192,8 @@ export function formatSessionCompletionMessage({ sessionName, sessionInfo, statu
|
|
|
183
192
|
|
|
184
193
|
// Issue #594: --show-limits virtual option appends snapshot/delta sections
|
|
185
194
|
// (Markdown code blocks) below the standard completion details.
|
|
186
|
-
const
|
|
195
|
+
const mergedWithRunnerFailureSection = failed && !killed && pullRequestMerged ? [`✅ ${text(messageLocale, 'telegram.work_session_merged_success', 'The requested pull request was merged successfully.')}`, `⚠️ ${text(messageLocale, 'telegram.work_session_runner_also_failed', 'The runner also failed; its exit code is preserved for investigation.')}`].join('\n') : null;
|
|
196
|
+
const extras = [mergedWithRunnerFailureSection, ...(Array.isArray(extraSections) ? extraSections : [])].filter(Boolean);
|
|
187
197
|
if (extras.length > 0) {
|
|
188
198
|
message += `\n\n${extras.join('\n\n')}`;
|
|
189
199
|
}
|