neural-loom 0.3.0 → 0.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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/diagnostics/route-bundle-stats.json +1 -1
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page/react-loadable-manifest.json +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tq0maq._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tq0maq._.js.map +1 -1
- package/.next/server/middleware/middleware-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/middleware-manifest.json +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/{14~-ojwsxf~oe.js → 0jboepcsqzdio.js} +4 -4
- package/.next/static/chunks/{0uyuco-451j.n.js → 125kj92.ndij4.js} +1 -1
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/package.json +1 -1
- package/src/app/components/TerminalConsole.tsx +60 -19
- package/src/lib/agents/SessionPersister.ts +24 -8
- /package/.next/static/{04j4ht7eCOOXSLQ5Wixq0 → 3wzjx3BGzCrq3vZdFE3N-}/_buildManifest.js +0 -0
- /package/.next/static/{04j4ht7eCOOXSLQ5Wixq0 → 3wzjx3BGzCrq3vZdFE3N-}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{04j4ht7eCOOXSLQ5Wixq0 → 3wzjx3BGzCrq3vZdFE3N-}/_ssgManifest.js +0 -0
|
@@ -19,22 +19,33 @@ interface LogBufferItem {
|
|
|
19
19
|
message?: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
interface CopyResult {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
method?: "clipboard-api" | "execCommand";
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function copyTextToClipboard(text: string): Promise<CopyResult> {
|
|
29
|
+
if (typeof window === "undefined") return { ok: false, error: "no window" };
|
|
30
|
+
if (!text) return { ok: false, error: "empty selection" };
|
|
31
|
+
|
|
32
|
+
let apiError = "";
|
|
24
33
|
|
|
25
34
|
// Preferred path: async Clipboard API. Reliable in a secure context, which
|
|
26
35
|
// includes localhost (NeuralLoom's default bind).
|
|
27
36
|
if (window.isSecureContext && navigator.clipboard?.writeText) {
|
|
28
37
|
try {
|
|
29
38
|
await navigator.clipboard.writeText(text);
|
|
30
|
-
return true;
|
|
31
|
-
} catch {
|
|
32
|
-
// Fall through to the execCommand fallback (e.g.
|
|
39
|
+
return { ok: true, method: "clipboard-api" };
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// Fall through to the execCommand fallback (e.g. not focused / denied).
|
|
42
|
+
apiError = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
33
43
|
}
|
|
44
|
+
} else if (!window.isSecureContext) {
|
|
45
|
+
apiError = "not a secure context (access via http://localhost for the Clipboard API)";
|
|
34
46
|
}
|
|
35
47
|
|
|
36
|
-
// Fallback: hidden textarea + execCommand("copy")
|
|
37
|
-
// (e.g. accessed over a plain-HTTP LAN address).
|
|
48
|
+
// Fallback: hidden textarea + execCommand("copy").
|
|
38
49
|
try {
|
|
39
50
|
const textArea = document.createElement("textarea");
|
|
40
51
|
textArea.value = text;
|
|
@@ -47,10 +58,11 @@ async function copyTextToClipboard(text: string): Promise<boolean> {
|
|
|
47
58
|
textArea.select();
|
|
48
59
|
const successful = document.execCommand("copy");
|
|
49
60
|
document.body.removeChild(textArea);
|
|
50
|
-
return
|
|
61
|
+
if (successful) return { ok: true, method: "execCommand" };
|
|
62
|
+
return { ok: false, error: `execCommand returned false${apiError ? ` (clipboard API: ${apiError})` : ""}` };
|
|
51
63
|
} catch (err) {
|
|
52
|
-
|
|
53
|
-
return false;
|
|
64
|
+
const ex = err instanceof Error ? err.message : String(err);
|
|
65
|
+
return { ok: false, error: `${ex}${apiError ? ` | clipboard API: ${apiError}` : ""}` };
|
|
54
66
|
}
|
|
55
67
|
}
|
|
56
68
|
|
|
@@ -72,21 +84,34 @@ export default function TerminalConsole({ sessionId, height = "400px", wsPort =
|
|
|
72
84
|
const [caseSensitive, setCaseSensitive] = useState(false);
|
|
73
85
|
const searchAddonRef = useRef<SearchAddon | null>(null);
|
|
74
86
|
|
|
75
|
-
// Copy feedback ("Copied!" indicator)
|
|
87
|
+
// Copy feedback ("Copied!" indicator) and last error (surfaced for diagnosis)
|
|
76
88
|
const [copied, setCopied] = useState(false);
|
|
89
|
+
const [copyError, setCopyError] = useState<string | null>(null);
|
|
77
90
|
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
78
91
|
|
|
79
92
|
// Copy the given text (or the current terminal selection) to the clipboard.
|
|
80
93
|
// Stable identity so the terminal effect can use it without re-initializing.
|
|
81
|
-
|
|
94
|
+
// `explicit` = triggered by a button/shortcut (surface "nothing selected").
|
|
95
|
+
const copySelection = useCallback((text?: string, explicit = false): void => {
|
|
82
96
|
const term = xtermRef.current;
|
|
83
97
|
const selection = (text ?? term?.getSelection() ?? "").trim();
|
|
84
|
-
if (!selection)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
if (!selection) {
|
|
99
|
+
if (explicit) {
|
|
100
|
+
setCopyError("Nothing selected to copy");
|
|
101
|
+
setTimeout(() => setCopyError(null), 2500);
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
copyTextToClipboard(selection).then((result) => {
|
|
106
|
+
if (result.ok) {
|
|
107
|
+
setCopyError(null);
|
|
108
|
+
setCopied(true);
|
|
109
|
+
if (copiedTimerRef.current) clearTimeout(copiedTimerRef.current);
|
|
110
|
+
copiedTimerRef.current = setTimeout(() => setCopied(false), 1200);
|
|
111
|
+
} else {
|
|
112
|
+
console.error("[TerminalConsole] copy failed:", result.error);
|
|
113
|
+
setCopyError(result.error || "copy failed");
|
|
114
|
+
}
|
|
90
115
|
});
|
|
91
116
|
}, []);
|
|
92
117
|
|
|
@@ -578,8 +603,24 @@ export default function TerminalConsole({ sessionId, height = "400px", wsPort =
|
|
|
578
603
|
✓ Copied
|
|
579
604
|
</span>
|
|
580
605
|
)}
|
|
606
|
+
{copyError && (
|
|
607
|
+
<span
|
|
608
|
+
title={copyError}
|
|
609
|
+
style={{
|
|
610
|
+
color: "#ff6b6b",
|
|
611
|
+
fontSize: "0.65rem",
|
|
612
|
+
fontWeight: 600,
|
|
613
|
+
maxWidth: "260px",
|
|
614
|
+
overflow: "hidden",
|
|
615
|
+
textOverflow: "ellipsis",
|
|
616
|
+
whiteSpace: "nowrap",
|
|
617
|
+
}}
|
|
618
|
+
>
|
|
619
|
+
⚠ Copy failed: {copyError}
|
|
620
|
+
</span>
|
|
621
|
+
)}
|
|
581
622
|
<button
|
|
582
|
-
onClick={() => copySelection()}
|
|
623
|
+
onClick={() => copySelection(undefined, true)}
|
|
583
624
|
style={{
|
|
584
625
|
padding: "2px 6px",
|
|
585
626
|
borderRadius: "3px",
|
|
@@ -75,20 +75,36 @@ export class SessionPersister {
|
|
|
75
75
|
try {
|
|
76
76
|
const dir = this.getSessionsDir();
|
|
77
77
|
const filePath = path.join(dir, `${id}.json`);
|
|
78
|
+
const json = JSON.stringify(data, null, 2);
|
|
78
79
|
// Write to a temp file then rename, so a crash mid-write can't leave a
|
|
79
80
|
// truncated/corrupt session file (rename is atomic on the same filesystem).
|
|
80
81
|
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
81
|
-
fs.writeFile(tmpPath,
|
|
82
|
+
fs.writeFile(tmpPath, json, "utf8", (err) => {
|
|
82
83
|
if (err) {
|
|
83
|
-
console.error(`[SessionPersister] Failed to save session ${id}
|
|
84
|
+
console.error(`[SessionPersister] Failed to save session ${id}:`, err);
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
// On Windows the rename can transiently fail with EPERM/EACCES/EBUSY when
|
|
88
|
+
// the destination is momentarily locked (AV scanners, the search indexer,
|
|
89
|
+
// or another open handle). Retry briefly, then fall back to overwriting
|
|
90
|
+
// the destination directly so the save still lands.
|
|
91
|
+
const tryRename = (attempts: number) => {
|
|
92
|
+
fs.rename(tmpPath, filePath, (renameErr) => {
|
|
93
|
+
if (!renameErr) return;
|
|
94
|
+
const code = (renameErr as NodeJS.ErrnoException).code;
|
|
95
|
+
if ((code === "EPERM" || code === "EACCES" || code === "EBUSY") && attempts > 0) {
|
|
96
|
+
setTimeout(() => tryRename(attempts - 1), 60);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
fs.writeFile(filePath, json, "utf8", (writeErr) => {
|
|
100
|
+
if (writeErr) {
|
|
101
|
+
console.error(`[SessionPersister] Failed to save session ${id}:`, writeErr);
|
|
102
|
+
}
|
|
103
|
+
fs.unlink(tmpPath, () => {});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
tryRename(5);
|
|
92
108
|
});
|
|
93
109
|
} catch (err) {
|
|
94
110
|
console.error(`[SessionPersister] Failed to save session ${id}:`, err);
|
|
File without changes
|
/package/.next/static/{04j4ht7eCOOXSLQ5Wixq0 → 3wzjx3BGzCrq3vZdFE3N-}/_clientMiddlewareManifest.js
RENAMED
|
File without changes
|
|
File without changes
|