@saadnvd1/agent-os 0.3.497 → 0.3.498

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.
@@ -5,6 +5,8 @@ import type { ChatItem } from "@/lib/chat/events";
5
5
  import { cn } from "@/lib/utils";
6
6
  import { leadingCommand } from "@/lib/chat/commands";
7
7
  import { compactedLine } from "@/lib/chat/stale";
8
+ import { splitInlineCode, splitUserText } from "@/lib/chat/user-text";
9
+ import { CodeBlock } from "./Code";
8
10
  import { CopyButton } from "./CopyButton";
9
11
  import { ImageThumb } from "./ImageThumb";
10
12
  import { Markdown } from "./Markdown";
@@ -13,13 +15,44 @@ import { useRowState } from "./rowState";
13
15
 
14
16
  type Of<K extends ChatItem["kind"]> = Extract<ChatItem, { kind: K }>;
15
17
 
18
+ function Prose({ text }: { text: string }) {
19
+ return (
20
+ <>
21
+ {splitInlineCode(text).map((p, i) =>
22
+ p.code ? (
23
+ <code
24
+ key={i}
25
+ className="bg-foreground/[0.06] rounded px-1 py-0.5 font-mono text-[0.9em]"
26
+ >
27
+ {p.text}
28
+ </code>
29
+ ) : (
30
+ <span key={i}>{p.text}</span>
31
+ )
32
+ )}
33
+ </>
34
+ );
35
+ }
36
+
16
37
  function UserText({ text }: { text: string }) {
17
- const command = leadingCommand(text);
18
- if (!command) return <>{text}</>;
19
38
  return (
20
39
  <>
21
- <span className="text-primary font-mono">{command}</span>
22
- {text.slice(command.length)}
40
+ {splitUserText(text).map((part, i) => {
41
+ if (part.kind === "code")
42
+ return (
43
+ <div key={i} className="my-1.5 first:mt-0 last:mb-0">
44
+ <CodeBlock code={part.code} language={part.language} />
45
+ </div>
46
+ );
47
+ const command = i === 0 ? leadingCommand(part.text) : null;
48
+ if (!command) return <Prose key={i} text={part.text} />;
49
+ return (
50
+ <span key={i}>
51
+ <span className="text-primary font-mono">{command}</span>
52
+ <Prose text={part.text.slice(command.length)} />
53
+ </span>
54
+ );
55
+ })}
23
56
  </>
24
57
  );
25
58
  }
@@ -0,0 +1,64 @@
1
+ export type UserTextPart =
2
+ | { kind: "text"; text: string }
3
+ | { kind: "code"; code: string; language?: string };
4
+
5
+ const OPEN = /^ {0,3}(`{3,}|~{3,})\s*([\w+-]*)[^\n`]*$/;
6
+
7
+ // A sent message split at its fenced code blocks. Everything else stays the
8
+ // text it was typed as: no emphasis, no HTML, no reflow. An unclosed fence
9
+ // runs to the end, as in markdown.
10
+ export function splitUserText(text: string): UserTextPart[] {
11
+ if (!text.includes("```") && !text.includes("~~~"))
12
+ return [{ kind: "text", text }];
13
+ const parts: UserTextPart[] = [];
14
+ let prose: string[] = [];
15
+ let code: string[] | null = null;
16
+ let fence = "";
17
+ let language: string | undefined;
18
+ const flushProse = () => {
19
+ if (prose.length) parts.push({ kind: "text", text: prose.join("\n") });
20
+ prose = [];
21
+ };
22
+ for (const line of text.replace(/\r\n?/g, "\n").split("\n")) {
23
+ if (code === null) {
24
+ const m = OPEN.exec(line);
25
+ if (m) {
26
+ flushProse();
27
+ code = [];
28
+ fence = m[1];
29
+ language = m[2] || undefined;
30
+ } else prose.push(line);
31
+ continue;
32
+ }
33
+ const t = line.trim();
34
+ if (
35
+ t.startsWith(fence[0]) &&
36
+ t.length >= fence.length &&
37
+ !/[^`~]/.test(t) &&
38
+ t[0] === fence[0]
39
+ ) {
40
+ parts.push({ kind: "code", code: code.join("\n"), language });
41
+ code = null;
42
+ } else code.push(line);
43
+ }
44
+ if (code !== null)
45
+ parts.push({ kind: "code", code: code.join("\n"), language });
46
+ flushProse();
47
+ return parts;
48
+ }
49
+
50
+ // Backtick spans inside prose, split out so they can be set in monospace.
51
+ export function splitInlineCode(
52
+ text: string
53
+ ): { code: boolean; text: string }[] {
54
+ const out: { code: boolean; text: string }[] = [];
55
+ let last = 0;
56
+ for (const m of text.matchAll(/(`+)(?!`)([^\n]*?[^`\n])\1(?!`)/g)) {
57
+ if (m.index > last)
58
+ out.push({ code: false, text: text.slice(last, m.index) });
59
+ out.push({ code: true, text: m[2] });
60
+ last = m.index + m[0].length;
61
+ }
62
+ if (last < text.length) out.push({ code: false, text: text.slice(last) });
63
+ return out;
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.497",
3
+ "version": "0.3.498",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"