shraga 0.1.48 → 0.1.50
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shraga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.50",
|
|
4
4
|
"description": "The teammate you delegate coding to — a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
package/src/server/data-sync.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { hostname } from 'node:os';
|
|
4
5
|
import { DATA_DIR } from './paths.ts';
|
|
@@ -116,10 +117,12 @@ export class DataSync {
|
|
|
116
117
|
// Defer heavy sync I/O (reads all tracked files + execSync) to avoid blocking
|
|
117
118
|
// WS connections and page loads during startup.
|
|
118
119
|
setTimeout(() => {
|
|
119
|
-
this.scanForConflictMarkers()
|
|
120
|
-
console.warn(`${TAG} Post-init conflict scan failed:`, (err as Error).message)
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
this.scanForConflictMarkers()
|
|
121
|
+
.catch(err => console.warn(`${TAG} Post-init conflict scan failed:`, (err as Error).message))
|
|
122
|
+
// execSync inside the audit blocks too — keep it off the scan's tick so the two
|
|
123
|
+
// never add up into one long freeze.
|
|
124
|
+
.then(() => new Promise<void>(r => setImmediate(r)))
|
|
125
|
+
.then(() => this.runIntegrityAudit());
|
|
123
126
|
}, 60_000);
|
|
124
127
|
}
|
|
125
128
|
|
|
@@ -442,13 +445,20 @@ export class DataSync {
|
|
|
442
445
|
try {
|
|
443
446
|
const tracked = (await this.git('ls-files')).split('\n').filter(Boolean);
|
|
444
447
|
const conflicted: string[] = [];
|
|
445
|
-
|
|
446
|
-
|
|
448
|
+
// ASYNC + YIELDING on purpose. This walks every tracked file (1000+, GBs on a real
|
|
449
|
+
// deployment); doing it synchronously froze the event loop for ~45s — the server stopped
|
|
450
|
+
// answering /api/version entirely, which also made self-upgrade verification fail and
|
|
451
|
+
// auto-revert healthy versions. Deferring a synchronous freeze only moves it; it has to
|
|
452
|
+
// not block at all.
|
|
453
|
+
for (let i = 0; i < tracked.length; i++) {
|
|
454
|
+
const abs = path.join(DATA_DIR, tracked[i]);
|
|
447
455
|
try {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
456
|
+
const info = await stat(abs).catch(() => null);
|
|
457
|
+
if (!info || info.isDirectory()) continue;
|
|
458
|
+
const content = await readFile(abs, 'utf-8');
|
|
459
|
+
if (/^<{7} /m.test(content)) conflicted.push(tracked[i]);
|
|
451
460
|
} catch { /* skip unreadable */ }
|
|
461
|
+
if (i % 25 === 24) await new Promise<void>(r => setImmediate(r)); // let the server breathe
|
|
452
462
|
}
|
|
453
463
|
if (!conflicted.length) return;
|
|
454
464
|
|
|
@@ -181,7 +181,13 @@ export class SelfUpgrade {
|
|
|
181
181
|
return null;
|
|
182
182
|
}
|
|
183
183
|
try { unlinkSync(this.o.reportFile); } catch { /* report already gone; emit anyway */ }
|
|
184
|
-
|
|
184
|
+
// Clear the marker only if it belongs to THIS report. A boot can find a leftover report from
|
|
185
|
+
// the previous upgrade while a NEW one is already in flight (that is exactly the sequence when
|
|
186
|
+
// two upgrades run back to back) — deleting that marker would unguard the live attempt.
|
|
187
|
+
const marker = this.inFlight();
|
|
188
|
+
if (!marker || marker.target === report.target) {
|
|
189
|
+
try { unlinkSync(this.o.lockFile); } catch { /* no lock to clear */ }
|
|
190
|
+
}
|
|
185
191
|
|
|
186
192
|
console.log(`${TAG} ${report.status}: ${report.detail}`);
|
|
187
193
|
emitEvent('self-upgrade.finished', report);
|
|
@@ -136,10 +136,22 @@ wait_for_version() { # version timeout
|
|
|
136
136
|
|
|
137
137
|
# A server that boots, flips the version, then dies 20s later has NOT upgraded successfully — that is
|
|
138
138
|
# exactly the crash-loop an unattended upgrade must catch. Keep probing for the whole soak window.
|
|
139
|
+
# One dropped probe is not a crash-loop. This runs on a busy box where a single request can lose a
|
|
140
|
+
# 10s race (MCP mounts, a GC pause, the drain window of the restart we just did), and a zero-
|
|
141
|
+
# tolerance soak turns that blip into an automatic rollback of a perfectly good version — observed
|
|
142
|
+
# reverting 0.1.48 on feedox while the process stayed up throughout. Require CONSECUTIVE misses, so
|
|
143
|
+
# a real crash (which never answers again) still fails fast.
|
|
144
|
+
SOAK_MISS_LIMIT="${SOAK_MISS_LIMIT:-3}"
|
|
139
145
|
soak() { # version seconds
|
|
140
|
-
local deadline=$(( SECONDS + $2 ))
|
|
146
|
+
local deadline=$(( SECONDS + $2 )) misses=0
|
|
141
147
|
while [ "$SECONDS" -lt "$deadline" ]; do
|
|
142
|
-
reports_version "$1"
|
|
148
|
+
if reports_version "$1"; then
|
|
149
|
+
misses=0
|
|
150
|
+
else
|
|
151
|
+
misses=$(( misses + 1 ))
|
|
152
|
+
say "soak probe missed ($misses/$SOAK_MISS_LIMIT)"
|
|
153
|
+
[ "$misses" -ge "$SOAK_MISS_LIMIT" ] && return 1
|
|
154
|
+
fi
|
|
143
155
|
sleep 5
|
|
144
156
|
done
|
|
145
157
|
return 0
|