@link-assistant/hive-mind 1.77.1 → 1.78.1
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 +67 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +2 -1
- package/src/claude.lib.mjs +2 -2
- package/src/claude.runtime-switch.lib.mjs +2 -1
- package/src/codex.lib.mjs +2 -1
- package/src/config.lib.mjs +2 -1
- package/src/contributing-guidelines.lib.mjs +2 -1
- package/src/gemini.lib.mjs +2 -1
- package/src/github-entity-validation.lib.mjs +2 -1
- package/src/github-error-reporter.lib.mjs +2 -1
- package/src/github.batch.lib.mjs +2 -1
- package/src/github.lib.mjs +2 -1
- package/src/handoff-skill.lib.mjs +2 -1
- package/src/hive.mjs +11 -10
- package/src/interactive-codex-events.lib.mjs +167 -0
- package/src/interactive-mode.lib.mjs +62 -152
- package/src/interactive-mode.shared.lib.mjs +1 -0
- package/src/interactive-system-events.lib.mjs +224 -0
- package/src/isolation-runner.lib.mjs +2 -1
- package/src/lenv-reader.lib.mjs +2 -1
- package/src/lib.mjs +2 -1
- package/src/lino.lib.mjs +2 -1
- package/src/local-ci-checks.lib.mjs +2 -1
- package/src/log-upload.lib.mjs +2 -1
- package/src/memory-check.mjs +2 -1
- package/src/models/index.mjs +2 -1
- package/src/npm-global-prefix.lib.mjs +160 -0
- package/src/opencode.lib.mjs +2 -1
- package/src/playwright-mcp.lib.mjs +2 -1
- package/src/protect-branch.mjs +2 -1
- package/src/queue-config.lib.mjs +2 -1
- package/src/qwen.lib.mjs +2 -1
- package/src/review.mjs +2 -1
- package/src/reviewers-hive.mjs +2 -1
- package/src/solve.auto-continue.lib.mjs +2 -1
- package/src/solve.auto-ensure.lib.mjs +2 -1
- package/src/solve.auto-merge-helpers.lib.mjs +2 -1
- package/src/solve.auto-merge.lib.mjs +2 -1
- package/src/solve.bootstrap.lib.mjs +2 -1
- package/src/solve.escalate.lib.mjs +2 -1
- package/src/solve.execution.lib.mjs +2 -1
- package/src/solve.fork-detection.lib.mjs +2 -1
- package/src/solve.fork-sync.lib.mjs +2 -1
- package/src/solve.keep-working.lib.mjs +2 -1
- package/src/solve.mjs +2 -2
- package/src/solve.repository.lib.mjs +2 -1
- package/src/solve.restart-shared.lib.mjs +2 -1
- package/src/solve.results.lib.mjs +2 -1
- package/src/solve.validation.lib.mjs +2 -1
- package/src/solve.watch.lib.mjs +2 -1
- package/src/telegram-bot.mjs +2 -1
- package/src/telegram-safe-reply.lib.mjs +112 -10
- package/src/telegram-solve-queue.helpers.lib.mjs +117 -14
- package/src/telegram-solve-queue.lib.mjs +38 -44
- package/src/token-sanitization.lib.mjs +2 -1
- package/src/tool-comments.lib.mjs +2 -1
- package/src/use-m-bootstrap.lib.mjs +23 -0
- package/src/useless-tools.lib.mjs +2 -1
- package/src/youtrack/youtrack.lib.mjs +2 -1
|
@@ -61,27 +61,129 @@ function findTelegramSplitIndex(text, limit) {
|
|
|
61
61
|
return limit;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// A Markdown fenced code block opens/closes with ``` or ~~~ (optionally indented
|
|
65
|
+
// and, on the opening fence, followed by a language/info string). We track these
|
|
66
|
+
// so a split that lands inside a code block can close the fence on the current
|
|
67
|
+
// chunk and reopen it — repeating the language — on the next one (issue #1891).
|
|
68
|
+
const CODE_FENCE_RE = /^(\s*)(```+|~~~+)(.*)$/;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse a single line as a Markdown code-fence delimiter.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} line
|
|
74
|
+
* @returns {{indent: string, marker: string, info: string}|null}
|
|
75
|
+
* The fence parts, or `null` when the line is not a fence.
|
|
76
|
+
*/
|
|
77
|
+
export function parseCodeFence(line) {
|
|
78
|
+
const match = CODE_FENCE_RE.exec(line);
|
|
79
|
+
if (!match) return null;
|
|
80
|
+
return { indent: match[1], marker: match[2], info: match[3].trim() };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Hard-split a single physical line that is itself longer than `limit` into
|
|
85
|
+
* pieces that each fit, preferring a break at a natural separator near the end
|
|
86
|
+
* and falling back to a hard character cut. Used only for pathologically long
|
|
87
|
+
* lines (normal queue/help lines are short).
|
|
88
|
+
*
|
|
89
|
+
* @param {string} line
|
|
90
|
+
* @param {number} limit
|
|
91
|
+
* @returns {string[]}
|
|
92
|
+
*/
|
|
93
|
+
function splitLongLine(line, limit) {
|
|
94
|
+
const pieces = [];
|
|
95
|
+
let remaining = line;
|
|
96
|
+
while (remaining.length > limit) {
|
|
97
|
+
let splitAt = findTelegramSplitIndex(remaining, limit);
|
|
98
|
+
if (splitAt <= 0 || splitAt > limit) splitAt = limit;
|
|
99
|
+
let piece = remaining.slice(0, splitAt);
|
|
100
|
+
if (!piece.trim()) {
|
|
101
|
+
splitAt = limit;
|
|
102
|
+
piece = remaining.slice(0, splitAt);
|
|
103
|
+
}
|
|
104
|
+
pieces.push(piece);
|
|
105
|
+
remaining = remaining.slice(splitAt);
|
|
106
|
+
}
|
|
107
|
+
if (remaining) pieces.push(remaining);
|
|
108
|
+
return pieces;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Split a (possibly oversized) Telegram message into chunks that each stay
|
|
113
|
+
* within `limit` characters.
|
|
114
|
+
*
|
|
115
|
+
* Splitting happens on line boundaries so inline Markdown entities (bold,
|
|
116
|
+
* italic, links — none of which may span a newline in Telegram's legacy
|
|
117
|
+
* Markdown) are never cut in half. Fenced code blocks, which *do* span lines,
|
|
118
|
+
* are kept valid across the split: when a break lands inside a code block the
|
|
119
|
+
* current chunk gets a closing fence appended and the next chunk re-opens the
|
|
120
|
+
* fence with the same marker and language (issue #1891).
|
|
121
|
+
*
|
|
122
|
+
* @param {string} text
|
|
123
|
+
* @param {number} [limit=TELEGRAM_TEXT_LIMIT]
|
|
124
|
+
* @returns {string[]} One or more chunks; always at least one element.
|
|
125
|
+
*/
|
|
64
126
|
export function splitTelegramMessageText(text, limit = TELEGRAM_TEXT_LIMIT) {
|
|
65
127
|
const source = String(text ?? '');
|
|
66
128
|
if (source.length <= limit) return [source];
|
|
67
129
|
|
|
130
|
+
// Reserve headroom on each physical line for a possible fence reopen/close
|
|
131
|
+
// pair so re-wrapping a code block never pushes a chunk past the limit.
|
|
132
|
+
const FENCE_HEADROOM = 16;
|
|
133
|
+
const lineLimit = Math.max(1, limit - FENCE_HEADROOM);
|
|
134
|
+
|
|
135
|
+
// Expand into physical lines, pre-splitting any line that alone exceeds the
|
|
136
|
+
// budget so the chunker below only ever deals with lines that fit.
|
|
137
|
+
const lines = [];
|
|
138
|
+
for (const raw of source.split('\n')) {
|
|
139
|
+
if (raw.length <= lineLimit) lines.push(raw);
|
|
140
|
+
else lines.push(...splitLongLine(raw, lineLimit));
|
|
141
|
+
}
|
|
142
|
+
|
|
68
143
|
const chunks = [];
|
|
69
|
-
let
|
|
144
|
+
let current = '';
|
|
145
|
+
let openFence = null; // { indent, marker, info } while inside a code block
|
|
70
146
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
let chunk = remaining.slice(0, splitAt).trimEnd();
|
|
147
|
+
const closeFenceLine = () => `${openFence.indent}${openFence.marker}`;
|
|
148
|
+
const reopenFenceLine = () => `${openFence.indent}${openFence.marker}${openFence.info}`;
|
|
74
149
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
150
|
+
const flush = () => {
|
|
151
|
+
let chunk = current;
|
|
152
|
+
if (openFence) {
|
|
153
|
+
// Close the still-open code block at the end of this chunk.
|
|
154
|
+
chunk = chunk.length ? `${chunk}\n${closeFenceLine()}` : closeFenceLine();
|
|
78
155
|
}
|
|
79
|
-
|
|
80
156
|
chunks.push(chunk);
|
|
81
|
-
|
|
157
|
+
// Re-open the fence at the start of the next chunk so the code block (and
|
|
158
|
+
// its language) continues seamlessly.
|
|
159
|
+
current = openFence ? reopenFenceLine() : '';
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
for (const line of lines) {
|
|
163
|
+
const separatorLength = current.length ? 1 : 0;
|
|
164
|
+
const closeReserve = openFence ? closeFenceLine().length + 1 : 0;
|
|
165
|
+
const projected = current.length + separatorLength + line.length + closeReserve;
|
|
166
|
+
|
|
167
|
+
if (current.length > 0 && projected > limit) {
|
|
168
|
+
flush();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
current = current.length ? `${current}\n${line}` : line;
|
|
172
|
+
|
|
173
|
+
const fence = parseCodeFence(line);
|
|
174
|
+
if (fence) {
|
|
175
|
+
// A fence line toggles code-block state: open when outside, close when
|
|
176
|
+
// already inside.
|
|
177
|
+
openFence = openFence ? null : fence;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Defensive: close any fence left open by unbalanced input.
|
|
182
|
+
if (openFence && current.length) {
|
|
183
|
+
current = `${current}\n${closeFenceLine()}`;
|
|
82
184
|
}
|
|
185
|
+
if (current.length || chunks.length === 0) chunks.push(current);
|
|
83
186
|
|
|
84
|
-
if (remaining) chunks.push(remaining);
|
|
85
187
|
return chunks;
|
|
86
188
|
}
|
|
87
189
|
|
|
@@ -50,18 +50,18 @@ export function formatQueueItemLink(url) {
|
|
|
50
50
|
* @param {boolean} [opts.withError] - Append `— error` when the item failed.
|
|
51
51
|
* @returns {string} The formatted section (empty string when no items).
|
|
52
52
|
*/
|
|
53
|
-
export function formatQueueHistorySection({ items, emoji, label, max, locale, withError = false }) {
|
|
53
|
+
export function formatQueueHistorySection({ items, emoji, label, max, locale, withError = false, indent = '' }) {
|
|
54
54
|
if (!items || items.length === 0) return '';
|
|
55
|
-
let section =
|
|
55
|
+
let section = `${indent}*${label}* (${items.length}):\n`;
|
|
56
56
|
for (const item of [...items].reverse().slice(0, max)) {
|
|
57
|
-
section +=
|
|
57
|
+
section += `${indent} ${emoji} ${formatQueueItemLink(item.url)}`;
|
|
58
58
|
if (withError && item.error) section += ` — ${item.error}`;
|
|
59
59
|
section += '\n';
|
|
60
60
|
}
|
|
61
61
|
if (items.length > max) {
|
|
62
|
-
section +=
|
|
62
|
+
section += `${indent} ... ${lt('queue_and_more', { count: items.length - max }, { locale })}\n`;
|
|
63
63
|
}
|
|
64
|
-
return
|
|
64
|
+
return section;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -128,25 +128,128 @@ export function collectExecutingItems({ processingItems = [], sessionItems = [],
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
* Render the per-tool "executing" lines
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* Render the per-tool "executing" lines for the detailed queue status as a
|
|
132
|
+
* compact, de-duplicated list (issue #1891):
|
|
133
|
+
*
|
|
134
|
+
* `• owner/repo#number (▶️ 2h 14m 16s)`
|
|
135
|
+
*
|
|
136
|
+
* The ▶️ emoji replaces the repeated literal "processing" status word that
|
|
137
|
+
* appeared on every line in the old format. Items are listed in full by
|
|
138
|
+
* default; pass a finite `max` to cap them with a localized "... and N more"
|
|
139
|
+
* line.
|
|
134
140
|
*
|
|
135
141
|
* @param {object} opts
|
|
136
142
|
* @param {Array} opts.items - Output of {@link collectExecutingItems}.
|
|
137
|
-
* @param {number} opts.max - Maximum items
|
|
143
|
+
* @param {number} [opts.max=Infinity] - Maximum items before collapsing.
|
|
144
|
+
* @param {string|null} opts.locale - Locale for labels/durations.
|
|
145
|
+
* @param {string} [opts.label] - Optional sub-list heading (e.g. "Processing").
|
|
146
|
+
* When set, a ` *Label* (count):` header is rendered above the items so each
|
|
147
|
+
* status gets its own clearly-labeled list (issue #1891 follow-up).
|
|
148
|
+
* @returns {string} The formatted lines (empty string when no items).
|
|
149
|
+
*/
|
|
150
|
+
export function formatQueueExecutingItems({ items, max = Infinity, locale, label }) {
|
|
151
|
+
if (!items || items.length === 0) return '';
|
|
152
|
+
// Items nest one level under their section label when labeled (issue #1891).
|
|
153
|
+
const itemIndent = label ? ' ' : ' ';
|
|
154
|
+
let out = label ? ` *${label}* (${items.length}):\n` : '';
|
|
155
|
+
for (const item of items.slice(0, max)) {
|
|
156
|
+
out += `${itemIndent}• ${formatQueueItemLink(item.url)} (▶️ ${formatDuration(item.waitMs, { locale })})\n`;
|
|
157
|
+
}
|
|
158
|
+
if (items.length > max) {
|
|
159
|
+
out += `${itemIndent} ... ${lt('queue_and_more', { count: items.length - max }, { locale })}\n`;
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Backwards-compatible alias for {@link formatQueueExecutingItems}.
|
|
166
|
+
* @deprecated Use {@link formatQueueExecutingItems}.
|
|
167
|
+
*/
|
|
168
|
+
export const formatQueueProcessingItems = formatQueueExecutingItems;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Group queue history items (completed/failed) by their `tool` key so each tool
|
|
172
|
+
* queue can render its own Completed/Failed list (issue #1891 follow-up).
|
|
173
|
+
*
|
|
174
|
+
* @param {Array<{tool?: string}>} items
|
|
175
|
+
* @returns {Object<string, Array>} Map of tool key → items.
|
|
176
|
+
*/
|
|
177
|
+
export function groupQueueItemsByTool(items) {
|
|
178
|
+
const byTool = {};
|
|
179
|
+
for (const item of items || []) {
|
|
180
|
+
const tool = item.tool || 'claude';
|
|
181
|
+
(byTool[tool] ||= []).push(item);
|
|
182
|
+
}
|
|
183
|
+
return byTool;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Render one tool queue's block for the detailed status as a set of *separate,
|
|
188
|
+
* individually-labeled lists* — Processing, Pending, Completed, Failed — instead
|
|
189
|
+
* of one merged bullet list (issue #1891 follow-up). Empty lists are omitted.
|
|
190
|
+
*
|
|
191
|
+
* @param {object} opts
|
|
192
|
+
* @param {string} opts.tool - Tool key (header label).
|
|
193
|
+
* @param {Array} opts.executing - Output of {@link collectExecutingItems}.
|
|
194
|
+
* @param {Array} opts.pendingItems - `{url, waitMs, waitingReason}` per pending item.
|
|
195
|
+
* @param {Array} opts.completed - Completed history items for this tool.
|
|
196
|
+
* @param {Array} opts.failed - Failed history items for this tool.
|
|
197
|
+
* @param {object} opts.labels - Localized labels (`pendingLower`, `processingLower`,
|
|
198
|
+
* `pending`, `processing`, `completed`, `failed`).
|
|
199
|
+
* @param {number} opts.max - Max history items before the "… and N more" collapse.
|
|
200
|
+
* @param {string|null} opts.locale - Locale for labels/durations.
|
|
201
|
+
* @returns {string} The formatted block (with a trailing blank line).
|
|
202
|
+
*/
|
|
203
|
+
export function formatQueueToolSection({ tool, executing, pendingItems, completed, failed, labels, max, locale }) {
|
|
204
|
+
let block = `*${tool}*\n`;
|
|
205
|
+
|
|
206
|
+
// Processing list.
|
|
207
|
+
block += formatQueueExecutingItems({ items: executing, locale, label: labels.processing });
|
|
208
|
+
|
|
209
|
+
// Pending list + the shared waiting reason once (when all items agree on it).
|
|
210
|
+
block += formatQueuePendingItems({ items: pendingItems, locale, label: labels.pending });
|
|
211
|
+
const distinctReasons = [...new Set(pendingItems.map(item => item.waitingReason).filter(Boolean))];
|
|
212
|
+
if (distinctReasons.length === 1) {
|
|
213
|
+
block += ` ⏳ ${distinctReasons[0].replace(/\n+/g, '; ')}\n`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Completed / Failed lists for this tool (most-recent-first, capped), indented
|
|
217
|
+
// so the label sits under the tool header and items under it.
|
|
218
|
+
block += formatQueueHistorySection({ items: completed, emoji: '✅', label: labels.completed, max, locale, indent: ' ' });
|
|
219
|
+
block += formatQueueHistorySection({ items: failed, emoji: '❌', label: labels.failed, max, locale, withError: true, indent: ' ' });
|
|
220
|
+
|
|
221
|
+
return `${block}\n`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Render the per-tool "pending/waiting" lines for the detailed queue status as a
|
|
226
|
+
* compact list (issue #1891):
|
|
227
|
+
*
|
|
228
|
+
* `• owner/repo#number (⏳ 5m 2s)`
|
|
229
|
+
*
|
|
230
|
+
* The per-item waiting *reason* is deliberately omitted here — it is almost
|
|
231
|
+
* always identical across pending items, so the caller shows it once for the
|
|
232
|
+
* whole tool instead of repeating it on every line.
|
|
233
|
+
*
|
|
234
|
+
* @param {object} opts
|
|
235
|
+
* @param {Array<{url: string, waitMs: number}>} opts.items - Pending items.
|
|
236
|
+
* @param {number} [opts.max=Infinity] - Maximum items before collapsing.
|
|
138
237
|
* @param {string|null} opts.locale - Locale for labels/durations.
|
|
238
|
+
* @param {string} [opts.label] - Optional sub-list heading (e.g. "Pending").
|
|
239
|
+
* When set, a ` *Label* (count):` header is rendered above the items so each
|
|
240
|
+
* status gets its own clearly-labeled list (issue #1891 follow-up).
|
|
139
241
|
* @returns {string} The formatted lines (empty string when no items).
|
|
140
242
|
*/
|
|
141
|
-
export function
|
|
243
|
+
export function formatQueuePendingItems({ items, max = Infinity, locale, label }) {
|
|
142
244
|
if (!items || items.length === 0) return '';
|
|
143
|
-
|
|
245
|
+
// Items nest one level under their section label when labeled (issue #1891).
|
|
246
|
+
const itemIndent = label ? ' ' : ' ';
|
|
247
|
+
let out = label ? ` *${label}* (${items.length}):\n` : '';
|
|
144
248
|
for (const item of items.slice(0, max)) {
|
|
145
|
-
|
|
146
|
-
out += ` ▶️ ${formatQueueItemLink(item.url)} (${label}, ${formatDuration(item.waitMs, { locale })})\n`;
|
|
249
|
+
out += `${itemIndent}• ${formatQueueItemLink(item.url)} (⏳ ${formatDuration(item.waitMs, { locale })})\n`;
|
|
147
250
|
}
|
|
148
251
|
if (items.length > max) {
|
|
149
|
-
out +=
|
|
252
|
+
out += `${itemIndent} ... ${lt('queue_and_more', { count: items.length - max }, { locale })}\n`;
|
|
150
253
|
}
|
|
151
254
|
return out;
|
|
152
255
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { getCachedClaudeLimits, getCachedCodexLimits, getCachedGitHubLimits, getCachedMemoryInfo, getCachedCpuInfo, getCachedDiskInfo, getLimitCache } from './limits.lib.mjs';
|
|
19
19
|
export { formatDuration, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses } from './telegram-solve-queue.helpers.lib.mjs';
|
|
20
|
-
import { collectExecutingItems, formatDuration,
|
|
20
|
+
import { collectExecutingItems, formatDuration, formatQueueToolSection, formatWaitingReason, getRunningAgentProcesses, getRunningClaudeProcesses, getRunningCodexProcesses, getRunningGeminiProcesses, getRunningProcesses, getRunningQwenProcesses, getRunningSessionItems, groupQueueItemsByTool } from './telegram-solve-queue.helpers.lib.mjs';
|
|
21
21
|
export { QUEUE_CONFIG, THRESHOLD_STRATEGIES } from './queue-config.lib.mjs';
|
|
22
22
|
import { QUEUE_CONFIG } from './queue-config.lib.mjs';
|
|
23
23
|
import { formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage } from './work-session-formatting.lib.mjs';
|
|
@@ -46,10 +46,6 @@ function appendRemainingDuration(reason, ms, locale) {
|
|
|
46
46
|
return `${reason} (${lt('remaining', { duration: formatDuration(ms, { locale }) }, { locale })})`;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
function queueStatusLabel(status, locale) {
|
|
50
|
-
return lt(`queue_status_${status}`, {}, { locale });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
49
|
/**
|
|
54
50
|
* Queue item representing a /solve command request
|
|
55
51
|
*/
|
|
@@ -1324,65 +1320,63 @@ export class SolveQueue {
|
|
|
1324
1320
|
|
|
1325
1321
|
/**
|
|
1326
1322
|
* Format detailed queue status for Telegram message.
|
|
1327
|
-
* Groups output by tool queue (clickable links per item), then lists the
|
|
1328
|
-
* Completed and Failed history as clickable links, capped per section.
|
|
1329
1323
|
*
|
|
1330
|
-
*
|
|
1331
|
-
*
|
|
1324
|
+
* Each tool queue renders as a set of *separate, individually-labeled lists* —
|
|
1325
|
+
* Processing, Pending, Completed, Failed — instead of one merged bullet list
|
|
1326
|
+
* (issue #1891). Empty lists (and fully-empty tool queues) are skipped, items
|
|
1327
|
+
* render as compact `• link (emoji dur)` rows, and the shared waiting reason
|
|
1328
|
+
* is shown once per tool.
|
|
1332
1329
|
*
|
|
1333
1330
|
* @returns {Promise<string>}
|
|
1334
|
-
* @see https://github.com/link-assistant/hive-mind/issues/1159
|
|
1335
1331
|
* @see https://github.com/link-assistant/hive-mind/issues/1267
|
|
1336
1332
|
* @see https://github.com/link-assistant/hive-mind/issues/1837
|
|
1333
|
+
* @see https://github.com/link-assistant/hive-mind/issues/1891
|
|
1337
1334
|
*/
|
|
1338
1335
|
async formatDetailedStatus(options = {}) {
|
|
1339
1336
|
const locale = getLocale(options);
|
|
1340
1337
|
const stats = this.getStats();
|
|
1341
|
-
const externalProcessing = await this.getExternalProcessingSnapshot(Object.keys(this.queues));
|
|
1342
1338
|
// Currently-executing detached sessions (with issue/PR URLs). These are the
|
|
1343
1339
|
// real running tasks; the queue's own `processing` Map is emptied once a task
|
|
1344
1340
|
// is dispatched, so without this the executing items are never listed (#1837).
|
|
1345
1341
|
const runningSessionItems = await this.getRunningSessionItemsFn(this.verbose);
|
|
1346
1342
|
|
|
1347
|
-
//
|
|
1348
|
-
|
|
1349
|
-
|
|
1343
|
+
// Section labels: each per-tool sub-list uses a capitalized heading.
|
|
1344
|
+
const cap = s => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s);
|
|
1345
|
+
const labels = {
|
|
1346
|
+
pending: cap(lt('queue_pending', {}, { locale })),
|
|
1347
|
+
processing: cap(lt('queue_processing', {}, { locale })),
|
|
1348
|
+
completed: cap(lt('queue_completed', {}, { locale })),
|
|
1349
|
+
failed: cap(lt('queue_failed', {}, { locale })),
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
// Group the (globally-capped) completed/failed history by tool so each tool
|
|
1353
|
+
// queue shows its own Completed/Failed list (issue #1891 follow-up).
|
|
1354
|
+
const completedByTool = groupQueueItemsByTool(this.completed);
|
|
1355
|
+
const failedByTool = groupQueueItemsByTool(this.failed);
|
|
1356
|
+
|
|
1350
1357
|
let message = `📋 *${lt('solve_queue_status', {}, { locale })}*\n\n`;
|
|
1358
|
+
const max = QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE;
|
|
1351
1359
|
|
|
1352
|
-
//
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
const processing = externalProcessing.byTool[tool] || 0;
|
|
1356
|
-
message += `*${tool}* (${lt('queue_pending', {}, { locale })}: ${pending}, ${lt('queue_processing', {}, { locale })}: ${processing})\n`;
|
|
1360
|
+
// Every tool with *any* activity (queued, processing, or in history) so
|
|
1361
|
+
// per-tool history shows even after a tool's live queue has drained.
|
|
1362
|
+
const tools = [...new Set([...Object.keys(this.queues), ...Object.keys(completedByTool), ...Object.keys(failedByTool)])];
|
|
1357
1363
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
//
|
|
1364
|
+
for (const tool of tools) {
|
|
1365
|
+
const toolQueue = this.queues[tool] || [];
|
|
1366
|
+
const pending = toolQueue.length;
|
|
1367
|
+
// Executing tasks: merge the in-memory processing Map with tracked
|
|
1368
|
+
// detached sessions, deduped by URL, so they list even after dispatch
|
|
1369
|
+
// (issue #1837).
|
|
1362
1370
|
const executing = collectExecutingItems({ processingItems: this.processing.values(), sessionItems: runningSessionItems, tool });
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
// Show first queued items for this tool with clickable links
|
|
1366
|
-
const displayItems = toolQueue.slice(0, QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE);
|
|
1367
|
-
for (const item of displayItems) {
|
|
1368
|
-
const waitTime = formatDuration(item.getWaitTime(), { locale });
|
|
1369
|
-
message += ` • ${formatQueueItemLink(item.url)} (${queueStatusLabel(item.status, locale)}, ${waitTime})\n`;
|
|
1370
|
-
if (item.waitingReason) {
|
|
1371
|
-
message += ` └ ${item.waitingReason}\n`;
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
if (toolQueue.length > QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE) {
|
|
1375
|
-
message += ` ... ${lt('queue_and_more', { count: toolQueue.length - QUEUE_CONFIG.MAX_DISPLAY_ITEMS_PER_QUEUE }, { locale })}\n`;
|
|
1376
|
-
}
|
|
1371
|
+
const completed = completedByTool[tool] || [];
|
|
1372
|
+
const failed = failedByTool[tool] || [];
|
|
1377
1373
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1374
|
+
// Skip tools with nothing to show in any list.
|
|
1375
|
+
if (pending === 0 && executing.length === 0 && completed.length === 0 && failed.length === 0) continue;
|
|
1380
1376
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
message += formatQueueHistorySection({ items: this.completed, emoji: '✅', label: lt('queue_completed', {}, { locale }), max, locale });
|
|
1385
|
-
message += formatQueueHistorySection({ items: this.failed, emoji: '❌', label: lt('queue_failed', {}, { locale }), max, locale, withError: true });
|
|
1377
|
+
const pendingItems = toolQueue.map(item => ({ url: item.url, waitMs: item.getWaitTime(), waitingReason: item.waitingReason }));
|
|
1378
|
+
message += formatQueueToolSection({ tool, executing, pendingItems, completed, failed, labels, max, locale });
|
|
1379
|
+
}
|
|
1386
1380
|
|
|
1387
1381
|
// Summary stats
|
|
1388
1382
|
message += `${lt('queue_completed', {}, { locale })}: ${stats.completed}, ${lt('queue_failed', {}, { locale })}: ${stats.failed}\n`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
2
3
|
/**
|
|
3
4
|
* Token sanitization utilities for log content
|
|
4
5
|
* Dual approach: Uses both secretlint AND custom patterns for comprehensive coverage
|
|
@@ -240,7 +241,7 @@ export const getGitHubTokensFromFiles = async () => {
|
|
|
240
241
|
*/
|
|
241
242
|
export const getGitHubTokensFromCommand = async () => {
|
|
242
243
|
if (typeof globalThis.use === 'undefined') {
|
|
243
|
-
|
|
244
|
+
await ensureUseM();
|
|
244
245
|
}
|
|
245
246
|
const { $ } = await globalThis.use('command-stream');
|
|
246
247
|
const tokens = [];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Centralized definitions for GitHub comments posted by solve.mjs itself
|
|
@@ -290,7 +291,7 @@ export const postTrackedCommentFromFile = async ({ $, owner, repo, targetNumber,
|
|
|
290
291
|
throw new Error('postTrackedCommentFromFile requires a command-stream $ helper');
|
|
291
292
|
}
|
|
292
293
|
if (typeof globalThis.use === 'undefined') {
|
|
293
|
-
|
|
294
|
+
await ensureUseM();
|
|
294
295
|
}
|
|
295
296
|
const fs = (await globalThis.use('fs')).promises;
|
|
296
297
|
let body;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { ensureWritableNpmGlobalPrefix } from './npm-global-prefix.lib.mjs';
|
|
4
|
+
|
|
5
|
+
const defaultFetchUseMCode = async () => (await fetch('https://unpkg.com/use-m/use.js')).text();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Load the use-m bootstrap after npm's global prefix has been made safe for
|
|
9
|
+
* use-m's Node resolver.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} [options]
|
|
12
|
+
* @param {(message: string) => void} [options.log]
|
|
13
|
+
* @param {() => Promise<string>} [options.fetchUseMCode]
|
|
14
|
+
* @returns {Promise<Function>} The global use-m `use` function.
|
|
15
|
+
*/
|
|
16
|
+
export const ensureUseM = async (options = {}) => {
|
|
17
|
+
const { log = message => console.log(message), fetchUseMCode = defaultFetchUseMCode } = options;
|
|
18
|
+
await ensureWritableNpmGlobalPrefix({ log });
|
|
19
|
+
if (typeof globalThis.use === 'undefined') {
|
|
20
|
+
globalThis.use = (await eval(await fetchUseMCode())).use;
|
|
21
|
+
}
|
|
22
|
+
return globalThis.use;
|
|
23
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
2
3
|
// Useless Claude Code tools and MCP servers for autonomous headless workflows.
|
|
3
4
|
//
|
|
4
5
|
// Hive-mind runs `claude` inside Docker with `--print --dangerously-skip-permissions`
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
// Related issue: https://github.com/link-assistant/hive-mind/issues/1627
|
|
18
19
|
|
|
19
20
|
if (typeof globalThis.use === 'undefined') {
|
|
20
|
-
|
|
21
|
+
await ensureUseM();
|
|
21
22
|
}
|
|
22
23
|
const fs = (await use('fs')).promises;
|
|
23
24
|
const os = await use('os');
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ensureUseM } from '../use-m-bootstrap.lib.mjs';
|
|
2
3
|
// YouTrack-related utility functions
|
|
3
4
|
|
|
4
5
|
// Check if use is already defined (when imported from other modules)
|
|
5
6
|
// If not, fetch it (when running standalone)
|
|
6
7
|
if (typeof use === 'undefined') {
|
|
7
|
-
|
|
8
|
+
await ensureUseM();
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
// Import log and other utilities from general lib
|