@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,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
2
  import { Box, Text } from "ink";
4
- import ToolCallDisplay from "./ToolCallDisplay.js";
5
- import Markdown from "./Markdown.js";
3
+ import React from "react";
6
4
  import { useTheme } from "../utils/theme.js";
5
+ import Markdown from "./Markdown.js";
6
+ import ToolCallDisplay from "./ToolCallDisplay.js";
7
7
  export default function Messages({ messages, toolCalls }) {
8
8
  const theme = useTheme();
9
9
  return (_jsx(Box, { flexDirection: "column", children: messages.map((msg, i) => {
10
10
  const showDivider = msg.role === "user" && i > 0;
11
- return (_jsxs(React.Fragment, { children: [showDivider && (_jsx(Text, { color: theme.dim, children: "─".repeat(60) })), _jsx(MessageRow, { message: msg, toolCalls: toolCalls, theme: theme })] }, msg.uuid));
11
+ return (_jsxs(React.Fragment, { children: [showDivider && _jsx(Text, { color: theme.dim, children: "─".repeat(60) }), _jsx(MessageRow, { message: msg, toolCalls: toolCalls, theme: theme })] }, msg.uuid));
12
12
  }) }));
13
13
  }
14
14
  function MessageRow({ message, toolCalls, theme, }) {
@@ -4,6 +4,6 @@ type Props = {
4
4
  riskLevel: string;
5
5
  onResolve: (allowed: boolean) => void;
6
6
  };
7
- export default function PermissionPrompt({ toolName, description, riskLevel, onResolve, }: Props): import("react/jsx-runtime").JSX.Element;
7
+ export default function PermissionPrompt({ toolName, description, riskLevel, onResolve }: Props): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
9
9
  //# sourceMappingURL=PermissionPrompt.d.ts.map
@@ -1,16 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { execSync } from "node:child_process";
3
+ import { unlinkSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
3
6
  import { Box, Text, useInput } from "ink";
7
+ import { useState } from "react";
8
+ import { extractDiffInfo } from "../renderer/diff.js";
4
9
  import { useTheme } from "../utils/theme.js";
5
10
  import { summarizeToolArgs } from "../utils/tool-summary.js";
6
- import { extractDiffInfo } from "../renderer/diff.js";
7
11
  import DiffView from "./DiffView.js";
8
- import { writeFileSync, unlinkSync } from "node:fs";
9
- import { execSync } from "node:child_process";
10
- import { join } from "node:path";
11
- import { tmpdir } from "node:os";
12
12
  // extractFileInfo moved to shared renderer/diff.ts as extractDiffInfo
13
- export default function PermissionPrompt({ toolName, description, riskLevel, onResolve, }) {
13
+ export default function PermissionPrompt({ toolName, description, riskLevel, onResolve }) {
14
14
  const theme = useTheme();
15
15
  const [showDiff, setShowDiff] = useState(false);
16
16
  const fileInfo = extractDiffInfo(toolName, description);
@@ -22,27 +22,25 @@ export default function PermissionPrompt({ toolName, description, riskLevel, onR
22
22
  if (key === "n")
23
23
  onResolve(false);
24
24
  if (key === "d" && hasDiff)
25
- setShowDiff(prev => !prev);
25
+ setShowDiff((prev) => !prev);
26
26
  if (key === "e" && hasDiff && fileInfo?.newContent) {
27
27
  // Open new content in $EDITOR
28
- const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
28
+ const editor = process.env.EDITOR || process.env.VISUAL || "vi";
29
29
  const tmpFile = join(tmpdir(), `oh-edit-${Date.now()}.tmp`);
30
30
  try {
31
31
  writeFileSync(tmpFile, fileInfo.newContent);
32
- execSync(`${editor} "${tmpFile}"`, { stdio: 'inherit' });
32
+ execSync(`${editor} "${tmpFile}"`, { stdio: "inherit" });
33
33
  // User may have modified — we can't easily integrate the edit back into the tool call
34
34
  // Just show a message
35
35
  unlinkSync(tmpFile);
36
36
  }
37
- catch { /* ignore */ }
37
+ catch {
38
+ /* ignore */
39
+ }
38
40
  }
39
41
  });
40
- const borderColor = riskLevel === "high"
41
- ? theme.error
42
- : riskLevel === "medium"
43
- ? theme.warning
44
- : theme.success;
42
+ const borderColor = riskLevel === "high" ? theme.error : riskLevel === "medium" ? theme.warning : theme.success;
45
43
  const suggestion = summarizeToolArgs(toolName, description);
46
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: borderColor, paddingX: 2, paddingY: 0, marginY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: borderColor, bold: true, children: "⚠ " }), _jsx(Text, { bold: true, children: toolName }), _jsxs(Text, { color: theme.dim, children: [" ", riskLevel, " risk"] })] }), suggestion && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: theme.dim, children: suggestion }) })), !showDiff && (_jsx(Box, { marginLeft: 2, marginY: 0, children: _jsx(Text, { children: description.slice(0, 300) }) })), showDiff && hasDiff && (_jsx(Box, { marginLeft: 2, marginY: 0, children: _jsx(DiffView, { oldContent: fileInfo.oldContent, newContent: fileInfo.newContent, filePath: fileInfo.filePath ?? '' }) })), _jsx(Box, { marginTop: 0, children: _jsxs(Text, { children: ["[", _jsx(Text, { color: theme.success, bold: true, children: "Y" }), "]es", " ", "[", _jsx(Text, { color: theme.error, bold: true, children: "N" }), "]o", hasDiff && (_jsxs(_Fragment, { children: [" ", "[", _jsx(Text, { color: "cyan", bold: true, children: "D" }), "]iff", " ", "[", _jsx(Text, { color: "yellow", bold: true, children: "E" }), "]dit"] }))] }) })] }));
44
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: borderColor, paddingX: 2, paddingY: 0, marginY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: borderColor, bold: true, children: "⚠ " }), _jsx(Text, { bold: true, children: toolName }), _jsxs(Text, { color: theme.dim, children: [" ", riskLevel, " risk"] })] }), suggestion && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: theme.dim, children: suggestion }) })), !showDiff && (_jsx(Box, { marginLeft: 2, marginY: 0, children: _jsx(Text, { children: description.slice(0, 300) }) })), showDiff && hasDiff && (_jsx(Box, { marginLeft: 2, marginY: 0, children: _jsx(DiffView, { oldContent: fileInfo.oldContent, newContent: fileInfo.newContent, filePath: fileInfo.filePath ?? "" }) })), _jsx(Box, { marginTop: 0, children: _jsxs(Text, { children: ["[", _jsx(Text, { color: theme.success, bold: true, children: "Y" }), "]es [", _jsx(Text, { color: theme.error, bold: true, children: "N" }), "]o", hasDiff && (_jsxs(_Fragment, { children: [" ", "[", _jsx(Text, { color: "cyan", bold: true, children: "D" }), "]iff [", _jsx(Text, { color: "yellow", bold: true, children: "E" }), "]dit"] }))] }) })] }));
47
45
  }
