@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.
Files changed (231) hide show
  1. package/README.md +4 -4
  2. package/dist/DeferredTool.js +3 -1
  3. package/dist/Tool.d.ts +1 -1
  4. package/dist/agents/roles.js +58 -62
  5. package/dist/commands/cybergotchi.d.ts +1 -1
  6. package/dist/commands/cybergotchi.js +30 -30
  7. package/dist/commands/index.js +288 -132
  8. package/dist/components/App.d.ts +1 -1
  9. package/dist/components/App.js +6 -6
  10. package/dist/components/CompanionFooter.d.ts +1 -1
  11. package/dist/components/CompanionFooter.js +6 -8
  12. package/dist/components/CybergotchiBubble.js +5 -5
  13. package/dist/components/CybergotchiPanel.d.ts +1 -1
  14. package/dist/components/CybergotchiPanel.js +7 -7
  15. package/dist/components/CybergotchiPanelConnected.js +2 -2
  16. package/dist/components/CybergotchiSetup.js +26 -24
  17. package/dist/components/CybergotchiSprite.d.ts +1 -1
  18. package/dist/components/CybergotchiSprite.js +8 -12
  19. package/dist/components/DiffView.d.ts +1 -1
  20. package/dist/components/DiffView.js +10 -10
  21. package/dist/components/ErrorBoundary.d.ts +1 -1
  22. package/dist/components/ErrorBoundary.js +1 -1
  23. package/dist/components/InitWizard.js +65 -33
  24. package/dist/components/Markdown.js +2 -4
  25. package/dist/components/Messages.js +4 -4
  26. package/dist/components/PermissionPrompt.d.ts +1 -1
  27. package/dist/components/PermissionPrompt.js +15 -17
  28. package/dist/components/REPL.d.ts +1 -1
  29. package/dist/components/REPL.js +74 -49
  30. package/dist/components/Spinner.js +2 -2
  31. package/dist/components/TextInput.js +35 -29
  32. package/dist/components/ToolCallDisplay.js +3 -5
  33. package/dist/cybergotchi/bones.d.ts +1 -1
  34. package/dist/cybergotchi/bones.js +8 -8
  35. package/dist/cybergotchi/config.d.ts +2 -2
  36. package/dist/cybergotchi/config.js +13 -13
  37. package/dist/cybergotchi/events.d.ts +5 -5
  38. package/dist/cybergotchi/events.js +7 -7
  39. package/dist/cybergotchi/needs.d.ts +2 -2
  40. package/dist/cybergotchi/needs.js +7 -9
  41. package/dist/cybergotchi/personality.d.ts +2 -2
  42. package/dist/cybergotchi/personality.js +2 -2
  43. package/dist/cybergotchi/species.d.ts +1 -1
  44. package/dist/cybergotchi/species.js +145 -217
  45. package/dist/cybergotchi/speech.d.ts +2 -2
  46. package/dist/cybergotchi/speech.js +43 -43
  47. package/dist/cybergotchi/types.d.ts +4 -4
  48. package/dist/cybergotchi/types.js +26 -26
  49. package/dist/cybergotchi/useCybergotchi.d.ts +1 -1
  50. package/dist/cybergotchi/useCybergotchi.js +29 -25
  51. package/dist/git/index.js +11 -9
  52. package/dist/harness/checkpoints.js +29 -21
  53. package/dist/harness/config.d.ts +3 -3
  54. package/dist/harness/config.js +15 -9
  55. package/dist/harness/context-warning.d.ts +1 -1
  56. package/dist/harness/context-warning.js +1 -1
  57. package/dist/harness/cost.js +1 -1
  58. package/dist/harness/credentials.js +13 -13
  59. package/dist/harness/hooks.js +7 -5
  60. package/dist/harness/keybindings.js +20 -18
  61. package/dist/harness/marketplace.d.ts +3 -3
  62. package/dist/harness/marketplace.js +55 -42
  63. package/dist/harness/memory.d.ts +23 -5
  64. package/dist/harness/memory.js +142 -41
  65. package/dist/harness/onboarding.js +30 -10
  66. package/dist/harness/plugins.d.ts +9 -1
  67. package/dist/harness/plugins.js +54 -30
  68. package/dist/harness/rules.js +12 -7
  69. package/dist/harness/sandbox.js +15 -15
  70. package/dist/harness/session-db.d.ts +55 -0
  71. package/dist/harness/session-db.js +165 -0
  72. package/dist/harness/session.d.ts +1 -1
  73. package/dist/harness/session.js +34 -15
  74. package/dist/harness/store.d.ts +3 -3
  75. package/dist/harness/store.js +6 -4
  76. package/dist/harness/submit-handler.d.ts +4 -4
  77. package/dist/harness/submit-handler.js +25 -23
  78. package/dist/harness/telemetry.d.ts +1 -1
  79. package/dist/harness/telemetry.js +23 -19
  80. package/dist/harness/traces.d.ts +2 -2
  81. package/dist/harness/traces.js +39 -33
  82. package/dist/harness/verification.d.ts +1 -1
  83. package/dist/harness/verification.js +50 -44
  84. package/dist/lsp/client.js +44 -40
  85. package/dist/main.js +114 -59
  86. package/dist/mcp/DeferredMcpTool.d.ts +4 -4
  87. package/dist/mcp/DeferredMcpTool.js +9 -5
  88. package/dist/mcp/McpTool.d.ts +4 -4
  89. package/dist/mcp/McpTool.js +8 -4
  90. package/dist/mcp/client.d.ts +2 -2
  91. package/dist/mcp/client.js +21 -21
  92. package/dist/mcp/loader.d.ts +1 -1
  93. package/dist/mcp/loader.js +17 -12
  94. package/dist/mcp/registry.d.ts +3 -3
  95. package/dist/mcp/registry.js +97 -97
  96. package/dist/mcp/schema.d.ts +1 -1
  97. package/dist/mcp/schema.js +16 -16
  98. package/dist/mcp/server.d.ts +1 -1
  99. package/dist/mcp/server.js +21 -21
  100. package/dist/mcp/types.d.ts +3 -3
  101. package/dist/providers/anthropic.d.ts +2 -2
  102. package/dist/providers/anthropic.js +10 -9
  103. package/dist/providers/base.d.ts +1 -1
  104. package/dist/providers/index.js +10 -3
  105. package/dist/providers/llamacpp.d.ts +2 -2
  106. package/dist/providers/llamacpp.js +1 -3
  107. package/dist/providers/ollama.d.ts +2 -2
  108. package/dist/providers/ollama.js +3 -4
  109. package/dist/providers/openai.d.ts +2 -2
  110. package/dist/providers/openai.js +3 -5
  111. package/dist/providers/openrouter.d.ts +2 -2
  112. package/dist/providers/router.d.ts +1 -1
  113. package/dist/providers/router.js +7 -7
  114. package/dist/query/compress.d.ts +2 -2
  115. package/dist/query/compress.js +22 -21
  116. package/dist/query/context-manager.d.ts +1 -1
  117. package/dist/query/context-manager.js +5 -5
  118. package/dist/query/errors.js +1 -1
  119. package/dist/query/index.d.ts +1 -1
  120. package/dist/query/index.js +42 -24
  121. package/dist/query/tools.js +15 -12
  122. package/dist/query/types.d.ts +3 -1
  123. package/dist/query.d.ts +1 -1
  124. package/dist/query.js +1 -1
  125. package/dist/remote/auth.d.ts +2 -2
  126. package/dist/remote/auth.js +8 -8
  127. package/dist/remote/server.d.ts +3 -3
  128. package/dist/remote/server.js +60 -60
  129. package/dist/renderer/cells.js +9 -9
  130. package/dist/renderer/colors.js +24 -6
  131. package/dist/renderer/diff.d.ts +2 -2
  132. package/dist/renderer/diff.js +27 -19
  133. package/dist/renderer/differ.d.ts +1 -1
  134. package/dist/renderer/differ.js +9 -9
  135. package/dist/renderer/image.js +19 -19
  136. package/dist/renderer/index.d.ts +6 -6
  137. package/dist/renderer/index.js +163 -93
  138. package/dist/renderer/input.js +66 -48
  139. package/dist/renderer/layout.d.ts +6 -6
  140. package/dist/renderer/layout.js +163 -124
  141. package/dist/renderer/markdown.d.ts +2 -2
  142. package/dist/renderer/markdown.js +173 -54
  143. package/dist/renderer/session-browser.d.ts +2 -2
  144. package/dist/renderer/session-browser.js +19 -21
  145. package/dist/repl.d.ts +5 -5
  146. package/dist/repl.js +311 -198
  147. package/dist/sdk/index.d.ts +5 -5
  148. package/dist/sdk/index.js +32 -26
  149. package/dist/services/AgentDispatcher.d.ts +3 -3
  150. package/dist/services/AgentDispatcher.js +33 -29
  151. package/dist/services/CronExecutor.d.ts +4 -4
  152. package/dist/services/CronExecutor.js +12 -8
  153. package/dist/services/EvaluatorLoop.d.ts +3 -3
  154. package/dist/services/EvaluatorLoop.js +29 -21
  155. package/dist/services/MetaHarness.d.ts +1 -1
  156. package/dist/services/MetaHarness.js +34 -32
  157. package/dist/services/PipelineExecutor.d.ts +1 -1
  158. package/dist/services/PipelineExecutor.js +23 -25
  159. package/dist/services/SkillExtractor.d.ts +43 -0
  160. package/dist/services/SkillExtractor.js +163 -0
  161. package/dist/services/StreamingToolExecutor.d.ts +2 -2
  162. package/dist/services/StreamingToolExecutor.js +11 -7
  163. package/dist/services/a2a.d.ts +8 -8
  164. package/dist/services/a2a.js +44 -34
  165. package/dist/services/agent-messaging.d.ts +33 -15
  166. package/dist/services/agent-messaging.js +65 -13
  167. package/dist/services/cron.js +16 -16
  168. package/dist/tools/AgentTool/index.d.ts +5 -2
  169. package/dist/tools/AgentTool/index.js +25 -39
  170. package/dist/tools/AskUserTool/index.js +1 -1
  171. package/dist/tools/BashTool/index.d.ts +2 -2
  172. package/dist/tools/BashTool/index.js +18 -10
  173. package/dist/tools/CronTool/index.js +30 -12
  174. package/dist/tools/DiagnosticsTool/index.js +28 -22
  175. package/dist/tools/EnterPlanModeTool/index.js +93 -14
  176. package/dist/tools/EnterWorktreeTool/index.js +7 -3
  177. package/dist/tools/ExitPlanModeTool/index.d.ts +22 -1
  178. package/dist/tools/ExitPlanModeTool/index.js +20 -5
  179. package/dist/tools/ExitWorktreeTool/index.js +11 -4
  180. package/dist/tools/FileEditTool/index.js +3 -5
  181. package/dist/tools/FileReadTool/index.js +16 -10
  182. package/dist/tools/FileWriteTool/index.js +2 -2
  183. package/dist/tools/GlobTool/index.js +5 -9
  184. package/dist/tools/GrepTool/index.d.ts +2 -2
  185. package/dist/tools/GrepTool/index.js +14 -9
  186. package/dist/tools/ImageReadTool/index.js +2 -2
  187. package/dist/tools/KillProcessTool/index.js +11 -7
  188. package/dist/tools/LSTool/index.js +3 -3
  189. package/dist/tools/MemoryTool/index.d.ts +5 -5
  190. package/dist/tools/MemoryTool/index.js +28 -14
  191. package/dist/tools/MonitorTool/index.js +24 -19
  192. package/dist/tools/MultiEditTool/index.js +9 -5
  193. package/dist/tools/NotebookEditTool/index.js +3 -3
  194. package/dist/tools/ParallelAgentTool/index.d.ts +4 -4
  195. package/dist/tools/ParallelAgentTool/index.js +12 -6
  196. package/dist/tools/PipelineTool/index.js +3 -3
  197. package/dist/tools/PowerShellTool/index.js +10 -6
  198. package/dist/tools/RemoteTriggerTool/index.js +8 -4
  199. package/dist/tools/ScheduleWakeupTool/index.d.ts +42 -0
  200. package/dist/tools/ScheduleWakeupTool/index.js +115 -0
  201. package/dist/tools/SendMessageTool/index.js +25 -7
  202. package/dist/tools/SessionSearchTool/index.d.ts +15 -0
  203. package/dist/tools/SessionSearchTool/index.js +36 -0
  204. package/dist/tools/SkillTool/index.d.ts +3 -0
  205. package/dist/tools/SkillTool/index.js +39 -9
  206. package/dist/tools/TaskCreateTool/index.d.ts +2 -2
  207. package/dist/tools/TaskCreateTool/index.js +2 -2
  208. package/dist/tools/TaskGetTool/index.js +2 -2
  209. package/dist/tools/TaskListTool/index.js +3 -5
  210. package/dist/tools/TaskOutputTool/index.js +2 -2
  211. package/dist/tools/TaskStopTool/index.js +3 -3
  212. package/dist/tools/TaskUpdateTool/index.d.ts +4 -4
  213. package/dist/tools/TaskUpdateTool/index.js +2 -2
  214. package/dist/tools/ToolSearchTool/index.js +9 -6
  215. package/dist/tools/WebFetchTool/index.js +1 -1
  216. package/dist/tools/WebSearchTool/index.js +2 -6
  217. package/dist/tools.js +31 -30
  218. package/dist/types/permissions.js +15 -9
  219. package/dist/utils/bash-safety.d.ts +1 -1
  220. package/dist/utils/bash-safety.js +64 -54
  221. package/dist/utils/diff-algorithm.d.ts +3 -3
  222. package/dist/utils/diff-algorithm.js +7 -7
  223. package/dist/utils/fs.js +3 -3
  224. package/dist/utils/safe-env.js +1 -1
  225. package/dist/utils/theme-data.d.ts +1 -1
  226. package/dist/utils/theme-data.js +1 -1
  227. package/dist/utils/theme.d.ts +1 -1
  228. package/dist/utils/theme.js +1 -1
  229. package/dist/utils/tool-summary.d.ts +1 -1
  230. package/dist/utils/tool-summary.js +27 -9
  231. 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", "FileWrite", "FileEdit", "Glob", "Grep", "LS",
