@dreb/coding-agent 2.29.0 → 2.30.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.
- package/README.md +1 -1
- package/dist/modes/interactive/components/copy-selector.d.ts +3 -3
- package/dist/modes/interactive/components/copy-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/copy-selector.js +4 -5
- package/dist/modes/interactive/components/copy-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +25 -13
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +21 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +74 -11
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +47 -10
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/message-text.d.ts +12 -0
- package/dist/utils/message-text.d.ts.map +1 -1
- package/dist/utils/message-text.js +31 -19
- package/dist/utils/message-text.js.map +1 -1
- package/docs/rpc.md +15 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQlD,SAAS,kBAAkB,CAAC,OAAmC,EAAQ;IACtE,IAAI,CAAC;QACJ,QAAQ,CAAC,4BAA4B,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACR,0CAAwC;QACxC,QAAQ,CAAC,0BAA0B,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;AAAA,CACD;AAID,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAA4B;IAC7E,6DAA6D;IAC7D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,OAAO,MAAM,CAAC,CAAC;IAEjD,
|
|
1
|
+
{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync, spawn } from \"child_process\";\nimport { platform } from \"os\";\nimport { isWaylandSession } from \"./clipboard-image.js\";\nimport { clipboard } from \"./clipboard-native.js\";\n\ntype NativeClipboardExecOptions = {\n\tinput: string;\n\ttimeout: number;\n\tstdio: [\"pipe\", \"ignore\", \"ignore\"];\n};\n\nfunction copyToX11Clipboard(options: NativeClipboardExecOptions): void {\n\ttry {\n\t\texecSync(\"xclip -selection clipboard\", options);\n\t} catch {\n\t\t// xclip unavailable — fall back to xsel\n\t\texecSync(\"xsel --clipboard --input\", options);\n\t}\n}\n\nexport type ClipboardResult = { method: \"native\" | \"platform\" | \"osc52\" };\n\n/**\n * Best-effort write to the native clipboard module. Returns true on success.\n *\n * On Linux this is the source of issue 286: the native module (clipboard-rs)\n * uses an X11 backend whose in-process selection-serving thread calls\n * `println!(\"Somebody else owns the clipboard now\")` (clipboard-rs\n * src/platform/x11.rs) on `SelectionClear` — i.e. whenever another app takes\n * the clipboard. That write goes to the real stdout file descriptor from native\n * code, so no JS-level stdout/stderr guard can intercept it; it lands in the TUI\n * input region. Callers therefore use this only where it is safe (macOS/Windows,\n * which have no serving thread) or as a Linux last resort when no CLI clipboard\n * tool exists.\n */\nasync function tryNativeClipboard(text: string): Promise<boolean> {\n\ttry {\n\t\tif (clipboard) {\n\t\t\tawait clipboard.setText(text);\n\t\t\treturn true;\n\t\t}\n\t} catch {\n\t\t/* Native clipboard module threw — caller falls through to other methods */\n\t}\n\treturn false;\n}\n\nexport async function copyToClipboard(text: string): Promise<ClipboardResult> {\n\t// Always emit OSC 52 - works over SSH/mosh, harmless locally\n\tconst encoded = Buffer.from(text).toString(\"base64\");\n\tprocess.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n\n\tconst p = platform();\n\n\t// On macOS/Windows the native module talks to OS clipboard APIs directly and\n\t// does not spawn a background selection-serving thread, so prefer it there.\n\t// On Linux we intentionally do NOT try the native module first — see\n\t// tryNativeClipboard for why (issue 286) — and prefer controlled-stdio\n\t// subprocess tools instead, falling back to native only as a last resort.\n\tif (p !== \"linux\") {\n\t\tif (await tryNativeClipboard(text)) {\n\t\t\treturn { method: \"native\" };\n\t\t}\n\t}\n\n\tconst options: NativeClipboardExecOptions = { input: text, timeout: 5000, stdio: [\"pipe\", \"ignore\", \"ignore\"] };\n\n\ttry {\n\t\tif (p === \"darwin\") {\n\t\t\texecSync(\"pbcopy\", options);\n\t\t\treturn { method: \"platform\" };\n\t\t} else if (p === \"win32\") {\n\t\t\texecSync(\"clip\", options);\n\t\t\treturn { method: \"platform\" };\n\t\t} else {\n\t\t\t// Linux. Prefer controlled-stdio subprocess tools (Termux, Wayland,\n\t\t\t// X11). Each owns the selection in its own process with stdout/stderr\n\t\t\t// redirected to /dev/null, so its clipboard-ownership chatter cannot\n\t\t\t// leak into the TUI — unlike the in-process native module (issue 286).\n\t\t\tif (process.env.TERMUX_VERSION) {\n\t\t\t\ttry {\n\t\t\t\t\texecSync(\"termux-clipboard-set\", options);\n\t\t\t\t\treturn { method: \"platform\" };\n\t\t\t\t} catch {\n\t\t\t\t\t/* termux-clipboard-set unavailable — fall back to Wayland or X11 tools */\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst hasWaylandDisplay = Boolean(process.env.WAYLAND_DISPLAY);\n\t\t\tconst hasX11Display = Boolean(process.env.DISPLAY);\n\t\t\tconst isWayland = isWaylandSession();\n\t\t\tif (isWayland && hasWaylandDisplay) {\n\t\t\t\ttry {\n\t\t\t\t\t// Verify wl-copy exists (spawn errors are async and won't be caught)\n\t\t\t\t\texecSync(\"which wl-copy\", { stdio: \"ignore\" });\n\t\t\t\t\t// wl-copy with execSync hangs due to fork behavior; use spawn instead.\n\t\t\t\t\t// detached: true puts wl-copy in its own session (setsid) so it keeps\n\t\t\t\t\t// serving the clipboard after we exit and has no controlling terminal.\n\t\t\t\t\tconst proc = spawn(\"wl-copy\", [], { stdio: [\"pipe\", \"ignore\", \"ignore\"], detached: true });\n\t\t\t\t\tproc.on(\"error\", () => {\n\t\t\t\t\t\t// Spawn failed after which check (TOCTOU, permissions, etc.)\n\t\t\t\t\t});\n\t\t\t\t\tproc.stdin.on(\"error\", () => {\n\t\t\t\t\t\t// Ignore EPIPE errors if wl-copy exits early\n\t\t\t\t\t});\n\t\t\t\t\tproc.stdin.write(text);\n\t\t\t\t\tproc.stdin.end();\n\t\t\t\t\tproc.unref();\n\t\t\t\t\t// Can't confirm wl-copy succeeded before unref — report osc52 (already emitted above)\n\t\t\t\t\treturn { method: \"osc52\" };\n\t\t\t\t} catch {\n\t\t\t\t\t/* wl-copy unavailable or failed — fall back to X11 if available */\n\t\t\t\t\tif (hasX11Display) {\n\t\t\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\t\t\treturn { method: \"platform\" };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (hasX11Display) {\n\t\t\t\tcopyToX11Clipboard(options);\n\t\t\t\treturn { method: \"platform\" };\n\t\t\t}\n\n\t\t\t// Linux last resort: the native module. Only reached when no CLI\n\t\t\t// clipboard tool is available. This can reintroduce the issue-286\n\t\t\t// stdout leak on X11 ownership changes, but a working clipboard beats\n\t\t\t// none, and OSC 52 was already emitted above regardless.\n\t\t\tif (await tryNativeClipboard(text)) {\n\t\t\t\treturn { method: \"native\" };\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t/* Platform clipboard tools failed — OSC 52 already emitted above as fallback */\n\t}\n\n\treturn { method: \"osc52\" };\n}\n"]}
|
|
@@ -13,6 +13,18 @@ export declare function extractCopyableText(message: AgentMessage): string;
|
|
|
13
13
|
* Get a short label for a message's role (used in the copy selector UI).
|
|
14
14
|
*/
|
|
15
15
|
export declare function getMessageRoleLabel(message: AgentMessage): string;
|
|
16
|
+
/**
|
|
17
|
+
* Extract the thinking/reasoning text from a message, if any.
|
|
18
|
+
* Only assistant messages carry thinking blocks. Returns a single labeled block
|
|
19
|
+
* (`[thinking]\n<reasoning>`) suitable for copying, or an empty string when there
|
|
20
|
+
* is no thinking content (non-assistant role, or assistant with no thinking blocks).
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractThinkingText(message: AgentMessage): string;
|
|
23
|
+
/**
|
|
24
|
+
* Normalize text to a single-line preview: collapse whitespace, trim, truncate to ~200 chars.
|
|
25
|
+
* Caller handles further width truncation for display. Returns "[no text content]" for empty input.
|
|
26
|
+
*/
|
|
27
|
+
export declare function toSingleLinePreview(text: string): string;
|
|
16
28
|
/**
|
|
17
29
|
* Get a single-line preview of a message's content (for display in selector).
|
|
18
30
|
* Returns plain text, no ANSI, truncated to ~200 chars. Caller handles width truncation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-text.d.ts","sourceRoot":"","sources":["../../src/utils/message-text.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AASrD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAmBjE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAmBjE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"message-text.d.ts","sourceRoot":"","sources":["../../src/utils/message-text.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AASrD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAmBjE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAmBjE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAcjE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUxD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAE/D","sourcesContent":["/**\n * Utilities for extracting copyable plain text from AgentMessages.\n * Used by the copy selector modal to present message content for clipboard copy.\n */\n\nimport type { AgentMessage } from \"@dreb/agent-core\";\nimport type { AssistantMessage, TextContent, ToolResultMessage, UserMessage } from \"@dreb/ai\";\nimport type {\n\tBashExecutionMessage,\n\tBranchSummaryMessage,\n\tCompactionSummaryMessage,\n\tCustomMessage,\n} from \"../core/messages.js\";\n\n/**\n * Extract copyable plain text from any AgentMessage.\n * Returns the original source text without any terminal wrapping or ANSI codes.\n * Returns empty string for messages with no meaningful text content (e.g., image-only).\n */\nexport function extractCopyableText(message: AgentMessage): string {\n\tswitch (message.role) {\n\t\tcase \"user\":\n\t\t\treturn extractUserText(message);\n\t\tcase \"assistant\":\n\t\t\treturn extractAssistantText(message);\n\t\tcase \"toolResult\":\n\t\t\treturn extractToolResultText(message);\n\t\tcase \"bashExecution\":\n\t\t\treturn extractBashText(message);\n\t\tcase \"branchSummary\":\n\t\t\treturn (message as BranchSummaryMessage).summary;\n\t\tcase \"compactionSummary\":\n\t\t\treturn (message as CompactionSummaryMessage).summary;\n\t\tcase \"custom\":\n\t\t\treturn extractCustomText(message as CustomMessage);\n\t\tdefault:\n\t\t\treturn \"\";\n\t}\n}\n\n/**\n * Get a short label for a message's role (used in the copy selector UI).\n */\nexport function getMessageRoleLabel(message: AgentMessage): string {\n\tswitch (message.role) {\n\t\tcase \"user\":\n\t\t\treturn \"You\";\n\t\tcase \"assistant\":\n\t\t\treturn \"Assistant\";\n\t\tcase \"toolResult\":\n\t\t\treturn `Tool: ${(message as ToolResultMessage).toolName}`;\n\t\tcase \"bashExecution\":\n\t\t\treturn \"Bash\";\n\t\tcase \"branchSummary\":\n\t\t\treturn \"Branch\";\n\t\tcase \"compactionSummary\":\n\t\t\treturn \"Summary\";\n\t\tcase \"custom\":\n\t\t\treturn `Custom: ${(message as CustomMessage).customType}`;\n\t\tdefault:\n\t\t\treturn \"Unknown\";\n\t}\n}\n\n/**\n * Extract the thinking/reasoning text from a message, if any.\n * Only assistant messages carry thinking blocks. Returns a single labeled block\n * (`[thinking]\\n<reasoning>`) suitable for copying, or an empty string when there\n * is no thinking content (non-assistant role, or assistant with no thinking blocks).\n */\nexport function extractThinkingText(message: AgentMessage): string {\n\tif (message.role !== \"assistant\") {\n\t\treturn \"\";\n\t}\n\tconst thinkingParts = (message as AssistantMessage).content\n\t\t.filter(\n\t\t\t(block): block is { type: \"thinking\"; thinking: string } => block.type === \"thinking\" && \"thinking\" in block,\n\t\t)\n\t\t.map((block) => block.thinking);\n\n\tif (thinkingParts.length === 0) {\n\t\treturn \"\";\n\t}\n\treturn `[thinking]\\n${thinkingParts.join(\"\\n\\n\")}`;\n}\n\n/**\n * Normalize text to a single-line preview: collapse whitespace, trim, truncate to ~200 chars.\n * Caller handles further width truncation for display. Returns \"[no text content]\" for empty input.\n */\nexport function toSingleLinePreview(text: string): string {\n\tif (!text) {\n\t\treturn \"[no text content]\";\n\t}\n\t// Normalize to single line: replace newlines with spaces, collapse whitespace\n\tconst singleLine = text.replace(/\\n/g, \" \").replace(/\\s+/g, \" \").trim();\n\tif (singleLine.length <= 200) {\n\t\treturn singleLine;\n\t}\n\treturn singleLine.slice(0, 200);\n}\n\n/**\n * Get a single-line preview of a message's content (for display in selector).\n * Returns plain text, no ANSI, truncated to ~200 chars. Caller handles width truncation.\n */\nexport function getMessagePreview(message: AgentMessage): string {\n\treturn toSingleLinePreview(extractCopyableText(message));\n}\n\n// --- Internal helpers ---\n\nfunction extractTextBlocks(content: (TextContent | { type: string })[]): string {\n\treturn content\n\t\t.filter((block): block is TextContent => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n\nfunction extractUserText(message: UserMessage): string {\n\tif (typeof message.content === \"string\") {\n\t\treturn message.content;\n\t}\n\treturn extractTextBlocks(message.content);\n}\n\nfunction extractAssistantText(message: AssistantMessage): string {\n\t// Only visible text blocks. Thinking is excluded by default and surfaced\n\t// separately via extractThinkingText (see issue 285).\n\treturn extractTextBlocks(message.content);\n}\n\nfunction extractToolResultText(message: ToolResultMessage): string {\n\tconst textContent = extractTextBlocks(message.content);\n\tif (!textContent) {\n\t\treturn \"\";\n\t}\n\treturn `[${message.toolName}]\\n${textContent}`;\n}\n\nfunction extractBashText(message: BashExecutionMessage): string {\n\treturn `$ ${message.command}\\n${message.output}`;\n}\n\nfunction extractCustomText(message: CustomMessage): string {\n\tif (typeof message.content === \"string\") {\n\t\treturn message.content;\n\t}\n\tif (Array.isArray(message.content)) {\n\t\treturn extractTextBlocks(message.content);\n\t}\n\treturn \"\";\n}\n"]}
|
|
@@ -51,11 +51,28 @@ export function getMessageRoleLabel(message) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* Extract the thinking/reasoning text from a message, if any.
|
|
55
|
+
* Only assistant messages carry thinking blocks. Returns a single labeled block
|
|
56
|
+
* (`[thinking]\n<reasoning>`) suitable for copying, or an empty string when there
|
|
57
|
+
* is no thinking content (non-assistant role, or assistant with no thinking blocks).
|
|
56
58
|
*/
|
|
57
|
-
export function
|
|
58
|
-
|
|
59
|
+
export function extractThinkingText(message) {
|
|
60
|
+
if (message.role !== "assistant") {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
const thinkingParts = message.content
|
|
64
|
+
.filter((block) => block.type === "thinking" && "thinking" in block)
|
|
65
|
+
.map((block) => block.thinking);
|
|
66
|
+
if (thinkingParts.length === 0) {
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
return `[thinking]\n${thinkingParts.join("\n\n")}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Normalize text to a single-line preview: collapse whitespace, trim, truncate to ~200 chars.
|
|
73
|
+
* Caller handles further width truncation for display. Returns "[no text content]" for empty input.
|
|
74
|
+
*/
|
|
75
|
+
export function toSingleLinePreview(text) {
|
|
59
76
|
if (!text) {
|
|
60
77
|
return "[no text content]";
|
|
61
78
|
}
|
|
@@ -66,6 +83,13 @@ export function getMessagePreview(message) {
|
|
|
66
83
|
}
|
|
67
84
|
return singleLine.slice(0, 200);
|
|
68
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Get a single-line preview of a message's content (for display in selector).
|
|
88
|
+
* Returns plain text, no ANSI, truncated to ~200 chars. Caller handles width truncation.
|
|
89
|
+
*/
|
|
90
|
+
export function getMessagePreview(message) {
|
|
91
|
+
return toSingleLinePreview(extractCopyableText(message));
|
|
92
|
+
}
|
|
69
93
|
// --- Internal helpers ---
|
|
70
94
|
function extractTextBlocks(content) {
|
|
71
95
|
return content
|
|
@@ -80,21 +104,9 @@ function extractUserText(message) {
|
|
|
80
104
|
return extractTextBlocks(message.content);
|
|
81
105
|
}
|
|
82
106
|
function extractAssistantText(message) {
|
|
83
|
-
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
.filter((block) => block.type === "text")
|
|
87
|
-
.map((block) => block.text);
|
|
88
|
-
if (textParts.length > 0) {
|
|
89
|
-
parts.push(textParts.join("\n"));
|
|
90
|
-
}
|
|
91
|
-
// Collect thinking blocks with header
|
|
92
|
-
for (const block of message.content) {
|
|
93
|
-
if (block.type === "thinking" && "thinking" in block) {
|
|
94
|
-
parts.push(`[thinking]\n${block.thinking}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return parts.join("\n");
|
|
107
|
+
// Only visible text blocks. Thinking is excluded by default and surfaced
|
|
108
|
+
// separately via extractThinkingText (see issue 285).
|
|
109
|
+
return extractTextBlocks(message.content);
|
|
98
110
|
}
|
|
99
111
|
function extractToolResultText(message) {
|
|
100
112
|
const textContent = extractTextBlocks(message.content);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-text.js","sourceRoot":"","sources":["../../src/utils/message-text.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAqB,EAAU;IAClE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,MAAM;YACV,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;QACjC,KAAK,WAAW;YACf,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACtC,KAAK,YAAY;YAChB,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACvC,KAAK,eAAe;YACnB,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;QACjC,KAAK,eAAe;YACnB,OAAQ,OAAgC,CAAC,OAAO,CAAC;QAClD,KAAK,mBAAmB;YACvB,OAAQ,OAAoC,CAAC,OAAO,CAAC;QACtD,KAAK,QAAQ;YACZ,OAAO,iBAAiB,CAAC,OAAwB,CAAC,CAAC;QACpD;YACC,OAAO,EAAE,CAAC;IACZ,CAAC;AAAA,CACD;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAqB,EAAU;IAClE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,MAAM;YACV,OAAO,KAAK,CAAC;QACd,KAAK,WAAW;YACf,OAAO,WAAW,CAAC;QACpB,KAAK,YAAY;YAChB,OAAO,SAAU,OAA6B,CAAC,QAAQ,EAAE,CAAC;QAC3D,KAAK,eAAe;YACnB,OAAO,MAAM,CAAC;QACf,KAAK,eAAe;YACnB,OAAO,QAAQ,CAAC;QACjB,KAAK,mBAAmB;YACvB,OAAO,SAAS,CAAC;QAClB,KAAK,QAAQ;YACZ,OAAO,WAAY,OAAyB,CAAC,UAAU,EAAE,CAAC;QAC3D;YACC,OAAO,SAAS,CAAC;IACnB,CAAC;AAAA,CACD;AAED
|
|
1
|
+
{"version":3,"file":"message-text.js","sourceRoot":"","sources":["../../src/utils/message-text.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAqB,EAAU;IAClE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,MAAM;YACV,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;QACjC,KAAK,WAAW;YACf,OAAO,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACtC,KAAK,YAAY;YAChB,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACvC,KAAK,eAAe;YACnB,OAAO,eAAe,CAAC,OAAO,CAAC,CAAC;QACjC,KAAK,eAAe;YACnB,OAAQ,OAAgC,CAAC,OAAO,CAAC;QAClD,KAAK,mBAAmB;YACvB,OAAQ,OAAoC,CAAC,OAAO,CAAC;QACtD,KAAK,QAAQ;YACZ,OAAO,iBAAiB,CAAC,OAAwB,CAAC,CAAC;QACpD;YACC,OAAO,EAAE,CAAC;IACZ,CAAC;AAAA,CACD;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAqB,EAAU;IAClE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,MAAM;YACV,OAAO,KAAK,CAAC;QACd,KAAK,WAAW;YACf,OAAO,WAAW,CAAC;QACpB,KAAK,YAAY;YAChB,OAAO,SAAU,OAA6B,CAAC,QAAQ,EAAE,CAAC;QAC3D,KAAK,eAAe;YACnB,OAAO,MAAM,CAAC;QACf,KAAK,eAAe;YACnB,OAAO,QAAQ,CAAC;QACjB,KAAK,mBAAmB;YACvB,OAAO,SAAS,CAAC;QAClB,KAAK,QAAQ;YACZ,OAAO,WAAY,OAAyB,CAAC,UAAU,EAAE,CAAC;QAC3D;YACC,OAAO,SAAS,CAAC;IACnB,CAAC;AAAA,CACD;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAqB,EAAU;IAClE,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,EAAE,CAAC;IACX,CAAC;IACD,MAAM,aAAa,GAAI,OAA4B,CAAC,OAAO;SACzD,MAAM,CACN,CAAC,KAAK,EAAmD,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,UAAU,IAAI,KAAK,CAC5G;SACA,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAEjC,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,OAAO,EAAE,CAAC;IACX,CAAC;IACD,OAAO,eAAe,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;AAAA,CACnD;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAU;IACzD,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,mBAAmB,CAAC;IAC5B,CAAC;IACD,8EAA8E;IAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACxE,IAAI,UAAU,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;QAC9B,OAAO,UAAU,CAAC;IACnB,CAAC;IACD,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAAA,CAChC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAqB,EAAU;IAChE,OAAO,mBAAmB,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,CACzD;AAED,2BAA2B;AAE3B,SAAS,iBAAiB,CAAC,OAA2C,EAAU;IAC/E,OAAO,OAAO;SACZ,MAAM,CAAC,CAAC,KAAK,EAAwB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC;SAC9D,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACb;AAED,SAAS,eAAe,CAAC,OAAoB,EAAU;IACtD,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzC,OAAO,OAAO,CAAC,OAAO,CAAC;IACxB,CAAC;IACD,OAAO,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAAA,CAC1C;AAED,SAAS,oBAAoB,CAAC,OAAyB,EAAU;IAChE,yEAAyE;IACzE,sDAAsD;IACtD,OAAO,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAAA,CAC1C;AAED,SAAS,qBAAqB,CAAC,OAA0B,EAAU;IAClE,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,OAAO,EAAE,CAAC;IACX,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,QAAQ,MAAM,WAAW,EAAE,CAAC;AAAA,CAC/C;AAED,SAAS,eAAe,CAAC,OAA6B,EAAU;IAC/D,OAAO,KAAK,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;AAAA,CACjD;AAED,SAAS,iBAAiB,CAAC,OAAsB,EAAU;IAC1D,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzC,OAAO,OAAO,CAAC,OAAO,CAAC;IACxB,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpC,OAAO,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,EAAE,CAAC;AAAA,CACV","sourcesContent":["/**\n * Utilities for extracting copyable plain text from AgentMessages.\n * Used by the copy selector modal to present message content for clipboard copy.\n */\n\nimport type { AgentMessage } from \"@dreb/agent-core\";\nimport type { AssistantMessage, TextContent, ToolResultMessage, UserMessage } from \"@dreb/ai\";\nimport type {\n\tBashExecutionMessage,\n\tBranchSummaryMessage,\n\tCompactionSummaryMessage,\n\tCustomMessage,\n} from \"../core/messages.js\";\n\n/**\n * Extract copyable plain text from any AgentMessage.\n * Returns the original source text without any terminal wrapping or ANSI codes.\n * Returns empty string for messages with no meaningful text content (e.g., image-only).\n */\nexport function extractCopyableText(message: AgentMessage): string {\n\tswitch (message.role) {\n\t\tcase \"user\":\n\t\t\treturn extractUserText(message);\n\t\tcase \"assistant\":\n\t\t\treturn extractAssistantText(message);\n\t\tcase \"toolResult\":\n\t\t\treturn extractToolResultText(message);\n\t\tcase \"bashExecution\":\n\t\t\treturn extractBashText(message);\n\t\tcase \"branchSummary\":\n\t\t\treturn (message as BranchSummaryMessage).summary;\n\t\tcase \"compactionSummary\":\n\t\t\treturn (message as CompactionSummaryMessage).summary;\n\t\tcase \"custom\":\n\t\t\treturn extractCustomText(message as CustomMessage);\n\t\tdefault:\n\t\t\treturn \"\";\n\t}\n}\n\n/**\n * Get a short label for a message's role (used in the copy selector UI).\n */\nexport function getMessageRoleLabel(message: AgentMessage): string {\n\tswitch (message.role) {\n\t\tcase \"user\":\n\t\t\treturn \"You\";\n\t\tcase \"assistant\":\n\t\t\treturn \"Assistant\";\n\t\tcase \"toolResult\":\n\t\t\treturn `Tool: ${(message as ToolResultMessage).toolName}`;\n\t\tcase \"bashExecution\":\n\t\t\treturn \"Bash\";\n\t\tcase \"branchSummary\":\n\t\t\treturn \"Branch\";\n\t\tcase \"compactionSummary\":\n\t\t\treturn \"Summary\";\n\t\tcase \"custom\":\n\t\t\treturn `Custom: ${(message as CustomMessage).customType}`;\n\t\tdefault:\n\t\t\treturn \"Unknown\";\n\t}\n}\n\n/**\n * Extract the thinking/reasoning text from a message, if any.\n * Only assistant messages carry thinking blocks. Returns a single labeled block\n * (`[thinking]\\n<reasoning>`) suitable for copying, or an empty string when there\n * is no thinking content (non-assistant role, or assistant with no thinking blocks).\n */\nexport function extractThinkingText(message: AgentMessage): string {\n\tif (message.role !== \"assistant\") {\n\t\treturn \"\";\n\t}\n\tconst thinkingParts = (message as AssistantMessage).content\n\t\t.filter(\n\t\t\t(block): block is { type: \"thinking\"; thinking: string } => block.type === \"thinking\" && \"thinking\" in block,\n\t\t)\n\t\t.map((block) => block.thinking);\n\n\tif (thinkingParts.length === 0) {\n\t\treturn \"\";\n\t}\n\treturn `[thinking]\\n${thinkingParts.join(\"\\n\\n\")}`;\n}\n\n/**\n * Normalize text to a single-line preview: collapse whitespace, trim, truncate to ~200 chars.\n * Caller handles further width truncation for display. Returns \"[no text content]\" for empty input.\n */\nexport function toSingleLinePreview(text: string): string {\n\tif (!text) {\n\t\treturn \"[no text content]\";\n\t}\n\t// Normalize to single line: replace newlines with spaces, collapse whitespace\n\tconst singleLine = text.replace(/\\n/g, \" \").replace(/\\s+/g, \" \").trim();\n\tif (singleLine.length <= 200) {\n\t\treturn singleLine;\n\t}\n\treturn singleLine.slice(0, 200);\n}\n\n/**\n * Get a single-line preview of a message's content (for display in selector).\n * Returns plain text, no ANSI, truncated to ~200 chars. Caller handles width truncation.\n */\nexport function getMessagePreview(message: AgentMessage): string {\n\treturn toSingleLinePreview(extractCopyableText(message));\n}\n\n// --- Internal helpers ---\n\nfunction extractTextBlocks(content: (TextContent | { type: string })[]): string {\n\treturn content\n\t\t.filter((block): block is TextContent => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n\nfunction extractUserText(message: UserMessage): string {\n\tif (typeof message.content === \"string\") {\n\t\treturn message.content;\n\t}\n\treturn extractTextBlocks(message.content);\n}\n\nfunction extractAssistantText(message: AssistantMessage): string {\n\t// Only visible text blocks. Thinking is excluded by default and surfaced\n\t// separately via extractThinkingText (see issue 285).\n\treturn extractTextBlocks(message.content);\n}\n\nfunction extractToolResultText(message: ToolResultMessage): string {\n\tconst textContent = extractTextBlocks(message.content);\n\tif (!textContent) {\n\t\treturn \"\";\n\t}\n\treturn `[${message.toolName}]\\n${textContent}`;\n}\n\nfunction extractBashText(message: BashExecutionMessage): string {\n\treturn `$ ${message.command}\\n${message.output}`;\n}\n\nfunction extractCustomText(message: CustomMessage): string {\n\tif (typeof message.content === \"string\") {\n\t\treturn message.content;\n\t}\n\tif (Array.isArray(message.content)) {\n\t\treturn extractTextBlocks(message.content);\n\t}\n\treturn \"\";\n}\n"]}
|
package/docs/rpc.md
CHANGED
|
@@ -4,6 +4,21 @@ RPC mode enables headless operation of the coding agent via a JSON protocol over
|
|
|
4
4
|
|
|
5
5
|
**Note for Node.js/TypeScript users**: If you're building a Node.js application, consider using `AgentSession` directly from `@dreb/coding-agent` instead of spawning a subprocess. See [`src/core/agent-session.ts`](../src/core/agent-session.ts) for the API. For a subprocess-based TypeScript client, see [`src/modes/rpc/rpc-client.ts`](../src/modes/rpc/rpc-client.ts).
|
|
6
6
|
|
|
7
|
+
### Running the agent child as a specific OS user
|
|
8
|
+
|
|
9
|
+
When using the `RpcClient` from `@dreb/coding-agent`, `RpcClientOptions` accepts optional `uid` and `gid` fields. When set, they are forwarded directly to `child_process.spawn`, so the agent child (and every subprocess it spawns, including `bash`) runs under that OS user/group. When unset they are omitted entirely, leaving spawn behavior unchanged.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { RpcClient } from "@dreb/coding-agent";
|
|
13
|
+
|
|
14
|
+
// Parent must hold CAP_SETUID / CAP_SETGID (e.g. run as root) for this to succeed.
|
|
15
|
+
const client = new RpcClient({ cwd: "/srv/users/alice", uid: 4001, gid: 4001 });
|
|
16
|
+
await client.start();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This enables per-user filesystem isolation by plain Unix DAC: give each authenticated user a dedicated UID and a working directory owned by that UID at mode `0700`. If the parent lacks the required capability (or the platform doesn't support `uid`/`gid`, e.g. Windows), the spawn fails and `start()` rejects rather than silently running as the parent user.
|
|
20
|
+
|
|
21
|
+
|
|
7
22
|
## Starting RPC Mode
|
|
8
23
|
|
|
9
24
|
```bash
|