@productbrain/cli 0.1.0-beta.4957 → 0.1.0-beta.4965
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.
|
@@ -1,25 +1,83 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
3
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
4
|
+
const NOW_MS = new Date('2026-08-25T12:00:00.000Z').getTime();
|
|
5
|
+
const CONFIG_DIR = '/mock-home/.config/productbrain';
|
|
2
6
|
const files = new Map();
|
|
7
|
+
const statErrors = new Set();
|
|
8
|
+
const unlinkErrors = new Set();
|
|
9
|
+
let onUnlink = null;
|
|
10
|
+
let nextIno = 1;
|
|
11
|
+
function markerPath(name) {
|
|
12
|
+
return `${CONFIG_DIR}/${name}`;
|
|
13
|
+
}
|
|
14
|
+
function putMarker(name, ageMs = 0) {
|
|
15
|
+
const path = markerPath(name);
|
|
16
|
+
files.set(path, { mtimeMs: Date.now() - ageMs, ino: nextIno++ });
|
|
17
|
+
return path;
|
|
18
|
+
}
|
|
3
19
|
vi.mock('node:os', () => ({ homedir: () => '/mock-home' }));
|
|
4
20
|
vi.mock('node:fs', () => ({
|
|
5
21
|
existsSync: (p) => files.has(p),
|
|
6
22
|
mkdirSync: vi.fn(),
|
|
7
|
-
statSync: () =>
|
|
23
|
+
statSync: (p) => {
|
|
24
|
+
if (statErrors.has(p))
|
|
25
|
+
throw new Error('stat race');
|
|
26
|
+
const file = files.get(p);
|
|
27
|
+
if (!file)
|
|
28
|
+
throw new Error('ENOENT');
|
|
29
|
+
return { mtimeMs: file.mtimeMs, dev: 1, ino: file.ino };
|
|
30
|
+
},
|
|
8
31
|
readdirSync: () => [...files.keys()].map((p) => p.split('/').pop()),
|
|
9
|
-
unlinkSync: (p) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
32
|
+
unlinkSync: (p) => {
|
|
33
|
+
if (unlinkErrors.has(p))
|
|
34
|
+
throw new Error('unlink race');
|
|
35
|
+
onUnlink?.(p);
|
|
36
|
+
files.delete(p);
|
|
37
|
+
},
|
|
38
|
+
utimesSync: (p, _atime, mtime) => {
|
|
39
|
+
const file = files.get(p);
|
|
40
|
+
if (!file)
|
|
41
|
+
throw new Error('ENOENT');
|
|
42
|
+
file.mtimeMs = mtime.getTime();
|
|
43
|
+
},
|
|
44
|
+
linkSync: (source, destination) => {
|
|
45
|
+
const file = files.get(source);
|
|
46
|
+
if (!file)
|
|
47
|
+
throw new Error('ENOENT');
|
|
48
|
+
if (files.has(destination))
|
|
49
|
+
throw new Error('EEXIST');
|
|
50
|
+
files.set(destination, file);
|
|
51
|
+
},
|
|
52
|
+
openSync: (p) => {
|
|
53
|
+
if (files.has(p)) {
|
|
54
|
+
const error = new Error('EEXIST');
|
|
55
|
+
error.code = 'EEXIST';
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
files.set(p, { mtimeMs: Date.now(), ino: nextIno++ });
|
|
59
|
+
return 1;
|
|
60
|
+
},
|
|
15
61
|
closeSync: vi.fn(),
|
|
16
62
|
}));
|
|
17
63
|
vi.mock('../lib/session.js', () => ({ readSession: vi.fn(() => ({ sessionId: 'sess-abc' })) }));
|
|
18
64
|
import { claimNotice } from '../lib/notice-marker.js';
|
|
19
65
|
import { readSession } from '../lib/session.js';
|
|
20
66
|
describe('notice-marker', () => {
|
|
21
|
-
beforeEach(() =>
|
|
22
|
-
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
vi.useFakeTimers();
|
|
69
|
+
vi.setSystemTime(NOW_MS);
|
|
70
|
+
files.clear();
|
|
71
|
+
nextIno = 1;
|
|
72
|
+
statErrors.clear();
|
|
73
|
+
unlinkErrors.clear();
|
|
74
|
+
onUnlink = null;
|
|
75
|
+
vi.mocked(readSession).mockReturnValue({ sessionId: 'sess-abc' });
|
|
76
|
+
});
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
vi.useRealTimers();
|
|
79
|
+
vi.clearAllMocks();
|
|
80
|
+
});
|
|
23
81
|
it('first claim in a session wins, second is silent', () => {
|
|
24
82
|
expect(claimNotice('agent')).toBe(true);
|
|
25
83
|
expect(claimNotice('agent')).toBe(false);
|
|
@@ -28,10 +86,87 @@ describe('notice-marker', () => {
|
|
|
28
86
|
expect(claimNotice('agent')).toBe(true);
|
|
29
87
|
expect(claimNotice('human')).toBe(true);
|
|
30
88
|
});
|
|
89
|
+
it('prunes stale markers before a fresh current marker loses the atomic claim', () => {
|
|
90
|
+
const current = putMarker('notice-session-sess-abc');
|
|
91
|
+
const staleSession = putMarker('notice-session-old', 8 * HOUR_MS + 1);
|
|
92
|
+
const staleDaily = putMarker('notice-agent-daily-old', 7 * DAY_MS + 1);
|
|
93
|
+
const freshDaily = putMarker('notice-human-current', 7 * DAY_MS);
|
|
94
|
+
const unrelated = putMarker('profiles.json', 30 * DAY_MS);
|
|
95
|
+
expect(claimNotice('agent')).toBe(false);
|
|
96
|
+
expect(files.has(current)).toBe(true);
|
|
97
|
+
expect(files.has(staleSession)).toBe(false);
|
|
98
|
+
expect(files.has(staleDaily)).toBe(false);
|
|
99
|
+
expect(files.has(freshDaily)).toBe(true);
|
|
100
|
+
expect(files.has(unrelated)).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
it('keeps daily markers that are older than a session but younger than seven days', () => {
|
|
103
|
+
const staleSession = putMarker('notice-session-old', 8 * HOUR_MS + 1);
|
|
104
|
+
const sameAgeDaily = putMarker('notice-agent-daily-recent', 8 * HOUR_MS + 1);
|
|
105
|
+
expect(claimNotice('agent')).toBe(true);
|
|
106
|
+
expect(files.has(staleSession)).toBe(false);
|
|
107
|
+
expect(files.has(sameAgeDaily)).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
it('keeps a stale marker for a renewed current session and refreshes its heartbeat', () => {
|
|
110
|
+
const current = putMarker('notice-session-sess-abc', 8 * HOUR_MS + 1);
|
|
111
|
+
expect(claimNotice('agent')).toBe(false);
|
|
112
|
+
expect(files.get(current)?.mtimeMs).toBe(NOW_MS);
|
|
113
|
+
});
|
|
114
|
+
it('locks stale eviction so a concurrent claim cannot replace the marker before unlink', () => {
|
|
115
|
+
const current = putMarker('notice-session-sess-abc');
|
|
116
|
+
const stale = putMarker('notice-session-old', 9 * HOUR_MS);
|
|
117
|
+
let concurrentClaim = null;
|
|
118
|
+
onUnlink = (path) => {
|
|
119
|
+
if (path !== stale)
|
|
120
|
+
return;
|
|
121
|
+
vi.mocked(readSession).mockReturnValue({ sessionId: 'old' });
|
|
122
|
+
concurrentClaim = claimNotice('agent');
|
|
123
|
+
vi.mocked(readSession).mockReturnValue({ sessionId: 'sess-abc' });
|
|
124
|
+
};
|
|
125
|
+
expect(claimNotice('agent')).toBe(false);
|
|
126
|
+
expect(concurrentClaim).toBe(false);
|
|
127
|
+
expect(files.has(stale)).toBe(false);
|
|
128
|
+
expect(files.has(current)).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
it('reclaims abandoned marker locks and stale recovery residue', () => {
|
|
131
|
+
const currentLock = putMarker('.notice-marker-lock-notice-session-sess-abc', HOUR_MS);
|
|
132
|
+
const orphanLock = putMarker('.notice-marker-lock-notice-session-old', HOUR_MS);
|
|
133
|
+
const orphanRecovery = putMarker('.notice-marker-lock-notice-session-old.reclaim', HOUR_MS);
|
|
134
|
+
const orphanPin = putMarker('.notice-marker-lock-notice-session-old.reclaim.target', HOUR_MS);
|
|
135
|
+
expect(claimNotice('agent')).toBe(true);
|
|
136
|
+
expect(files.has(currentLock)).toBe(false);
|
|
137
|
+
expect(files.has(orphanLock)).toBe(false);
|
|
138
|
+
expect(files.has(orphanRecovery)).toBe(false);
|
|
139
|
+
expect(files.has(orphanPin)).toBe(false);
|
|
140
|
+
expect(files.has(markerPath('notice-session-sess-abc'))).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
it('continues pruning after per-file stat and unlink failures', () => {
|
|
143
|
+
const current = putMarker('notice-session-sess-abc');
|
|
144
|
+
const statRace = putMarker('notice-session-stat-race', 9 * HOUR_MS);
|
|
145
|
+
const unlinkRace = putMarker('notice-session-unlink-race', 9 * HOUR_MS);
|
|
146
|
+
const removable = putMarker('notice-session-removable', 9 * HOUR_MS);
|
|
147
|
+
statErrors.add(statRace);
|
|
148
|
+
unlinkErrors.add(unlinkRace);
|
|
149
|
+
expect(claimNotice('agent')).toBe(false);
|
|
150
|
+
expect(files.has(current)).toBe(true);
|
|
151
|
+
expect(files.has(statRace)).toBe(true);
|
|
152
|
+
expect(files.has(unlinkRace)).toBe(true);
|
|
153
|
+
expect(files.has(removable)).toBe(false);
|
|
154
|
+
});
|
|
31
155
|
});
|
|
32
156
|
describe('notice-marker — null session (daily fallback key)', () => {
|
|
33
|
-
beforeEach(() =>
|
|
34
|
-
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
vi.useFakeTimers();
|
|
159
|
+
vi.setSystemTime(NOW_MS);
|
|
160
|
+
files.clear();
|
|
161
|
+
nextIno = 1;
|
|
162
|
+
statErrors.clear();
|
|
163
|
+
unlinkErrors.clear();
|
|
164
|
+
onUnlink = null;
|
|
165
|
+
});
|
|
166
|
+
afterEach(() => {
|
|
167
|
+
vi.useRealTimers();
|
|
168
|
+
vi.clearAllMocks();
|
|
169
|
+
});
|
|
35
170
|
it('missing sessionId → daily fallback key: first call true, second false same UTC day', () => {
|
|
36
171
|
vi.mocked(readSession).mockReturnValue(null);
|
|
37
172
|
expect(claimNotice('agent')).toBe(true);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notice-marker.test.js","sourceRoot":"","sources":["../../src/__tests__/notice-marker.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzE,MAAM,KAAK,GAAG,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"notice-marker.test.js","sourceRoot":"","sources":["../../src/__tests__/notice-marker.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notice-marker.d.ts","sourceRoot":"","sources":["../../src/lib/notice-marker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"notice-marker.d.ts","sourceRoot":"","sources":["../../src/lib/notice-marker.ts"],"names":[],"mappings":"AA+KA,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAsB/D"}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Once-per-session (agent) / once-per-day (human) notice claim via O_EXCL.
|
|
3
3
|
* First process to create the marker wins (returns true => may print). All others,
|
|
4
|
-
* and any error, fail OPEN to no-print (return false).
|
|
5
|
-
*
|
|
4
|
+
* and any error, fail OPEN to no-print (return false). A per-marker O_EXCL lock
|
|
5
|
+
* serializes stale-marker eviction against that claim. Markers expire by key
|
|
6
|
+
* (session id rotates per session; daily key rolls per UTC day) and by age, except
|
|
7
|
+
* that a losing claim heartbeats the current marker so a renewed session survives
|
|
8
|
+
* the sweep. DEC-2.
|
|
6
9
|
*/
|
|
7
|
-
import { openSync, closeSync, mkdirSync, statSync, readdirSync, unlinkSync } from 'node:fs';
|
|
10
|
+
import { openSync, closeSync, existsSync, linkSync, mkdirSync, statSync, readdirSync, unlinkSync, utimesSync } from 'node:fs';
|
|
8
11
|
import { homedir } from 'node:os';
|
|
9
12
|
import { join } from 'node:path';
|
|
10
13
|
import { readSession } from './session.js';
|
|
11
14
|
const CONFIG_DIR = join(homedir(), '.config', 'productbrain');
|
|
15
|
+
// Non-current session residue becomes stale after one server expiry lease. The
|
|
16
|
+
// same ID can be renewed, so claimNotice heartbeats the locked current marker.
|
|
17
|
+
// Lease source: convex/intelligence/agentSessions.ts MAX_SESSION_DURATION_MS.
|
|
18
|
+
const SESSION_STALE_MS = 8 * 60 * 60 * 1000;
|
|
12
19
|
const DAILY_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
20
|
+
// Lock actions contain only synchronous local filesystem calls. A one-minute
|
|
21
|
+
// lease bounds crash residue while leaving ample headroom for a live holder.
|
|
22
|
+
const LOCK_STALE_MS = 60 * 1000;
|
|
23
|
+
const LOCK_PREFIX = '.notice-marker-lock-';
|
|
24
|
+
const LOCK_RECLAIM_SUFFIX = '.reclaim';
|
|
25
|
+
const LOCK_PIN_SUFFIX = '.target';
|
|
13
26
|
function utcDateStamp() {
|
|
14
27
|
const d = new Date();
|
|
15
28
|
const p = (n) => String(n).padStart(2, '0');
|
|
@@ -23,28 +36,185 @@ function markerKey(channel) {
|
|
|
23
36
|
// If no active session, fall back to a daily key so the per-day throttle still holds.
|
|
24
37
|
return session?.sessionId ? `notice-session-${session.sessionId}` : `notice-agent-daily-${utcDateStamp()}`;
|
|
25
38
|
}
|
|
26
|
-
function
|
|
39
|
+
function markerRetentionMs(name) {
|
|
40
|
+
if (name.startsWith('notice-session-'))
|
|
41
|
+
return SESSION_STALE_MS;
|
|
42
|
+
if (name.startsWith('notice-human-') || name.startsWith('notice-agent-daily-')) {
|
|
43
|
+
return DAILY_STALE_MS;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function markerLockPath(name) {
|
|
48
|
+
return join(CONFIG_DIR, `${LOCK_PREFIX}${name}`);
|
|
49
|
+
}
|
|
50
|
+
function cleanupStaleRecovery(lockPath, now) {
|
|
51
|
+
const reclaimPath = `${lockPath}${LOCK_RECLAIM_SUFFIX}`;
|
|
52
|
+
const pinPath = `${reclaimPath}${LOCK_PIN_SUFFIX}`;
|
|
53
|
+
try {
|
|
54
|
+
if (now - statSync(reclaimPath).mtimeMs <= LOCK_STALE_MS)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
unlinkSync(pinPath);
|
|
58
|
+
}
|
|
59
|
+
catch { /* absent or unavailable identity pin */ }
|
|
60
|
+
unlinkSync(reclaimPath);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
catch { /* absent, fresh, or unavailable recovery election */ }
|
|
64
|
+
try {
|
|
65
|
+
if (now - statSync(pinPath).mtimeMs > LOCK_STALE_MS)
|
|
66
|
+
unlinkSync(pinPath);
|
|
67
|
+
}
|
|
68
|
+
catch { /* absent or unavailable orphan identity pin */ }
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Reclaim one expired lock without a pathname-only unlink race. O_EXCL elects
|
|
72
|
+
* one reclaimer; its hard link pins the exact stale file instance to remove.
|
|
73
|
+
*/
|
|
74
|
+
function reclaimStaleLock(lockPath, now) {
|
|
75
|
+
const reclaimPath = `${lockPath}${LOCK_RECLAIM_SUFFIX}`;
|
|
76
|
+
const pinPath = `${reclaimPath}${LOCK_PIN_SUFFIX}`;
|
|
77
|
+
let reclaimFd;
|
|
78
|
+
let observed;
|
|
79
|
+
try {
|
|
80
|
+
observed = statSync(lockPath);
|
|
81
|
+
if (now - observed.mtimeMs <= LOCK_STALE_MS)
|
|
82
|
+
return false;
|
|
83
|
+
cleanupStaleRecovery(lockPath, now);
|
|
84
|
+
reclaimFd = openSync(reclaimPath, 'wx');
|
|
85
|
+
closeSync(reclaimFd);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
linkSync(lockPath, pinPath);
|
|
92
|
+
const pinned = statSync(pinPath);
|
|
93
|
+
if (observed.dev !== pinned.dev || observed.ino !== pinned.ino)
|
|
94
|
+
return false;
|
|
95
|
+
const current = statSync(lockPath);
|
|
96
|
+
if (current.dev !== pinned.dev || current.ino !== pinned.ino)
|
|
97
|
+
return false;
|
|
98
|
+
unlinkSync(lockPath);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
try {
|
|
106
|
+
unlinkSync(pinPath);
|
|
107
|
+
}
|
|
108
|
+
catch { /* bounded by the next stale sweep */ }
|
|
109
|
+
try {
|
|
110
|
+
unlinkSync(reclaimPath);
|
|
111
|
+
}
|
|
112
|
+
catch { /* bounded by the next stale sweep */ }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function acquireMarkerLock(name) {
|
|
116
|
+
const lockPath = markerLockPath(name);
|
|
27
117
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
118
|
+
return openSync(lockPath, 'wx');
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
if (!reclaimStaleLock(lockPath, Date.now()))
|
|
122
|
+
return null;
|
|
123
|
+
try {
|
|
124
|
+
return openSync(lockPath, 'wx');
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function withMarkerLock(name, action) {
|
|
132
|
+
const lockPath = markerLockPath(name);
|
|
133
|
+
const lockFd = acquireMarkerLock(name);
|
|
134
|
+
if (lockFd === null)
|
|
135
|
+
return null;
|
|
136
|
+
try {
|
|
137
|
+
closeSync(lockFd);
|
|
138
|
+
return action();
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
try {
|
|
142
|
+
unlinkSync(lockPath);
|
|
143
|
+
}
|
|
144
|
+
catch { /* best-effort lock release */ }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function cleanupAbandonedMarkerLocks(names, now) {
|
|
148
|
+
for (const name of names) {
|
|
149
|
+
if (!name.startsWith(LOCK_PREFIX))
|
|
150
|
+
continue;
|
|
151
|
+
if (name.endsWith(LOCK_RECLAIM_SUFFIX)) {
|
|
152
|
+
cleanupStaleRecovery(join(CONFIG_DIR, name.slice(0, -LOCK_RECLAIM_SUFFIX.length)), now);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (name.endsWith(`${LOCK_RECLAIM_SUFFIX}${LOCK_PIN_SUFFIX}`))
|
|
156
|
+
continue;
|
|
157
|
+
const path = join(CONFIG_DIR, name);
|
|
158
|
+
try {
|
|
159
|
+
if (now - statSync(path).mtimeMs <= LOCK_STALE_MS)
|
|
32
160
|
continue;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
161
|
+
reclaimStaleLock(path, now);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Lock recovery is best-effort and isolated per file, like marker GC.
|
|
36
165
|
}
|
|
37
166
|
}
|
|
38
|
-
|
|
167
|
+
}
|
|
168
|
+
function cleanupOldNoticeMarkers(currentName) {
|
|
169
|
+
let names;
|
|
170
|
+
try {
|
|
171
|
+
names = readdirSync(CONFIG_DIR);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
cleanupAbandonedMarkerLocks(names, now);
|
|
178
|
+
for (const name of names) {
|
|
179
|
+
if (name === currentName)
|
|
180
|
+
continue;
|
|
181
|
+
const staleAfterMs = markerRetentionMs(name);
|
|
182
|
+
if (staleAfterMs === null)
|
|
183
|
+
continue;
|
|
184
|
+
withMarkerLock(name, () => {
|
|
185
|
+
try {
|
|
186
|
+
const p = join(CONFIG_DIR, name);
|
|
187
|
+
if (now - statSync(p).mtimeMs > staleAfterMs)
|
|
188
|
+
unlinkSync(p);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// A marker can disappear or become unavailable while the sweep runs.
|
|
192
|
+
// Keep cleanup best-effort without skipping the remaining files.
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
39
196
|
}
|
|
40
197
|
/** Returns true exactly once per (channel, session/day). Never throws. */
|
|
41
198
|
export function claimNotice(channel) {
|
|
42
|
-
const
|
|
199
|
+
const name = markerKey(channel);
|
|
200
|
+
const path = join(CONFIG_DIR, name);
|
|
43
201
|
try {
|
|
44
202
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
203
|
+
return withMarkerLock(name, () => {
|
|
204
|
+
cleanupOldNoticeMarkers(name);
|
|
205
|
+
if (existsSync(path)) {
|
|
206
|
+
// A live server session can be renewed under the same ID. Refreshing the
|
|
207
|
+
// marker on each suppressed notice keeps that active session out of GC.
|
|
208
|
+
try {
|
|
209
|
+
const now = new Date();
|
|
210
|
+
utimesSync(path, now, now);
|
|
211
|
+
}
|
|
212
|
+
catch { /* the existing claim still wins */ }
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
closeSync(openSync(path, 'wx')); // 'wx' = O_EXCL: throws EEXIST if present
|
|
216
|
+
return true;
|
|
217
|
+
}) ?? false;
|
|
48
218
|
}
|
|
49
219
|
catch {
|
|
50
220
|
return false; // already claimed, or fs error => fail open to no-print
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notice-marker.js","sourceRoot":"","sources":["../../src/lib/notice-marker.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"notice-marker.js","sourceRoot":"","sources":["../../src/lib/notice-marker.ts"],"names":[],"mappings":"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"}
|