synabun 2026.4.2-beta.0 → 2026.4.20-beta.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.
|
@@ -45,6 +45,13 @@ function runUpdate(key, badge) {
|
|
|
45
45
|
if (btn?.dataset.tooltipOriginal) btn.dataset.tooltip = btn.dataset.tooltipOriginal;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
+
// Poll for the new installed version after the install completes.
|
|
49
|
+
// npm-global installs typically take 5–45s; opencode upgrade similar.
|
|
50
|
+
// Re-check a few times, bail once the badge is cleared or retries run out.
|
|
51
|
+
const delays = [20000, 45000, 90000];
|
|
52
|
+
for (const ms of delays) {
|
|
53
|
+
setTimeout(() => { forceCheckToolUpdates(); }, ms);
|
|
54
|
+
}
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
function wireParentIntercept(parentId, badgeId, key) {
|
|
@@ -2224,10 +2224,36 @@ function resolveLocalCodexBin() {
|
|
|
2224
2224
|
}
|
|
2225
2225
|
}
|
|
2226
2226
|
|
|
2227
|
+
function ensureBundledCodexExecutable(bundledJsPath) {
|
|
2228
|
+
if (process.platform === 'win32') return;
|
|
2229
|
+
try {
|
|
2230
|
+
const st = statSync(bundledJsPath);
|
|
2231
|
+
if (!(st.mode & 0o111)) chmodSync(bundledJsPath, st.mode | 0o755);
|
|
2232
|
+
} catch {}
|
|
2233
|
+
const PLATFORM_PACKAGE = {
|
|
2234
|
+
'darwin-arm64': { pkg: '@openai/codex-darwin-arm64', triple: 'aarch64-apple-darwin' },
|
|
2235
|
+
'darwin-x64': { pkg: '@openai/codex-darwin-x64', triple: 'x86_64-apple-darwin' },
|
|
2236
|
+
'linux-x64': { pkg: '@openai/codex-linux-x64', triple: 'x86_64-unknown-linux-musl' },
|
|
2237
|
+
'linux-arm64': { pkg: '@openai/codex-linux-arm64', triple: 'aarch64-unknown-linux-musl' },
|
|
2238
|
+
};
|
|
2239
|
+
const info = PLATFORM_PACKAGE[`${process.platform}-${process.arch}`];
|
|
2240
|
+
if (!info) return;
|
|
2241
|
+
try {
|
|
2242
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
2243
|
+
const pkgJson = requireFromHere.resolve(`${info.pkg}/package.json`);
|
|
2244
|
+
const nativeBin = join(dirname(pkgJson), 'vendor', info.triple, 'codex', 'codex');
|
|
2245
|
+
if (existsSync(nativeBin)) {
|
|
2246
|
+
const st = statSync(nativeBin);
|
|
2247
|
+
if (!(st.mode & 0o111)) chmodSync(nativeBin, st.mode | 0o755);
|
|
2248
|
+
}
|
|
2249
|
+
} catch {}
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2227
2252
|
function getCodexBin() {
|
|
2228
2253
|
if (_codexBinPath) return _codexBinPath;
|
|
2229
2254
|
const bundledBin = resolveLocalCodexBin();
|
|
2230
2255
|
if (bundledBin && existsSync(bundledBin)) {
|
|
2256
|
+
ensureBundledCodexExecutable(bundledBin);
|
|
2231
2257
|
_codexBinPath = bundledBin;
|
|
2232
2258
|
_codexBinSource = 'bundled';
|
|
2233
2259
|
return _codexBinPath;
|
|
@@ -2879,7 +2905,7 @@ CRITICAL — When you need clarification during planning:
|
|
|
2879
2905
|
if (child) return child;
|
|
2880
2906
|
let codexBin = getCodexBin();
|
|
2881
2907
|
const args = ['app-server'];
|
|
2882
|
-
if (
|
|
2908
|
+
if (/\.js$/i.test(codexBin)) {
|
|
2883
2909
|
args.unshift(codexBin);
|
|
2884
2910
|
codexBin = process.execPath;
|
|
2885
2911
|
}
|
|
@@ -9689,7 +9715,12 @@ function semverNewer(a, b) {
|
|
|
9689
9715
|
}
|
|
9690
9716
|
|
|
9691
9717
|
async function checkToolVersions() {
|
|
9692
|
-
|
|
9718
|
+
// Strip node_modules/.bin entries so the version check reflects the user's
|
|
9719
|
+
// global install rather than any copy bundled with SynaBun itself.
|
|
9720
|
+
const augPath = getAugmentedPath()
|
|
9721
|
+
.split(delimiter)
|
|
9722
|
+
.filter(p => !/[\\/]node_modules[\\/]\.bin[\\/]?$/i.test(p))
|
|
9723
|
+
.join(delimiter);
|
|
9693
9724
|
const execOpts = { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, PATH: augPath } };
|
|
9694
9725
|
|
|
9695
9726
|
const results = {};
|
|
@@ -9739,7 +9770,7 @@ app.get('/api/system/tool-versions', async (req, res) => {
|
|
|
9739
9770
|
try { await checkToolVersions(); } catch {}
|
|
9740
9771
|
} else {
|
|
9741
9772
|
const age = Date.now() - new Date(_toolVersionCache.checkedAt).getTime();
|
|
9742
|
-
if (age >
|
|
9773
|
+
if (age > 15 * 60 * 1000) {
|
|
9743
9774
|
try { await checkToolVersions(); } catch {}
|
|
9744
9775
|
}
|
|
9745
9776
|
}
|
|
@@ -16989,8 +17020,9 @@ app.get('/api/terminal/branches', (req, res) => {
|
|
|
16989
17020
|
current = execSync('git branch --show-current', { cwd: dir, stdio: 'pipe', timeout: 3000 }).toString().trim();
|
|
16990
17021
|
} catch {}
|
|
16991
17022
|
|
|
16992
|
-
// All local branches
|
|
16993
|
-
const
|
|
17023
|
+
// All local branches — spawnSync with argv so the %(...) format isn't mangled by cmd.exe variable expansion on Windows
|
|
17024
|
+
const branchResult = spawnSync('git', ['branch', '--format=%(refname:short)'], { cwd: dir, stdio: 'pipe', timeout: 5000 });
|
|
17025
|
+
const raw = branchResult.status === 0 ? branchResult.stdout.toString().trim() : '';
|
|
16994
17026
|
const branches = raw ? raw.split('\n').map(b => b.trim()).filter(Boolean) : [];
|
|
16995
17027
|
|
|
16996
17028
|
res.json({ branches, current });
|
|
@@ -21279,6 +21311,7 @@ function handleWhiteboardWebSocket(ws) {
|
|
|
21279
21311
|
const buf = readFileSync(filePath);
|
|
21280
21312
|
const ext = extname(filename).toLowerCase();
|
|
21281
21313
|
const mime = ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' : 'image/png';
|
|
21314
|
+
if (_imageRecentlySeen(buf)) return;
|
|
21282
21315
|
// Save a copy to data/images/ for reliable access (avoids macOS temp/permission issues)
|
|
21283
21316
|
const localName = `screenshot-${Date.now()}${ext}`;
|
|
21284
21317
|
const localPath = join(IMAGES_DIR, localName);
|
|
@@ -21300,6 +21333,99 @@ function handleWhiteboardWebSocket(ws) {
|
|
|
21300
21333
|
}
|
|
21301
21334
|
}
|
|
21302
21335
|
|
|
21336
|
+
// Shared dedup across fs watcher + clipboard watcher — Win+PrtScr fires both.
|
|
21337
|
+
const _recentImageHashes = new Map();
|
|
21338
|
+
const _IMAGE_DEDUP_TTL = 5000;
|
|
21339
|
+
function _imageRecentlySeen(buf) {
|
|
21340
|
+
const hash = createHash('sha256').update(buf).digest('hex');
|
|
21341
|
+
const now = Date.now();
|
|
21342
|
+
for (const [h, ts] of _recentImageHashes) {
|
|
21343
|
+
if (now - ts > _IMAGE_DEDUP_TTL) _recentImageHashes.delete(h);
|
|
21344
|
+
}
|
|
21345
|
+
if (_recentImageHashes.has(hash)) return true;
|
|
21346
|
+
_recentImageHashes.set(hash, now);
|
|
21347
|
+
return false;
|
|
21348
|
+
}
|
|
21349
|
+
|
|
21350
|
+
// ── Clipboard Image Watcher → auto-paste to whiteboard (Windows only) ──
|
|
21351
|
+
// Mac screenshots always go to a file (handled above). On Windows, PrintScreen
|
|
21352
|
+
// and Snipping Tool often land only in the clipboard, so we poll it via PS.
|
|
21353
|
+
if (process.platform === 'win32') {
|
|
21354
|
+
const _psScript = [
|
|
21355
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
21356
|
+
"Add-Type -AssemblyName System.Drawing",
|
|
21357
|
+
"if ([System.Windows.Forms.Clipboard]::ContainsImage()) {",
|
|
21358
|
+
" try {",
|
|
21359
|
+
" $img = [System.Windows.Forms.Clipboard]::GetImage()",
|
|
21360
|
+
" $ms = New-Object System.IO.MemoryStream",
|
|
21361
|
+
" $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)",
|
|
21362
|
+
" [Convert]::ToBase64String($ms.ToArray())",
|
|
21363
|
+
" } catch { 'NONE' }",
|
|
21364
|
+
"} else { 'NONE' }",
|
|
21365
|
+
].join('; ');
|
|
21366
|
+
const _psEncoded = Buffer.from(_psScript, 'utf16le').toString('base64');
|
|
21367
|
+
const _psArgs = ['-Sta', '-NoProfile', '-NonInteractive', '-EncodedCommand', _psEncoded];
|
|
21368
|
+
|
|
21369
|
+
let _clipLastHash = null;
|
|
21370
|
+
let _clipPolling = false;
|
|
21371
|
+
let _clipFailures = 0;
|
|
21372
|
+
let _clipIntervalId = null;
|
|
21373
|
+
|
|
21374
|
+
const _pollClipboard = (seed = false) => {
|
|
21375
|
+
if (_clipPolling) return;
|
|
21376
|
+
_clipPolling = true;
|
|
21377
|
+
let child;
|
|
21378
|
+
try {
|
|
21379
|
+
child = spawn('powershell.exe', _psArgs, { windowsHide: true });
|
|
21380
|
+
} catch (err) {
|
|
21381
|
+
_clipPolling = false;
|
|
21382
|
+
if (_clipIntervalId) clearInterval(_clipIntervalId);
|
|
21383
|
+
console.warn('[clipboard-watcher] Could not spawn PowerShell:', err.message);
|
|
21384
|
+
return;
|
|
21385
|
+
}
|
|
21386
|
+
let stdout = '';
|
|
21387
|
+
const killTimer = setTimeout(() => { try { child.kill(); } catch {} }, 5000);
|
|
21388
|
+
child.stdout.on('data', (d) => { stdout += d.toString('utf8'); });
|
|
21389
|
+
child.on('error', () => { /* surface via close */ });
|
|
21390
|
+
child.on('close', (code) => {
|
|
21391
|
+
clearTimeout(killTimer);
|
|
21392
|
+
_clipPolling = false;
|
|
21393
|
+
if (code !== 0 && code !== null) {
|
|
21394
|
+
_clipFailures++;
|
|
21395
|
+
if (_clipFailures >= 5 && _clipIntervalId) {
|
|
21396
|
+
clearInterval(_clipIntervalId);
|
|
21397
|
+
console.warn('[clipboard-watcher] Disabled after repeated PowerShell failures');
|
|
21398
|
+
}
|
|
21399
|
+
return;
|
|
21400
|
+
}
|
|
21401
|
+
_clipFailures = 0;
|
|
21402
|
+
const out = stdout.trim();
|
|
21403
|
+
if (!out || out === 'NONE') return;
|
|
21404
|
+
try {
|
|
21405
|
+
const buf = Buffer.from(out, 'base64');
|
|
21406
|
+
if (buf.length < 100) return;
|
|
21407
|
+
const hash = createHash('sha256').update(buf).digest('hex');
|
|
21408
|
+
if (hash === _clipLastHash) return;
|
|
21409
|
+
_clipLastHash = hash;
|
|
21410
|
+
if (seed) return; // Suppress whatever was already in clipboard at startup
|
|
21411
|
+
if (_imageRecentlySeen(buf)) return;
|
|
21412
|
+
const localName = `screenshot-${Date.now()}.png`;
|
|
21413
|
+
const localPath = join(IMAGES_DIR, localName);
|
|
21414
|
+
try { writeFileSync(localPath, buf); } catch {}
|
|
21415
|
+
const dataUrl = `data:image/png;base64,${out}`;
|
|
21416
|
+
console.log(`[clipboard-watcher] New clipboard image (${(buf.length / 1024).toFixed(0)}KB) → ${localName}`);
|
|
21417
|
+
broadcastToWhiteboard({ type: 'screenshot:auto', dataUrl, filename: localName, localPath });
|
|
21418
|
+
} catch (err) {
|
|
21419
|
+
console.warn('[clipboard-watcher] Failed to process clipboard image:', err.message);
|
|
21420
|
+
}
|
|
21421
|
+
});
|
|
21422
|
+
};
|
|
21423
|
+
|
|
21424
|
+
_pollClipboard(true); // seed: record current clipboard hash without broadcasting
|
|
21425
|
+
_clipIntervalId = setInterval(() => _pollClipboard(false), 1500);
|
|
21426
|
+
console.log('[clipboard-watcher] Windows clipboard image polling enabled');
|
|
21427
|
+
}
|
|
21428
|
+
|
|
21303
21429
|
// ── Cards WebSocket clients (Claude MCP integration) ──
|
|
21304
21430
|
const cardsClients = new Set(); // Set<WebSocket>
|
|
21305
21431
|
const cardsPendingOps = new Map(); // requestId → { resolve, reject, timer }
|