48
46
  //# sourceMappingURL=PermissionPrompt.js.map
@@ -1,6 +1,6 @@
1
- import type { Message } from "../types/message.js";
2
1
  import type { Provider } from "../providers/base.js";
3
2
  import type { Tools } from "../Tool.js";
3
+ import type { Message } from "../types/message.js";
4
4
  import type { PermissionMode } from "../types/permissions.js";
5
5
  type REPLProps = {
6
6
  provider: Provider;
@@ -1,44 +1,46 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useCallback, useRef, useEffect } from "react";
3
- import { Box, Text, Static, useApp, useInput } from "ink";
4
- import { createAssistantMessage, createUserMessage, createMessage, createInfoMessage } from "../types/message.js";
5
- import { query } from "../query/index.js";
6
2
  import { homedir } from "node:os";
7
3
  import { join } from "node:path";
8
- import { createSession, saveSession, loadSession } from "../harness/session.js";
9
- import { CostTracker, estimateCost } from "../harness/cost.js";
4
+ import { Box, Static, Text, useApp, useInput } from "ink";
5
+ import TextInputComponent from "ink-text-input";
6
+ import { useCallback, useEffect, useRef, useState } from "react";
10
7
  import { processSlashCommand } from "../commands/index.js";
11
- import { estimateMessageTokens, getContextWarning } from "../harness/context-warning.js";
8
+ import { cybergotchiEvents } from "../cybergotchi/events.js";
12
9
  import { autoCommitAIEdits, isGitRepo } from "../git/index.js";
13
- import Spinner from "./Spinner.js";
14
- import TextInput from "./TextInput.js";
15
- import TextInputComponent from "ink-text-input";
16
- import PermissionPrompt from "./PermissionPrompt.js";
10
+ import { estimateMessageTokens, getContextWarning } from "../harness/context-warning.js";
11
+ import { CostTracker, estimateCost } from "../harness/cost.js";
12
+ import { createSession, loadSession, saveSession } from "../harness/session.js";
13
+ import { query } from "../query/index.js";
14
+ import { createAssistantMessage, createInfoMessage, createMessage, createUserMessage } from "../types/message.js";
17
15
  import CybergotchiPanelConnected from "./CybergotchiPanelConnected.js";
18
16
  import CybergotchiSetup from "./CybergotchiSetup.js";
19
- import { cybergotchiEvents } from "../cybergotchi/events.js";
17
+ import PermissionPrompt from "./PermissionPrompt.js";
18
+ import Spinner from "./Spinner.js";
19
+ import TextInput from "./TextInput.js";
20
20
  /** Minimum terminal width to show the companion */
21
21
  const MIN_WIDTH_FOR_COMPANION = 40;
22
22
  function getTerminalWidth() {
23
23
  return process.stdout.columns ?? 80;
24
24
  }
25
25
  import { loadCompanionConfig, saveCompanionConfig } from "../cybergotchi/config.js";
