@zhijiewang/openharness 2.1.0 → 2.3.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/README.md +4 -4
- package/dist/DeferredTool.js +3 -1
- package/dist/Tool.d.ts +1 -1
- package/dist/agents/roles.js +58 -62
- package/dist/commands/cybergotchi.d.ts +1 -1
- package/dist/commands/cybergotchi.js +30 -30
- package/dist/commands/index.js +288 -132
- package/dist/components/App.d.ts +1 -1
- package/dist/components/App.js +6 -6
- package/dist/components/CompanionFooter.d.ts +1 -1
- package/dist/components/CompanionFooter.js +6 -8
- package/dist/components/CybergotchiBubble.js +5 -5
- package/dist/components/CybergotchiPanel.d.ts +1 -1
- package/dist/components/CybergotchiPanel.js +7 -7
- package/dist/components/CybergotchiPanelConnected.js +2 -2
- package/dist/components/CybergotchiSetup.js +26 -24
- package/dist/components/CybergotchiSprite.d.ts +1 -1
- package/dist/components/CybergotchiSprite.js +8 -12
- package/dist/components/DiffView.d.ts +1 -1
- package/dist/components/DiffView.js +10 -10
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/InitWizard.js +65 -33
- package/dist/components/Markdown.js +2 -4
- package/dist/components/Messages.js +4 -4
- package/dist/components/PermissionPrompt.d.ts +1 -1
- package/dist/components/PermissionPrompt.js +15 -17
- package/dist/components/REPL.d.ts +1 -1
- package/dist/components/REPL.js +74 -49
- package/dist/components/Spinner.js +2 -2
- package/dist/components/TextInput.js +35 -29
- package/dist/components/ToolCallDisplay.js +3 -5
- package/dist/cybergotchi/bones.d.ts +1 -1
- package/dist/cybergotchi/bones.js +8 -8
- package/dist/cybergotchi/config.d.ts +2 -2
- package/dist/cybergotchi/config.js +13 -13
- package/dist/cybergotchi/events.d.ts +5 -5
- package/dist/cybergotchi/events.js +7 -7
- package/dist/cybergotchi/needs.d.ts +2 -2
- package/dist/cybergotchi/needs.js +7 -9
- package/dist/cybergotchi/personality.d.ts +2 -2
- package/dist/cybergotchi/personality.js +2 -2
- package/dist/cybergotchi/species.d.ts +1 -1
- package/dist/cybergotchi/species.js +145 -217
- package/dist/cybergotchi/speech.d.ts +2 -2
- package/dist/cybergotchi/speech.js +43 -43
- package/dist/cybergotchi/types.d.ts +4 -4
- package/dist/cybergotchi/types.js +26 -26
- package/dist/cybergotchi/useCybergotchi.d.ts +1 -1
- package/dist/cybergotchi/useCybergotchi.js +29 -25
- package/dist/git/index.js +11 -9
- package/dist/harness/checkpoints.js +29 -21
- package/dist/harness/config.d.ts +3 -3
- package/dist/harness/config.js +15 -9
- package/dist/harness/context-warning.d.ts +1 -1
- package/dist/harness/context-warning.js +1 -1
- package/dist/harness/cost.js +1 -1
- package/dist/harness/credentials.js +13 -13
- package/dist/harness/hooks.js +7 -5
- package/dist/harness/keybindings.js +20 -18
- package/dist/harness/marketplace.d.ts +3 -3
- package/dist/harness/marketplace.js +55 -42
- package/dist/harness/memory.d.ts +23 -5
- package/dist/harness/memory.js +142 -41
- package/dist/harness/onboarding.js +30 -10
- package/dist/harness/plugins.d.ts +9 -1
- package/dist/harness/plugins.js +54 -30
- package/dist/harness/rules.js +12 -7
- package/dist/harness/sandbox.js +15 -15
- package/dist/harness/session-db.d.ts +55 -0
- package/dist/harness/session-db.js +165 -0
- package/dist/harness/session.d.ts +1 -1
- package/dist/harness/session.js +34 -15
- package/dist/harness/store.d.ts +3 -3
- package/dist/harness/store.js +6 -4
- package/dist/harness/submit-handler.d.ts +4 -4
- package/dist/harness/submit-handler.js +25 -23
- package/dist/harness/telemetry.d.ts +1 -1
- package/dist/harness/telemetry.js +23 -19
- package/dist/harness/traces.d.ts +2 -2
- package/dist/harness/traces.js +39 -33
- package/dist/harness/verification.d.ts +1 -1
- package/dist/harness/verification.js +50 -44
- package/dist/lsp/client.js +44 -40
- package/dist/main.js +114 -59
- package/dist/mcp/DeferredMcpTool.d.ts +4 -4
- package/dist/mcp/DeferredMcpTool.js +9 -5
- package/dist/mcp/McpTool.d.ts +4 -4
- package/dist/mcp/McpTool.js +8 -4
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/client.js +21 -21
- package/dist/mcp/loader.d.ts +1 -1
- package/dist/mcp/loader.js +17 -12
- package/dist/mcp/registry.d.ts +3 -3
- package/dist/mcp/registry.js +97 -97
- package/dist/mcp/schema.d.ts +1 -1
- package/dist/mcp/schema.js +16 -16
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +21 -21
- package/dist/mcp/types.d.ts +3 -3
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +10 -9
- package/dist/providers/base.d.ts +1 -1
- package/dist/providers/index.js +10 -3
- package/dist/providers/llamacpp.d.ts +2 -2
- package/dist/providers/llamacpp.js +1 -3
- package/dist/providers/ollama.d.ts +2 -2
- package/dist/providers/ollama.js +3 -4
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.js +3 -5
- package/dist/providers/openrouter.d.ts +2 -2
- package/dist/providers/router.d.ts +1 -1
- package/dist/providers/router.js +7 -7
- package/dist/query/compress.d.ts +2 -2
- package/dist/query/compress.js +22 -21
- package/dist/query/context-manager.d.ts +1 -1
- package/dist/query/context-manager.js +5 -5
- package/dist/query/errors.js +1 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +42 -24
- package/dist/query/tools.js +15 -12
- package/dist/query/types.d.ts +3 -1
- package/dist/query.d.ts +1 -1
- package/dist/query.js +1 -1
- package/dist/remote/auth.d.ts +2 -2
- package/dist/remote/auth.js +8 -8
- package/dist/remote/server.d.ts +3 -3
- package/dist/remote/server.js +60 -60
- package/dist/renderer/cells.js +9 -9
- package/dist/renderer/colors.js +24 -6
- package/dist/renderer/diff.d.ts +2 -2
- package/dist/renderer/diff.js +27 -19
- package/dist/renderer/differ.d.ts +1 -1
- package/dist/renderer/differ.js +9 -9
- package/dist/renderer/image.js +19 -19
- package/dist/renderer/index.d.ts +6 -6
- package/dist/renderer/index.js +163 -93
- package/dist/renderer/input.js +66 -48
- package/dist/renderer/layout.d.ts +6 -6
- package/dist/renderer/layout.js +163 -124
- package/dist/renderer/markdown.d.ts +2 -2
- package/dist/renderer/markdown.js +173 -54
- package/dist/renderer/session-browser.d.ts +2 -2
- package/dist/renderer/session-browser.js +19 -21
- package/dist/repl.d.ts +5 -5
- package/dist/repl.js +311 -198
- package/dist/sdk/index.d.ts +5 -5
- package/dist/sdk/index.js +32 -26
- package/dist/services/AgentDispatcher.d.ts +3 -3
- package/dist/services/AgentDispatcher.js +33 -29
- package/dist/services/CronExecutor.d.ts +4 -4
- package/dist/services/CronExecutor.js +12 -8
- package/dist/services/EvaluatorLoop.d.ts +3 -3
- package/dist/services/EvaluatorLoop.js +29 -21
- package/dist/services/MetaHarness.d.ts +1 -1
- package/dist/services/MetaHarness.js +34 -32
- package/dist/services/PipelineExecutor.d.ts +1 -1
- package/dist/services/PipelineExecutor.js +23 -25
- package/dist/services/SkillExtractor.d.ts +43 -0
- package/dist/services/SkillExtractor.js +163 -0
- package/dist/services/StreamingToolExecutor.d.ts +2 -2
- package/dist/services/StreamingToolExecutor.js +11 -7
- package/dist/services/a2a.d.ts +8 -8
- package/dist/services/a2a.js +44 -34
- package/dist/services/agent-messaging.d.ts +33 -15
- package/dist/services/agent-messaging.js +65 -13
- package/dist/services/cron.js +16 -16
- package/dist/tools/AgentTool/index.d.ts +5 -2
- package/dist/tools/AgentTool/index.js +25 -39
- package/dist/tools/AskUserTool/index.js +1 -1
- package/dist/tools/BashTool/index.d.ts +2 -2
- package/dist/tools/BashTool/index.js +18 -10
- package/dist/tools/CronTool/index.js +30 -12
- package/dist/tools/DiagnosticsTool/index.js +28 -22
- package/dist/tools/EnterPlanModeTool/index.js +93 -14
- package/dist/tools/EnterWorktreeTool/index.js +7 -3
- package/dist/tools/ExitPlanModeTool/index.d.ts +22 -1
- package/dist/tools/ExitPlanModeTool/index.js +20 -5
- package/dist/tools/ExitWorktreeTool/index.js +11 -4
- package/dist/tools/FileEditTool/index.js +3 -5
- package/dist/tools/FileReadTool/index.js +16 -10
- package/dist/tools/FileWriteTool/index.js +2 -2
- package/dist/tools/GlobTool/index.js +5 -9
- package/dist/tools/GrepTool/index.d.ts +2 -2
- package/dist/tools/GrepTool/index.js +14 -9
- package/dist/tools/ImageReadTool/index.js +2 -2
- package/dist/tools/KillProcessTool/index.js +11 -7
- package/dist/tools/LSTool/index.js +3 -3
- package/dist/tools/MemoryTool/index.d.ts +5 -5
- package/dist/tools/MemoryTool/index.js +28 -14
- package/dist/tools/MonitorTool/index.js +24 -19
- package/dist/tools/MultiEditTool/index.js +9 -5
- package/dist/tools/NotebookEditTool/index.js +3 -3
- package/dist/tools/ParallelAgentTool/index.d.ts +4 -4
- package/dist/tools/ParallelAgentTool/index.js +12 -6
- package/dist/tools/PipelineTool/index.js +3 -3
- package/dist/tools/PowerShellTool/index.js +10 -6
- package/dist/tools/RemoteTriggerTool/index.js +8 -4
- package/dist/tools/ScheduleWakeupTool/index.d.ts +42 -0
- package/dist/tools/ScheduleWakeupTool/index.js +115 -0
- package/dist/tools/SendMessageTool/index.js +25 -7
- package/dist/tools/SessionSearchTool/index.d.ts +15 -0
- package/dist/tools/SessionSearchTool/index.js +36 -0
- package/dist/tools/SkillTool/index.d.ts +3 -0
- package/dist/tools/SkillTool/index.js +39 -9
- package/dist/tools/TaskCreateTool/index.d.ts +2 -2
- package/dist/tools/TaskCreateTool/index.js +2 -2
- package/dist/tools/TaskGetTool/index.js +2 -2
- package/dist/tools/TaskListTool/index.js +3 -5
- package/dist/tools/TaskOutputTool/index.js +2 -2
- package/dist/tools/TaskStopTool/index.js +3 -3
- package/dist/tools/TaskUpdateTool/index.d.ts +4 -4
- package/dist/tools/TaskUpdateTool/index.js +2 -2
- package/dist/tools/ToolSearchTool/index.js +9 -6
- package/dist/tools/WebFetchTool/index.js +1 -1
- package/dist/tools/WebSearchTool/index.js +2 -6
- package/dist/tools.js +31 -30
- package/dist/types/permissions.js +15 -9
- package/dist/utils/bash-safety.d.ts +1 -1
- package/dist/utils/bash-safety.js +64 -54
- package/dist/utils/diff-algorithm.d.ts +3 -3
- package/dist/utils/diff-algorithm.js +7 -7
- package/dist/utils/fs.js +3 -3
- package/dist/utils/safe-env.js +1 -1
- package/dist/utils/theme-data.d.ts +1 -1
- package/dist/utils/theme-data.js +1 -1
- package/dist/utils/theme.d.ts +1 -1
- package/dist/utils/theme.js +1 -1
- package/dist/utils/tool-summary.d.ts +1 -1
- package/dist/utils/tool-summary.js +27 -9
- package/package.json +10 -3
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
import { analyzeBashCommand } from "../utils/bash-safety.js";
|
|
5
5
|
/** Tools auto-approved in acceptEdits mode */
|
|
6
6
|
const EDIT_SAFE_TOOLS = new Set([
|
|
7
|
-
"FileRead",
|
|
8
|
-
"
|
|
7
|
+
"FileRead",
|
|
8
|
+
"FileWrite",
|
|
9
|
+
"FileEdit",
|
|
10
|
+
"Glob",
|
|
11
|
+
"Grep",
|
|
12
|
+
"LS",
|
|
13
|
+
"ImageRead",
|
|
14
|
+
"NotebookEdit",
|
|
9
15
|
]);
|
|
10
16
|
/** Parse a tool specifier like "Bash(npm run *)" into tool name + pattern */
|
|
11
17
|
function parseToolSpecifier(specifier) {
|
|
@@ -28,10 +34,10 @@ function matchToolPattern(pattern, toolName) {
|
|
|
28
34
|
function matchArgGlob(pattern, value) {
|
|
29
35
|
// Convert glob to regex: * → [^/]*, ** → .*, escape other regex chars
|
|
30
36
|
const regexStr = pattern
|
|
31
|
-
.replace(/[.+^${}()|[\]\\]/g,
|
|
32
|
-
.replace(/\*\*/g,
|
|
33
|
-
.replace(/\*/g,
|
|
34
|
-
.replace(/\{\{DOUBLESTAR\}\}/g,
|
|
37
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&") // escape regex chars (except * and ?)
|
|
38
|
+
.replace(/\*\*/g, "{{DOUBLESTAR}}")
|
|
39
|
+
.replace(/\*/g, "[^/]*")
|
|
40
|
+
.replace(/\{\{DOUBLESTAR\}\}/g, ".*");
|
|
35
41
|
try {
|
|
36
42
|
return new RegExp(`^${regexStr}$`).test(value);
|
|
37
43
|
}
|
|
@@ -43,7 +49,7 @@ function matchArgGlob(pattern, value) {
|
|
|
43
49
|
function findToolRule(rules, toolName, toolInput) {
|
|
44
50
|
if (!rules || rules.length === 0)
|
|
45
51
|
return undefined;
|
|
46
|
-
return rules.find(r => {
|
|
52
|
+
return rules.find((r) => {
|
|
47
53
|
const { toolName: specToolName, argPattern } = parseToolSpecifier(r.tool);
|
|
48
54
|
// Check tool name match (with prefix * support)
|
|
49
55
|
if (!matchToolPattern(specToolName, toolName))
|
|
@@ -52,11 +58,11 @@ function findToolRule(rules, toolName, toolInput) {
|
|
|
52
58
|
if (argPattern && toolInput) {
|
|
53
59
|
const input = toolInput;
|
|
54
60
|
// For Bash: match against command string
|
|
55
|
-
if (toolName ===
|
|
61
|
+
if (toolName === "Bash" && typeof input.command === "string") {
|
|
56
62
|
return matchArgGlob(argPattern, input.command);
|
|
57
63
|
}
|
|
58
64
|
// For file tools: match against file_path
|
|
59
|
-
if ([
|
|
65
|
+
if (["Edit", "Write", "Read"].includes(toolName) && typeof input.file_path === "string") {
|
|
60
66
|
return matchArgGlob(argPattern, input.file_path);
|
|
61
67
|
}
|
|
62
68
|
return false; // Has pattern but no matching field
|
|
@@ -6,30 +6,39 @@
|
|
|
6
6
|
* Inspired by Claude Code's bash AST analysis for permission gating.
|
|
7
7
|
*/
|
|
8
8
|
// Commands that destroy data or are hard to reverse
|
|
9
|
-
const DESTRUCTIVE_COMMANDS = new Set([
|
|
10
|
-
'rm', 'rmdir', 'mkfs', 'dd', 'shred',
|
|
11
|
-
'truncate', 'wipefs',
|
|
12
|
-
]);
|
|
9
|
+
const DESTRUCTIVE_COMMANDS = new Set(["rm", "rmdir", "mkfs", "dd", "shred", "truncate", "wipefs"]);
|
|
13
10
|
// Git commands that are destructive or affect shared state
|
|
14
11
|
const DANGEROUS_GIT = new Set([
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
"push --force",
|
|
13
|
+
"push -f",
|
|
14
|
+
"reset --hard",
|
|
15
|
+
"clean -f",
|
|
16
|
+
"clean -fd",
|
|
17
|
+
"clean -fx",
|
|
18
|
+
"checkout .",
|
|
19
|
+
"checkout --",
|
|
20
|
+
"restore .",
|
|
21
|
+
"branch -D",
|
|
22
|
+
"branch -d",
|
|
19
23
|
]);
|
|
20
24
|
// Commands that change system/file permissions or ownership
|
|
21
|
-
const PERMISSION_COMMANDS = new Set([
|
|
22
|
-
'chmod', 'chown', 'chgrp', 'setfacl',
|
|
23
|
-
]);
|
|
25
|
+
const PERMISSION_COMMANDS = new Set(["chmod", "chown", "chgrp", "setfacl"]);
|
|
24
26
|
// Commands that install or modify system packages
|
|
25
27
|
const INSTALL_COMMANDS = new Set([
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
"apt",
|
|
29
|
+
"apt-get",
|
|
30
|
+
"yum",
|
|
31
|
+
"dnf",
|
|
32
|
+
"brew",
|
|
33
|
+
"pacman",
|
|
34
|
+
"snap",
|
|
35
|
+
"pip",
|
|
36
|
+
"npm",
|
|
37
|
+
"yarn",
|
|
38
|
+
"pnpm",
|
|
28
39
|
]);
|
|
29
40
|
// Commands that send data externally
|
|
30
|
-
const NETWORK_EXFIL = new Set([
|
|
31
|
-
'curl', 'wget', 'nc', 'ncat', 'socat', 'ssh', 'scp', 'rsync',
|
|
32
|
-
]);
|
|
41
|
+
const NETWORK_EXFIL = new Set(["curl", "wget", "nc", "ncat", "socat", "ssh", "scp", "rsync"]);
|
|
33
42
|
/**
|
|
34
43
|
* Analyze a bash command string for safety risks.
|
|
35
44
|
* Does lightweight structural parsing — splits on pipes, semicolons,
|
|
@@ -45,29 +54,29 @@ export function analyzeBashCommand(command) {
|
|
|
45
54
|
if (tokens.length === 0)
|
|
46
55
|
continue;
|
|
47
56
|
const cmd = tokens[0];
|
|
48
|
-
const args = tokens.slice(1).join(
|
|
49
|
-
const
|
|
57
|
+
const args = tokens.slice(1).join(" ");
|
|
58
|
+
const _fullCmd = `${cmd} ${args}`.trim();
|
|
50
59
|
// 1. Destructive commands (also check xargs + destructive)
|
|
51
|
-
const effectiveCmd = cmd ===
|
|
60
|
+
const effectiveCmd = cmd === "xargs" && tokens[1] ? tokens[1] : cmd;
|
|
52
61
|
if (DESTRUCTIVE_COMMANDS.has(effectiveCmd)) {
|
|
53
62
|
reasons.push(`destructive command: ${effectiveCmd}`);
|
|
54
63
|
// rm -rf / is especially dangerous
|
|
55
|
-
const fullArgs = tokens.slice(1).join(
|
|
56
|
-
if (effectiveCmd ===
|
|
57
|
-
reasons.push(
|
|
64
|
+
const fullArgs = tokens.slice(1).join(" ");
|
|
65
|
+
if (effectiveCmd === "rm" && /\s-[a-zA-Z]*r[a-zA-Z]*f|\s-[a-zA-Z]*f[a-zA-Z]*r/.test(` ${fullArgs}`)) {
|
|
66
|
+
reasons.push("recursive force delete (rm -rf)");
|
|
58
67
|
}
|
|
59
68
|
}
|
|
60
69
|
// 2. Dangerous git operations
|
|
61
|
-
if (cmd ===
|
|
70
|
+
if (cmd === "git") {
|
|
62
71
|
for (const pattern of DANGEROUS_GIT) {
|
|
63
|
-
if (args.includes(pattern.replace(
|
|
72
|
+
if (args.includes(pattern.replace("git ", ""))) {
|
|
64
73
|
reasons.push(`dangerous git operation: git ${pattern}`);
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
76
|
// git push to main/master
|
|
68
|
-
if (args.includes(
|
|
69
|
-
if (args.includes(
|
|
70
|
-
reasons.push(
|
|
77
|
+
if (args.includes("push") && (/\bmain\b/.test(args) || /\bmaster\b/.test(args))) {
|
|
78
|
+
if (args.includes("--force") || args.includes("-f")) {
|
|
79
|
+
reasons.push("force push to main/master");
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
82
|
}
|
|
@@ -77,7 +86,8 @@ export function analyzeBashCommand(command) {
|
|
|
77
86
|
const pipeIdx = subCommands.indexOf(sub);
|
|
78
87
|
if (pipeIdx < subCommands.length - 1) {
|
|
79
88
|
const nextCmd = tokenize(subCommands[pipeIdx + 1])[0];
|
|
80
|
-
if (nextCmd &&
|
|
89
|
+
if (nextCmd &&
|
|
90
|
+
["bash", "sh", "zsh", "eval", "source", ".", "python", "node", "perl", "ruby"].includes(nextCmd)) {
|
|
81
91
|
reasons.push(`pipe to execution: ${cmd} | ${nextCmd}`);
|
|
82
92
|
}
|
|
83
93
|
}
|
|
@@ -96,42 +106,42 @@ export function analyzeBashCommand(command) {
|
|
|
96
106
|
// 5. Permission/ownership changes
|
|
97
107
|
if (PERMISSION_COMMANDS.has(cmd)) {
|
|
98
108
|
reasons.push(`permission change: ${cmd}`);
|
|
99
|
-
if (args.includes(
|
|
100
|
-
reasons.push(
|
|
109
|
+
if (args.includes("777") || args.includes("+s") || args.includes("u+s")) {
|
|
110
|
+
reasons.push("dangerous permission mode");
|
|
101
111
|
}
|
|
102
112
|
}
|
|
103
113
|
// 6. Package installation (moderate risk)
|
|
104
|
-
if (INSTALL_COMMANDS.has(cmd) && (args.includes(
|
|
114
|
+
if (INSTALL_COMMANDS.has(cmd) && (args.includes("install") || args.includes("add") || args.includes("-S"))) {
|
|
105
115
|
reasons.push(`package installation: ${cmd}`);
|
|
106
116
|
}
|
|
107
117
|
// 7. Environment variable manipulation that could affect security
|
|
108
|
-
if (cmd ===
|
|
109
|
-
reasons.push(`modifying security-sensitive env var: ${args.split(
|
|
118
|
+
if (cmd === "export" && /PATH|LD_PRELOAD|LD_LIBRARY_PATH/.test(args)) {
|
|
119
|
+
reasons.push(`modifying security-sensitive env var: ${args.split("=")[0]}`);
|
|
110
120
|
}
|
|
111
121
|
// 8. Process killing
|
|
112
|
-
if ((cmd ===
|
|
122
|
+
if ((cmd === "kill" || cmd === "killall" || cmd === "pkill") && args.includes("-9")) {
|
|
113
123
|
reasons.push(`force kill: ${cmd} -9`);
|
|
114
124
|
}
|
|
115
125
|
// 9. Disk operations
|
|
116
|
-
if (cmd ===
|
|
126
|
+
if (cmd === "dd" || cmd === "mkfs" || cmd === "fdisk" || cmd === "parted") {
|
|
117
127
|
reasons.push(`disk operation: ${cmd}`);
|
|
118
128
|
}
|
|
119
129
|
// 10. Redirect overwrite to important files
|
|
120
130
|
if (/>\s*\/etc\/|>\s*~\/\.\w/.test(sub)) {
|
|
121
|
-
reasons.push(
|
|
131
|
+
reasons.push("redirect to system/dotfile");
|
|
122
132
|
}
|
|
123
133
|
}
|
|
124
134
|
if (reasons.length === 0) {
|
|
125
|
-
return { level:
|
|
135
|
+
return { level: "safe", reasons: [] };
|
|
126
136
|
}
|
|
127
137
|
// Classify: any pipe-to-exec, destructive, or force-push is 'dangerous'; rest is 'moderate'
|
|
128
|
-
const isDangerous = reasons.some(r => r.includes(
|
|
129
|
-
r.includes(
|
|
130
|
-
r.includes(
|
|
131
|
-
r.includes(
|
|
132
|
-
r.includes(
|
|
138
|
+
const isDangerous = reasons.some((r) => r.includes("pipe to execution") ||
|
|
139
|
+
r.includes("recursive force delete") ||
|
|
140
|
+
r.includes("force push to main") ||
|
|
141
|
+
r.includes("disk operation") ||
|
|
142
|
+
r.includes("dangerous permission mode"));
|
|
133
143
|
return {
|
|
134
|
-
level: isDangerous ?
|
|
144
|
+
level: isDangerous ? "dangerous" : "moderate",
|
|
135
145
|
reasons,
|
|
136
146
|
};
|
|
137
147
|
}
|
|
@@ -141,7 +151,7 @@ export function analyzeBashCommand(command) {
|
|
|
141
151
|
*/
|
|
142
152
|
function splitCommands(cmd) {
|
|
143
153
|
const parts = [];
|
|
144
|
-
let current =
|
|
154
|
+
let current = "";
|
|
145
155
|
let inSingle = false;
|
|
146
156
|
let inDouble = false;
|
|
147
157
|
let depth = 0; // $() nesting
|
|
@@ -162,12 +172,12 @@ function splitCommands(cmd) {
|
|
|
162
172
|
current += ch;
|
|
163
173
|
continue;
|
|
164
174
|
}
|
|
165
|
-
if (ch ===
|
|
175
|
+
if (ch === "$" && next === "(") {
|
|
166
176
|
depth++;
|
|
167
177
|
current += ch;
|
|
168
178
|
continue;
|
|
169
179
|
}
|
|
170
|
-
if (ch ===
|
|
180
|
+
if (ch === ")" && depth > 0) {
|
|
171
181
|
depth--;
|
|
172
182
|
current += ch;
|
|
173
183
|
continue;
|
|
@@ -177,15 +187,15 @@ function splitCommands(cmd) {
|
|
|
177
187
|
continue;
|
|
178
188
|
}
|
|
179
189
|
// Split on pipe, semicolon, &&, ||
|
|
180
|
-
if (ch ===
|
|
190
|
+
if (ch === "|" && next !== "|") {
|
|
181
191
|
parts.push(current.trim());
|
|
182
|
-
current =
|
|
192
|
+
current = "";
|
|
183
193
|
continue;
|
|
184
194
|
}
|
|
185
|
-
if (ch ===
|
|
195
|
+
if (ch === ";" || (ch === "&" && next === "&") || (ch === "|" && next === "|")) {
|
|
186
196
|
parts.push(current.trim());
|
|
187
|
-
current =
|
|
188
|
-
if (next ===
|
|
197
|
+
current = "";
|
|
198
|
+
if (next === "&" || next === "|")
|
|
189
199
|
i++; // skip second char of && or ||
|
|
190
200
|
continue;
|
|
191
201
|
}
|
|
@@ -200,7 +210,7 @@ function splitCommands(cmd) {
|
|
|
200
210
|
*/
|
|
201
211
|
function tokenize(cmd) {
|
|
202
212
|
const tokens = [];
|
|
203
|
-
let current =
|
|
213
|
+
let current = "";
|
|
204
214
|
let inSingle = false;
|
|
205
215
|
let inDouble = false;
|
|
206
216
|
for (const ch of cmd) {
|
|
@@ -212,10 +222,10 @@ function tokenize(cmd) {
|
|
|
212
222
|
inDouble = !inDouble;
|
|
213
223
|
continue;
|
|
214
224
|
}
|
|
215
|
-
if (ch ===
|
|
225
|
+
if (ch === " " && !inSingle && !inDouble) {
|
|
216
226
|
if (current)
|
|
217
227
|
tokens.push(current);
|
|
218
|
-
current =
|
|
228
|
+
current = "";
|
|
219
229
|
continue;
|
|
220
230
|
}
|
|
221
231
|
current += ch;
|
|
@@ -8,17 +8,17 @@
|
|
|
8
8
|
* Returns an array of { type, line } entries.
|
|
9
9
|
*/
|
|
10
10
|
export declare function computeDiff(oldText: string, newText: string): Array<{
|
|
11
|
-
type:
|
|
11
|
+
type: "add" | "remove" | "context";
|
|
12
12
|
line: string;
|
|
13
13
|
}>;
|
|
14
14
|
/**
|
|
15
15
|
* Filter diff to show only changed lines with N lines of context.
|
|
16
16
|
*/
|
|
17
17
|
export declare function filterWithContext(diff: Array<{
|
|
18
|
-
type:
|
|
18
|
+
type: "add" | "remove" | "context";
|
|
19
19
|
line: string;
|
|
20
20
|
}>, contextLines?: number): Array<{
|
|
21
|
-
type:
|
|
21
|
+
type: "add" | "remove" | "context" | "separator";
|
|
22
22
|
line: string;
|
|
23
23
|
}>;
|
|
24
24
|
//# sourceMappingURL=diff-algorithm.d.ts.map
|
|
@@ -8,23 +8,23 @@
|
|
|
8
8
|
* Returns an array of { type, line } entries.
|
|
9
9
|
*/
|
|
10
10
|
export function computeDiff(oldText, newText) {
|
|
11
|
-
const oldLines = oldText.split(
|
|
12
|
-
const newLines = newText.split(
|
|
11
|
+
const oldLines = oldText.split("\n");
|
|
12
|
+
const newLines = newText.split("\n");
|
|
13
13
|
const result = [];
|
|
14
14
|
let oi = 0;
|
|
15
15
|
let ni = 0;
|
|
16
16
|
while (oi < oldLines.length || ni < newLines.length) {
|
|
17
17
|
if (oi < oldLines.length && ni < newLines.length && oldLines[oi] === newLines[ni]) {
|
|
18
|
-
result.push({ type:
|
|
18
|
+
result.push({ type: "context", line: oldLines[oi] });
|
|
19
19
|
oi++;
|
|
20
20
|
ni++;
|
|
21
21
|
}
|
|
22
22
|
else if (ni < newLines.length && (oi >= oldLines.length || !oldLines.slice(oi).includes(newLines[ni]))) {
|
|
23
|
-
result.push({ type:
|
|
23
|
+
result.push({ type: "add", line: newLines[ni] });
|
|
24
24
|
ni++;
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
|
-
result.push({ type:
|
|
27
|
+
result.push({ type: "remove", line: oldLines[oi] });
|
|
28
28
|
oi++;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -36,7 +36,7 @@ export function computeDiff(oldText, newText) {
|
|
|
36
36
|
export function filterWithContext(diff, contextLines = 3) {
|
|
37
37
|
const changed = new Set();
|
|
38
38
|
diff.forEach((d, i) => {
|
|
39
|
-
if (d.type !==
|
|
39
|
+
if (d.type !== "context") {
|
|
40
40
|
for (let j = Math.max(0, i - contextLines); j <= Math.min(diff.length - 1, i + contextLines); j++) {
|
|
41
41
|
changed.add(j);
|
|
42
42
|
}
|
|
@@ -47,7 +47,7 @@ export function filterWithContext(diff, contextLines = 3) {
|
|
|
47
47
|
diff.forEach((d, i) => {
|
|
48
48
|
if (changed.has(i)) {
|
|
49
49
|
if (i > lastShown + 1 && lastShown >= 0) {
|
|
50
|
-
result.push({ type:
|
|
50
|
+
result.push({ type: "separator", line: "..." });
|
|
51
51
|
}
|
|
52
52
|
result.push(d);
|
|
53
53
|
lastShown = i;
|
package/dist/utils/fs.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Shared filesystem utilities — directory walking and glob matching.
|
|
3
3
|
* Used by GrepTool, GlobTool, and other file-scanning tools.
|
|
4
4
|
*/
|
|
5
|
-
import * as fs from "fs/promises";
|
|
6
|
-
import * as path from "path";
|
|
5
|
+
import * as fs from "node:fs/promises";
|
|
6
|
+
import * as path from "node:path";
|
|
7
7
|
/**
|
|
8
8
|
* Recursively walk a directory, returning all file paths.
|
|
9
9
|
* Skips dotfiles, node_modules, and unreadable directories.
|
|
@@ -55,7 +55,7 @@ export function matchGlob(filePath, pattern) {
|
|
|
55
55
|
.join("")
|
|
56
56
|
.replace(/GLOBSTAR/g, ".*");
|
|
57
57
|
try {
|
|
58
|
-
return new RegExp(
|
|
58
|
+
return new RegExp(`(^|/)${escaped}$`).test(normalized);
|
|
59
59
|
}
|
|
60
60
|
catch {
|
|
61
61
|
return normalized.includes(pattern.replace(/\*/g, ""));
|
package/dist/utils/safe-env.js
CHANGED
|
@@ -28,7 +28,7 @@ export function safeEnv(extra) {
|
|
|
28
28
|
for (const [key, value] of Object.entries(process.env)) {
|
|
29
29
|
if (value === undefined)
|
|
30
30
|
continue;
|
|
31
|
-
if (BLOCKED_PATTERNS.some(p => p.test(key)))
|
|
31
|
+
if (BLOCKED_PATTERNS.some((p) => p.test(key)))
|
|
32
32
|
continue;
|
|
33
33
|
env[key] = value;
|
|
34
34
|
}
|
|
@@ -27,5 +27,5 @@ export declare const darkTheme: Theme;
|
|
|
27
27
|
export declare const lightTheme: Theme;
|
|
28
28
|
export declare function getTheme(): Theme;
|
|
29
29
|
/** Set the active theme. Must be called before renderer modules initialize. */
|
|
30
|
-
export declare function setActiveTheme(name:
|
|
30
|
+
export declare function setActiveTheme(name: "dark" | "light"): void;
|
|
31
31
|
//# sourceMappingURL=theme-data.d.ts.map
|
package/dist/utils/theme-data.js
CHANGED
|
@@ -50,6 +50,6 @@ export function getTheme() {
|
|
|
50
50
|
}
|
|
51
51
|
/** Set the active theme. Must be called before renderer modules initialize. */
|
|
52
52
|
export function setActiveTheme(name) {
|
|
53
|
-
activeTheme = name ===
|
|
53
|
+
activeTheme = name === "light" ? lightTheme : darkTheme;
|
|
54
54
|
}
|
|
55
55
|
//# sourceMappingURL=theme-data.js.map
|
package/dist/utils/theme.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Re-exports theme data from theme-data.ts (no React dependency).
|
|
4
4
|
*/
|
|
5
5
|
import React from "react";
|
|
6
|
-
export {
|
|
6
|
+
export { darkTheme, getTheme, lightTheme, setActiveTheme, type Theme } from "./theme-data.js";
|
|
7
7
|
import type { Theme } from "./theme-data.js";
|
|
8
8
|
export declare const ThemeProvider: React.Provider<Theme>;
|
|
9
9
|
export declare function useTheme(): Theme;
|
package/dist/utils/theme.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Re-exports theme data from theme-data.ts (no React dependency).
|
|
4
4
|
*/
|
|
5
5
|
import React from "react";
|
|
6
|
-
export { darkTheme,
|
|
6
|
+
export { darkTheme, getTheme, lightTheme, setActiveTheme } from "./theme-data.js";
|
|
7
7
|
import { darkTheme } from "./theme-data.js";
|
|
8
8
|
const ThemeContext = React.createContext(darkTheme);
|
|
9
9
|
export const ThemeProvider = ThemeContext.Provider;
|
|
@@ -11,7 +11,7 @@ export declare function summarizeToolArgs(toolName: string, argsJson: string): s
|
|
|
11
11
|
* Extract a readable summary of tool arguments for display.
|
|
12
12
|
* Simpler version for inline display (tool call rows).
|
|
13
13
|
*/
|
|
14
|
-
export declare function formatToolArgs(
|
|
14
|
+
export declare function formatToolArgs(_toolName: string, args: Record<string, unknown>): string;
|
|
15
15
|
/**
|
|
16
16
|
* Summarize tool result output for compact display.
|
|
17
17
|
* Returns a short string like "42 lines" or "1.2 KB".
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export function summarizeToolArgs(toolName, argsJson) {
|
|
10
10
|
const lower = toolName.toLowerCase();
|
|
11
|
-
if (lower ===
|
|
11
|
+
if (lower === "bash" || lower === "shell" || lower === "execute") {
|
|
12
12
|
const cmdMatch = argsJson.match(/command[:\s]+["`]?(.+?)["`]?(?:\n|$)/i);
|
|
13
13
|
if (cmdMatch)
|
|
14
14
|
return `$ ${cmdMatch[1]}`;
|
|
@@ -17,13 +17,25 @@ export function summarizeToolArgs(toolName, argsJson) {
|
|
|
17
17
|
if (args.command)
|
|
18
18
|
return `$ ${args.command.slice(0, 60)}`;
|
|
19
19
|
}
|
|
20
|
-
catch {
|
|
20
|
+
catch {
|
|
21
|
+
/* ignore */
|
|
22
|
+
}
|
|
21
23
|
}
|
|
22
|
-
if (lower.includes(
|
|
24
|
+
if (lower.includes("read") ||
|
|
25
|
+
lower.includes("write") ||
|
|
26
|
+
lower.includes("edit") ||
|
|
27
|
+
lower.includes("glob") ||
|
|
28
|
+
lower.includes("grep")) {
|
|
23
29
|
try {
|
|
24
30
|
const args = JSON.parse(argsJson);
|
|
25
31
|
if (args.file_path) {
|
|
26
|
-
const action = lower.includes(
|
|
32
|
+
const action = lower.includes("read")
|
|
33
|
+
? "reading"
|
|
34
|
+
: lower.includes("write")
|
|
35
|
+
? "writing"
|
|
36
|
+
: lower.includes("edit")
|
|
37
|
+
? "editing"
|
|
38
|
+
: lower;
|
|
27
39
|
return `${action} ${args.file_path}`;
|
|
28
40
|
}
|
|
29
41
|
if (args.pattern)
|
|
@@ -32,7 +44,13 @@ export function summarizeToolArgs(toolName, argsJson) {
|
|
|
32
44
|
catch {
|
|
33
45
|
const pathMatch = argsJson.match(/(?:path|file)[:\s]+["`]?([^\s"`]+)/i);
|
|
34
46
|
if (pathMatch) {
|
|
35
|
-
const action = lower.includes(
|
|
47
|
+
const action = lower.includes("read")
|
|
48
|
+
? "reading"
|
|
49
|
+
: lower.includes("write")
|
|
50
|
+
? "writing"
|
|
51
|
+
: lower.includes("edit")
|
|
52
|
+
? "editing"
|
|
53
|
+
: lower;
|
|
36
54
|
return `${action} ${pathMatch[1]}`;
|
|
37
55
|
}
|
|
38
56
|
}
|
|
@@ -43,7 +61,7 @@ export function summarizeToolArgs(toolName, argsJson) {
|
|
|
43
61
|
* Extract a readable summary of tool arguments for display.
|
|
44
62
|
* Simpler version for inline display (tool call rows).
|
|
45
63
|
*/
|
|
46
|
-
export function formatToolArgs(
|
|
64
|
+
export function formatToolArgs(_toolName, args) {
|
|
47
65
|
if (args.file_path)
|
|
48
66
|
return args.file_path;
|
|
49
67
|
if (args.command)
|
|
@@ -56,7 +74,7 @@ export function formatToolArgs(toolName, args) {
|
|
|
56
74
|
return args.url;
|
|
57
75
|
// Compact JSON for other args
|
|
58
76
|
const json = JSON.stringify(args);
|
|
59
|
-
return json.length > 60 ? json.slice(0, 57)
|
|
77
|
+
return json.length > 60 ? `${json.slice(0, 57)}...` : json;
|
|
60
78
|
}
|
|
61
79
|
/**
|
|
62
80
|
* Summarize tool result output for compact display.
|
|
@@ -64,8 +82,8 @@ export function formatToolArgs(toolName, args) {
|
|
|
64
82
|
*/
|
|
65
83
|
export function summarizeToolOutput(output) {
|
|
66
84
|
if (!output)
|
|
67
|
-
return
|
|
68
|
-
const lines = output.split(
|
|
85
|
+
return "";
|
|
86
|
+
const lines = output.split("\n").length;
|
|
69
87
|
if (lines === 1 && output.length < 60)
|
|
70
88
|
return output.trim();
|
|
71
89
|
return `${lines} lines`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhijiewang/openharness",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Open-source terminal coding agent. Works with any LLM.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,15 +23,18 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"dev": "tsx src/main.tsx",
|
|
25
25
|
"build": "tsc",
|
|
26
|
-
"prepare": "
|
|
26
|
+
"prepare": "husky",
|
|
27
27
|
"prepublishOnly": "npm run build",
|
|
28
28
|
"test": "node scripts/test.mjs",
|
|
29
29
|
"test:coverage": "node scripts/coverage.mjs",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
|
+
"lint": "biome check src/",
|
|
32
|
+
"lint:fix": "biome check --write src/",
|
|
33
|
+
"format": "biome format --write src/",
|
|
31
34
|
"start": "node dist/main.js"
|
|
32
35
|
},
|
|
33
36
|
"dependencies": {
|
|
34
|
-
"
|
|
37
|
+
"better-sqlite3": "^12.9.0",
|
|
35
38
|
"chalk": "^5.4.1",
|
|
36
39
|
"commander": "^13.0.0",
|
|
37
40
|
"ink": "^5.2.0",
|
|
@@ -43,9 +46,13 @@
|
|
|
43
46
|
"zod": "^3.24.0"
|
|
44
47
|
},
|
|
45
48
|
"devDependencies": {
|
|
49
|
+
"@biomejs/biome": "^2.4.11",
|
|
50
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
51
|
+
"@types/marked": "^5.0.2",
|
|
46
52
|
"@types/node": "^22.0.0",
|
|
47
53
|
"@types/react": "^18.3.0",
|
|
48
54
|
"c8": "^11.0.0",
|
|
55
|
+
"husky": "^9.1.7",
|
|
49
56
|
"sharp": "^0.34.5",
|
|
50
57
|
"tsx": "^4.19.0",
|
|
51
58
|
"typescript": "^5.8.0"
|