@link-assistant/hive-mind 1.59.6 → 1.60.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 +44 -0
- package/package.json +3 -1
- package/src/cli-arguments.lib.mjs +68 -0
- package/src/configure-claude.lib.mjs +37 -14
- package/src/hive-screens.lib.mjs +36 -22
- package/src/hive.mjs +10 -16
- package/src/memory-check.mjs +10 -11
- package/src/review.mjs +72 -59
- package/src/reviewers-hive.mjs +108 -92
- package/src/solve.auto-merge.lib.mjs +37 -0
- package/src/solve.config.lib.mjs +12 -17
- package/src/solve.mjs +17 -35
- package/src/solve.results.lib.mjs +86 -12
- package/src/solve.watch.lib.mjs +36 -0
- package/src/start-screen.mjs +74 -15
- package/src/task.agent-command.lib.mjs +61 -0
- package/src/task.config.lib.mjs +122 -0
- package/src/task.mjs +217 -232
- package/src/task.split.lib.mjs +221 -0
- package/src/telegram-bot.mjs +27 -111
- package/src/telegram-command-execution.lib.mjs +98 -0
- package/src/telegram-solve-queue.lib.mjs +2 -1
- package/src/telegram-task-command.lib.mjs +133 -0
- package/src/tool-comments.lib.mjs +12 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { buildUserMention } from './buildUserMention.lib.mjs';
|
|
2
|
+
import { validateModelName } from './models/index.mjs';
|
|
3
|
+
import { parseTaskIssueUrl } from './task.split.lib.mjs';
|
|
4
|
+
import { escapeMarkdown } from './telegram-markdown.lib.mjs';
|
|
5
|
+
import { extractIsolationFromArgs, isValidPerCommandIsolation } from './telegram-isolation.lib.mjs';
|
|
6
|
+
import { moveArgumentToFront, parseCommandArgs } from './telegram-solve-command.lib.mjs';
|
|
7
|
+
import { formatStartingWorkSessionMessage } from './work-session-formatting.lib.mjs';
|
|
8
|
+
|
|
9
|
+
export const TASK_COMMAND_NAMES = Object.freeze(['task', 'split']);
|
|
10
|
+
|
|
11
|
+
export function getTaskCommandNameFromText(text) {
|
|
12
|
+
if (!text || typeof text !== 'string') return null;
|
|
13
|
+
const firstLine = text.split('\n')[0].trim();
|
|
14
|
+
const match = firstLine.match(/^\/(\w+)(?:@\S+)?(?:\s|$)/);
|
|
15
|
+
const command = match ? match[1].toLowerCase() : null;
|
|
16
|
+
return TASK_COMMAND_NAMES.includes(command) ? command : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function applyTaskCommandDefaults(args) {
|
|
20
|
+
const hasSplit = args.includes('--split') || args.some(arg => arg.startsWith('--split='));
|
|
21
|
+
return hasSplit ? args : [...args, '--split'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function findTaskIssueUrl(args) {
|
|
25
|
+
return args.find(arg => !arg.startsWith('-') && parseTaskIssueUrl(arg).valid) || null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getTaskToolFromArgs(args) {
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
if (args[i] === '--tool' && i + 1 < args.length) return args[i + 1];
|
|
31
|
+
if (args[i].startsWith('--tool=')) return args[i].substring('--tool='.length);
|
|
32
|
+
}
|
|
33
|
+
return 'claude';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getModelFromArgs(args) {
|
|
37
|
+
for (let i = 0; i < args.length; i++) {
|
|
38
|
+
if ((args[i] === '--model' || args[i] === '-m') && i + 1 < args.length) return args[i + 1];
|
|
39
|
+
if (args[i].startsWith('--model=')) return args[i].substring('--model='.length);
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateTaskModel(args) {
|
|
45
|
+
const model = getModelFromArgs(args);
|
|
46
|
+
if (!model) return null;
|
|
47
|
+
const validation = validateModelName(model, getTaskToolFromArgs(args));
|
|
48
|
+
return validation.valid ? null : validation.message;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildTaskCommandArgs(text) {
|
|
52
|
+
const args = applyTaskCommandDefaults(parseCommandArgs(text));
|
|
53
|
+
const issueUrl = findTaskIssueUrl(args);
|
|
54
|
+
return {
|
|
55
|
+
args: issueUrl ? moveArgumentToFront(args, issueUrl) : args,
|
|
56
|
+
issueUrl,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function registerTaskCommands(bot, options) {
|
|
61
|
+
const { VERBOSE, taskEnabled, addBreadcrumb, isOldMessage, isGroupChat, isTopicAuthorized, buildAuthErrorMessage, isChatStopped, getStoppedChatRejectMessage, safeReply, executeAndUpdateMessage } = options;
|
|
62
|
+
|
|
63
|
+
async function handleTaskCommand(ctx) {
|
|
64
|
+
const commandName = getTaskCommandNameFromText(ctx.message?.text) || 'task';
|
|
65
|
+
const commandDisplay = `/${commandName}`;
|
|
66
|
+
VERBOSE && console.log(`[VERBOSE] ${commandDisplay} command received`);
|
|
67
|
+
|
|
68
|
+
await addBreadcrumb({
|
|
69
|
+
category: 'telegram.command',
|
|
70
|
+
message: `${commandDisplay} command received`,
|
|
71
|
+
level: 'info',
|
|
72
|
+
data: { chatId: ctx.chat?.id, chatType: ctx.chat?.type, userId: ctx.from?.id, username: ctx.from?.username },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!taskEnabled) {
|
|
76
|
+
await ctx.reply('❌ The task command is disabled on this bot instance.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (isOldMessage(ctx)) return;
|
|
80
|
+
if (!isGroupChat(ctx)) {
|
|
81
|
+
await ctx.reply(`❌ The ${commandDisplay} command only works in group chats. Please add this bot to a group and make it an admin.`, { reply_to_message_id: ctx.message.message_id });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!isTopicAuthorized(ctx)) {
|
|
85
|
+
await ctx.reply(buildAuthErrorMessage(ctx), { reply_to_message_id: ctx.message.message_id });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (isChatStopped(ctx.chat.id)) {
|
|
89
|
+
await safeReply(ctx, getStoppedChatRejectMessage(ctx.chat.id, 'Task'), { reply_to_message_id: ctx.message.message_id });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const built = buildTaskCommandArgs(ctx.message.text);
|
|
94
|
+
if (!built.issueUrl) {
|
|
95
|
+
await safeReply(ctx, `❌ Missing GitHub issue URL. Usage: \`${commandDisplay} <github-issue-url> [options]\`\n\nExample: \`${commandDisplay} https://github.com/owner/repo/issues/123\``, { reply_to_message_id: ctx.message.message_id });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const parsedIssue = parseTaskIssueUrl(built.issueUrl);
|
|
100
|
+
if (!parsedIssue.valid) {
|
|
101
|
+
await safeReply(ctx, `❌ ${escapeMarkdown(parsedIssue.error || 'Invalid GitHub issue URL')}`, { reply_to_message_id: ctx.message.message_id });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { backend: perCommandIsolation, filteredArgs } = extractIsolationFromArgs(built.args);
|
|
106
|
+
if (perCommandIsolation && !isValidPerCommandIsolation(perCommandIsolation)) {
|
|
107
|
+
await safeReply(ctx, `❌ Invalid --isolation value '${escapeMarkdown(perCommandIsolation)}'. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const modelError = validateTaskModel(filteredArgs);
|
|
112
|
+
if (modelError) {
|
|
113
|
+
await safeReply(ctx, `❌ ${escapeMarkdown(modelError)}`, { reply_to_message_id: ctx.message.message_id });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const requester = buildUserMention({ user: ctx.from, parseMode: 'Markdown' });
|
|
118
|
+
const userOptionsRaw = built.args.slice(1).join(' ');
|
|
119
|
+
let infoBlock = `Requested by: ${requester}\nIssue: ${escapeMarkdown(built.issueUrl)}`;
|
|
120
|
+
if (userOptionsRaw) infoBlock += `\n\n🛠 Options: ${escapeMarkdown(userOptionsRaw)}`;
|
|
121
|
+
|
|
122
|
+
const taskUrlContext = { owner: parsedIssue.owner, repo: parsedIssue.repo, number: parsedIssue.number, type: parsedIssue.type, normalized: parsedIssue.normalized || built.issueUrl };
|
|
123
|
+
const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
|
|
124
|
+
await executeAndUpdateMessage(ctx, startingMessage, 'task', filteredArgs, infoBlock, perCommandIsolation || null, getTaskToolFromArgs(filteredArgs), taskUrlContext);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
bot.command(
|
|
128
|
+
TASK_COMMAND_NAMES.map(command => new RegExp(`^${command}$`, 'i')),
|
|
129
|
+
handleTaskCommand
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return { handleTaskCommand, TASK_COMMAND_NAMES };
|
|
133
|
+
}
|
|
@@ -55,6 +55,17 @@ export const AUTO_MERGED_MARKER = 'Auto-merged';
|
|
|
55
55
|
// solve.auto-merge.lib.mjs — billing-limit notification (spending cap / free tier)
|
|
56
56
|
export const BILLING_LIMIT_MARKER = 'GitHub Actions Billing Limit';
|
|
57
57
|
|
|
58
|
+
// solve.results.lib.mjs — working session summary comments posted by
|
|
59
|
+
// --attach-solution-summary / --auto-attach-solution-summary at the end of
|
|
60
|
+
// every working session (top-level solve, auto-restart-until-mergeable
|
|
61
|
+
// iteration, or watch-mode iteration). Issue #1728: Renamed from
|
|
62
|
+
// "Solution summary" because not every working session is a solution draft —
|
|
63
|
+
// many are continuation/restart iterations that are part of an in-progress
|
|
64
|
+
// solution. Tracking it as a tool-generated marker prevents the next
|
|
65
|
+
// iteration's --auto-attach-solution-summary check from mistaking a
|
|
66
|
+
// previous iteration's summary for an AI-authored comment.
|
|
67
|
+
export const WORKING_SESSION_SUMMARY_MARKER = 'Working session summary';
|
|
68
|
+
|
|
58
69
|
// github.lib.mjs — fork contributor "Allow edits by maintainers" request
|
|
59
70
|
export const MAINTAINER_ACCESS_REQUEST_MARKER = 'Allow edits by maintainers';
|
|
60
71
|
|
|
@@ -88,7 +99,7 @@ export const USAGE_LIMIT_REACHED_MARKER = 'Usage Limit Reached';
|
|
|
88
99
|
* named constants above so that adding a new marker only requires adding
|
|
89
100
|
* the constant and appending it here.
|
|
90
101
|
*/
|
|
91
|
-
export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER];
|
|
102
|
+
export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, WORKING_SESSION_SUMMARY_MARKER];
|
|
92
103
|
|
|
93
104
|
/**
|
|
94
105
|
* Markers that indicate the end of a working session. Used by
|