26
+ import { createKeybindingMatcher } from "../harness/keybindings.js";
26
27
  import { detectMemories, saveMemory } from "../harness/memory.js";
27
28
  import { resolveMcpMention } from "../mcp/loader.js";
28
- import { createKeybindingMatcher } from "../harness/keybindings.js";
29
29
  export default function REPL({ provider, tools, permissionMode, systemPrompt, model, initialMessages, resumeSessionId, }) {
30
30
  const { exit } = useApp();
31
31
  // Session and cost tracking
32
32
  const sessionRef = useRef(resumeSessionId
33
- ? (() => { try {
34
- return loadSession(resumeSessionId);
35
- }
36
- catch {
37
- return createSession(provider.name, model ?? "");
38
- } })()
33
+ ? (() => {
34
+ try {
35
+ return loadSession(resumeSessionId);
36
+ }
37
+ catch {
38
+ return createSession(provider.name, model ?? "");
39
+ }
40
+ })()
39
41
  : createSession(provider.name, model ?? ""));
40
42
  const costRef = useRef(new CostTracker());
41
- const [totalCost, setTotalCost] = useState(0);
43
+ const [_totalCost, setTotalCost] = useState(0);
42
44
  const [sessionId] = useState(sessionRef.current.id);
43
45
  const [messages, setMessages] = useState(resumeSessionId ? sessionRef.current.messages : (initialMessages ?? []));
44
46
  const [loading, setLoading] = useState(false);
@@ -73,7 +75,9 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
73
75
  try {
74
76
  saveSession(sessionRef.current);
75
77
  }
76
- catch { /* ignore */ }
78
+ catch {
79
+ /* ignore */
80
+ }
77
81
  };
78
82
  }, [messages]);
79
83
  // Long-wait detection: emit cybergotchi event after 30s of loading
