From 42c864f3e3458f29b49b1dcd870c04f6a6fc0740 Mon Sep 17 00:00:00 2001 From: Serge Alex Date: Thu, 5 Nov 2026 14:46:32 +0300 Subject: [PATCH] fix-status-indicator-and-add-scripts --- bridge/config.example.json | 13 +++++++++ bridge/config.json | 13 --------- content/content.js | 21 ++++++++++++-- content/overlay.css | 3 ++ content/panel.js | 2 +- scripts/pair.ps1 | 67 ++++++++++++++++++++++++++++++++++++++++++++ scripts/start-dsh-memory.cmd | 21 ++++++++++++++ scripts/stop-dsh-memory.cmd | 12 ++++++++ 8 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 bridge/config.example.json delete mode 100644 bridge/config.json create mode 100644 scripts/pair.ps1 create mode 100644 scripts/start-dsh-memory.cmd create mode 100644 scripts/stop-dsh-memory.cmd diff --git a/bridge/config.example.json b/bridge/config.example.json new file mode 100644 index 0000000..894f7a5 --- /dev/null +++ b/bridge/config.example.json @@ -0,0 +1,13 @@ +{ + "token": "", + "port": 8766, + "allowedRoots": [ + "V:/Site" + ], + "bashTimeoutMs": 60000, + "bashTimeoutMaxMs": 120000, + "handoffTimeoutMs": 300000, + "handoffTimeoutMaxMs": 1800000, + "dailySendLimit": 100000, + "memoryDir": "memory" +} diff --git a/bridge/config.json b/bridge/config.json deleted file mode 100644 index 242b9b2..0000000 --- a/bridge/config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "token": "6d6f58acd21d37bea9a768616b189c5c858c8da26e0faecbf219f34172b5e826", - "port": 8766, - "allowedRoots": [ - "V:/Site" - ], - "bashTimeoutMs": 60000, - "bashTimeoutMaxMs": 120000, - "handoffTimeoutMs": 300000, - "handoffTimeoutMaxMs": 1800000, - "dailySendLimit": 100000, - "memoryDir": "memory" -} diff --git a/content/content.js b/content/content.js index 5a3b74d..bf23e6e 100644 --- a/content/content.js +++ b/content/content.js @@ -15,7 +15,7 @@ var CALL_HASH_CAP = 100; var cfgToken = ''; var execMode = 'dom'; var lastSsePid = ''; var domDebugSeen = new Set(); var domDebugCount = 0; if (UI) { UI.onResetLoops(function () { queue.length = 0; pending.clear(); seenHashes.clear(); pumping = false; if (UI) UI.log('loops reset'); }); - UI.onStop(function () { queue.length = 0; started = false; try { obs.disconnect(); } catch (e) {} if (UI) { UI.setPhase('off'); UI.setStartEnabled(true); UI.log('stopped'); } }); + UI.onStop(function () { queue.length = 0; started = false; try { obs.disconnect(); } catch (e) {} if (UI) { UI.setPhase('off'); UI.setStartEnabled(true); if (UI.setDot) UI.setDot('stopped'); UI.log('stopped'); } }); UI.onSaveToken(function (v) { chrome.storage.local.set({ 'dshmem-bridge-token': v }, function () { if (UI) UI.log('token saved, reload page'); }); }); } if (UI) { UI.onStart(function () { if (!canStart) { if (UI.setStartEnabled) UI.setStartEnabled(true); return; } started = true; markExistingAsProcessed(); obs.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); UI.setPhase('idle'); UI.setConnected(true); UI.log('started'); }); } @@ -276,7 +276,7 @@ if (parsed.calls.length === 0) { if (hasMarker) { try { var __dh = hashText(text if (results.length > 0) { var payload = buildResultMessage(results); if (UI) UI.setPhase('sending'); - var anyFail = results.some(function (r) { return !r || !r.ok; }); sendText(payload).then(function () { if (UI) { UI.setPhase('idle'); if (UI.setDot) UI.setDot(anyFail ? 'busy' : 'ok'); } }).catch(function (err) { console.warn('[dsh-memory] sendText failed', String(err)); if (UI) { UI.setPhase('idle'); if (UI.setDot) UI.setDot('busy'); } }); + var anyFail = results.some(function (r) { return !r || !r.ok; }); sendText(payload).then(function () { if (UI) { UI.setPhase('idle'); if (UI.setDot) UI.setDot(anyFail ? 'error' : 'ok'); } }).catch(function (err) { console.warn('[dsh-memory] sendText failed', String(err)); if (UI) { UI.setPhase('idle'); if (UI.setDot) UI.setDot('error'); } }); } else { if (UI) UI.setPhase('idle'); } return; } @@ -354,7 +354,22 @@ if (parsed.calls.length === 0) { if (hasMarker) { try { var __dh = hashText(text characterData: true }); console.log("[dsh-memory] content.js active"); - if (UI) { UI.setPhase('off'); UI.setConnected(true); canStart = true; UI.log('ready, press Start'); } __refreshStats(); + BRIDGE.health().then(function (r) { + if (UI) { + UI.setPhase('off'); + UI.setConnected(!!(r && r.ok)); + if (!r || !r.ok) UI.log('bridge down: ' + ((r && r.error && r.error.message) || '?')); + else UI.log('ready, press Start'); + } + canStart = true; + }).catch(function (e) { + if (UI) { UI.setPhase('off'); UI.setConnected(false); UI.log('bridge down: ' + String(e)); } + canStart = true; + }); + setInterval(function () { + BRIDGE.health().then(function (r) { if (UI && UI.setConnected) UI.setConnected(!!(r && r.ok)); }); + }, 30000); + __refreshStats(); }); }); function __refreshStats() { try { var SB = globalThis.STORAGE_BRIDGE; if (!SB || !UI || !UI.setStats) return; SB.getState().then(function (st) { var mN = st && st.memories ? Object.keys(st.memories).length : 0; var sN = st && st.skills && st.skills.length ? st.skills.length : 0; return SB.authHeaders().then(function (h) { return fetch(SB.BU + '/health', { headers: h }); }).then(function (r) { return r.json(); }).then(function (j) { UI.setStats((j && j.workspace) || '?', mN, sN); }); }).catch(function () { UI.setStats('?', 0, 0); }); } catch (e) { console.warn('[dsh-memory] stats ' + String(e)); } } diff --git a/content/overlay.css b/content/overlay.css index ed797ce..7622225 100644 --- a/content/overlay.css +++ b/content/overlay.css @@ -36,6 +36,9 @@ .dshmem-dot.ok { background: #34c759; } .dshmem-dot.err { background: #ff5f57; } .dshmem-dot.busy { background: #ffcc00; animation: dshmem-blink 1s infinite; } .dshmem-dot.ok-busy { background: #34c759; animation: dshmem-blink 1s infinite; } +.dshmem-dot.off { background: #666; } +.dshmem-dot.stopped { background: #666; } +.dshmem-dot.error { background: #ff5f57; animation: dshmem-blink 1s infinite; } @keyframes dshmem-blink { 50% { opacity: 0.35; } } .dshmem-title { font-weight: 600; letter-spacing: 0.4px; } diff --git a/content/panel.js b/content/panel.js index 1414f1e..13052ab 100644 --- a/content/panel.js +++ b/content/panel.js @@ -75,7 +75,7 @@ } var API = { setPhase: function (p) { state.phase = p; if (el.phase) el.phase.textContent = p; }, - setConnected: function (b) { state.connected = !!b; API._applyDot(); }, setDot: function (m) { state.dotMode = (m === 'ok') ? null : m; API._applyDot(); }, _applyDot: function () { if (!el.dot) return; var md = state.dotMode || (state.connected ? 'ok' : 'err'); var cls = 'dshmem-dot'; if (md === 'ok' || md === 'err' || md === 'busy' || md === 'ok-busy') cls += ' ' + md; el.dot.className = cls; }, + setConnected: function (b) { state.connected = !!b; API._applyDot(); }, setDot: function (m) { state.dotMode = m || null; API._applyDot(); }, _applyDot: function () { if (!el.dot) return; var cls = 'dshmem-dot'; if (!state.connected) { cls += ' err'; } else { var m = state.dotMode || 'off'; if (m === 'busy' || m === 'ok-busy') cls += ' busy'; else if (m === 'error') cls += ' error'; else if (m === 'stopped') cls += ' stopped'; else if (m === 'ok') cls += ' ok'; else cls += ' off'; } el.dot.className = cls; }, setStartEnabled: function (b) { if (el.start) el.start.disabled = !b; }, setStats: function (ws, memN, skillN) { if (!el.stats) return; el.stats.textContent = 'WS: ' + (ws || '?') + ' mem: ' + (memN || 0) + ' skills: ' + (skillN || 0); }, setMetrics: function (m) { if (!el.metrics) return; if (!m) { el.metrics.textContent = 'metrics: —'; return; } state.lastMetrics = m; var __pct = 0; if (state.accLimit > 0) __pct = Math.round((m.accum || 0) * 100 / state.accLimit); var __cls = 'dshmem-acc-ok'; if (__pct > 100) __cls = 'dshmem-acc-crit'; else if (__pct >= 80) __cls = 'dshmem-acc-err'; else if (__pct >= 40) __cls = 'dshmem-acc-warn'; el.metrics.className = 'dshmem-stats ' + __cls; el.metrics.textContent = 'acc: ' + m.accum + ' · turn: ' + m.delta + ' · msg: ' + m.chars + 'ch (~' + m.est_tok + 't) ctx ' + (m.ctx_tok || 0) + 't ' + (m.ctx_pct || 0) + 'pct'; }, toggleForceInject: function () { state.forceInject = !state.forceInject; diff --git a/scripts/pair.ps1 b/scripts/pair.ps1 new file mode 100644 index 0000000..12bb792 --- /dev/null +++ b/scripts/pair.ps1 @@ -0,0 +1,67 @@ +# dsh-memory pair script +# Generates a random 64-hex token, writes it into bridge/config.json, +# copies it to the clipboard, and starts the bridge if not running. +# Usage: powershell -NoProfile -ExecutionPolicy Bypass -File pair.ps1 +$ErrorActionPreference = 'Stop' + +$root = Split-Path -Parent $PSScriptRoot +$cfgPath = Join-Path $root 'bridge\config.json' + +# Load or create config +if (Test-Path $cfgPath) { + $raw = Get-Content $cfgPath -Raw -Encoding UTF8 + $cfg = $raw | ConvertFrom-Json +} else { + Write-Host "[dsh-memory] config.json not found, creating defaults." + $cfg = [pscustomobject]@{ + token = '' + port = 8766 + allowedRoots = @((Split-Path -Parent $root)) + bashTimeoutMs = 20000 + bashTimeoutMaxMs = 120000 + handoffTimeoutMs = 300000 + handoffTimeoutMaxMs = 1800000 + dailySendLimit = 200 + } +} + +# Generate 32 random bytes -> 64 hex chars +$bytes = New-Object byte[] 32 +$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() +$rng.GetBytes($bytes) +$token = -join ($bytes | ForEach-Object { $_.ToString('x2') }) +$rng.Dispose() + +$cfg.token = $token +if (-not $cfg.allowedRoots -or $cfg.allowedRoots.Count -eq 0) { + $cfg | Add-Member -NotePropertyName allowedRoots -NotePropertyValue @((Split-Path -Parent $root)) -Force +} +$cfg | ConvertTo-Json -Depth 5 | Set-Content $cfgPath -Encoding UTF8 +Write-Host "[dsh-memory] token written to $cfgPath" + +# Copy token to clipboard +try { + Set-Clipboard -Value $token + Write-Host "[dsh-memory] token copied to clipboard. Paste it into the dsh-memory panel." +} catch { + Write-Host "[dsh-memory] Could not access clipboard. Token value:" + Write-Host $token +} + +# Start bridge if not already listening +$port = if ($cfg.port) { [int]$cfg.port } else { 8766 } +$listening = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue +if ($listening) { + Write-Host "[dsh-memory] bridge already running on port $port." +} else { + Write-Host "[dsh-memory] starting bridge on port $port ..." + $serverJs = Join-Path $root 'bridge\server.js' + Start-Process -FilePath 'cmd.exe' -ArgumentList '/c', "title dsh-memory bridge && node `"$serverJs`" & pause" -WorkingDirectory $root + Start-Sleep -Seconds 2 + $check = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue + if ($check) { + Write-Host "[dsh-memory] bridge started." + } else { + Write-Host "[dsh-memory] bridge may have failed to start. Is node in PATH? Check the bridge window for errors." + } +} diff --git a/scripts/start-dsh-memory.cmd b/scripts/start-dsh-memory.cmd new file mode 100644 index 0000000..2cce072 --- /dev/null +++ b/scripts/start-dsh-memory.cmd @@ -0,0 +1,21 @@ +@echo off +rem dsh-memory - idempotent bridge starter +setlocal +set "ROOT=%~dp0.." + +if not exist "%ROOT%\bridge\config.json" ( + echo [dsh-memory] bridge\config.json not found. Run scripts\pair.ps1 first. + pause + exit /b 1 +) + +powershell -NoProfile -Command "if (Get-NetTCPConnection -LocalPort 8766 -State Listen -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }" +if %errorlevel%==0 ( + echo [dsh-memory] bridge already running on port 8766. +) else ( + echo [dsh-memory] starting bridge... + start "dsh-memory bridge" /D "%ROOT%" cmd /c "title dsh-memory bridge && node bridge\server.js & pause" + ping -n 3 127.0.0.1 >nul + powershell -NoProfile -Command "if (Get-NetTCPConnection -LocalPort 8766 -State Listen -ErrorAction SilentlyContinue) { Write-Host '[dsh-memory] bridge started.' } else { Write-Host '[dsh-memory] bridge NOT started. Is node in PATH? Check the bridge window.' }" +) +endlocal diff --git a/scripts/stop-dsh-memory.cmd b/scripts/stop-dsh-memory.cmd new file mode 100644 index 0000000..61fd040 --- /dev/null +++ b/scripts/stop-dsh-memory.cmd @@ -0,0 +1,12 @@ +@echo off +rem dsh-memory - stop bridge (graceful via /shutdown) and clean up leftover dsh headless processes +setlocal +set "ROOT=%~dp0.." + +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$cfgPath = Join-Path (Split-Path -Parent '%~dp0') 'bridge\config.json';" ^ + "try { $cfg = Get-Content $cfgPath -Raw -Encoding UTF8 | ConvertFrom-Json; $port = if ($cfg.port) { $cfg.port } else { 8766 }; Invoke-RestMethod -Method Post -Uri ('http://127.0.0.1:' + $port + '/shutdown') -Headers @{ Authorization = ('Bearer ' + $cfg.token) } -TimeoutSec 5 | Out-Null; Write-Host '[dsh-memory] bridge stopped.' } catch { Write-Host '[dsh-memory] bridge not reachable (already stopped?)' };" ^ + "$dsh = Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" | Where-Object { $_.CommandLine -like '*@deepseek-ai*dsh*bin.js*--profile headless*' };" ^ + "foreach ($p in $dsh) { Write-Host ('[dsh-memory] killing leftover dsh headless pid ' + $p.ProcessId); Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue }" + +endlocal