@steerable/agent-shell 0.6.45 → 0.6.47
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/dist/image-attachment.js +105 -10
- package/dist/sidecar/supervisor.js +8 -0
- package/package.json +4 -4
package/dist/image-attachment.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* should know an image was attached even when it was too large to send.
|
|
19
19
|
*/
|
|
20
20
|
import { getNativeImage } from './runtime.js';
|
|
21
|
-
import { statSync } from 'fs';
|
|
21
|
+
import { readFileSync, statSync } from 'fs';
|
|
22
22
|
import { basename, extname } from 'path';
|
|
23
23
|
/** Refuse to read source files larger than this (10 MB). */
|
|
24
24
|
export const IMAGE_MAX_SOURCE_BYTES = 10 * 1024 * 1024;
|
|
@@ -82,13 +82,6 @@ export function processViewImage(input, nativeImage = getNativeImage()) {
|
|
|
82
82
|
needsFollowup: true,
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
-
if (!nativeImage) {
|
|
86
|
-
return {
|
|
87
|
-
success: false,
|
|
88
|
-
error: '当前运行环境不支持图片解码',
|
|
89
|
-
needsFollowup: true,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
85
|
let sourceBytes;
|
|
93
86
|
try {
|
|
94
87
|
sourceBytes = statSync(input.path).size;
|
|
@@ -111,6 +104,9 @@ export function processViewImage(input, nativeImage = getNativeImage()) {
|
|
|
111
104
|
needsFollowup: true,
|
|
112
105
|
};
|
|
113
106
|
}
|
|
107
|
+
if (!nativeImage) {
|
|
108
|
+
return viewImageWithoutDecoder(input, sourceBytes, maxEdge);
|
|
109
|
+
}
|
|
114
110
|
let rendered = nativeImage.createFromPath(input.path);
|
|
115
111
|
if (rendered.isEmpty()) {
|
|
116
112
|
return { success: false, error: '不是可识别的图片', needsFollowup: true };
|
|
@@ -159,6 +155,88 @@ export function processViewImage(input, nativeImage = getNativeImage()) {
|
|
|
159
155
|
},
|
|
160
156
|
};
|
|
161
157
|
}
|
|
158
|
+
function viewImageWithoutDecoder(input, sourceBytes, maxEdge) {
|
|
159
|
+
if (input.region) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: '当前环境不能裁剪图片',
|
|
163
|
+
needsFollowup: true,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (sourceBytes > IMAGE_MAX_ENCODED_BYTES) {
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
error: `源文件 ${formatMb(sourceBytes)}MB 超过 ${formatMb(IMAGE_MAX_ENCODED_BYTES)}MB,当前环境不能缩放`,
|
|
170
|
+
needsFollowup: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const sourceExt = extname(input.path).toLowerCase();
|
|
174
|
+
if (sourceExt !== '.png' && sourceExt !== '.jpg' && sourceExt !== '.jpeg') {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: '当前环境只能直接发送 PNG 和 JPEG',
|
|
178
|
+
needsFollowup: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const natural = sourceExt === '.png' ? 'png' : 'jpeg';
|
|
182
|
+
if (input.format != null && input.format !== natural) {
|
|
183
|
+
return {
|
|
184
|
+
success: false,
|
|
185
|
+
error: '当前环境不能转码',
|
|
186
|
+
needsFollowup: true,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const bytes = readFileSync(input.path);
|
|
190
|
+
const size = readRasterSize(bytes);
|
|
191
|
+
if (size && (size.width > maxEdge || size.height > maxEdge)) {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error: '当前环境不能缩放',
|
|
195
|
+
needsFollowup: true,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const mediaType = natural === 'jpeg' ? 'image/jpeg' : 'image/png';
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
data: {
|
|
202
|
+
width: size?.width ?? 0,
|
|
203
|
+
height: size?.height ?? 0,
|
|
204
|
+
sourcePath: input.path,
|
|
205
|
+
mediaType,
|
|
206
|
+
_image: { b64: bytes.toString('base64'), media_type: mediaType },
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function readRasterSize(bytes) {
|
|
211
|
+
if (bytes.length >= 24 &&
|
|
212
|
+
bytes[0] === 137 &&
|
|
213
|
+
bytes.toString('ascii', 1, 4) === 'PNG') {
|
|
214
|
+
return { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) };
|
|
215
|
+
}
|
|
216
|
+
if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8)
|
|
217
|
+
return null;
|
|
218
|
+
let offset = 2;
|
|
219
|
+
while (offset + 9 < bytes.length) {
|
|
220
|
+
if (bytes[offset] !== 0xff)
|
|
221
|
+
return null;
|
|
222
|
+
const marker = bytes[offset + 1];
|
|
223
|
+
if (marker === 0xd8 || marker === 0xd9) {
|
|
224
|
+
offset += 2;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const length = bytes.readUInt16BE(offset + 2);
|
|
228
|
+
if (length < 2 || offset + 2 + length > bytes.length)
|
|
229
|
+
return null;
|
|
230
|
+
if (marker >= 0xc0 && marker <= 0xc3) {
|
|
231
|
+
return {
|
|
232
|
+
height: bytes.readUInt16BE(offset + 5),
|
|
233
|
+
width: bytes.readUInt16BE(offset + 7),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
offset += 2 + length;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
162
240
|
function resolveCrop(region, size) {
|
|
163
241
|
const values = [region.x, region.y, region.w, region.h];
|
|
164
242
|
if (!values.every(Number.isFinite))
|
|
@@ -208,8 +286,25 @@ export function processImageAttachments(files) {
|
|
|
208
286
|
}
|
|
209
287
|
const nativeImage = getNativeImage();
|
|
210
288
|
if (!nativeImage) {
|
|
211
|
-
//
|
|
212
|
-
|
|
289
|
+
// Tauri and the browser server run plain Node, which has no Electron
|
|
290
|
+
// nativeImage. The bytes are already a PNG/JPEG the provider can read;
|
|
291
|
+
// pass them through when they fit the encoded cap. Resize stays on the
|
|
292
|
+
// Electron path.
|
|
293
|
+
if (sourceBytes > IMAGE_MAX_ENCODED_BYTES) {
|
|
294
|
+
notes.push(`- ${label}:源文件 ${formatMb(sourceBytes)}MB 超过 ${formatMb(IMAGE_MAX_ENCODED_BYTES)}MB,当前环境不能缩放,未附加`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const ext = extname(file.path).toLowerCase();
|
|
298
|
+
const mediaType = ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg'
|
|
299
|
+
: ext === '.gif' ? 'image/gif'
|
|
300
|
+
: ext === '.webp' ? 'image/webp'
|
|
301
|
+
: ext === '.bmp' ? 'image/bmp'
|
|
302
|
+
: 'image/png';
|
|
303
|
+
images.push({
|
|
304
|
+
data: readFileSync(file.path).toString('base64'),
|
|
305
|
+
mediaType,
|
|
306
|
+
});
|
|
307
|
+
notes.push(`- ${label}(原图)`);
|
|
213
308
|
continue;
|
|
214
309
|
}
|
|
215
310
|
const image = nativeImage.createFromPath(file.path);
|
|
@@ -508,11 +508,19 @@ export class SidecarSupervisor extends EventEmitter {
|
|
|
508
508
|
(allowResolver ? ' + resolver only (web tools egress via the per-host proxy)' : '') +
|
|
509
509
|
')');
|
|
510
510
|
this.sandboxPosture = { backend: 'seatbelt', enforcement: 'partial', reason: 'active' };
|
|
511
|
+
// macOS TMPDIR is /var/folders, which the Seatbelt profile does not
|
|
512
|
+
// allow. run_code / run_js materialize their workers there and fail
|
|
513
|
+
// with EPERM, so the model cannot list or read local folders.
|
|
514
|
+
const confinedTmp = join(writableRoots[0], 'tmp');
|
|
515
|
+
mkdirSync(confinedTmp, { recursive: true });
|
|
511
516
|
return {
|
|
512
517
|
command: SEATBELT_EXECUTABLE,
|
|
513
518
|
args: ['-p', profile, command, ...args],
|
|
514
519
|
env: {
|
|
515
520
|
PYTHONDONTWRITEBYTECODE: '1',
|
|
521
|
+
TMPDIR: confinedTmp,
|
|
522
|
+
TMP: confinedTmp,
|
|
523
|
+
TEMP: confinedTmp,
|
|
516
524
|
// macOS denies a nested sandbox_apply once the outer profile allows
|
|
517
525
|
// outbound network, so a layer-1-confined sidecar cannot wrap its own
|
|
518
526
|
// run_code child. The marker tells the sidecar to let that child
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steerable/agent-shell",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.47",
|
|
4
4
|
"description": "Steerable framework — product-neutral Node HostRuntime and headless HTTP server shared by Tauri desktop and browser deployments; scenario packs extend it through @steerable/pack-sdk.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://steerableframework.com/",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"he": "^1.2.0",
|
|
56
56
|
"node-pty": "^1.1.0",
|
|
57
57
|
"semver": "^7.8.0",
|
|
58
|
-
"@steerable/
|
|
59
|
-
"@steerable/
|
|
60
|
-
"@steerable/agent-
|
|
58
|
+
"@steerable/pack-sdk": "0.6.47",
|
|
59
|
+
"@steerable/agent-harness": "0.6.47",
|
|
60
|
+
"@steerable/agent-protocol": "0.6.47"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/better-sqlite3": "^7.6.13",
|