@@ -81,7 +85,7 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
81
85
  useEffect(() => {
82
86
  if (loading) {
83
87
  loadingTimerRef.current = setTimeout(() => {
84
- cybergotchiEvents.emit('cybergotchi', { type: 'longWait' });
88
+ cybergotchiEvents.emit("cybergotchi", { type: "longWait" });
85
89
  }, 30_000);
86
90
  }
87
91
  else {
@@ -114,7 +118,7 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
114
118
  });
115
119
  // Queue prompt submissions — useEffect picks them up for async processing
116
120
  const pendingPromptRef = useRef(null);
117
- const [submitCount, setSubmitCount] = useState(0);
121
+ const [_submitCount, setSubmitCount] = useState(0);
118
122
  const messagesRef = useRef(messages);
119
123
  messagesRef.current = messages;
120
124
  useEffect(() => {
@@ -146,10 +150,14 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
146
150
  const askUserQuestion = (question, options) => {
147
151
  return new Promise((resolve) => {
148
152
  setQuestionAnswer("");
149
- setPendingQuestion({ question, options, resolve: (answer) => {
153
+ setPendingQuestion({
154
+ question,
155
+ options,
156
+ resolve: (answer) => {
150
157
  setPendingQuestion(null);
151
158
  resolve(answer);
152
- } });
159
+ },
160
+ });
153
161
  });
154
162
  };
155
163
  const config = {
@@ -165,7 +173,7 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
165
173
  // Resolve @mentions to MCP resource content
166
174
  let resolvedPrompt = prompt;
167
175
  const mentionPattern = /@(\w[\w.-]*)/g;
168
- const mentions = [...prompt.matchAll(mentionPattern)].map(m => m[1]);
176
+ const mentions = [...prompt.matchAll(mentionPattern)].map((m) => m[1]);
169
177
  const companionName = cybergotchiConfigRef.current?.soul?.name?.toLowerCase();
170
178
  for (const mention of mentions) {
171
179
  if (companionName && mention.toLowerCase() === companionName)
@@ -176,7 +184,9 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
176
184
  resolvedPrompt += `\n\n[Resource @${mention}]:\n${content.slice(0, 5000)}`;
177
185
  }
178
186
  }
179
- catch { /* ignore */ }
187
+ catch {
188
+ /* ignore */
189
+ }
180
190
  }
181
191
  let accumulated = "";
182
192
  try {
@@ -188,23 +198,24 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
188
198
  case "thinking_delta":
189
199
  setThinkingText((prev) => prev + event.content);
190
200
  break;
191
- case "text_delta":
201
+ case "text_delta": {
192
202
  accumulated += event.content;
193
203
  // Move completed lines to Static (messages array) — keep only the current partial line in live area
194
- const deltaLines = accumulated.split('\n');
204
+ const deltaLines = accumulated.split("\n");
195
205
  if (deltaLines.length > 1) {
196
- const completedText = deltaLines.slice(0, -1).join('\n');
206
+ const completedText = deltaLines.slice(0, -1).join("\n");
197
207
  setMessages((prev) => {
198
208
  const last = prev[prev.length - 1];
199
209
  if (last?.meta?.isStreaming) {
200
- return [...prev.slice(0, -1), { ...last, content: last.content + completedText + '\n' }];
210
+ return [...prev.slice(0, -1), { ...last, content: `${last.content + completedText}\n` }];
201
211
  }
202
- return [...prev, createMessage('assistant', completedText + '\n', { meta: { isStreaming: true } })];
212
+ return [...prev, createMessage("assistant", `${completedText}\n`, { meta: { isStreaming: true } })];
203
213
  });
204
214
  accumulated = deltaLines[deltaLines.length - 1];
205
215
  }
206
216
  setStreamingText(accumulated);
207
217
  break;
218
+ }
208
219
  case "tool_call_start":
209
220
  setToolCalls((prev) => {
210
221
  const next = new Map(prev);
@@ -235,16 +246,16 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
235
246
  const next = new Map(prev);
236
247
  const existing = next.get(event.callId);
237
248
  if (existing) {
238
- const lines = (existing.liveOutput ?? []);
249
+ const lines = existing.liveOutput ?? [];
239
250
  // Split chunk by newlines and append
240
251
  const chunks = event.chunk.split("\n");
241
252
  const merged = [...lines];
242
253
  if (merged.length > 0 && !event.chunk.startsWith("\n")) {
243
254
  merged[merged.length - 1] = (merged[merged.length - 1] ?? "") + chunks[0];
244
- merged.push(...chunks.slice(1).filter(c => c !== ""));
255
+ merged.push(...chunks.slice(1).filter((c) => c !== ""));
245
256
  }
246
257
  else {
247
- merged.push(...chunks.filter(c => c !== ""));
258
+ merged.push(...chunks.filter((c) => c !== ""));
248
259
  }
249
260
  next.set(event.callId, { ...existing, liveOutput: merged });
250
261
  }
@@ -264,8 +275,8 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
264
275
  return next;
265
276
  });
266
277
  // Emit cybergotchi event
267
- cybergotchiEvents.emit('cybergotchi', {
268
- type: event.isError ? 'toolError' : 'toolSuccess',
278
+ cybergotchiEvents.emit("cybergotchi", {
279
+ type: event.isError ? "toolError" : "toolSuccess",
269
280
  toolName,
270
281
  });
271
282
  // Git auto-commit for write tools
@@ -278,7 +289,7 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
278
289
  const hash = autoCommitAIEdits(toolName, files, process.cwd());
279
290
  if (hash) {
280
291
  setMessages((prev) => [...prev, createInfoMessage(`git: committed ${hash}`)]);
281
- cybergotchiEvents.emit('cybergotchi', { type: 'commit' });
292
+ cybergotchiEvents.emit("cybergotchi", { type: "commit" });
282
293
  }
283
294
  }
284
295
  }
@@ -322,7 +333,9 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
322
333
  try {
323
334
  saveSession(sessionRef.current);
324
335
  }
325
- catch { /* ignore */ }
336
+ catch {
337
+ /* ignore */
338
+ }
326
339
  break;
327
340
  }
328
341
  }
@@ -343,17 +356,19 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
343
356
  const msgCount = messagesRef.current.length;
344
357
  if (msgCount > 0 && msgCount % 10 === 0) {
345
358
  detectMemories(provider, messagesRef.current.slice(-10), model)
346
- .then(memories => {
359
+ .then((memories) => {
347
360
  for (const m of memories) {
348
361
  saveMemory(m.name, m.type, m.description, m.content);
349
362
  }
350
363
  })
351
- .catch(() => { });
364
+ .catch(() => {
365
+ /* ignore memory detection errors */
366
+ });
352
367
  }
353
368
  }
354
369
  };
355
370
  run();
356
- }, [submitCount, loading, provider, tools, systemPrompt, permissionMode]);
371
+ }, [loading, provider, tools, systemPrompt, permissionMode, currentModel, model]);
357
372
  const handleSubmit = useCallback((input) => {
358
373
  const trimmed = input.trim();
359
374
  if (trimmed === "exit" || trimmed === "quit" || trimmed === "/exit" || trimmed === "/quit") {
@@ -367,14 +382,14 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
367
382
  const name = gotchiCfg.soul.name.toLowerCase();
368
383
  const lower = trimmed.toLowerCase();
369
384
  if (lower.startsWith(`@${name}`) || lower.startsWith(`${name},`) || lower.startsWith(`${name} `)) {
370
- cybergotchiEvents.emit('cybergotchi', { type: 'userAddressed', text: trimmed });
385
+ cybergotchiEvents.emit("cybergotchi", { type: "userAddressed", text: trimmed });
371
386
  return;
372
387
  }
373
388
  }
374
389
  }
375
390
  // Handle /vim toggle directly
376
391
  if (trimmed === "/vim") {
377
- setVimMode(v => !v);
392
+ setVimMode((v) => !v);
378
393
  setMessages((prev) => [...prev, createInfoMessage(vimMode ? "Vim mode OFF" : "Vim mode ON")]);
379
394
  return;
380
395
  }
@@ -403,7 +418,9 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
403
418
  sessionRef.current = sess;
404
419
  setMessages(sess.messages);
405
420
  }
