shraga 0.1.46 → 0.1.48
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.48",
|
|
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
|
@@ -107,8 +107,8 @@ export class DataSync {
|
|
|
107
107
|
}
|
|
108
108
|
this.ensureGitignore();
|
|
109
109
|
if (!this.verifyDeploymentId()) return;
|
|
110
|
+
await this.configureGit(); // must precede untrackIgnored — it neutralises per-user excludes
|
|
110
111
|
await this.untrackIgnored();
|
|
111
|
-
await this.configureGit();
|
|
112
112
|
this.ready = true;
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -624,7 +624,13 @@ export class DataSync {
|
|
|
624
624
|
/** Commit a refreshed .gitignore and untrack any committed files that now match it. */
|
|
625
625
|
private async untrackIgnored(): Promise<void> {
|
|
626
626
|
await this.git('add', '.gitignore').catch(() => {});
|
|
627
|
-
|
|
627
|
+
// core.excludesFile=/dev/null: --exclude-standard would otherwise fold in the USER'S global
|
|
628
|
+
// gitignore (~/.gitignore_global) and untrack files that are ignored on this host only. The
|
|
629
|
+
// data repo is shared, so a personal rule must never decide what leaves the remote. Real
|
|
630
|
+
// near-miss 2026-08-20: a laptop's global `.agent/` rule staged 50 files for deletion —
|
|
631
|
+
// including workspace/.agent/memory/MEMORY.md, the agent's own memory — and only
|
|
632
|
+
// guardMassDeletions() stopped it. Only the repo's OWN .gitignore may untrack anything.
|
|
633
|
+
const out = await this.git('-c', 'core.excludesFile=/dev/null', 'ls-files', '-i', '-c', '--exclude-standard').catch(() => '');
|
|
628
634
|
// Only untrack files that actually exist on disk AND match .gitignore.
|
|
629
635
|
// git ls-files -i can falsely report tracked files missing from the worktree
|
|
630
636
|
// (remote-only files restored during init). Removing those wipes remote data.
|
|
@@ -673,6 +679,8 @@ export class DataSync {
|
|
|
673
679
|
const name = process.env.APP_NAME || 'shraga';
|
|
674
680
|
await this.git('config', 'user.name', `${name} agent`).catch(() => {});
|
|
675
681
|
await this.git('config', 'user.email', `agent@${name}.local`).catch(() => {});
|
|
682
|
+
// The data repo must behave identically on every host: no per-user global gitignore.
|
|
683
|
+
await this.git('config', 'core.excludesFile', '/dev/null').catch(() => {});
|
|
676
684
|
}
|
|
677
685
|
|
|
678
686
|
private authedUrl(): string {
|
|
@@ -25,6 +25,8 @@ export class SelfUpgrade {
|
|
|
25
25
|
|
|
26
26
|
private get o(): SelfUpgradeOptions { return this.options as SelfUpgradeOptions; }
|
|
27
27
|
|
|
28
|
+
private watching = false;
|
|
29
|
+
|
|
28
30
|
/** Version of `pkg` this deployment currently has pinned in its app-root package.json. */
|
|
29
31
|
public currentVersion(): string | null {
|
|
30
32
|
try {
|
|
@@ -170,7 +172,14 @@ export class SelfUpgrade {
|
|
|
170
172
|
*/
|
|
171
173
|
public deliverPendingReport(): UpgradeReport | null {
|
|
172
174
|
const report = this.lastReport();
|
|
173
|
-
if (!report)
|
|
175
|
+
if (!report) {
|
|
176
|
+
// The supervisor writes the report only AFTER this process has booted and soaked, so on a
|
|
177
|
+
// successful upgrade there is NOTHING here yet — boot always loses the race. Without this
|
|
178
|
+
// watch the outcome DM was never delivered, and the in-flight marker sat until it aged out
|
|
179
|
+
// (30m), blocking every retry in between. Keep watching while the marker is live.
|
|
180
|
+
if (this.inFlight()) this.watchForReport();
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
174
183
|
try { unlinkSync(this.o.reportFile); } catch { /* report already gone; emit anyway */ }
|
|
175
184
|
try { unlinkSync(this.o.lockFile); } catch { /* no lock to clear */ }
|
|
176
185
|
|
|
@@ -178,6 +187,21 @@ export class SelfUpgrade {
|
|
|
178
187
|
emitEvent('self-upgrade.finished', report);
|
|
179
188
|
return report;
|
|
180
189
|
}
|
|
190
|
+
|
|
191
|
+
/** Poll until the supervisor's report lands (or its marker ages out), then deliver it exactly
|
|
192
|
+
* once. Unref'd: a pending upgrade watch must never hold the process open. */
|
|
193
|
+
private watchForReport(): void {
|
|
194
|
+
if (this.watching) return;
|
|
195
|
+
this.watching = true;
|
|
196
|
+
const timer = setInterval(() => {
|
|
197
|
+
if (!this.inFlight()) { clearInterval(timer); this.watching = false; return; } // aged out
|
|
198
|
+
if (!existsSync(this.o.reportFile)) return;
|
|
199
|
+
clearInterval(timer);
|
|
200
|
+
this.watching = false;
|
|
201
|
+
this.deliverPendingReport();
|
|
202
|
+
}, this.o.reportPollMs);
|
|
203
|
+
timer.unref?.();
|
|
204
|
+
}
|
|
181
205
|
}
|
|
182
206
|
|
|
183
207
|
export class SelfUpgradeOptions {
|
|
@@ -193,6 +217,8 @@ export class SelfUpgradeOptions {
|
|
|
193
217
|
public supervisorScript: string = path.join(PACKAGE_ROOT, 'src', 'server', 'self-upgrade', 'supervisor.sh');
|
|
194
218
|
public reportFile: string = dataPath('.self-upgrade', 'report.json');
|
|
195
219
|
public lockFile: string = dataPath('.self-upgrade', 'in-flight.json');
|
|
220
|
+
/** How often the post-boot watch looks for the supervisor's report. */
|
|
221
|
+
public reportPollMs: number = 5_000;
|
|
196
222
|
public bootTimeoutSec: number = 180;
|
|
197
223
|
public soakSec: number = 60;
|
|
198
224
|
/** After this, an in-flight marker is treated as abandoned (supervisor killed by a reboot). */
|