@zhijiewang/openharness 2.30.0 → 2.31.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/dist/commands/ai.js +4 -4
- package/dist/commands/git.js +1 -1
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/index.js +1 -1
- package/dist/commands/info.js +4 -8
- package/dist/commands/session.js +1 -2
- package/dist/commands/settings.d.ts +1 -1
- package/dist/commands/settings.js +1 -5
- package/dist/commands/skills.js +2 -5
- package/dist/components/InitWizard.js +1 -1
- package/dist/harness/config.d.ts +0 -8
- package/dist/harness/config.js +3 -7
- package/dist/harness/plugins.js +1 -1
- package/dist/harness/project-purge.d.ts +56 -0
- package/dist/harness/project-purge.js +198 -0
- package/dist/harness/telemetry.js +18 -12
- package/dist/harness/traces.d.ts +24 -1
- package/dist/harness/traces.js +72 -8
- package/dist/main.js +56 -0
- package/dist/providers/anthropic.js +4 -1
- package/dist/repl.js +1 -1
- package/dist/services/AgentDispatcher.js +15 -28
- package/dist/services/StreamingToolExecutor.js +97 -11
- package/dist/tools/CronTool/index.d.ts +2 -2
- package/dist/tools/DiagnosticsTool/index.d.ts +1 -1
- package/dist/tools/FileReadTool/index.js +7 -4
- package/dist/tools/GrepTool/index.d.ts +2 -2
- package/dist/tools/ImageReadTool/index.js +6 -1
- package/dist/tools/PowerShellTool/index.js +11 -2
- package/dist/utils/image-downscale.d.ts +34 -0
- package/dist/utils/image-downscale.js +89 -0
- package/package.json +3 -3
- package/dist/harness/sandbox.d.ts +0 -34
- package/dist/harness/sandbox.js +0 -104
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image auto-downscale — bound the longest dimension to a fixed maximum
|
|
3
|
+
* before encoding the image as base64 for the model.
|
|
4
|
+
*
|
|
5
|
+
* Why: most providers reject or downsample images above ~1568-2048px on
|
|
6
|
+
* the longest side. Shipping a 4000px screenshot wastes input tokens, can
|
|
7
|
+
* exceed the request size limit, and historically broke the session
|
|
8
|
+
* outright when an oversized image landed in the conversation history.
|
|
9
|
+
*
|
|
10
|
+
* The function is a no-op for images already within bounds, for formats
|
|
11
|
+
* sharp doesn't process (PDF, SVG), and when sharp itself isn't installed
|
|
12
|
+
* (it's an `optionalDependency` so unsupported platforms still install).
|
|
13
|
+
* Any sharp error returns the original buffer unchanged — we never break a
|
|
14
|
+
* tool call over a downscale failure.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_MAX_DIMENSION = 2000;
|
|
17
|
+
const SHARP_SUPPORTED_TYPES = new Set([
|
|
18
|
+
"image/png",
|
|
19
|
+
"image/jpeg",
|
|
20
|
+
"image/jpg",
|
|
21
|
+
"image/gif",
|
|
22
|
+
"image/webp",
|
|
23
|
+
"image/avif",
|
|
24
|
+
"image/tiff",
|
|
25
|
+
]);
|
|
26
|
+
let _sharpModule;
|
|
27
|
+
/** Lazy-load sharp; cache the result so we don't pay the import cost per image. */
|
|
28
|
+
async function getSharp() {
|
|
29
|
+
if (_sharpModule !== undefined)
|
|
30
|
+
return _sharpModule;
|
|
31
|
+
try {
|
|
32
|
+
const mod = (await import("sharp"));
|
|
33
|
+
_sharpModule = (mod.default ?? mod);
|
|
34
|
+
return _sharpModule;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
_sharpModule = null;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** @internal Test-only reset of the lazy sharp cache. */
|
|
42
|
+
export function _resetSharpCacheForTest() {
|
|
43
|
+
_sharpModule = undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Downscale `buffer` so its longest dimension is ≤ `maxDimension` (default 2000).
|
|
47
|
+
* Aspect ratio preserved. Format preserved (PNG stays PNG, JPEG stays JPEG, etc.).
|
|
48
|
+
*
|
|
49
|
+
* Pure pass-through for: PDF, SVG, BMP (sharp doesn't handle reliably),
|
|
50
|
+
* already-small images, missing sharp, and any sharp error.
|
|
51
|
+
*/
|
|
52
|
+
export async function downscaleIfLarge(buffer, mediaType, maxDimension = DEFAULT_MAX_DIMENSION) {
|
|
53
|
+
if (!SHARP_SUPPORTED_TYPES.has(mediaType)) {
|
|
54
|
+
return { buffer, downscaled: false, reason: "unsupported-format" };
|
|
55
|
+
}
|
|
56
|
+
const sharp = await getSharp();
|
|
57
|
+
if (!sharp) {
|
|
58
|
+
return { buffer, downscaled: false, reason: "sharp-unavailable" };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const pipeline = sharp(buffer);
|
|
62
|
+
const meta = await pipeline.metadata();
|
|
63
|
+
const w = meta.width ?? 0;
|
|
64
|
+
const h = meta.height ?? 0;
|
|
65
|
+
if (w === 0 || h === 0) {
|
|
66
|
+
// Animated GIFs can report 0 here; pass through rather than mangle.
|
|
67
|
+
return { buffer, downscaled: false, reason: "unsupported-format" };
|
|
68
|
+
}
|
|
69
|
+
if (Math.max(w, h) <= maxDimension) {
|
|
70
|
+
return { buffer, downscaled: false, reason: "within-bounds" };
|
|
71
|
+
}
|
|
72
|
+
// `fit: "inside"` + `withoutEnlargement: true` resizes proportionally
|
|
73
|
+
// so the longest side equals maxDimension, with no upscaling.
|
|
74
|
+
const out = await pipeline
|
|
75
|
+
.resize({
|
|
76
|
+
width: maxDimension,
|
|
77
|
+
height: maxDimension,
|
|
78
|
+
fit: "inside",
|
|
79
|
+
withoutEnlargement: true,
|
|
80
|
+
})
|
|
81
|
+
.toBuffer();
|
|
82
|
+
return { buffer: out, downscaled: true };
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Corrupt image, unsupported subformat, etc. — never fail the tool over this.
|
|
86
|
+
return { buffer, downscaled: false, reason: "sharp-error" };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=image-downscale.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhijiewang/openharness",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.0",
|
|
4
4
|
"description": "Open-source terminal coding agent. Works with any LLM.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -63,7 +63,6 @@
|
|
|
63
63
|
"@types/react": "^18.3.0",
|
|
64
64
|
"c8": "^11.0.0",
|
|
65
65
|
"husky": "^9.1.7",
|
|
66
|
-
"sharp": "^0.34.5",
|
|
67
66
|
"tsx": "^4.19.0",
|
|
68
67
|
"typescript": "^5.8.0"
|
|
69
68
|
},
|
|
@@ -92,6 +91,7 @@
|
|
|
92
91
|
},
|
|
93
92
|
"homepage": "https://github.com/zhijiewong/openharness#readme",
|
|
94
93
|
"optionalDependencies": {
|
|
95
|
-
"@napi-rs/keyring": "^1.2.0"
|
|
94
|
+
"@napi-rs/keyring": "^1.2.0",
|
|
95
|
+
"sharp": "^0.34.5"
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sandbox — filesystem and network restrictions for tool execution.
|
|
3
|
-
*
|
|
4
|
-
* Limits what tools can access:
|
|
5
|
-
* - File tools: only write to allowed paths
|
|
6
|
-
* - Web tools: only access allowed domains
|
|
7
|
-
* - Bash: restricted commands (no curl/wget by default)
|
|
8
|
-
*
|
|
9
|
-
* Reduces permission prompts while maintaining security.
|
|
10
|
-
*/
|
|
11
|
-
export type SandboxConfig = {
|
|
12
|
-
enabled: boolean;
|
|
13
|
-
/** Paths tools can write to (glob-style, relative to cwd) */
|
|
14
|
-
allowedPaths: string[];
|
|
15
|
-
/** Domains WebFetch/WebSearch can access */
|
|
16
|
-
allowedDomains: string[];
|
|
17
|
-
/** Block all network access */
|
|
18
|
-
blockNetwork: boolean;
|
|
19
|
-
/** Commands blocked in Bash (default: curl, wget) */
|
|
20
|
-
blockedCommands: string[];
|
|
21
|
-
};
|
|
22
|
-
/** Get the current sandbox config */
|
|
23
|
-
export declare function getSandboxConfig(): SandboxConfig;
|
|
24
|
-
/** Reset cached config */
|
|
25
|
-
export declare function invalidateSandboxCache(): void;
|
|
26
|
-
/** Check if a file path is allowed for writing */
|
|
27
|
-
export declare function isPathAllowed(filePath: string): boolean;
|
|
28
|
-
/** Check if a domain is allowed for network access */
|
|
29
|
-
export declare function isDomainAllowed(url: string): boolean;
|
|
30
|
-
/** Check if a bash command is allowed */
|
|
31
|
-
export declare function isCommandAllowed(command: string): boolean;
|
|
32
|
-
/** Get a human-readable sandbox status */
|
|
33
|
-
export declare function sandboxStatus(): string;
|
|
34
|
-
//# sourceMappingURL=sandbox.d.ts.map
|
package/dist/harness/sandbox.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sandbox — filesystem and network restrictions for tool execution.
|
|
3
|
-
*
|
|
4
|
-
* Limits what tools can access:
|
|
5
|
-
* - File tools: only write to allowed paths
|
|
6
|
-
* - Web tools: only access allowed domains
|
|
7
|
-
* - Bash: restricted commands (no curl/wget by default)
|
|
8
|
-
*
|
|
9
|
-
* Reduces permission prompts while maintaining security.
|
|
10
|
-
*/
|
|
11
|
-
import { relative, resolve } from "node:path";
|
|
12
|
-
import { readOhConfig } from "./config.js";
|
|
13
|
-
const DEFAULT_SANDBOX = {
|
|
14
|
-
enabled: false,
|
|
15
|
-
allowedPaths: ["."], // current directory
|
|
16
|
-
allowedDomains: [], // empty = all allowed
|
|
17
|
-
blockNetwork: false,
|
|
18
|
-
blockedCommands: ["curl", "wget"],
|
|
19
|
-
};
|
|
20
|
-
// ── Sandbox Manager ──
|
|
21
|
-
let _config = null;
|
|
22
|
-
/** Get the current sandbox config */
|
|
23
|
-
export function getSandboxConfig() {
|
|
24
|
-
if (_config)
|
|
25
|
-
return _config;
|
|
26
|
-
const ohConfig = readOhConfig();
|
|
27
|
-
if (ohConfig?.sandbox) {
|
|
28
|
-
_config = {
|
|
29
|
-
...DEFAULT_SANDBOX,
|
|
30
|
-
...ohConfig.sandbox,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
_config = DEFAULT_SANDBOX;
|
|
35
|
-
}
|
|
36
|
-
return _config;
|
|
37
|
-
}
|
|
38
|
-
/** Reset cached config */
|
|
39
|
-
export function invalidateSandboxCache() {
|
|
40
|
-
_config = null;
|
|
41
|
-
}
|
|
42
|
-
/** Check if a file path is allowed for writing */
|
|
43
|
-
export function isPathAllowed(filePath) {
|
|
44
|
-
const config = getSandboxConfig();
|
|
45
|
-
if (!config.enabled)
|
|
46
|
-
return true;
|
|
47
|
-
const resolved = resolve(filePath);
|
|
48
|
-
const cwd = process.cwd();
|
|
49
|
-
for (const allowed of config.allowedPaths) {
|
|
50
|
-
const allowedResolved = resolve(cwd, allowed);
|
|
51
|
-
// Check if the file is within the allowed directory
|
|
52
|
-
const rel = relative(allowedResolved, resolved);
|
|
53
|
-
if (!rel.startsWith("..") && !rel.startsWith("/"))
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
/** Check if a domain is allowed for network access */
|
|
59
|
-
export function isDomainAllowed(url) {
|
|
60
|
-
const config = getSandboxConfig();
|
|
61
|
-
if (!config.enabled)
|
|
62
|
-
return true;
|
|
63
|
-
if (config.blockNetwork)
|
|
64
|
-
return false;
|
|
65
|
-
if (config.allowedDomains.length === 0)
|
|
66
|
-
return true;
|
|
67
|
-
try {
|
|
68
|
-
const hostname = new URL(url).hostname.toLowerCase();
|
|
69
|
-
return config.allowedDomains.some((d) => hostname === d.toLowerCase() || hostname.endsWith(`.${d.toLowerCase()}`));
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/** Check if a bash command is allowed */
|
|
76
|
-
export function isCommandAllowed(command) {
|
|
77
|
-
const config = getSandboxConfig();
|
|
78
|
-
if (!config.enabled)
|
|
79
|
-
return true;
|
|
80
|
-
const firstWord = command.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
81
|
-
return !config.blockedCommands.includes(firstWord);
|
|
82
|
-
}
|
|
83
|
-
/** Get a human-readable sandbox status */
|
|
84
|
-
export function sandboxStatus() {
|
|
85
|
-
const config = getSandboxConfig();
|
|
86
|
-
if (!config.enabled)
|
|
87
|
-
return "Sandbox: disabled";
|
|
88
|
-
const lines = ["Sandbox: enabled"];
|
|
89
|
-
lines.push(` Allowed paths: ${config.allowedPaths.join(", ") || "none"}`);
|
|
90
|
-
if (config.blockNetwork) {
|
|
91
|
-
lines.push(" Network: blocked");
|
|
92
|
-
}
|
|
93
|
-
else if (config.allowedDomains.length > 0) {
|
|
94
|
-
lines.push(` Allowed domains: ${config.allowedDomains.join(", ")}`);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
lines.push(" Network: unrestricted");
|
|
98
|
-
}
|
|
99
|
-
if (config.blockedCommands.length > 0) {
|
|
100
|
-
lines.push(` Blocked commands: ${config.blockedCommands.join(", ")}`);
|
|
101
|
-
}
|
|
102
|
-
return lines.join("\n");
|
|
103
|
-
}
|
|
104
|
-
//# sourceMappingURL=sandbox.js.map
|