406
- catch { /* already shown error in output */ }
421
+ catch {
422
+ /* already shown error in output */
423
+ }
407
424
  }
408
425
  if (result.clearMessages) {
409
426
  setMessages([]);
@@ -435,7 +452,7 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
435
452
  setMessages((prev) => [...prev, userMsg]);
436
453
  pendingPromptRef.current = input;
437
454
  setSubmitCount((c) => c + 1);
438
- }, [exit, currentModel, permissionMode, sessionId]);
455
+ }, [exit, currentModel, permissionMode, sessionId, vimMode, provider.name]);
439
456
  // Process pending keybinding actions
440
457
  useEffect(() => {
441
458
  const action = pendingKeybindAction.current;
@@ -446,7 +463,13 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
446
463
  });
447
464
  // Show cybergotchi setup if needed (first run or /cybergotchi reset)
448
465
  if (cybergotchiConfigRef.current === null || showCybergotchiSetup) {
449
- return (_jsx(CybergotchiSetup, { onComplete: () => { cybergotchiConfigRef.current = loadCompanionConfig(); setShowCybergotchiSetup(false); }, onSkip: () => { cybergotchiConfigRef.current = loadCompanionConfig(); setShowCybergotchiSetup(false); } }));
466
+ return (_jsx(CybergotchiSetup, { onComplete: () => {
467
+ cybergotchiConfigRef.current = loadCompanionConfig();
468
+ setShowCybergotchiSetup(false);
469
+ }, onSkip: () => {
470
+ cybergotchiConfigRef.current = loadCompanionConfig();
471
+ setShowCybergotchiSetup(false);
472
+ } }));
450
473
  }
451
474
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: messages, children: (msg, i) => {
452
475
  const showDivider = msg.role === "user" && i > 0;
@@ -460,8 +483,10 @@ export default function REPL({ provider, tools, permissionMode, systemPrompt, mo
460
483
  return (_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [" ", msg.content] }) }, msg.uuid));
461
484
  }
462
485
  return _jsx(Box, {}, msg.uuid);