8
- "ImageRead", "NotebookEdit",
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, '\\$&') // escape regex chars (except * and ?)
32
- .replace(/\*\*/g, '{{DOUBLESTAR}}')
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 === 'Bash' && typeof input.command === 'string') {
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 (['Edit', 'Write', 'Read'].includes(toolName) && typeof input.file_path === 'string') {
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,7 +6,7 @@
6
6
  * Inspired by Claude Code's bash AST analysis for permission gating.
7
7
  */
8
8
  export type BashRisk = {
9
- level: 'safe' | 'moderate' | 'dangerous';
9
+ level: "safe" | "moderate" | "dangerous";
10
10
  reasons: string[];
11
11
  };
12
12
  /**
@@ -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
- 'push --force', 'push -f', 'reset --hard',
16
- 'clean -f', 'clean -fd', 'clean -fx',
17
- 'checkout .', 'checkout --', 'restore .',
18
- 'branch -D', 'branch -d',
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
- 'apt', 'apt-get', 'yum', 'dnf', 'brew',
27
- 'pacman', 'snap', 'pip', 'npm', 'yarn', 'pnpm',
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 fullCmd = `${cmd} ${args}`.trim();
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 === 'xargs' && tokens[1] ? tokens[1] : 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 === 'rm' && /\s-[a-zA-Z]*r[a-zA-Z]*f|\s-[a-zA-Z]*f[a-zA-Z]*r/.test(` ${fullArgs}`)) {
57
- reasons.push('recursive force delete (rm -rf)');
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 === 'git') {
70
+ if (cmd === "git") {
62
71
  for (const pattern of DANGEROUS_GIT) {
63
- if (args.includes(pattern.replace('git ', ''))) {
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('push') && (/\bmain\b/.test(args) || /\bmaster\b/.test(args))) {
69
- if (args.includes('--force') || args.includes('-f')) {
70
- reasons.push('force push to main/master');
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 && ['bash', 'sh', 'zsh', 'eval', 'source', '.', 'python', 'node', 'perl', 'ruby'].includes(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('777') || args.includes('+s') || args.includes('u+s')) {
100
- reasons.push('dangerous permission mode');
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('install') || args.includes('add') || args.includes('-S'))) {
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 === 'export' && /PATH|LD_PRELOAD|LD_LIBRARY_PATH/.test(args)) {
109
- reasons.push(`modifying security-sensitive env var: ${args.split('=')[0]}`);
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 === 'kill' || cmd === 'killall' || cmd === 'pkill') && args.includes('-9')) {
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 === 'dd' || cmd === 'mkfs' || cmd === 'fdisk' || cmd === 'parted') {
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('redirect to system/dotfile');
131
+ reasons.push("redirect to system/dotfile");
122
132
  }
123
133
  }
124
134
  if (reasons.length === 0) {
125
- return { level: 'safe', reasons: [] };
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('pipe to execution') ||
129
- r.includes('recursive force delete') ||
130
- r.includes('force push to main') ||
131
- r.includes('disk operation') ||
132
- r.includes('dangerous permission mode'));
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 ? 'dangerous' : 'moderate',
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 === '$' && next === '(') {
175
+ if (ch === "$" && next === "(") {
166
176
  depth++;
167
177
  current += ch;
168
178
  continue;
169
179
  }
170
- if (ch === ')' && depth > 0) {
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 === '|' && next !== '|') {
190
+ if (ch === "|" && next !== "|") {
181
191
  parts.push(current.trim());
182
- current = '';
192
+ current = "";
183
193
  continue;
184
194
  }
185
- if (ch === ';' || (ch === '&' && next === '&') || (ch === '|' && next === '|')) {
195
+ if (ch === ";" || (ch === "&" && next === "&") || (ch === "|" && next === "|")) {
186
196
  parts.push(current.trim());
187
- current = '';
188
- if (next === '&' || 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 === ' ' && !inSingle && !inDouble) {
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: 'add' | 'remove' | 'context';
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: 'add' | 'remove' | 'context';
18
+ type: "add" | "remove" | "context";
19
19
  line: string;
20
20
  }>, contextLines?: number): Array<{
21
- type: 'add' | 'remove' | 'context' | 'separator';
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('\n');
12
- const newLines = newText.split('\n');
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: 'context', line: oldLines[oi] });
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: 'add', line: newLines[ni] });
23
+ result.push({ type: "add", line: newLines[ni] });
24
24
  ni++;
25
25
  }
26
26
  else {
27
- result.push({ type: 'remove', line: oldLines[oi] });
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 !== 'context') {
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: 'separator', line: '...' });
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("(^|/)" + escaped + "$").test(normalized);
58
+ return new RegExp(`(^|/)${escaped}$`).test(normalized);
59
59
  }
60
60
  catch {
61
61
  return normalized.includes(pattern.replace(/\*/g, ""));
