@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
@@ -1,5 +1,5 @@
1
+ import { spawn } from "node:child_process";
1
2
  import { z } from "zod";
2
- import { spawn } from "child_process";
3
3
  import { safeEnv } from "../../utils/safe-env.js";
4
4
  const inputSchema = z.object({
5
5
  command: z.string(),
@@ -8,7 +8,7 @@ const inputSchema = z.object({
8
8
  run_in_background: z.boolean().optional(),
9
9
  });
10
10
  const MAX_OUTPUT = 100_000;
11
- const DEFAULT_TIMEOUT = 120_000;
11
+ const _DEFAULT_TIMEOUT = 120_000;
12
12
  const MAX_TIMEOUT = 600_000;
13
13
  export const BashTool = {
14
14
  name: "Bash",
@@ -38,17 +38,25 @@ export const BashTool = {
38
38
  });
39
39
  let stdout = "";
40
40
  let stderr = "";
41
- proc.stdout.on("data", (chunk) => { stdout += chunk.toString(); });
42
- proc.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
43
- const timer = setTimeout(() => { proc.kill("SIGTERM"); }, timeoutMs);
41
+ proc.stdout.on("data", (chunk) => {
42
+ stdout += chunk.toString();
43
+ });
44
+ proc.stderr.on("data", (chunk) => {
45
+ stderr += chunk.toString();
46
+ });
47
+ const timer = setTimeout(() => {
48
+ proc.kill("SIGTERM");
49
+ }, timeoutMs);
44
50
  if (context.abortSignal) {
45
- context.abortSignal.addEventListener("abort", () => { proc.kill("SIGTERM"); });
51
+ context.abortSignal.addEventListener("abort", () => {
52
+ proc.kill("SIGTERM");
53
+ });
46
54
  }
47
55
  proc.on("close", (code) => {
48
56
  clearTimeout(timer);
49
- let output = stdout + (stderr ? "\n[stderr]\n" + stderr : "");
57
+ let output = stdout + (stderr ? `\n[stderr]\n${stderr}` : "");
50
58
  if (output.length > MAX_OUTPUT) {
51
- output = output.slice(0, MAX_OUTPUT) + "\n... [truncated]";
59
+ output = `${output.slice(0, MAX_OUTPUT)}\n... [truncated]`;
52
60
  }
53
61
  // Notify via output chunk when background process completes
54
62
  if (context.onOutputChunk && context.callId) {
@@ -94,9 +102,9 @@ export const BashTool = {
94
102
  }
95
103
  proc.on("close", (code) => {
96
104
  clearTimeout(timer);
97
- let output = stdout + (stderr ? "\n[stderr]\n" + stderr : "");
105
+ let output = stdout + (stderr ? `\n[stderr]\n${stderr}` : "");
98
106
  if (output.length > MAX_OUTPUT) {
99
- output = output.slice(0, MAX_OUTPUT) + "\n... [truncated]";
107
+ output = `${output.slice(0, MAX_OUTPUT)}\n... [truncated]`;
100
108
  }
101
109
  if (killed) {
102
110
  output += "\n[timed out]";
@@ -13,47 +13,65 @@ const deleteSchema = z.object({
13
13
  const listSchema = z.object({
14
14
  action: z.literal("list"),
15
15
  });
16
- const inputSchema = z.discriminatedUnion("action", [createSchema, deleteSchema, listSchema]);
16
+ const _inputSchema = z.discriminatedUnion("action", [createSchema, deleteSchema, listSchema]);
17
17
  export const CronCreateTool = {
18
18
  name: "CronCreate",
19
19
  description: "Create a scheduled recurring task that runs a prompt on an interval.",
20
20
  inputSchema: createSchema,
21
21
  riskLevel: "medium",
22
- isReadOnly() { return false; },
23
- isConcurrencySafe() { return true; },
22
+ isReadOnly() {
23
+ return false;
24
+ },
25
+ isConcurrencySafe() {
26
+ return true;
27
+ },
24
28
  async call(input) {
25
29
  const cron = createCron(input.name, input.schedule, input.prompt);
26
30
  return { output: `Created cron '${cron.name}' (${cron.id}) — schedule: ${cron.schedule}`, isError: false };
27
31
  },
28
- prompt() { return "CronCreate: Schedule a recurring task. Schedules: 'every 5m', 'every 2h', 'every 1d'."; },
32
+ prompt() {
33
+ return "CronCreate: Schedule a recurring task. Schedules: 'every 5m', 'every 2h', 'every 1d'.";
34
+ },
29
35
  };
30
36
  export const CronDeleteTool = {
31
37
  name: "CronDelete",
32
38
  description: "Delete a scheduled recurring task.",
33
39
  inputSchema: deleteSchema,
34
40
  riskLevel: "medium",
35
- isReadOnly() { return false; },
36
- isConcurrencySafe() { return true; },
41
+ isReadOnly() {
42
+ return false;
43
+ },
44
+ isConcurrencySafe() {
45
+ return true;
46
+ },
37
47
  async call(input) {
38
48
  const ok = deleteCron(input.id);
39
49
  return { output: ok ? `Deleted cron ${input.id}` : `Cron ${input.id} not found`, isError: !ok };
40
50
  },
41
- prompt() { return "CronDelete: Remove a scheduled task by ID."; },
51
+ prompt() {
52
+ return "CronDelete: Remove a scheduled task by ID.";
53
+ },
42
54
  };
43
55
  export const CronListTool = {
44
56
  name: "CronList",
45
57
  description: "List all scheduled recurring tasks.",
46
58
  inputSchema: listSchema,
47
59
  riskLevel: "low",
48
- isReadOnly() { return true; },
49
- isConcurrencySafe() { return true; },
60
+ isReadOnly() {
61
+ return true;
62
+ },
63
+ isConcurrencySafe() {
64
+ return true;
65
+ },
50
66
  async call() {
51
67
  const crons = listCrons();
52
68
  if (crons.length === 0)
53
69
  return { output: "No scheduled tasks.", isError: false };
54
- const lines = crons.map(c => `${c.id} ${c.name.padEnd(20)} ${c.schedule.padEnd(12)} ${c.enabled ? '' : ''} runs: ${c.runCount}`);
55
- return { output: `Scheduled tasks:\n${lines.join('\n')}`, isError: false };
70
+ const lines = crons.map((c) => `${c.id} ${c.name.padEnd(20)} ${c.schedule.padEnd(12)} ${c.enabled ? "" : ""} runs: ${c.runCount}`);
71
+ return { output: `Scheduled tasks:\n${lines.join("\n")}`, isError: false };
72
+ },
73
+ prompt() {
74
+ return "CronList: Show all scheduled recurring tasks.";
56
75
  },
57
- prompt() { return "CronList: Show all scheduled recurring tasks."; },
58
76
  };
59
77
  //# sourceMappingURL=index.js.map
@@ -2,7 +2,9 @@ import { z } from "zod";
2
2
  import { LspClient } from "../../lsp/client.js";
3
3
  const inputSchema = z.object({
4
4
  file_path: z.string().describe("Absolute path to the file to check"),
5
- action: z.enum(["diagnostics", "definition", "references", "hover"]).default("diagnostics")
5
+ action: z
6
+ .enum(["diagnostics", "definition", "references", "hover"])
7
+ .default("diagnostics")
6
8
  .describe("Action: diagnostics (errors/warnings), definition (go-to-def), references (find-refs), hover (type info)"),
7
9
  line: z.number().optional().describe("Line number (0-indexed) for definition/references"),
8
10
  character: z.number().optional().describe("Column number (0-indexed) for definition/references"),
@@ -10,17 +12,17 @@ const inputSchema = z.object({
10
12
  // Singleton LSP client per language server
11
13
  const lspClients = new Map();
12
14
  function getLspCommand(filePath) {
13
- if (filePath.endsWith('.ts') || filePath.endsWith('.tsx') || filePath.endsWith('.js') || filePath.endsWith('.jsx')) {
14
- return { command: 'npx', args: ['typescript-language-server', '--stdio'] };
15
+ if (filePath.endsWith(".ts") || filePath.endsWith(".tsx") || filePath.endsWith(".js") || filePath.endsWith(".jsx")) {
16
+ return { command: "npx", args: ["typescript-language-server", "--stdio"] };
15
17
  }
16
- if (filePath.endsWith('.py')) {
17
- return { command: 'pylsp', args: [] };
18
+ if (filePath.endsWith(".py")) {
19
+ return { command: "pylsp", args: [] };
18
20
  }
19
- if (filePath.endsWith('.go')) {
20
- return { command: 'gopls', args: ['serve'] };
21
+ if (filePath.endsWith(".go")) {
22
+ return { command: "gopls", args: ["serve"] };
21
23
  }
22
- if (filePath.endsWith('.rs')) {
23
- return { command: 'rust-analyzer', args: [] };
24
+ if (filePath.endsWith(".rs")) {
25
+ return { command: "rust-analyzer", args: [] };
24
26
  }
25
27
  return null;
26
28
  }
@@ -45,8 +47,12 @@ export const DiagnosticsTool = {
45
47
  description: "Get code diagnostics (errors, warnings), go-to-definition, or find-references using the language server.",
46
48
  inputSchema,
47
49
  riskLevel: "low",
48
- isReadOnly() { return true; },
49
- isConcurrencySafe() { return true; },
50
+ isReadOnly() {
51
+ return true;
52
+ },
53
+ isConcurrencySafe() {
54
+ return true;
55
+ },
50
56
  async call(input, context) {
51
57
  const client = await getClient(input.file_path, context.workingDir);
52
58
  if (!client) {
@@ -62,11 +68,11 @@ export const DiagnosticsTool = {
62
68
  if (diags.length === 0)
63
69
  return { output: "No diagnostics found.", isError: false };
64
70
  const severityMap = { 1: "Error", 2: "Warning", 3: "Info", 4: "Hint" };
65
- const lines = diags.map(d => {
71
+ const lines = diags.map((d) => {
66
72
  const sev = severityMap[d.severity ?? 1] ?? "Unknown";
67
73
  return `${sev} [${d.source ?? ""}] L${d.range.start.line + 1}:${d.range.start.character}: ${d.message}`;
68
74
  });
69
- return { output: lines.join('\n'), isError: false };
75
+ return { output: lines.join("\n"), isError: false };
70
76
  }
71
77
  if (input.action === "definition") {
72
78
  if (input.line === undefined || input.character === undefined) {
@@ -76,8 +82,8 @@ export const DiagnosticsTool = {
76
82
  const locs = await client.getDefinition(input.file_path, input.line, input.character);
77
83
  if (locs.length === 0)
78
84
  return { output: "No definition found.", isError: false };
79
- const lines = locs.map(l => `${l.uri.replace('file://', '')}:${l.range.start.line + 1}:${l.range.start.character}`);
80
- return { output: lines.join('\n'), isError: false };
85
+ const lines = locs.map((l) => `${l.uri.replace("file://", "")}:${l.range.start.line + 1}:${l.range.start.character}`);
86
+ return { output: lines.join("\n"), isError: false };
81
87
  }
82
88
  if (input.action === "references") {
83
89
  if (input.line === undefined || input.character === undefined) {
@@ -87,8 +93,8 @@ export const DiagnosticsTool = {
87
93
  const refs = await client.getReferences(input.file_path, input.line, input.character);
88
94
  if (refs.length === 0)
89
95
  return { output: "No references found.", isError: false };
90
- const lines = refs.map(r => `${r.uri.replace('file://', '')}:${r.range.start.line + 1}:${r.range.start.character}`);
91
- return { output: `${refs.length} reference(s):\n${lines.join('\n')}`, isError: false };
96
+ const lines = refs.map((r) => `${r.uri.replace("file://", "")}:${r.range.start.line + 1}:${r.range.start.character}`);
97
+ return { output: `${refs.length} reference(s):\n${lines.join("\n")}`, isError: false };
92
98
  }
93
99
  if (input.action === "hover") {
94
100
  if (input.line === undefined || input.character === undefined) {
@@ -97,15 +103,15 @@ export const DiagnosticsTool = {
97
103
  await client.openFile(input.file_path);
98
104
  // Hover uses textDocument/hover which returns MarkupContent
99
105
  try {
100
- const result = await client.send('textDocument/hover', {
101
- textDocument: { uri: `file://${input.file_path.replace(/\\/g, '/')}` },
106
+ const result = await client.send("textDocument/hover", {
107
+ textDocument: { uri: `file://${input.file_path.replace(/\\/g, "/")}` },
102
108
  position: { line: input.line, character: input.character },
103
109
  });
104
- if (!result || !result.contents)
110
+ if (!result?.contents)
105
111
  return { output: "No hover information.", isError: false };
106
- const content = typeof result.contents === 'string'
112
+ const content = typeof result.contents === "string"
107
113
  ? result.contents
108
- : result.contents.value ?? JSON.stringify(result.contents);
114
+ : (result.contents.value ?? JSON.stringify(result.contents));
109
115
  return { output: content, isError: false };
110
116
  }
111
117
  catch {
@@ -1,10 +1,90 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
1
3
  import { z } from "zod";
2
- import * as fs from "fs/promises";
3
- import * as path from "path";
4
+ /**
5
+ * Generate a memorable plan filename from three random words.
6
+ * Pattern: adjective-verb-noun (e.g., "twinkling-riding-crown")
7
+ */
8
+ function generatePlanName() {
9
+ const adjectives = [
10
+ "bright",
11
+ "calm",
12
+ "dark",
13
+ "eager",
14
+ "fast",
15
+ "gentle",
16
+ "happy",
17
+ "keen",
18
+ "light",
19
+ "merry",
20
+ "noble",
21
+ "plain",
22
+ "quiet",
23
+ "rare",
24
+ "sharp",
25
+ "tall",
26
+ "vivid",
27
+ "warm",
28
+ "young",
29
+ "bold",
30
+ "clean",
31
+ "deep",
32
+ "fair",
33
+ "grand",
34
+ ];
35
+ const verbs = [
36
+ "flying",
37
+ "riding",
38
+ "singing",
39
+ "dancing",
40
+ "running",
41
+ "walking",
42
+ "building",
43
+ "crafting",
44
+ "drawing",
45
+ "growing",
46
+ "hiding",
47
+ "jumping",
48
+ "leading",
49
+ "making",
50
+ "passing",
51
+ "rising",
52
+ "saving",
53
+ "taking",
54
+ "turning",
55
+ "watching",
56
+ ];
57
+ const nouns = [
58
+ "arrow",
59
+ "badge",
60
+ "crown",
61
+ "dream",
62
+ "flame",
63
+ "grove",
64
+ "heart",
65
+ "ivory",
66
+ "jewel",
67
+ "knot",
68
+ "latch",
69
+ "maple",
70
+ "night",
71
+ "ocean",
72
+ "pearl",
73
+ "quest",
74
+ "ridge",
75
+ "stone",
76
+ "tower",
77
+ "vault",
78
+ "whale",
79
+ "zenith",
80
+ ];
81
+ const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
82
+ return `${pick(adjectives)}-${pick(verbs)}-${pick(nouns)}`;
83
+ }
4
84
  const inputSchema = z.object({});
5
85
  export const EnterPlanModeTool = {
6
86
  name: "EnterPlanMode",
7
- description: "Enter plan mode, creating .oh/plan.md if it does not exist.",
87
+ description: "Enter plan mode, creating a unique plan file in .oh/plans/.",
8
88
  inputSchema,
9
89
  riskLevel: "low",
10
90
  isReadOnly() {
@@ -14,24 +94,23 @@ export const EnterPlanModeTool = {
14
94
  return false;
15
95
  },
16
96
  async call(_input, context) {
17
- const dir = path.join(context.workingDir, ".oh");
18
- const filePath = path.join(dir, "plan.md");
97
+ const plansDir = path.join(context.workingDir, ".oh", "plans");
98
+ const planName = generatePlanName();
99
+ const filePath = path.join(plansDir, `${planName}.md`);
19
100
  try {
20
- await fs.mkdir(dir, { recursive: true });
21
- try {
22
- await fs.access(filePath);
23
- }
24
- catch {
25
- await fs.writeFile(filePath, "# Plan\n\n", "utf-8");
26
- }
27
- return { output: "Plan mode entered.", isError: false };
101
+ await fs.mkdir(plansDir, { recursive: true });
102
+ await fs.writeFile(filePath, `# Plan\n\n<!-- Write your plan here -->\n`, "utf-8");
103
+ return {
104
+ output: `Plan mode entered. Plan file: ${filePath}\nWrite your plan to this file using the Write or Edit tool.`,
105
+ isError: false,
106
+ };
28
107
  }
29
108
  catch (err) {
30
109
  return { output: `Error entering plan mode: ${err.message}`, isError: true };
31
110
  }
32
111
  },
33
112
  prompt() {
34
- return `Enter plan mode. Creates .oh/plan.md if it does not already exist. No parameters required.`;
113
+ return `Enter plan mode. Creates a unique plan file at .oh/plans/<name>.md. Write your plan to this file, then call ExitPlanMode when done. No parameters required.`;
35
114
  },
36
115
  };
37
116
  //# sourceMappingURL=index.js.map
@@ -8,9 +8,13 @@ export const EnterWorktreeTool = {
8
8
  description: "Create an isolated git worktree for safe experimentation. Changes won't affect the main working directory.",
9
9
  inputSchema,
10
10
  riskLevel: "medium",
11
- isReadOnly() { return false; },
12
- isConcurrencySafe() { return false; },
13
- async call(input, context) {
11
+ isReadOnly() {
12
+ return false;
13
+ },
14
+ isConcurrencySafe() {
15
+ return false;
16
+ },
17
+ async call(_input, context) {
14
18
  if (!isGitRepo(context.workingDir)) {
15
19
  return { output: "Not a git repository — worktrees require git.", isError: true };
16
20
  }
@@ -1,6 +1,27 @@
1
1
  import { z } from "zod";
2
2
  import type { Tool } from "../../Tool.js";
3
- declare const inputSchema: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
3
+ declare const inputSchema: z.ZodObject<{
4
+ allowedPrompts: z.ZodOptional<z.ZodArray<z.ZodObject<{
5
+ tool: z.ZodEnum<["Bash"]>;
6
+ prompt: z.ZodString;
7
+ }, "strip", z.ZodTypeAny, {
8
+ tool: "Bash";
9
+ prompt: string;
10
+ }, {
11
+ tool: "Bash";
12
+ prompt: string;
13
+ }>, "many">>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ allowedPrompts?: {
16
+ tool: "Bash";
17
+ prompt: string;
18
+ }[] | undefined;
19
+ }, {
20
+ allowedPrompts?: {
21
+ tool: "Bash";
22
+ prompt: string;
23
+ }[] | undefined;
24
+ }>;
4
25
  export declare const ExitPlanModeTool: Tool<typeof inputSchema>;
5
26
  export {};
6
27
  //# sourceMappingURL=index.d.ts.map
@@ -1,8 +1,16 @@
1
1
  import { z } from "zod";
2
- const inputSchema = z.object({});
2
+ const inputSchema = z.object({
3
+ allowedPrompts: z
4
+ .array(z.object({
5
+ tool: z.enum(["Bash"]).describe("The tool this prompt applies to"),
6
+ prompt: z.string().describe("Semantic description of the action, e.g. 'run tests', 'install dependencies'"),
7
+ }))
8
+ .optional()
9
+ .describe("Prompt-based permissions needed to implement the plan"),
10
+ });
3
11
  export const ExitPlanModeTool = {
4
12
  name: "ExitPlanMode",
5
- description: "Exit plan mode.",
13
+ description: "Exit plan mode and signal that the plan is ready for user approval.",
6
14
  inputSchema,
7
15
  riskLevel: "low",
8
16
  isReadOnly() {
@@ -11,11 +19,18 @@ export const ExitPlanModeTool = {
11
19
  isConcurrencySafe() {
12
20
  return false;
13
21
  },
14
- async call(_input, _context) {
15
- return { output: "Plan mode exited.", isError: false };
22
+ async call(input, _context) {
23
+ const parts = ["Plan mode exited. Plan is ready for review."];
24
+ if (input.allowedPrompts?.length) {
25
+ parts.push("Requested permissions:");
26
+ for (const p of input.allowedPrompts) {
27
+ parts.push(` - ${p.tool}: ${p.prompt}`);
28
+ }
29
+ }
30
+ return { output: parts.join("\n"), isError: false };
16
31
  },
17
32
  prompt() {
18
- return `Exit plan mode. No parameters required.`;
33
+ return `Exit plan mode and signal that the plan is ready for user approval. Optionally specify allowedPrompts to pre-authorize specific actions (e.g., running tests) during plan execution.`;
19
34
  },
20
35
  };
21
36
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { removeWorktree, hasWorktreeChanges } from "../../git/index.js";
2
+ import { hasWorktreeChanges, removeWorktree } from "../../git/index.js";
3
3
  const inputSchema = z.object({
4
4
  path: z.string().describe("Path to the worktree to remove"),
5
5
  force: z.boolean().optional().describe("Force removal even with uncommitted changes"),
@@ -9,8 +9,12 @@ export const ExitWorktreeTool = {
9
9
  description: "Remove a git worktree. Warns if there are uncommitted changes unless force is true.",
10
10
  inputSchema,
11
11
  riskLevel: "medium",
12
- isReadOnly() { return false; },
13
- isConcurrencySafe() { return false; },
12
+ isReadOnly() {
13
+ return false;
14
+ },
15
+ isConcurrencySafe() {
16
+ return false;
17
+ },
14
18
  async call(input) {
15
19
  if (!input.force && hasWorktreeChanges(input.path)) {
16
20
  return {
@@ -23,7 +27,10 @@ export const ExitWorktreeTool = {
23
27
  return { output: `Worktree removed: ${input.path}`, isError: false };
24
28
  }
25
29
  catch (err) {
26
- return { output: `Failed to remove worktree: ${err instanceof Error ? err.message : String(err)}`, isError: true };
30
+ return {
31
+ output: `Failed to remove worktree: ${err instanceof Error ? err.message : String(err)}`,
32
+ isError: true,
33
+ };
27
34
  }
28
35
  },
29
36
  prompt() {
@@ -1,6 +1,6 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
1
3
  import { z } from "zod";
2
- import * as fs from "fs/promises";
3
- import * as path from "path";
4
4
  const inputSchema = z.object({
5
5
  file_path: z.string(),
6
6
  old_string: z.string(),
@@ -44,9 +44,7 @@ export const FileEditTool = {
44
44
  ? content.split(input.old_string).join(input.new_string)
45
45
  : content.replace(input.old_string, input.new_string);
46
46
  await fs.writeFile(filePath, newContent, "utf-8");
47
- const occurrences = input.replace_all
48
- ? content.split(input.old_string).length - 1
49
- : 1;
47
+ const occurrences = input.replace_all ? content.split(input.old_string).length - 1 : 1;
50
48
  return {
51
49
  output: `Edited ${filePath}: replaced ${occurrences} occurrence(s).\n--- old\n${input.old_string}\n+++ new\n${input.new_string}`,
52
50
  isError: false,
@@ -1,6 +1,6 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
1
3
  import { z } from "zod";
2
- import * as fs from "fs/promises";
3
- import * as path from "path";
4
4
  const inputSchema = z.object({
5
5
  file_path: z.string(),
6
6
  offset: z.number().optional(),
@@ -16,14 +16,14 @@ function parsePageRange(pages) {
16
16
  const trimmed = part.trim();
17
17
  if (trimmed.includes("-")) {
18
18
  const [start, end] = trimmed.split("-").map(Number);
19
- if (!isNaN(start) && !isNaN(end)) {
19
+ if (!Number.isNaN(start) && !Number.isNaN(end)) {
20
20
  for (let i = start; i <= Math.min(end, start + 19); i++)
21
21
  result.push(i);
22
22
  }
23
23
  }
24
24
  else {
25
25
  const n = Number(trimmed);
26
- if (!isNaN(n))
26
+ if (!Number.isNaN(n))
27
27
  result.push(n);
28
28
  }
29
29
  }
@@ -55,8 +55,13 @@ export const FileReadTool = {
55
55
  const buffer = await fs.readFile(filePath);
56
56
  const base64 = buffer.toString("base64");
57
57
  const mimeTypes = {
58
- ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
59
- ".gif": "image/gif", ".webp": "image/webp", ".bmp": "image/bmp", ".svg": "image/svg+xml",
58
+ ".png": "image/png",
59
+ ".jpg": "image/jpeg",
60
+ ".jpeg": "image/jpeg",
61
+ ".gif": "image/gif",
62
+ ".webp": "image/webp",
63
+ ".bmp": "image/bmp",
64
+ ".svg": "image/svg+xml",
60
65
  };
61
66
  return { output: `__IMAGE__:${mimeTypes[ext] ?? "image/png"}:${base64}`, isError: false };
62
67
  }
@@ -64,7 +69,10 @@ export const FileReadTool = {
64
69
  if (ext === ".pdf") {
65
70
  // Guard against very large PDFs without page filter
66
71
  if (stat.size > 20 * 1024 * 1024 && !input.pages) {
67
- return { output: `PDF is ${(stat.size / 1024 / 1024).toFixed(1)} MB. Provide a 'pages' parameter (e.g., "1-5") to read specific pages.`, isError: true };
72
+ return {
73
+ output: `PDF is ${(stat.size / 1024 / 1024).toFixed(1)} MB. Provide a 'pages' parameter (e.g., "1-5") to read specific pages.`,
74
+ isError: true,
75
+ };
68
76
  }
69
77
  const buffer = await fs.readFile(filePath);
70
78
  // Basic PDF text extraction — look for text between BT/ET markers or stream content
@@ -128,9 +136,7 @@ export const FileReadTool = {
128
136
  const offset = Math.max(0, (input.offset ?? 1) - 1);
129
137
  const limit = input.limit ?? DEFAULT_LIMIT;
130
138
  const lines = allLines.slice(offset, offset + limit);
131
- const numbered = lines
132
- .map((line, i) => `${offset + i + 1}\t${line}`)
133
- .join("\n");
139
+ const numbered = lines.map((line, i) => `${offset + i + 1}\t${line}`).join("\n");
134
140
  const total = allLines.length;
135
141
  const shown = lines.length;
136
142
  let result = numbered;
@@ -1,6 +1,6 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
1
3
  import { z } from "zod";
2
- import * as fs from "fs/promises";
3
- import * as path from "path";
4
4
  const inputSchema = z.object({
5
5
  file_path: z.string(),
6
6
  content: z.string(),
@@ -1,7 +1,7 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
1
3
  import { z } from "zod";
2
- import * as fs from "fs/promises";
3
- import * as path from "path";
4
- import { walkDir, matchGlob } from "../../utils/fs.js";
4
+ import { matchGlob, walkDir } from "../../utils/fs.js";
5
5
  const inputSchema = z.object({
6
6
  pattern: z.string(),
7
7
  path: z.string().optional(),
@@ -35,9 +35,7 @@ export const GlobTool = {
35
35
  }
36
36
  matches.sort();
37
37
  return {
38
- output: matches.length
39
- ? matches.join("\n")
40
- : "No files matched the pattern.",
38
+ output: matches.length ? matches.join("\n") : "No files matched the pattern.",
41
39
  isError: false,
42
40
  };
43
41
  }
@@ -49,9 +47,7 @@ export const GlobTool = {
49
47
  .slice(0, MAX_RESULTS)
50
48
  .sort();
51
49
  return {
52
- output: matched.length
53
- ? matched.join("\n")
54
- : "No files matched the pattern.",
50
+ output: matched.length ? matched.join("\n") : "No files matched the pattern.",
55
51
  isError: false,
56
52
  };
57
53
  }
@@ -20,8 +20,8 @@ declare const inputSchema: z.ZodObject<{
20
20
  path?: string | undefined;
21
21
  type?: string | undefined;
22
22
  glob?: string | undefined;
23
- context?: number | undefined;
24
23
  offset?: number | undefined;
24
+ context?: number | undefined;
25
25
  output_mode?: "content" | "files_with_matches" | "count" | undefined;
26
26
  head_limit?: number | undefined;
27
27
  multiline?: boolean | undefined;
@@ -35,8 +35,8 @@ declare const inputSchema: z.ZodObject<{
35
35
  path?: string | undefined;
36
36
  type?: string | undefined;
37
37
  glob?: string | undefined;
38
- context?: number | undefined;
39
38
  offset?: number | undefined;
39
+ context?: number | undefined;
40
40
  output_mode?: "content" | "files_with_matches" | "count" | undefined;
41
41
  head_limit?: number | undefined;
42
42
  multiline?: boolean | undefined;