463
- } }), _jsxs(Box, { flexDirection: "column", children: [thinkingText && (_jsx(Box, { marginY: 0, children: _jsxs(Text, { dimColor: true, children: ["💭 ", thinkingText.split('\n').slice(-3).join('\n')] }) })), loading && streamingText && (_jsxs(Box, { marginY: 0, children: [_jsx(Text, { color: "magenta", bold: true, children: "◆ " }), _jsx(Text, { children: streamingText })] })), loading && !streamingText && _jsx(Spinner, { model: currentModel, tokens: costRef.current.totalOutputTokens }), error && (_jsx(Box, { marginY: 1, borderStyle: "round", borderColor: "red", paddingX: 1, children: _jsxs(Text, { color: "red", children: ["✗ ", error] }) })), pendingPermission && (_jsx(PermissionPrompt, { toolName: pendingPermission.toolName, description: pendingPermission.description, riskLevel: pendingPermission.riskLevel, onResolve: pendingPermission.resolve })), pendingQuestion && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsxs(Text, { color: "yellow", children: ["\u2753 ", pendingQuestion.question] }), pendingQuestion.options && pendingQuestion.options.length > 0 && (_jsx(Box, { flexDirection: "column", children: pendingQuestion.options.map((o, i) => (_jsxs(Text, { dimColor: true, children: [" ", i + 1, ". ", o] }, i))) })), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: '' }), _jsx(TextInputComponent, { value: questionAnswer, onChange: setQuestionAnswer, onSubmit: (val) => { if (val.trim())
464
- pendingQuestion.resolve(val.trim()); }, focus: true })] })] })), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: _jsx(TextInput, { onSubmit: handleSubmit, disabled: loading || !!pendingQuestion, vimMode: vimMode }) }), getTerminalWidth() >= MIN_WIDTH_FOR_COMPANION && (_jsx(Box, { flexShrink: 0, width: 16, children: _jsx(CybergotchiPanelConnected, { paused: loading }) }))] }), _jsxs(Text, { dimColor: true, children: ["exit to quit", loading ? " | Ctrl+C to interrupt" : "", cybergotchiConfigRef.current?.soul?.name ? ` | @${cybergotchiConfigRef.current.soul.name} to chat` : ""] }), (() => {
486
+ } }), _jsxs(Box, { flexDirection: "column", children: [thinkingText && (_jsx(Box, { marginY: 0, children: _jsxs(Text, { dimColor: true, children: ["💭 ", thinkingText.split("\n").slice(-3).join("\n")] }) })), loading && streamingText && (_jsxs(Box, { marginY: 0, children: [_jsx(Text, { color: "magenta", bold: true, children: "◆ " }), _jsx(Text, { children: streamingText })] })), loading && !streamingText && _jsx(Spinner, { model: currentModel, tokens: costRef.current.totalOutputTokens }), error && (_jsx(Box, { marginY: 1, borderStyle: "round", borderColor: "red", paddingX: 1, children: _jsxs(Text, { color: "red", children: ["✗ ", error] }) })), pendingPermission && (_jsx(PermissionPrompt, { toolName: pendingPermission.toolName, description: pendingPermission.description, riskLevel: pendingPermission.riskLevel, onResolve: pendingPermission.resolve })), pendingQuestion && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [_jsxs(Text, { color: "yellow", children: ["\u2753 ", pendingQuestion.question] }), pendingQuestion.options && pendingQuestion.options.length > 0 && (_jsx(Box, { flexDirection: "column", children: pendingQuestion.options.map((o, i) => (_jsxs(Text, { dimColor: true, children: [" ", i + 1, ". ", o] }, i))) })), _jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "" }), _jsx(TextInputComponent, { value: questionAnswer, onChange: setQuestionAnswer, onSubmit: (val) => {
487
+ if (val.trim())
488
+ pendingQuestion.resolve(val.trim());
489
+ }, focus: true })] })] })), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, children: _jsx(TextInput, { onSubmit: handleSubmit, disabled: loading || !!pendingQuestion, vimMode: vimMode }) }), getTerminalWidth() >= MIN_WIDTH_FOR_COMPANION && (_jsx(Box, { flexShrink: 0, width: 16, children: _jsx(CybergotchiPanelConnected, { paused: loading }) }))] }), _jsxs(Text, { dimColor: true, children: ["exit to quit", loading ? " | Ctrl+C to interrupt" : "", cybergotchiConfigRef.current?.soul?.name ? ` | @${cybergotchiConfigRef.current.soul.name} to chat` : ""] }), (() => {
465
490
  const warning = getContextWarning(estimateMessageTokens(messages), currentModel);
466
491
  if (!warning)
467
492
  return null;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from "react";
3
2
  import { Box, Text } from "ink";
4
- import { useTheme } from "../utils/theme.js";
3
+ import { useEffect, useState } from "react";
5
4
  import { formatTokenCount } from "../utils/format.js";
5
+ import { useTheme } from "../utils/theme.js";
6
6
  export default function Spinner({ model, tokens }) {
7
7
  const theme = useTheme();
8
8
  const [elapsed, setElapsed] = useState(0);
@@ -1,22 +1,24 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useState, useCallback } from "react";
3
2
  import { Box, Text, useInput } from "ink";
4
3
  import InkTextInput from "ink-text-input";
5
- import { useTheme } from "../utils/theme.js";
4
+ import { useCallback, useState } from "react";
6
5
  import { getCommandNames } from "../commands/index.js";
6
+ import { useTheme } from "../utils/theme.js";
7
7
  export default function TextInput({ onSubmit, disabled, vimMode = false }) {
8
8
  const theme = useTheme();
9
9
  const [value, setValue] = useState("");
10
10
  const [history, setHistory] = useState([]);
11
11
  const [historyIndex, setHistoryIndex] = useState(-1);
12
- const [vim, setVim] = useState(vimMode ? 'normal' : 'insert');
12
+ const [vim, setVim] = useState(vimMode ? "normal" : "insert");
13
13
  const [autocomplete, setAutocomplete] = useState([]);
14
14
  const [acIndex, setAcIndex] = useState(-1);
15
15
  // Autocomplete for slash commands
16
16
  const updateAutocomplete = useCallback((val) => {
17
- if (val.startsWith('/') && val.length > 1 && !val.includes(' ')) {
17
+ if (val.startsWith("/") && val.length > 1 && !val.includes(" ")) {
18
18
  const prefix = val.slice(1).toLowerCase();
19
- const matches = getCommandNames().filter(n => n.startsWith(prefix)).slice(0, 5);
19
+ const matches = getCommandNames()
20
+ .filter((n) => n.startsWith(prefix))
21
+ .slice(0, 5);
20
22
  setAutocomplete(matches);
21
23
  setAcIndex(-1);
22
24
  }
@@ -27,41 +29,41 @@ export default function TextInput({ onSubmit, disabled, vimMode = false }) {
27
29
  }, []);
28
30
  useInput((input, key) => {
29
31
  // Vim mode handling
30
- if (vimMode && vim === 'normal') {
31
- if (input === 'i') {
32
- setVim('insert');
32
+ if (vimMode && vim === "normal") {
33
+ if (input === "i") {
34
+ setVim("insert");
33
35
  return;
34
36
  }
35
- if (input === 'a') {
36
- setVim('insert');
37
+ if (input === "a") {
38
+ setVim("insert");
37
39
  return;
38
40
  }
39
- if (input === 'A') {
40
- setVim('insert');
41
+ if (input === "A") {
42
+ setVim("insert");
41
43
  return;
42
44
  }
43
- if (input === '0') {
45
+ if (input === "0") {
44
46
  return;
45
47
  } // cursor to start — not easily doable with InkTextInput
46
- if (input === '$') {
48
+ if (input === "$") {
47
49
  return;
48
50
  } // cursor to end
49
- if (input === 'x') {
50
- setValue(v => v.slice(0, -1));
51
+ if (input === "x") {
52
+ setValue((v) => v.slice(0, -1));
51
53
  return;
52
54
  }
53
- if (input === 'D' || input === 'C') {
54
- setValue('');
55
- if (input === 'C')
56
- setVim('insert');
55
+ if (input === "D" || input === "C") {
56
+ setValue("");
57
+ if (input === "C")
58
+ setVim("insert");
57
59
  return;
58
60
  }
59
- if (input === 'u') {
60
- setValue('');
61
+ if (input === "u") {
62
+ setValue("");
61
63
  return;
62
64
  } // undo = clear
63
65
  // History navigation in normal mode
64
- if (key.upArrow || input === 'k') {
66
+ if (key.upArrow || input === "k") {
65
67
  if (history.length > 0) {
66
68
  const next = Math.min(historyIndex + 1, history.length - 1);
67
69
  setHistoryIndex(next);
@@ -69,7 +71,7 @@ export default function TextInput({ onSubmit, disabled, vimMode = false }) {
69
71
  }
70
72
  return;
71
73
  }
72
- if (key.downArrow || input === 'j') {
74
+ if (key.downArrow || input === "j") {
73
75
  if (historyIndex <= 0) {
74
76
  setHistoryIndex(-1);
75
77
  setValue("");
@@ -86,7 +88,7 @@ export default function TextInput({ onSubmit, disabled, vimMode = false }) {
86
88
  // Escape → normal mode (vim) or clear autocomplete
87
89
  if (key.escape) {
88
90
  if (vimMode) {
89
- setVim('normal');
91
+ setVim("normal");
90
92
  return;
91
93
  }
92
94
  setAutocomplete([]);
@@ -134,11 +136,15 @@ export default function TextInput({ onSubmit, disabled, vimMode = false }) {
134
136
  setAutocomplete([]);
135
137
  setAcIndex(-1);
136
138
  if (vimMode)
137
- setVim('normal');
139
+ setVim("normal");
138
140
  onSubmit(submitted);
139
141
  }, [onSubmit, disabled, vimMode]);
140
- const placeholder = disabled ? "Waiting..." : vimMode && vim === 'normal' ? "-- NORMAL -- (i to insert)" : "Ask anything...";
141
- const modeIndicator = vimMode ? (vim === 'normal' ? '[N]' : '[I]') : null;
142
- return (_jsxs(Box, { flexDirection: "column", children: [autocomplete.length > 0 && (_jsx(Box, { flexDirection: "row", gap: 1, children: autocomplete.map((cmd, i) => (_jsxs(Text, { color: i === acIndex ? 'cyan' : undefined, dimColor: i !== acIndex, children: ["/", cmd] }, cmd))) })), _jsxs(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: theme.border, paddingX: 0, children: [modeIndicator && (_jsxs(Text, { color: vim === 'normal' ? 'blue' : 'green', bold: true, children: [modeIndicator, " "] })), _jsx(Text, { color: theme.user, bold: true, children: "❯ " }), _jsx(InkTextInput, { value: value, onChange: handleChange, onSubmit: handleSubmit, placeholder: placeholder })] })] }));
142
+ const placeholder = disabled
143
+ ? "Waiting..."
144
+ : vimMode && vim === "normal"
145
+ ? "-- NORMAL -- (i to insert)"
146
+ : "Ask anything...";
147
+ const modeIndicator = vimMode ? (vim === "normal" ? "[N]" : "[I]") : null;
148
+ return (_jsxs(Box, { flexDirection: "column", children: [autocomplete.length > 0 && (_jsx(Box, { flexDirection: "row", gap: 1, children: autocomplete.map((cmd, i) => (_jsxs(Text, { color: i === acIndex ? "cyan" : undefined, dimColor: i !== acIndex, children: ["/", cmd] }, cmd))) })), _jsxs(Box, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: theme.border, paddingX: 0, children: [modeIndicator && (_jsxs(Text, { color: vim === "normal" ? "blue" : "green", bold: true, children: [modeIndicator, " "] })), _jsx(Text, { color: theme.user, bold: true, children: "❯ " }), _jsx(InkTextInput, { value: value, onChange: handleChange, onSubmit: handleSubmit, placeholder: placeholder })] })] }));
143
149
  }
144
150
  //# sourceMappingURL=TextInput.js.map
@@ -5,16 +5,14 @@ const MAX_LIVE_LINES = 10;
5
5
  export default function ToolCallDisplay({ toolCall }) {
6
6
  const { toolName, status, output, args, liveOutput } = toolCall;
7
7
  const liveLines = liveOutput ?? [];
8
- const overflow = liveLines.length > MAX_LIVE_LINES
9
- ? liveLines.length - MAX_LIVE_LINES
10
- : 0;
8
+ const overflow = liveLines.length > MAX_LIVE_LINES ? liveLines.length - MAX_LIVE_LINES : 0;
11
9
  const visibleLive = overflow > 0 ? liveLines.slice(-MAX_LIVE_LINES) : liveLines;
12
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginY: 0, children: [_jsxs(Box, { children: [status === "running" ? (_jsxs(Text, { color: "yellow", children: [_jsx(InkSpinner, { type: "dots" }), " "] })) : status === "error" ? (_jsx(Text, { color: "red", children: "✗ " })) : (_jsx(Text, { color: "green", children: "✓ " })), _jsx(Text, { color: "yellow", bold: true, children: toolName }), status === "running" && args && (_jsxs(Text, { dimColor: true, children: [" ", args.slice(0, 60), args.length > 60 ? "..." : ""] }))] }), status === "running" && liveLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [overflow > 0 && (_jsx(Text, { dimColor: true, children: `... (${overflow} earlier lines)` })), visibleLive.map((line, i) => (_jsx(Text, { dimColor: true, children: line }, i)))] })), output != null && status !== "running" && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { color: status === "error" ? "red" : "gray", dimColor: true, children: truncate(output, 3) }) }))] }));
10
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginY: 0, children: [_jsxs(Box, { children: [status === "running" ? (_jsxs(Text, { color: "yellow", children: [_jsx(InkSpinner, { type: "dots" }), " "] })) : status === "error" ? (_jsx(Text, { color: "red", children: "✗ " })) : (_jsx(Text, { color: "green", children: "✓ " })), _jsx(Text, { color: "yellow", bold: true, children: toolName }), status === "running" && args && (_jsxs(Text, { dimColor: true, children: [" ", args.slice(0, 60), args.length > 60 ? "..." : ""] }))] }), status === "running" && liveLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [overflow > 0 && _jsx(Text, { dimColor: true, children: `... (${overflow} earlier lines)` }), visibleLive.map((line, i) => (_jsx(Text, { dimColor: true, children: line }, i)))] })), output != null && status !== "running" && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { color: status === "error" ? "red" : "gray", dimColor: true, children: truncate(output, 3) }) }))] }));
13
11
  }