@@ -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: 'dark' | 'light'): void;
30
+ export declare function setActiveTheme(name: "dark" | "light"): void;
31
31
  //# sourceMappingURL=theme-data.d.ts.map
@@ -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 === 'light' ? lightTheme : darkTheme;
53
+ activeTheme = name === "light" ? lightTheme : darkTheme;
54
54
  }
55
55
  //# sourceMappingURL=theme-data.js.map
@@ -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 { type Theme, darkTheme, lightTheme, getTheme, setActiveTheme } from "./theme-data.js";
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;
@@ -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, lightTheme, getTheme, setActiveTheme } from "./theme-data.js";
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(toolName: string, args: Record<string, unknown>): string;
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 === 'bash' || lower === 'shell' || lower === 'execute') {
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 { /* ignore */ }
20
+ catch {
21
+ /* ignore */
22
+ }
21
23
  }
22
- if (lower.includes('read') || lower.includes('write') || lower.includes('edit') || lower.includes('glob') || lower.includes('grep')) {
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('read') ? 'reading' : lower.includes('write') ? 'writing' : lower.includes('edit') ? 'editing' : lower;
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('read') ? 'reading' : lower.includes('write') ? 'writing' : lower.includes('edit') ? 'editing' : lower;
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(toolName, args) {
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) + '...' : json;
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('\n').length;
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.0",
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": "tsc",
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
- "@types/marked": "^5.0.2",
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"