@dreb/coding-agent 2.30.0 → 2.31.0
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 +12 -1
- package/dist/core/agent-session.d.ts +16 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +58 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/nested-context.d.ts +60 -0
- package/dist/core/nested-context.d.ts.map +1 -0
- package/dist/core/nested-context.js +276 -0
- package/dist/core/nested-context.js.map +1 -0
- package/dist/core/resource-loader.d.ts +5 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +12 -12
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +10 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -0
- package/dist/core/settings-manager.js.map +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/components/settings-selector.d.ts +3 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +10 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +29 -13
- package/dist/modes/interactive/interactive-mode.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/settings.md +18 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,eAAe,GAAG;IAAE,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"clipboard.d.ts","sourceRoot":"","sources":["../../src/utils/clipboard.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,eAAe,GAAG;IAAE,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAA;CAAE,CAAC;AA2B1E,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAwF5E","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"]}
|
package/dist/utils/clipboard.js
CHANGED
|
@@ -11,21 +11,46 @@ function copyToX11Clipboard(options) {
|
|
|
11
11
|
execSync("xsel --clipboard --input", options);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Best-effort write to the native clipboard module. Returns true on success.
|
|
16
|
+
*
|
|
17
|
+
* On Linux this is the source of issue 286: the native module (clipboard-rs)
|
|
18
|
+
* uses an X11 backend whose in-process selection-serving thread calls
|
|
19
|
+
* `println!("Somebody else owns the clipboard now")` (clipboard-rs
|
|
20
|
+
* src/platform/x11.rs) on `SelectionClear` — i.e. whenever another app takes
|
|
21
|
+
* the clipboard. That write goes to the real stdout file descriptor from native
|
|
22
|
+
* code, so no JS-level stdout/stderr guard can intercept it; it lands in the TUI
|
|
23
|
+
* input region. Callers therefore use this only where it is safe (macOS/Windows,
|
|
24
|
+
* which have no serving thread) or as a Linux last resort when no CLI clipboard
|
|
25
|
+
* tool exists.
|
|
26
|
+
*/
|
|
27
|
+
async function tryNativeClipboard(text) {
|
|
18
28
|
try {
|
|
19
29
|
if (clipboard) {
|
|
20
30
|
await clipboard.setText(text);
|
|
21
|
-
return
|
|
31
|
+
return true;
|
|
22
32
|
}
|
|
23
33
|
}
|
|
24
34
|
catch {
|
|
25
|
-
/* Native clipboard module threw —
|
|
35
|
+
/* Native clipboard module threw — caller falls through to other methods */
|
|
26
36
|
}
|
|
27
|
-
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
export async function copyToClipboard(text) {
|
|
40
|
+
// Always emit OSC 52 - works over SSH/mosh, harmless locally
|
|
41
|
+
const encoded = Buffer.from(text).toString("base64");
|
|
42
|
+
process.stdout.write(`\x1b]52;c;${encoded}\x07`);
|
|
28
43
|
const p = platform();
|
|
44
|
+
// On macOS/Windows the native module talks to OS clipboard APIs directly and
|
|
45
|
+
// does not spawn a background selection-serving thread, so prefer it there.
|
|
46
|
+
// On Linux we intentionally do NOT try the native module first — see
|
|
47
|
+
// tryNativeClipboard for why (issue 286) — and prefer controlled-stdio
|
|
48
|
+
// subprocess tools instead, falling back to native only as a last resort.
|
|
49
|
+
if (p !== "linux") {
|
|
50
|
+
if (await tryNativeClipboard(text)) {
|
|
51
|
+
return { method: "native" };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
29
54
|
const options = { input: text, timeout: 5000, stdio: ["pipe", "ignore", "ignore"] };
|
|
30
55
|
try {
|
|
31
56
|
if (p === "darwin") {
|
|
@@ -37,7 +62,10 @@ export async function copyToClipboard(text) {
|
|
|
37
62
|
return { method: "platform" };
|
|
38
63
|
}
|
|
39
64
|
else {
|
|
40
|
-
// Linux.
|
|
65
|
+
// Linux. Prefer controlled-stdio subprocess tools (Termux, Wayland,
|
|
66
|
+
// X11). Each owns the selection in its own process with stdout/stderr
|
|
67
|
+
// redirected to /dev/null, so its clipboard-ownership chatter cannot
|
|
68
|
+
// leak into the TUI — unlike the in-process native module (issue 286).
|
|
41
69
|
if (process.env.TERMUX_VERSION) {
|
|
42
70
|
try {
|
|
43
71
|
execSync("termux-clipboard-set", options);
|
|
@@ -54,8 +82,10 @@ export async function copyToClipboard(text) {
|
|
|
54
82
|
try {
|
|
55
83
|
// Verify wl-copy exists (spawn errors are async and won't be caught)
|
|
56
84
|
execSync("which wl-copy", { stdio: "ignore" });
|
|
57
|
-
// wl-copy with execSync hangs due to fork behavior; use spawn instead
|
|
58
|
-
|
|
85
|
+
// wl-copy with execSync hangs due to fork behavior; use spawn instead.
|
|
86
|
+
// detached: true puts wl-copy in its own session (setsid) so it keeps
|
|
87
|
+
// serving the clipboard after we exit and has no controlling terminal.
|
|
88
|
+
const proc = spawn("wl-copy", [], { stdio: ["pipe", "ignore", "ignore"], detached: true });
|
|
59
89
|
proc.on("error", () => {
|
|
60
90
|
// Spawn failed after which check (TOCTOU, permissions, etc.)
|
|
61
91
|
});
|
|
@@ -80,6 +110,13 @@ export async function copyToClipboard(text) {
|
|
|
80
110
|
copyToX11Clipboard(options);
|
|
81
111
|
return { method: "platform" };
|
|
82
112
|
}
|
|
113
|
+
// Linux last resort: the native module. Only reached when no CLI
|
|
114
|
+
// clipboard tool is available. This can reintroduce the issue-286
|
|
115
|
+
// stdout leak on X11 ownership changes, but a working clipboard beats
|
|
116
|
+
// none, and OSC 52 was already emitted above regardless.
|
|
117
|
+
if (await tryNativeClipboard(text)) {
|
|
118
|
+
return { method: "native" };
|
|
119
|
+
}
|
|
83
120
|
}
|
|
84
121
|
}
|
|
85
122
|
catch {
|
|
@@ -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":"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","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/settings.md
CHANGED
|
@@ -110,6 +110,24 @@ After the configured number of tool calls, dreb fires a single background LLM ca
|
|
|
110
110
|
}
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
### Context
|
|
114
|
+
|
|
115
|
+
| Setting | Type | Default | Description |
|
|
116
|
+
|---------|------|---------|-------------|
|
|
117
|
+
| `context.autoLoadNested` | boolean | `true` | Auto-load a directory's `AGENTS.md`/`CLAUDE.md` the first time a tool operates there |
|
|
118
|
+
|
|
119
|
+
When enabled, dreb loads nested context files that the startup upward-walk misses (e.g. a `CLAUDE.md` in a monorepo subpackage, or context in a different repo a subagent visits). The first tool to touch a directory triggers a walk up to a sensible ceiling — the session cwd for in-tree targets, otherwise the outermost git repo root, otherwise the outermost directory containing a context file — and the collected files are appended to that tool's result. Each file loads at most once per session. See [Context Files](../README.md#context-files).
|
|
120
|
+
|
|
121
|
+
**Security caution:** when working across untrusted or third-party repositories, their `AGENTS.md`/`CLAUDE.md` files may be auto-injected into the agent's context, which is a prompt-injection consideration. Set `context.autoLoadNested` to `false` to disable this behavior. Auto-loaded content is secret-scrubbed before injection, but extension `tool_result` transforms do not see it because nested context is injected after those transforms for cache safety.
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"context": {
|
|
126
|
+
"autoLoadNested": true
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
113
131
|
### Compaction
|
|
114
132
|
|
|
115
133
|
| Setting | Type | Default | Description |
|