14
12
  function truncate(text, maxLines) {
15
13
  const lines = text.split("\n");
16
14
  if (lines.length <= maxLines)
17
15
  return text;
18
- return lines.slice(0, maxLines).join("\n") + `\n... (${lines.length} lines)`;
16
+ return `${lines.slice(0, maxLines).join("\n")}\n... (${lines.length} lines)`;
19
17
  }
20
18
  //# sourceMappingURL=ToolCallDisplay.js.map
@@ -1,4 +1,4 @@
1
- import type { CompanionBones } from './types.js';
1
+ import type { CompanionBones } from "./types.js";
2
2
  /**
3
3
  * Compute deterministic companion bones from a seed string.
4
4
  * Always produces the same result for the same seed — never persisted.
@@ -1,8 +1,8 @@
1
- import { hostname, userInfo } from 'node:os';
2
- import { RARITY_TIERS } from './types.js';
3
- import { SPECIES } from './species.js';
4
- const SALT = 'openharness-2026';
5
- const STAT_KEYS = ['DEBUGGING', 'PATIENCE', 'CHAOS', 'WISDOM', 'SNARK'];
1
+ import { hostname, userInfo } from "node:os";
2
+ import { SPECIES } from "./species.js";
3
+ import { RARITY_TIERS } from "./types.js";
4
+ const SALT = "openharness-2026";
5
+ const STAT_KEYS = ["DEBUGGING", "PATIENCE", "CHAOS", "WISDOM", "SNARK"];
6
6
  /** FNV-1a 32-bit hash */
