@link-assistant/hive-mind 1.73.9 → 1.74.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 +51 -0
- package/README.hi.md +36 -0
- package/README.md +37 -0
- package/README.ru.md +37 -0
- package/README.zh.md +35 -0
- package/package.json +2 -1
- package/src/claude.lib.mjs +2 -0
- package/src/cleanup.lib.mjs +359 -0
- package/src/cleanup.mjs +288 -0
- package/src/cleanup.os.lib.mjs +404 -0
- package/src/codex.lib.mjs +2 -0
- package/src/interactive-image-render.lib.mjs +140 -0
- package/src/interactive-image-upload.lib.mjs +415 -0
- package/src/interactive-mode.lib.mjs +27 -8
- package/src/interactive-mode.shared.lib.mjs +97 -0
- package/src/solve.config.lib.mjs +9 -0
|
@@ -96,6 +96,103 @@ export const createRawJsonSection = data => {
|
|
|
96
96
|
return createCollapsible('📄 Raw JSON', '```json\n' + jsonContent + '\n```');
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Issue #1843: Deep-clone an event object, replacing base64 image payloads with
|
|
101
|
+
* a short `<image data: N base64 chars>` placeholder. Image base64 (Read tool,
|
|
102
|
+
* Playwright screenshots, MCP image results) can be many kilobytes on a single
|
|
103
|
+
* JSON line, which `truncateMiddle` cannot shrink — so without this the "Raw
|
|
104
|
+
* JSON" sections would bloat every image-bearing comment toward the API limit.
|
|
105
|
+
*
|
|
106
|
+
* Redaction is targeted at the three known image carriers and leaves all other
|
|
107
|
+
* fields intact for debugging:
|
|
108
|
+
* - `{ type:'image', source:{ data } }` → source.data
|
|
109
|
+
* - `{ type:'image', data }` → data (MCP shape)
|
|
110
|
+
* - `{ file:{ base64 } }` → file.base64 (Read tool_use_result)
|
|
111
|
+
*
|
|
112
|
+
* @param {*} data
|
|
113
|
+
* @returns {*} a redacted clone (primitives returned as-is)
|
|
114
|
+
*/
|
|
115
|
+
export const redactImageData = data => {
|
|
116
|
+
const seen = new WeakSet();
|
|
117
|
+
const placeholder = len => `<image data: ${len} base64 chars>`;
|
|
118
|
+
const walk = node => {
|
|
119
|
+
if (Array.isArray(node)) return node.map(walk);
|
|
120
|
+
if (node && typeof node === 'object') {
|
|
121
|
+
if (seen.has(node)) return '[Circular]';
|
|
122
|
+
seen.add(node);
|
|
123
|
+
const out = {};
|
|
124
|
+
for (const [k, v] of Object.entries(node)) out[k] = walk(v);
|
|
125
|
+
if (out.type === 'image' && out.source && typeof out.source === 'object' && typeof out.source.data === 'string') {
|
|
126
|
+
out.source = { ...out.source, data: placeholder(out.source.data.length) };
|
|
127
|
+
}
|
|
128
|
+
if (out.type === 'image' && typeof out.data === 'string' && out.data.length > 64) {
|
|
129
|
+
out.data = placeholder(out.data.length);
|
|
130
|
+
}
|
|
131
|
+
if (out.file && typeof out.file === 'object' && typeof out.file.base64 === 'string') {
|
|
132
|
+
out.file = { ...out.file, base64: placeholder(out.file.base64.length) };
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
return node;
|
|
137
|
+
};
|
|
138
|
+
return walk(data);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Issue #1843: Like createRawJsonSection, but strips base64 image data first.
|
|
143
|
+
* @param {*} data
|
|
144
|
+
* @returns {string}
|
|
145
|
+
*/
|
|
146
|
+
export const createRedactedRawJsonSection = data => createRawJsonSection(redactImageData(data));
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Format a byte count as a short human-readable string (e.g. "7.2 MB").
|
|
150
|
+
* @param {number} bytes
|
|
151
|
+
* @returns {string}
|
|
152
|
+
*/
|
|
153
|
+
export const formatBytes = bytes => {
|
|
154
|
+
if (typeof bytes !== 'number' || !isFinite(bytes) || bytes < 0) return '';
|
|
155
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
156
|
+
const units = ['KB', 'MB', 'GB'];
|
|
157
|
+
let value = bytes / 1024;
|
|
158
|
+
let i = 0;
|
|
159
|
+
while (value >= 1024 && i < units.length - 1) {
|
|
160
|
+
value /= 1024;
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
return `${value.toFixed(value >= 10 || Number.isInteger(value) ? 0 : 1)} ${units[i]}`;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Sanitize Markdown image alt text so it can't break the `` syntax.
|
|
168
|
+
* @param {string} text
|
|
169
|
+
* @returns {string}
|
|
170
|
+
*/
|
|
171
|
+
const escapeAltText = text => (!text || typeof text !== 'string' ? 'image' : text.replace(/[[\]\n\r]/g, ' ').trim() || 'image');
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Issue #1843: Render an "🖼️ Images" Markdown section for images surfaced in a
|
|
175
|
+
* tool result. Each entry that has a `url` is embedded inline with ``;
|
|
176
|
+
* entries without a `url` (upload disabled or failed) degrade to a compact
|
|
177
|
+
* metadata note instead of dumping base64.
|
|
178
|
+
*
|
|
179
|
+
* @param {Array<{ url?: string, mediaType?: string, originalSize?: number, name?: string }>} images
|
|
180
|
+
* @returns {string} Markdown (empty string when there are no images)
|
|
181
|
+
*/
|
|
182
|
+
export const formatImageEmbeds = images => {
|
|
183
|
+
if (!Array.isArray(images) || images.length === 0) return '';
|
|
184
|
+
const blocks = images.map((img, i) => {
|
|
185
|
+
const label = img.name || `image ${i + 1}`;
|
|
186
|
+
const meta = [img.mediaType, formatBytes(img.originalSize)].filter(Boolean).join(', ');
|
|
187
|
+
const caption = meta ? `${label} (${meta})` : label;
|
|
188
|
+
if (img.url) {
|
|
189
|
+
return `**${escapeMarkdown(caption)}**\n\n`;
|
|
190
|
+
}
|
|
191
|
+
return `**${escapeMarkdown(caption)}** — _image upload unavailable; not shown inline_`;
|
|
192
|
+
});
|
|
193
|
+
return `### 🖼️ Images\n\n${blocks.join('\n\n')}`;
|
|
194
|
+
};
|
|
195
|
+
|
|
99
196
|
export const formatDuration = ms => {
|
|
100
197
|
if (!ms || ms < 0) return 'unknown';
|
|
101
198
|
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -405,6 +405,15 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
405
405
|
description: '[EXPERIMENTAL] Post tool output as PR comments in real-time. Supported for --tool claude and --tool codex.',
|
|
406
406
|
default: false,
|
|
407
407
|
},
|
|
408
|
+
// Issue #1843: render images that Claude/Codex read/write inline in the PR
|
|
409
|
+
// comments interactive mode posts. Images are committed to hidden custom Git
|
|
410
|
+
// refs and embedded via commit-SHA ?raw=true blob URLs (GitHub strips data: URIs).
|
|
411
|
+
// Disable with --no-interactive-image-upload to fall back to a metadata note.
|
|
412
|
+
'interactive-image-upload': {
|
|
413
|
+
type: 'boolean',
|
|
414
|
+
description: '[EXPERIMENTAL] When --interactive-mode is on, upload images read/written by the AI to hidden custom Git refs (refs/hive-mind-media/...) and embed them inline in PR comments. Enabled by default; use --no-interactive-image-upload to disable.',
|
|
415
|
+
default: true,
|
|
416
|
+
},
|
|
408
417
|
// Issue #817: Bidirectional interactive options
|
|
409
418
|
'accept-incomming-comments-as-input': {
|
|
410
419
|
type: 'boolean',
|