@link-assistant/hive-mind 1.78.0 → 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 +31 -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/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
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* - system.task_started: Agent subtask started (Issue #1450)
|
|
11
11
|
* - system.task_progress: Agent subtask progress update (Issue #1450)
|
|
12
12
|
* - system.task_notification: Agent subtask completed/failed (Issue #1450)
|
|
13
|
+
* - system.thinking_tokens: Accumulated thinking progress (Issue #1900)
|
|
14
|
+
* - system.status / system.compact_boundary / system.task_updated: Status lifecycle events (Issue #1900)
|
|
13
15
|
* - assistant (text): AI text responses
|
|
14
16
|
* - assistant (tool_use): Tool invocations
|
|
15
17
|
* - user (tool_result): Tool execution results
|
|
@@ -32,7 +34,9 @@
|
|
|
32
34
|
* @experimental
|
|
33
35
|
*/
|
|
34
36
|
|
|
35
|
-
import { CONFIG, createCollapsible, createRawJsonSection, createRedactedRawJsonSection, escapeMarkdown, execFileAsync, formatCost, formatDuration, getToolIcon,
|
|
37
|
+
import { CONFIG, createCollapsible, createRawJsonSection, createRedactedRawJsonSection, escapeMarkdown, execFileAsync, formatCost, formatDuration, getToolIcon, safeJsonStringify, sanitizeUnicode, truncateMiddle } from './interactive-mode.shared.lib.mjs';
|
|
38
|
+
import { createCodexEventHandlers } from './interactive-codex-events.lib.mjs';
|
|
39
|
+
import { createSystemLifecycleHandlers } from './interactive-system-events.lib.mjs';
|
|
36
40
|
// Issue #1843: turn base64 image tool-results into inline PR-comment images.
|
|
37
41
|
import { createImageRenderer, extractImagePayload, isImageNode } from './interactive-image-render.lib.mjs';
|
|
38
42
|
// Issue #1625: track interactive-mode comment IDs so they're excluded from
|
|
@@ -108,6 +112,9 @@ export const createInteractiveHandler = options => {
|
|
|
108
112
|
// Track active agent tasks for progress update deduplication
|
|
109
113
|
// Map of task_id -> { commentId, toolUseId, description, commentIdPromise, resolveCommentId }
|
|
110
114
|
pendingTasks: new Map(),
|
|
115
|
+
// Track consecutive thinking-token events so high-frequency model progress
|
|
116
|
+
// edits one live comment instead of posting hundreds of PR comments.
|
|
117
|
+
activeThinking: null,
|
|
111
118
|
// Issue #1472: Diagnostic counters for tracking comment posting success/failure
|
|
112
119
|
eventsProcessed: 0,
|
|
113
120
|
commentsAttempted: 0,
|
|
@@ -190,10 +197,11 @@ export const createInteractiveHandler = options => {
|
|
|
190
197
|
* @param {string} body - Comment body
|
|
191
198
|
* @param {string} [toolId] - Optional tool ID for tracking pending tool calls
|
|
192
199
|
* @param {string} [taskId] - Optional task ID for tracking pending agent tasks
|
|
200
|
+
* @param {Function} [onPosted] - Optional callback invoked with the posted comment ID
|
|
193
201
|
* @returns {Promise<string|null>} Comment ID if successful, null if queued or failed
|
|
194
202
|
* @private
|
|
195
203
|
*/
|
|
196
|
-
const postComment = async (body, toolId = null, taskId = null) => {
|
|
204
|
+
const postComment = async (body, toolId = null, taskId = null, onPosted = null) => {
|
|
197
205
|
if (!prNumber || !owner || !repo) {
|
|
198
206
|
if (verbose) {
|
|
199
207
|
await log('⚠️ Interactive mode: Cannot post comment - missing PR info', { verbose: true });
|
|
@@ -210,7 +218,7 @@ export const createInteractiveHandler = options => {
|
|
|
210
218
|
|
|
211
219
|
if (timeSinceLastComment < CONFIG.MIN_COMMENT_INTERVAL) {
|
|
212
220
|
// Queue the comment for later with toolId/taskId for tracking
|
|
213
|
-
state.commentQueue.push({ body: safeBody, toolId, taskId });
|
|
221
|
+
state.commentQueue.push({ body: safeBody, toolId, taskId, onPosted });
|
|
214
222
|
if (verbose) {
|
|
215
223
|
await log(`📝 Interactive mode: Comment queued (${state.commentQueue.length} in queue)${toolId ? ` [tool: ${toolId}]` : ''}${taskId ? ` [task: ${taskId}]` : ''}`, { verbose: true });
|
|
216
224
|
}
|
|
@@ -247,6 +255,16 @@ export const createInteractiveHandler = options => {
|
|
|
247
255
|
// AI-authored-comment check. Tracking is a no-op when commentId is null.
|
|
248
256
|
trackToolCommentId(commentId);
|
|
249
257
|
|
|
258
|
+
if (commentId && typeof onPosted === 'function') {
|
|
259
|
+
try {
|
|
260
|
+
await onPosted(commentId);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (verbose) {
|
|
263
|
+
await log(`⚠️ Interactive mode: post-comment callback failed for ${commentId}: ${error.message}`, { verbose: true });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
250
268
|
if (verbose) {
|
|
251
269
|
await log(`✅ Interactive mode: Comment posted${commentId ? ` (ID: ${commentId})` : ''} (body: ${safeBody.length} chars)`, { verbose: true });
|
|
252
270
|
}
|
|
@@ -325,9 +343,9 @@ export const createInteractiveHandler = options => {
|
|
|
325
343
|
|
|
326
344
|
const queueItem = state.commentQueue.shift();
|
|
327
345
|
if (queueItem) {
|
|
328
|
-
const { body, toolId, taskId } = queueItem;
|
|
346
|
+
const { body, toolId, taskId, onPosted } = queueItem;
|
|
329
347
|
// Post the comment (don't pass toolId/taskId to avoid re-queueing)
|
|
330
|
-
const commentId = await postComment(body);
|
|
348
|
+
const commentId = await postComment(body, null, null, onPosted);
|
|
331
349
|
|
|
332
350
|
// If this was a tool use comment, update the pending call with the comment ID
|
|
333
351
|
if (toolId && commentId) {
|
|
@@ -1092,6 +1110,20 @@ ${createRawJsonSection(data)}`;
|
|
|
1092
1110
|
}
|
|
1093
1111
|
};
|
|
1094
1112
|
|
|
1113
|
+
const { handleThinkingTokens, finalizeThinkingGroup, handleSystemStatus, handleCompactBoundary, handleTaskUpdated } = createSystemLifecycleHandlers({
|
|
1114
|
+
state,
|
|
1115
|
+
owner,
|
|
1116
|
+
repo,
|
|
1117
|
+
prNumber,
|
|
1118
|
+
log,
|
|
1119
|
+
verbose,
|
|
1120
|
+
postComment,
|
|
1121
|
+
editComment,
|
|
1122
|
+
processQueue,
|
|
1123
|
+
handleTaskProgress,
|
|
1124
|
+
handleTaskNotification,
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1095
1127
|
/**
|
|
1096
1128
|
* Handle rate_limit_event (silently logged, no comment created)
|
|
1097
1129
|
* @param {Object} data - Event data
|
|
@@ -1104,153 +1136,12 @@ ${createRawJsonSection(data)}`;
|
|
|
1104
1136
|
}
|
|
1105
1137
|
};
|
|
1106
1138
|
|
|
1107
|
-
const handleCodexThreadStarted
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const comment = `## 🚀 ${INTERACTIVE_SESSION_STARTED_MARKER}
|
|
1114
|
-
|
|
1115
|
-
| Property | Value |
|
|
1116
|
-
|----------|-------|
|
|
1117
|
-
| **Session ID** | \`${state.sessionId || 'unknown'}\` |
|
|
1118
|
-
| **Model** | \`${data.model || 'unknown'}\` |
|
|
1119
|
-
| **Tool** | \`codex\` |
|
|
1120
|
-
|
|
1121
|
-
---
|
|
1122
|
-
|
|
1123
|
-
${createRawJsonSection(data)}`;
|
|
1124
|
-
|
|
1125
|
-
await postComment(comment);
|
|
1126
|
-
};
|
|
1127
|
-
|
|
1128
|
-
const handleCodexAgentMessage = async data => {
|
|
1129
|
-
const text = data.item?.text;
|
|
1130
|
-
if (typeof text !== 'string' || !text.trim()) return;
|
|
1131
|
-
await handleAssistantText(data, text);
|
|
1132
|
-
};
|
|
1133
|
-
|
|
1134
|
-
const handleCodexTodoList = async data => {
|
|
1135
|
-
const items = Array.isArray(data.item?.items) ? data.item.items : [];
|
|
1136
|
-
const todosPreview = items.length > 0 ? items.map(todo => `- [${todo?.completed ? 'x' : ' '}] ${todo?.text || ''}`).join('\n') : '_No tasks_';
|
|
1137
|
-
const completedCount = items.filter(todo => todo?.completed).length;
|
|
1138
|
-
|
|
1139
|
-
const comment = `## 📋 Codex todo list
|
|
1140
|
-
|
|
1141
|
-
${createCollapsible(`📋 Todos (${completedCount}/${items.length} items)`, todosPreview, true)}
|
|
1142
|
-
|
|
1143
|
-
---
|
|
1144
|
-
|
|
1145
|
-
${createRawJsonSection(data)}`;
|
|
1146
|
-
|
|
1147
|
-
await postComment(comment);
|
|
1148
|
-
};
|
|
1149
|
-
|
|
1150
|
-
const handleCodexCommandExecution = async data => {
|
|
1151
|
-
const item = data.item || {};
|
|
1152
|
-
const command = item.command || '';
|
|
1153
|
-
const output = item.aggregated_output || '';
|
|
1154
|
-
const status = item.status || (data.type === 'item.completed' ? 'completed' : data.type === 'item.updated' ? 'updated' : 'started');
|
|
1155
|
-
const body = `## 💻 Codex command execution
|
|
1156
|
-
|
|
1157
|
-
**Status:** \`${status}\`
|
|
1158
|
-
${command ? '\n' + createCollapsible('📋 Executed command', '```bash\n' + escapeMarkdown(command) + '\n```', true) : ''}
|
|
1159
|
-
${output ? '\n\n' + createCollapsible('📤 Output', '```\n' + escapeMarkdown(truncateMiddle(output, { maxLines: 60, keepStart: 25, keepEnd: 25 })) + '\n```', true) : ''}
|
|
1160
|
-
|
|
1161
|
-
---
|
|
1162
|
-
|
|
1163
|
-
${createRawJsonSection(data)}`;
|
|
1164
|
-
await postComment(body);
|
|
1165
|
-
};
|
|
1166
|
-
|
|
1167
|
-
const handleCodexMcpToolCall = async data => {
|
|
1168
|
-
const item = data.item || {};
|
|
1169
|
-
const summary = [`**Server:** \`${item.server || 'unknown'}\``, `**Tool:** \`${item.tool || 'unknown'}\``, `**Status:** \`${item.status || 'unknown'}\``].join('\n');
|
|
1170
|
-
const details = item.arguments != null ? createCollapsible('📥 Arguments', '```json\n' + safeJsonStringify(item.arguments, 2) + '\n```', true) : '';
|
|
1171
|
-
// Issue #1843: render MCP-result images inline; base64 is redacted from JSON below.
|
|
1172
|
-
const imagesSection = await imageRenderer.section([item.result], `${item.tool || 'mcp'} image`);
|
|
1173
|
-
const imagesBlock = imagesSection ? '\n\n' + imagesSection : '';
|
|
1174
|
-
const resultSection = item.result != null ? '\n\n' + createCollapsible('📤 Result', '```json\n' + safeJsonStringify(redactImageData(item.result), 2) + '\n```', false) : '';
|
|
1175
|
-
const errorSection = item.error != null ? '\n\n' + createCollapsible('❌ Error', '```json\n' + safeJsonStringify(item.error, 2) + '\n```', true) : '';
|
|
1176
|
-
|
|
1177
|
-
await postComment(`## 🔌 Codex MCP tool call
|
|
1178
|
-
|
|
1179
|
-
${summary}
|
|
1180
|
-
${details}${resultSection}${imagesBlock}${errorSection}
|
|
1181
|
-
|
|
1182
|
-
---
|
|
1183
|
-
|
|
1184
|
-
${createRedactedRawJsonSection(data)}`);
|
|
1185
|
-
};
|
|
1186
|
-
|
|
1187
|
-
const handleCodexWebSearch = async data => {
|
|
1188
|
-
const item = data.item || {};
|
|
1189
|
-
await postComment(`## 🌐 Codex web search
|
|
1190
|
-
|
|
1191
|
-
**Query:** ${escapeMarkdown(item.query || 'unknown')}
|
|
1192
|
-
${item.action ? `\n**Action:** \`${item.action}\`` : ''}
|
|
1193
|
-
|
|
1194
|
-
---
|
|
1195
|
-
|
|
1196
|
-
${createRawJsonSection(data)}`);
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
|
-
const handleCodexFileChange = async data => {
|
|
1200
|
-
const item = data.item || {};
|
|
1201
|
-
const changes = Array.isArray(item.changes) ? item.changes.map(change => `- \`${change?.kind || 'change'}\` ${change?.path || ''}`).join('\n') : '_No changes listed_';
|
|
1202
|
-
await postComment(`## 📝 Codex file changes
|
|
1203
|
-
|
|
1204
|
-
**Status:** \`${item.status || 'unknown'}\`
|
|
1205
|
-
${createCollapsible('📄 Files', changes, true)}
|
|
1206
|
-
|
|
1207
|
-
---
|
|
1208
|
-
|
|
1209
|
-
${createRawJsonSection(data)}`);
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
|
-
const handleCodexCollabToolCall = async data => {
|
|
1213
|
-
const item = data.item || {};
|
|
1214
|
-
const prompt = item.prompt || item.description || `${item.tool || 'collab_tool_call'} via codex`;
|
|
1215
|
-
await postComment(`## 🤝 Codex collab/sub-agent call
|
|
1216
|
-
|
|
1217
|
-
**Tool:** \`${item.tool || 'unknown'}\`
|
|
1218
|
-
**Status:** \`${item.status || 'unknown'}\`
|
|
1219
|
-
${createCollapsible('📝 Prompt', escapeMarkdown(truncateMiddle(prompt, { maxLines: 30, keepStart: 12, keepEnd: 12 })), true)}
|
|
1220
|
-
|
|
1221
|
-
---
|
|
1222
|
-
|
|
1223
|
-
${createRawJsonSection(data)}`);
|
|
1224
|
-
};
|
|
1225
|
-
|
|
1226
|
-
const handleCodexTurnCompleted = async data => {
|
|
1227
|
-
const usage = data.usage || {};
|
|
1228
|
-
let usageSection = '| Type | Count |\n|------|-------|\n';
|
|
1229
|
-
usageSection += `| Input | ${(usage.input_tokens || 0).toLocaleString()} |\n`;
|
|
1230
|
-
usageSection += `| Cache Read | ${(usage.cached_input_tokens || 0).toLocaleString()} |\n`;
|
|
1231
|
-
usageSection += `| Output | ${(usage.output_tokens || 0).toLocaleString()} |\n`;
|
|
1232
|
-
|
|
1233
|
-
await postComment(`## ✅ Codex turn completed
|
|
1234
|
-
|
|
1235
|
-
### 📊 Token Usage
|
|
1236
|
-
|
|
1237
|
-
${usageSection}
|
|
1238
|
-
|
|
1239
|
-
---
|
|
1240
|
-
|
|
1241
|
-
${createRawJsonSection(data)}`);
|
|
1242
|
-
};
|
|
1243
|
-
|
|
1244
|
-
const handleCodexError = async data => {
|
|
1245
|
-
const message = data.message || data.error?.message || 'Unknown Codex error';
|
|
1246
|
-
await postComment(`## ❌ Codex error
|
|
1247
|
-
|
|
1248
|
-
${createCollapsible('View error', escapeMarkdown(message), true)}
|
|
1249
|
-
|
|
1250
|
-
---
|
|
1251
|
-
|
|
1252
|
-
${createRawJsonSection(data)}`);
|
|
1253
|
-
};
|
|
1139
|
+
const { handleCodexThreadStarted, handleCodexAgentMessage, handleCodexTodoList, handleCodexCommandExecution, handleCodexMcpToolCall, handleCodexWebSearch, handleCodexFileChange, handleCodexCollabToolCall, handleCodexTurnCompleted, handleCodexError } = createCodexEventHandlers({
|
|
1140
|
+
state,
|
|
1141
|
+
postComment,
|
|
1142
|
+
handleAssistantText,
|
|
1143
|
+
imageRenderer,
|
|
1144
|
+
});
|
|
1254
1145
|
|
|
1255
1146
|
/**
|
|
1256
1147
|
* Handle unrecognized event types
|
|
@@ -1285,6 +1176,11 @@ ${createRawJsonSection(data)}`;
|
|
|
1285
1176
|
}
|
|
1286
1177
|
state.eventsProcessed++;
|
|
1287
1178
|
|
|
1179
|
+
const isThinkingTokenEvent = data.type === 'system' && data.subtype === 'thinking_tokens';
|
|
1180
|
+
if (!isThinkingTokenEvent) {
|
|
1181
|
+
await finalizeThinkingGroup();
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1288
1184
|
// Handle events without type as unrecognized
|
|
1289
1185
|
if (!data.type) {
|
|
1290
1186
|
await handleUnrecognized(data);
|
|
@@ -1301,6 +1197,14 @@ ${createRawJsonSection(data)}`;
|
|
|
1301
1197
|
await handleTaskProgress(data);
|
|
1302
1198
|
} else if (data.subtype === 'task_notification') {
|
|
1303
1199
|
await handleTaskNotification(data);
|
|
1200
|
+
} else if (data.subtype === 'thinking_tokens') {
|
|
1201
|
+
await handleThinkingTokens(data);
|
|
1202
|
+
} else if (data.subtype === 'status') {
|
|
1203
|
+
await handleSystemStatus(data);
|
|
1204
|
+
} else if (data.subtype === 'compact_boundary') {
|
|
1205
|
+
await handleCompactBoundary(data);
|
|
1206
|
+
} else if (data.subtype === 'task_updated') {
|
|
1207
|
+
await handleTaskUpdated(data);
|
|
1304
1208
|
} else {
|
|
1305
1209
|
// Unknown system subtype
|
|
1306
1210
|
await handleUnrecognized(data);
|
|
@@ -1392,6 +1296,7 @@ ${createRawJsonSection(data)}`;
|
|
|
1392
1296
|
* @returns {Promise<void>}
|
|
1393
1297
|
*/
|
|
1394
1298
|
const flush = async () => {
|
|
1299
|
+
await finalizeThinkingGroup();
|
|
1395
1300
|
await processQueue();
|
|
1396
1301
|
};
|
|
1397
1302
|
|
|
@@ -1417,6 +1322,11 @@ ${createRawJsonSection(data)}`;
|
|
|
1417
1322
|
handleTaskStarted,
|
|
1418
1323
|
handleTaskProgress,
|
|
1419
1324
|
handleTaskNotification,
|
|
1325
|
+
handleThinkingTokens,
|
|
1326
|
+
finalizeThinkingGroup,
|
|
1327
|
+
handleSystemStatus,
|
|
1328
|
+
handleCompactBoundary,
|
|
1329
|
+
handleTaskUpdated,
|
|
1420
1330
|
handleRateLimitEvent,
|
|
1421
1331
|
handleUnrecognized,
|
|
1422
1332
|
handleCodexThreadStarted,
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { CONFIG, createRawJsonSection, formatDuration, safeJsonStringify } from './interactive-mode.shared.lib.mjs';
|
|
4
|
+
|
|
5
|
+
const isFiniteNumber = value => typeof value === 'number' && Number.isFinite(value);
|
|
6
|
+
|
|
7
|
+
const formatNumber = value => (isFiniteNumber(value) ? value.toLocaleString() : 'unknown');
|
|
8
|
+
|
|
9
|
+
const formatSignedNumber = value => {
|
|
10
|
+
if (!isFiniteNumber(value)) return 'unknown';
|
|
11
|
+
return `${value > 0 ? '+' : ''}${value.toLocaleString()}`;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const pluralize = (count, singular, plural = `${singular}s`) => `${count} ${count === 1 ? singular : plural}`;
|
|
15
|
+
|
|
16
|
+
const formatThinkingDuration = ms => {
|
|
17
|
+
if (!isFiniteNumber(ms) || ms < 1000) return 'a moment';
|
|
18
|
+
|
|
19
|
+
const totalSeconds = Math.max(1, Math.round(ms / 1000));
|
|
20
|
+
if (totalSeconds < 60) return pluralize(totalSeconds, 'second');
|
|
21
|
+
|
|
22
|
+
const totalMinutes = Math.max(1, Math.round(totalSeconds / 60));
|
|
23
|
+
if (totalMinutes < 60) return pluralize(totalMinutes, 'minute');
|
|
24
|
+
|
|
25
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
26
|
+
const minutes = totalMinutes % 60;
|
|
27
|
+
return [pluralize(hours, 'hour'), minutes > 0 ? pluralize(minutes, 'minute') : ''].filter(Boolean).join(' ');
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getThinkingStats = events => {
|
|
31
|
+
const latest = events[events.length - 1] || {};
|
|
32
|
+
const estimatedTokens = isFiniteNumber(latest.estimated_tokens) ? latest.estimated_tokens : null;
|
|
33
|
+
const totalDelta = events.reduce((sum, event) => (isFiniteNumber(event.estimated_tokens_delta) ? sum + event.estimated_tokens_delta : sum), 0);
|
|
34
|
+
const latestDelta = isFiniteNumber(latest.estimated_tokens_delta) ? latest.estimated_tokens_delta : null;
|
|
35
|
+
return { estimatedTokens, totalDelta, latestDelta };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const buildThinkingComment = (thinking, { final = false } = {}) => {
|
|
39
|
+
const events = thinking.events;
|
|
40
|
+
const stats = getThinkingStats(events);
|
|
41
|
+
const elapsedMs = Math.max(0, thinking.lastEventTime - thinking.firstEventTime);
|
|
42
|
+
const header = final ? `## 🧠 Thought for ${formatThinkingDuration(elapsedMs)}.` : '## 🧠 Thinking...';
|
|
43
|
+
const summary = [`${formatNumber(stats.estimatedTokens)} estimated thinking tokens`, pluralize(events.length, 'thinking-token event'), `${formatSignedNumber(stats.totalDelta)} total delta`].join(' | ');
|
|
44
|
+
const latestDelta = stats.latestDelta == null ? 'unknown' : formatSignedNumber(stats.latestDelta);
|
|
45
|
+
|
|
46
|
+
return `${header}
|
|
47
|
+
|
|
48
|
+
${summary}
|
|
49
|
+
|
|
50
|
+
| Metric | Value |
|
|
51
|
+
|--------|-------|
|
|
52
|
+
| **Estimated tokens** | ${formatNumber(stats.estimatedTokens)} |
|
|
53
|
+
| **Latest delta** | ${latestDelta} |
|
|
54
|
+
| **Events grouped** | ${events.length.toLocaleString()} |
|
|
55
|
+
| **Elapsed** | ${formatDuration(elapsedMs)} |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
${createRawJsonSection(events)}`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const waitForCommentId = async commentIdPromise => {
|
|
63
|
+
let timeoutId;
|
|
64
|
+
const timeoutPromise = new Promise(resolve => {
|
|
65
|
+
timeoutId = setTimeout(() => resolve(null), 15000);
|
|
66
|
+
});
|
|
67
|
+
const commentId = await Promise.race([commentIdPromise, timeoutPromise]);
|
|
68
|
+
clearTimeout(timeoutId);
|
|
69
|
+
return commentId;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const createSystemLifecycleHandlers = ({ state, owner, repo, prNumber, log, verbose, postComment, editComment, processQueue, handleTaskProgress, handleTaskNotification }) => {
|
|
73
|
+
const resolveThinkingCommentId = async thinking => {
|
|
74
|
+
if (thinking.commentId) return thinking.commentId;
|
|
75
|
+
if (!prNumber || !owner || !repo) return null;
|
|
76
|
+
|
|
77
|
+
if (state.commentQueue.length > 0) {
|
|
78
|
+
const wasProcessing = state.isProcessing;
|
|
79
|
+
state.isProcessing = false;
|
|
80
|
+
await processQueue();
|
|
81
|
+
state.isProcessing = wasProcessing;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (thinking.commentId) return thinking.commentId;
|
|
85
|
+
if (!thinking.commentIdPromise) return null;
|
|
86
|
+
return waitForCommentId(thinking.commentIdPromise);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const rememberThinkingComment = thinking => commentId => {
|
|
90
|
+
thinking.commentId = commentId;
|
|
91
|
+
if (thinking.resolveCommentId) thinking.resolveCommentId(commentId);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleThinkingTokens = async data => {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
let thinking = state.activeThinking;
|
|
97
|
+
|
|
98
|
+
if (!thinking) {
|
|
99
|
+
let resolveCommentId;
|
|
100
|
+
const commentIdPromise = new Promise(resolve => {
|
|
101
|
+
resolveCommentId = resolve;
|
|
102
|
+
});
|
|
103
|
+
thinking = {
|
|
104
|
+
commentId: null,
|
|
105
|
+
commentIdPromise,
|
|
106
|
+
resolveCommentId,
|
|
107
|
+
events: [],
|
|
108
|
+
firstEventTime: now,
|
|
109
|
+
lastEventTime: now,
|
|
110
|
+
lastEditTime: now,
|
|
111
|
+
};
|
|
112
|
+
state.activeThinking = thinking;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
thinking.events.push(data);
|
|
116
|
+
thinking.lastEventTime = now;
|
|
117
|
+
|
|
118
|
+
if (thinking.events.length === 1) {
|
|
119
|
+
const rememberComment = rememberThinkingComment(thinking);
|
|
120
|
+
const commentId = await postComment(buildThinkingComment(thinking), null, null, rememberComment);
|
|
121
|
+
if (commentId) rememberComment(commentId);
|
|
122
|
+
if (verbose) {
|
|
123
|
+
await log(`🧠 Interactive mode: Thinking started (${formatNumber(data.estimated_tokens)} estimated tokens)`, { verbose: true });
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (now - thinking.lastEditTime >= CONFIG.MIN_THINKING_COMMENT_UPDATE_INTERVAL) {
|
|
129
|
+
const commentId = await resolveThinkingCommentId(thinking);
|
|
130
|
+
if (commentId) {
|
|
131
|
+
await editComment(commentId, buildThinkingComment(thinking));
|
|
132
|
+
thinking.lastEditTime = now;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const finalizeThinkingGroup = async () => {
|
|
138
|
+
const thinking = state.activeThinking;
|
|
139
|
+
if (!thinking) return;
|
|
140
|
+
|
|
141
|
+
state.activeThinking = null;
|
|
142
|
+
const commentId = await resolveThinkingCommentId(thinking);
|
|
143
|
+
if (commentId) await editComment(commentId, buildThinkingComment(thinking, { final: true }));
|
|
144
|
+
|
|
145
|
+
if (verbose) {
|
|
146
|
+
await log(`🧠 Interactive mode: Thinking finished after ${formatThinkingDuration(thinking.lastEventTime - thinking.firstEventTime)} (${thinking.events.length} events)`, {
|
|
147
|
+
verbose: true,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const handleSystemStatus = async data => {
|
|
153
|
+
if (verbose) await log(`ℹ️ Interactive mode: System status ${data.status || 'unknown'}`, { verbose: true });
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const handleCompactBoundary = async data => {
|
|
157
|
+
const metadata = data.compact_metadata || {};
|
|
158
|
+
const trigger = metadata.trigger || 'unknown';
|
|
159
|
+
const preTokens = isFiniteNumber(metadata.pre_tokens) ? metadata.pre_tokens : null;
|
|
160
|
+
const postTokens = isFiniteNumber(metadata.post_tokens) ? metadata.post_tokens : null;
|
|
161
|
+
const reduction = preTokens != null && postTokens != null ? preTokens - postTokens : null;
|
|
162
|
+
const durationText = isFiniteNumber(metadata.duration_ms) ? formatDuration(metadata.duration_ms) : 'unknown';
|
|
163
|
+
|
|
164
|
+
await postComment(`## 🧭 Context compacted
|
|
165
|
+
|
|
166
|
+
| Metric | Value |
|
|
167
|
+
|--------|-------|
|
|
168
|
+
| **Trigger** | \`${trigger}\` |
|
|
169
|
+
| **Before** | ${formatNumber(preTokens)} tokens |
|
|
170
|
+
| **After** | ${formatNumber(postTokens)} tokens |
|
|
171
|
+
| **Reduction** | ${formatNumber(reduction)} tokens |
|
|
172
|
+
| **Duration** | ${durationText} |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
${createRawJsonSection(data)}`);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const handleTaskUpdated = async data => {
|
|
180
|
+
const taskId = data.task_id;
|
|
181
|
+
const patch = data.patch && typeof data.patch === 'object' ? data.patch : {};
|
|
182
|
+
const pendingTask = state.pendingTasks.get(taskId);
|
|
183
|
+
|
|
184
|
+
if (!pendingTask) {
|
|
185
|
+
if (verbose) {
|
|
186
|
+
await log(`🤖 Interactive mode: Task update for unknown task ${taskId || 'unknown'}: ${safeJsonStringify(patch)}`, { verbose: true });
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const status = patch.status || data.status;
|
|
192
|
+
if (status) {
|
|
193
|
+
await handleTaskNotification({
|
|
194
|
+
...data,
|
|
195
|
+
status,
|
|
196
|
+
summary: data.summary || data.description || patch.summary || `Task ${status}`,
|
|
197
|
+
usage: data.usage || patch.usage || {},
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (patch.description || data.description || patch.last_tool_name || data.last_tool_name || patch.usage || data.usage) {
|
|
203
|
+
await handleTaskProgress({
|
|
204
|
+
...data,
|
|
205
|
+
subtype: 'task_progress',
|
|
206
|
+
description: patch.description || data.description || pendingTask.lastProgressDescription || pendingTask.description,
|
|
207
|
+
usage: data.usage || patch.usage || {},
|
|
208
|
+
last_tool_name: patch.last_tool_name || data.last_tool_name || '',
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
pendingTask.allEvents.push(data);
|
|
214
|
+
if (verbose) await log(`🤖 Interactive mode: Task update recorded for ${taskId}: ${safeJsonStringify(patch)}`, { verbose: true });
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
handleThinkingTokens,
|
|
219
|
+
finalizeThinkingGroup,
|
|
220
|
+
handleSystemStatus,
|
|
221
|
+
handleCompactBoundary,
|
|
222
|
+
handleTaskUpdated,
|
|
223
|
+
};
|
|
224
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* Isolation Runner for Telegram bot
|
|
3
4
|
*
|
|
@@ -19,7 +20,7 @@ import os from 'node:os';
|
|
|
19
20
|
import path from 'node:path';
|
|
20
21
|
|
|
21
22
|
if (typeof use === 'undefined') {
|
|
22
|
-
|
|
23
|
+
await ensureUseM();
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
const { $ } = await use('command-stream');
|
package/src/lenv-reader.lib.mjs
CHANGED
|
@@ -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
|
* lenv-reader.lib.mjs - LINO-based environment configuration reader
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
*/
|
|
31
32
|
|
|
32
33
|
if (typeof use === 'undefined') {
|
|
33
|
-
|
|
34
|
+
await ensureUseM();
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
const linoModule = await use('links-notation');
|
package/src/lib.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
2
3
|
|
|
3
4
|
// Shared library functions for hive-mind project
|
|
4
5
|
|
|
@@ -32,7 +33,7 @@ try {
|
|
|
32
33
|
// Check if use is already defined (when imported from solve.mjs)
|
|
33
34
|
// If not, fetch it (when running standalone)
|
|
34
35
|
if (typeof globalThis.use === 'undefined') {
|
|
35
|
-
|
|
36
|
+
await ensureUseM();
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const fs = (await use('fs')).promises;
|
package/src/lino.lib.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
1
2
|
if (typeof use === 'undefined') {
|
|
2
|
-
|
|
3
|
+
await ensureUseM();
|
|
3
4
|
}
|
|
4
5
|
|
|
5
6
|
// Issue #1710: hosted CI npm-install flake — retry once on a corrupt install.
|
|
@@ -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
|
* Local CI Checks Module
|
|
@@ -6,7 +7,7 @@
|
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
if (typeof globalThis.use === 'undefined') {
|
|
9
|
-
|
|
10
|
+
await ensureUseM();
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const { $ } = await use('command-stream');
|
package/src/log-upload.lib.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
2
3
|
|
|
3
4
|
// Log upload module for hive-mind
|
|
4
5
|
// Uses gh-upload-log for uploading log files to GitHub
|
|
5
6
|
|
|
6
7
|
// Use use-m to dynamically import modules for cross-runtime compatibility
|
|
7
8
|
if (typeof globalThis.use === 'undefined') {
|
|
8
|
-
|
|
9
|
+
await ensureUseM();
|
|
9
10
|
}
|
|
10
11
|
const use = globalThis.use;
|
|
11
12
|
|
package/src/memory-check.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
2
3
|
|
|
3
4
|
// Check if use is already defined (when imported from solve.mjs)
|
|
4
5
|
// If not, fetch it (when running standalone)
|
|
5
6
|
if (typeof globalThis.use === 'undefined') {
|
|
6
|
-
|
|
7
|
+
await ensureUseM();
|
|
7
8
|
}
|
|
8
9
|
const use = globalThis.use;
|
|
9
10
|
const { getLinoYargsFactory, hideBin, parseCliArgumentsWithLino } = await import('./cli-arguments.lib.mjs');
|
package/src/models/index.mjs
CHANGED
|
@@ -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
|
* Unified models module for hive-mind
|
|
@@ -18,7 +19,7 @@ import { promisify } from 'node:util';
|
|
|
18
19
|
// Check if use is already defined (when imported from solve.mjs)
|
|
19
20
|
// If not, fetch it (when running standalone)
|
|
20
21
|
if (typeof globalThis.use === 'undefined') {
|
|
21
|
-
|
|
22
|
+
await ensureUseM();
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
import { log } from '../lib.mjs';
|