7
7
  function fnv1a(str) {
8
8
  let hash = 0x811c9dc5;
@@ -16,7 +16,7 @@ function fnv1a(str) {
16
16
  function mulberry32(seed) {
17
17
  let state = seed | 0;
18
18
  return () => {
19
- state = (state + 0x6D2B79F5) | 0;
19
+ state = (state + 0x6d2b79f5) | 0;
20
20
  let t = Math.imul(state ^ (state >>> 15), 1 | state);
21
21
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
22
22
  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
@@ -31,7 +31,7 @@ function rollRarity(rand) {
31
31
  if (roll < acc)
32
32
  return tier.rarity;
33
33
  }
34
- return 'common';
34
+ return "common";
35
35
  }
36
36
  /** Generate stats with one peak, one dump, rest scattered */
37
37
  function rollStats(rand, floor) {
@@ -70,7 +70,7 @@ export function roll(seed) {
70
70
  const species = SPECIES[speciesIdx].name;
71
71
  // Rarity
72
72
  const rarity = rollRarity(rand);
73
- const floor = RARITY_TIERS.find(t => t.rarity === rarity).statFloor;
73
+ const floor = RARITY_TIERS.find((t) => t.rarity === rarity).statFloor;
74
74
  // Shiny (1% independent chance)
75
75
  const isShiny = rand() < 0.01;
76
76
  // Stats