@novaqore/atom 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novaqore/atom",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A full-system code agent with root-level access, built for isolated servers.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,7 +2,10 @@ import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import { renderMarkdown } from "../../utils/markdown.js";
4
4
 
5
- export function AssistantMessage({ content, streaming = false }) {
5
+ export const AssistantMessage = React.memo(function AssistantMessage({
6
+ content,
7
+ streaming = false,
8
+ }) {
6
9
  const rendered = renderMarkdown(content, { streaming });
7
10
  const nlIdx = rendered.indexOf("\n");
8
11
  const firstLine = nlIdx === -1 ? rendered : rendered.slice(0, nlIdx);
@@ -20,4 +23,4 @@ export function AssistantMessage({ content, streaming = false }) {
20
23
  ),
21
24
  rest ? React.createElement(Text, null, rest) : null
22
25
  );
23
- }
26
+ });
@@ -26,15 +26,15 @@ function shortPath(p) {
26
26
  return p;
27
27
  }
28
28
 
29
- export function ChatInput({
30
- value,
31
- onChange,
32
- onSubmit,
33
- cwd,
34
- totalTokens,
35
- loading,
36
- disabled,
37
- }) {
29
+ export function ChatInput({ onSubmit, cwd, totalTokens, loading, disabled }) {
30
+ const [value, setValue] = useState("");
31
+
32
+ const handleSubmit = (val) => {
33
+ if (!val.trim() || disabled) return;
34
+ setValue("");
35
+ onSubmit(val);
36
+ };
37
+
38
38
  return React.createElement(
39
39
  Box,
40
40
  { flexDirection: "column" },
@@ -74,8 +74,8 @@ export function ChatInput({
74
74
  React.createElement(Text, { dimColor: true }, "❯ "),
75
75
  React.createElement(TextInput, {
76
76
  value,
77
- onChange,
78
- onSubmit,
77
+ onChange: setValue,
78
+ onSubmit: handleSubmit,
79
79
  showCursor: !disabled,
80
80
  focus: !disabled,
81
81
  })
@@ -5,28 +5,49 @@ function formatName(name) {
5
5
  return name.charAt(0).toUpperCase() + name.slice(1);
6
6
  }
7
7
 
8
- function formatArgs(rawArgs) {
8
+ function parseArgs(rawArgs) {
9
+ if (!rawArgs) return null;
9
10
  try {
10
- const parsed = JSON.parse(rawArgs || "{}");
11
+ const parsed = JSON.parse(rawArgs);
11
12
  const keys = Object.keys(parsed);
13
+ if (keys.length === 0) return "";
12
14
  if (keys.length === 1) return String(parsed[keys[0]]);
13
15
  return keys.map((k) => `${k}=${JSON.stringify(parsed[k])}`).join(", ");
14
- } catch {
15
- return rawArgs || "";
16
+ } catch {}
17
+ const m = rawArgs.match(/"[^"]+"\s*:\s*"((?:\\.|[^"\\])*)/);
18
+ if (m) {
19
+ try {
20
+ return JSON.parse('"' + m[1] + '"');
21
+ } catch {
22
+ return m[1];
23
+ }
16
24
  }
25
+ return null;
17
26
  }
18
27
 
19
- export function ToolCall({ tc }) {
28
+ export const ToolCall = React.memo(function ToolCall({ tc, streaming = false }) {
29
+ const parsed = parseArgs(tc.function.arguments);
30
+ const name = React.createElement(
31
+ Text,
32
+ { bold: true, color: "yellow" },
33
+ formatName(tc.function.name)
34
+ );
35
+
36
+ let args;
37
+ if (parsed !== null) {
38
+ args = parsed;
39
+ } else if (streaming) {
40
+ return React.createElement(Text, null, name);
41
+ } else {
42
+ args = tc.function.arguments || "";
43
+ }
44
+
20
45
  return React.createElement(
21
46
  Text,
22
47
  null,
23
- React.createElement(
24
- Text,
25
- { bold: true, color: "yellow" },
26
- formatName(tc.function.name)
27
- ),
48
+ name,
28
49
  React.createElement(Text, { dimColor: true }, "("),
29
- formatArgs(tc.function.arguments),
50
+ args,
30
51
  React.createElement(Text, { dimColor: true }, ")")
31
52
  );
32
- }
53
+ });
@@ -7,7 +7,7 @@ function formatMs(ms) {
7
7
  return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
8
8
  }
9
9
 
10
- export function ToolResult({ msg, duration }) {
10
+ export const ToolResult = React.memo(function ToolResult({ msg, duration }) {
11
11
  const content = (msg.content || "").trimEnd();
12
12
  const dur = duration ? ` · ${formatMs(duration)}` : "";
13
13
 
@@ -47,4 +47,4 @@ export function ToolResult({ msg, duration }) {
47
47
  { flexDirection: "column", paddingLeft: 2, marginBottom: 1 },
48
48
  children
49
49
  );
50
- }
50
+ });
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
 
4
- export function UserMessage({ content }) {
4
+ export const UserMessage = React.memo(function UserMessage({ content }) {
5
5
  return React.createElement(
6
6
  Box,
7
7
  { marginBottom: 1 },
@@ -13,4 +13,4 @@ export function UserMessage({ content }) {
13
13
  React.createElement(Text, { dimColor: true }, content)
14
14
  )
15
15
  );
16
- }
16
+ });
@@ -4,7 +4,7 @@ import { Box, Text } from "ink";
4
4
  import { DISK } from "../utils/system.js";
5
5
  import { RUNTIMES } from "../utils/runtimes.js";
6
6
 
7
- export function Header({ version, unhinged }) {
7
+ export const Header = React.memo(function Header({ version, unhinged }) {
8
8
  const cpuList = cpus();
9
9
  const cpuCount = cpuList?.length || 0;
10
10
  const cpuSpeed = cpuList?.[0]?.speed || 0;
@@ -61,4 +61,4 @@ export function Header({ version, unhinged }) {
61
61
  React.createElement(Text, { dimColor: true }, hardwareParts.join(" · "))
62
62
  )
63
63
  );
64
- }
64
+ });
@@ -16,7 +16,6 @@ import { Header } from "../components/Header.js";
16
16
  export function ChatScreen({ version, unhinged, onBack }) {
17
17
  const [nq] = useState(() => new NovaQoreAI(SERVICE_FILE));
18
18
  const [messages, setMessages] = useState([]);
19
- const [input, setInput] = useState("");
20
19
  const [streaming, setStreaming] = useState("");
21
20
  const [streamingTools, setStreamingTools] = useState([]);
22
21
  const [sending, setSending] = useState(false);
@@ -68,7 +67,6 @@ export function ChatScreen({ version, unhinged, onBack }) {
68
67
 
69
68
  let convo = [...messages, { role: "user", content: text }];
70
69
  setMessages(convo);
71
- setInput("");
72
70
  setSending(true);
73
71
  setError(null);
74
72
 
@@ -235,7 +233,11 @@ export function ChatScreen({ version, unhinged, onBack }) {
235
233
  streaming: true,
236
234
  }),
237
235
  ...streamingTools.map((tc, j) =>
238
- React.createElement(ToolCall, { key: `streaming-tc-${j}`, tc })
236
+ React.createElement(ToolCall, {
237
+ key: `streaming-tc-${j}`,
238
+ tc,
239
+ streaming: true,
240
+ })
239
241
  ),
240
242
  running &&
241
243
  React.createElement(RunningIndicator, {
@@ -256,8 +258,6 @@ export function ChatScreen({ version, unhinged, onBack }) {
256
258
  })
257
259
  : React.createElement(ChatInput, {
258
260
  key: "input",
259
- value: input,
260
- onChange: setInput,
261
261
  onSubmit: handleSubmit,
262
262
  cwd,
263
263
  totalTokens: usage.total,
@@ -7,9 +7,6 @@ const ext = markedTerminal({
7
7
  text: chalk.white,
8
8
  });
9
9
 
10
- // Patch: marked-terminal v7's text renderer ignores nested inline tokens
11
- // (codespan, strong, em, link, etc.) when they appear inside list items.
12
- // Force it to parse them inline so styling works in lists too.
13
10
  const origText = ext.renderer.text;
14
11
  ext.renderer.text = function (token) {
15
12
  if (
@@ -40,18 +37,8 @@ function balanceForStreaming(buffer) {
40
37
  return out;
41
38
  }
42
39
 
43
- const cache = new Map();
44
- const MAX_CACHE = 500;
45
-
46
40
  export function renderMarkdown(text, { streaming = false } = {}) {
47
41
  if (!text) return "";
48
- if (streaming) {
49
- return marked.parse(balanceForStreaming(text)).trim();
50
- }
51
- const cached = cache.get(text);
52
- if (cached !== undefined) return cached;
53
- const result = marked.parse(text).trim();
54
- if (cache.size >= MAX_CACHE) cache.clear();
55
- cache.set(text, result);
56
- return result;
42
+ const input = streaming ? balanceForStreaming(text) : text;
43
+ return marked.parse(input).trim();
57
44
  }