@jsonstudio/llms 0.6.1449 → 0.6.1643
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/dist/conversion/codecs/gemini-openai-codec.js +6 -1
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.d.ts +4 -6
- package/dist/conversion/compat/actions/anthropic-claude-code-system-prompt.js +179 -41
- package/dist/conversion/compat/actions/antigravity-thought-signature-cache.js +73 -14
- package/dist/conversion/compat/actions/antigravity-thought-signature-prepare.js +165 -10
- package/dist/conversion/compat/actions/gemini-cli-request.js +72 -13
- package/dist/conversion/compat/antigravity-session-signature.d.ts +68 -1
- package/dist/conversion/compat/antigravity-session-signature.js +833 -21
- package/dist/conversion/compat/profiles/anthropic-claude-code.json +17 -0
- package/dist/conversion/compat/profiles/chat-gemini-cli.json +1 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +33 -8
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +17 -1
- package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +12 -3
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +24 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +20 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +26 -1
- package/dist/conversion/hub/process/chat-process.js +300 -67
- package/dist/conversion/hub/response/provider-response.js +4 -3
- package/dist/conversion/shared/gemini-tool-utils.js +134 -9
- package/dist/conversion/shared/text-markup-normalizer.js +90 -1
- package/dist/conversion/shared/thought-signature-validator.d.ts +1 -1
- package/dist/conversion/shared/thought-signature-validator.js +2 -1
- package/dist/quota/apikey-reset.d.ts +17 -0
- package/dist/quota/apikey-reset.js +43 -0
- package/dist/quota/index.d.ts +2 -0
- package/dist/quota/index.js +1 -0
- package/dist/quota/quota-manager.d.ts +44 -0
- package/dist/quota/quota-manager.js +491 -0
- package/dist/quota/quota-state.d.ts +6 -0
- package/dist/quota/quota-state.js +167 -0
- package/dist/quota/types.d.ts +61 -0
- package/dist/quota/types.js +1 -0
- package/dist/router/virtual-router/bootstrap.js +103 -6
- package/dist/router/virtual-router/engine-health.js +104 -0
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +18 -0
- package/dist/router/virtual-router/engine-selection/tier-priority.d.ts +1 -2
- package/dist/router/virtual-router/engine-selection/tier-priority.js +2 -2
- package/dist/router/virtual-router/engine-selection/tier-selection-select.js +34 -10
- package/dist/router/virtual-router/engine-selection/tier-selection.js +250 -6
- package/dist/router/virtual-router/engine-selection.js +2 -2
- package/dist/router/virtual-router/engine.d.ts +16 -1
- package/dist/router/virtual-router/engine.js +320 -42
- package/dist/router/virtual-router/features.js +20 -2
- package/dist/router/virtual-router/success-center.d.ts +10 -0
- package/dist/router/virtual-router/success-center.js +32 -0
- package/dist/router/virtual-router/types.d.ts +48 -0
- package/dist/servertool/clock/config.d.ts +2 -0
- package/dist/servertool/clock/config.js +10 -2
- package/dist/servertool/clock/daemon.js +3 -0
- package/dist/servertool/clock/ntp.d.ts +18 -0
- package/dist/servertool/clock/ntp.js +318 -0
- package/dist/servertool/clock/paths.d.ts +1 -0
- package/dist/servertool/clock/paths.js +3 -0
- package/dist/servertool/clock/state.d.ts +2 -0
- package/dist/servertool/clock/state.js +15 -2
- package/dist/servertool/clock/tasks.d.ts +1 -0
- package/dist/servertool/clock/tasks.js +24 -1
- package/dist/servertool/clock/types.d.ts +21 -0
- package/dist/servertool/engine.js +105 -1
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.d.ts +1 -0
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +201 -0
- package/dist/servertool/handlers/clock-auto.js +39 -4
- package/dist/servertool/handlers/clock.js +145 -16
- package/dist/servertool/handlers/followup-request-builder.js +84 -0
- package/dist/servertool/handlers/stop-message-auto.js +1 -1
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +1 -0
- package/dist/servertool/types.d.ts +2 -0
- package/dist/tools/apply-patch/execution-capturer.js +24 -3
- package/package.json +3 -2
|
@@ -1,11 +1,85 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const DUMMY_THOUGHT_SIGNATURE_SENTINEL = 'skip_thought_signature_validator';
|
|
5
|
+
// Antigravity-Manager alignment:
|
|
6
|
+
// - session_id has no time limit (continuity across long conversations / restarts)
|
|
7
|
+
// - signature cache is bounded by size, not by time
|
|
8
|
+
// Time-based expiry is disabled when SIGNATURE_TTL_MS <= 0.
|
|
9
|
+
const SIGNATURE_TTL_MS = 0;
|
|
10
|
+
// Still "touch" active sessions to refresh persisted timestamps / LRU ordering,
|
|
11
|
+
// but avoid excessive disk churn.
|
|
12
|
+
const SIGNATURE_TOUCH_INTERVAL_MS = 5 * 60 * 1000;
|
|
5
13
|
const MIN_SIGNATURE_LENGTH = 50;
|
|
6
14
|
const SESSION_CACHE_LIMIT = 1000;
|
|
15
|
+
// Keep rewind guard finite to avoid reusing stale signatures after a rewind.
|
|
16
|
+
const REWIND_BLOCK_MS = 2 * 60 * 60 * 1000;
|
|
17
|
+
const GLOBAL_PERSISTENCE_KEY = '__LLMSWITCH_ANTIGRAVITY_SESSION_SIGNATURE_PERSISTENCE__';
|
|
18
|
+
function getPersistenceState() {
|
|
19
|
+
const g = globalThis;
|
|
20
|
+
const existing = g[GLOBAL_PERSISTENCE_KEY];
|
|
21
|
+
if (existing && typeof existing === 'object') {
|
|
22
|
+
return existing;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function setPersistenceState(state) {
|
|
27
|
+
const g = globalThis;
|
|
28
|
+
if (!state) {
|
|
29
|
+
delete g[GLOBAL_PERSISTENCE_KEY];
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
g[GLOBAL_PERSISTENCE_KEY] = state;
|
|
33
|
+
}
|
|
34
|
+
export function configureAntigravitySessionSignaturePersistence(input) {
|
|
35
|
+
if (!input) {
|
|
36
|
+
const prior = getPersistenceState();
|
|
37
|
+
if (prior?.flushTimer) {
|
|
38
|
+
clearTimeout(prior.flushTimer);
|
|
39
|
+
}
|
|
40
|
+
setPersistenceState(null);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const stateDir = typeof input.stateDir === 'string' ? input.stateDir.trim() : '';
|
|
44
|
+
if (!stateDir) {
|
|
45
|
+
setPersistenceState(null);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const fileName = typeof input.fileName === 'string' && input.fileName.trim() ? input.fileName.trim() : 'antigravity-session-signatures.json';
|
|
49
|
+
const prior = getPersistenceState();
|
|
50
|
+
if (prior?.flushTimer) {
|
|
51
|
+
clearTimeout(prior.flushTimer);
|
|
52
|
+
}
|
|
53
|
+
setPersistenceState({
|
|
54
|
+
config: { stateDir, fileName },
|
|
55
|
+
loadedMtimeMs: null,
|
|
56
|
+
loadedOnce: false,
|
|
57
|
+
flushTimer: null
|
|
58
|
+
});
|
|
59
|
+
// Ensure we hydrate immediately so a short-lived process (or a server restart)
|
|
60
|
+
// never flushes an empty in-memory cache over an existing persisted file.
|
|
61
|
+
try {
|
|
62
|
+
hydrateSignaturesFromDiskIfNeeded(true);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// best-effort only
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function flushAntigravitySessionSignaturePersistenceSync() {
|
|
69
|
+
const state = getPersistenceState();
|
|
70
|
+
if (!state) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (state.flushTimer) {
|
|
74
|
+
clearTimeout(state.flushTimer);
|
|
75
|
+
state.flushTimer = null;
|
|
76
|
+
}
|
|
77
|
+
flushPersistedSignaturesSync(state);
|
|
78
|
+
}
|
|
7
79
|
const GLOBAL_SIGNATURE_CACHE_KEY = '__LLMSWITCH_ANTIGRAVITY_SESSION_SIGNATURE_CACHE__';
|
|
8
80
|
const GLOBAL_REQUEST_SESSION_CACHE_KEY = '__LLMSWITCH_ANTIGRAVITY_REQUEST_SESSION_ID_CACHE__';
|
|
81
|
+
const GLOBAL_PINNED_ALIAS_BY_SESSION_KEY = '__LLMSWITCH_ANTIGRAVITY_PINNED_ALIAS_BY_SESSION__';
|
|
82
|
+
const GLOBAL_PINNED_SESSION_BY_ALIAS_KEY = '__LLMSWITCH_ANTIGRAVITY_PINNED_SESSION_BY_ALIAS__';
|
|
9
83
|
function getGlobalSignatureCache() {
|
|
10
84
|
const g = globalThis;
|
|
11
85
|
const existing = g[GLOBAL_SIGNATURE_CACHE_KEY];
|
|
@@ -28,9 +102,152 @@ function getGlobalRequestSessionCache() {
|
|
|
28
102
|
return created;
|
|
29
103
|
}
|
|
30
104
|
const requestSessionIds = getGlobalRequestSessionCache();
|
|
105
|
+
const GLOBAL_REWIND_BLOCK_CACHE_KEY = '__LLMSWITCH_ANTIGRAVITY_SIGNATURE_REWIND_BLOCK_CACHE__';
|
|
106
|
+
function getGlobalRewindBlockCache() {
|
|
107
|
+
const g = globalThis;
|
|
108
|
+
const existing = g[GLOBAL_REWIND_BLOCK_CACHE_KEY];
|
|
109
|
+
if (existing instanceof Map) {
|
|
110
|
+
return existing;
|
|
111
|
+
}
|
|
112
|
+
const created = new Map();
|
|
113
|
+
g[GLOBAL_REWIND_BLOCK_CACHE_KEY] = created;
|
|
114
|
+
return created;
|
|
115
|
+
}
|
|
116
|
+
const rewindBlocks = getGlobalRewindBlockCache();
|
|
117
|
+
const GLOBAL_LATEST_SIGNATURE_BY_ALIAS_KEY = '__LLMSWITCH_ANTIGRAVITY_LATEST_SIGNATURE_BY_ALIAS__';
|
|
118
|
+
// Antigravity-Manager alignment: global thoughtSignature store (v2).
|
|
119
|
+
// - Shared across all Antigravity/GeminiCLI accounts for the SAME derived sessionId.
|
|
120
|
+
// - Still keyed by sessionId to avoid cross-session leakage.
|
|
121
|
+
export const ANTIGRAVITY_GLOBAL_ALIAS_KEY = 'antigravity.global';
|
|
122
|
+
function normalizeAliasKey(value) {
|
|
123
|
+
if (typeof value !== 'string') {
|
|
124
|
+
return 'antigravity.unknown';
|
|
125
|
+
}
|
|
126
|
+
const trimmed = value.trim();
|
|
127
|
+
if (!trimmed.length) {
|
|
128
|
+
return 'antigravity.unknown';
|
|
129
|
+
}
|
|
130
|
+
const lowered = trimmed.toLowerCase();
|
|
131
|
+
if (lowered === 'antigravity') {
|
|
132
|
+
return 'antigravity.unknown';
|
|
133
|
+
}
|
|
134
|
+
return lowered;
|
|
135
|
+
}
|
|
136
|
+
function normalizeSessionId(value) {
|
|
137
|
+
if (typeof value !== 'string') {
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
return value.trim();
|
|
141
|
+
}
|
|
142
|
+
function buildSignatureCacheKey(aliasKey, sessionId) {
|
|
143
|
+
const alias = normalizeAliasKey(aliasKey);
|
|
144
|
+
const sid = normalizeSessionId(sessionId);
|
|
145
|
+
if (!sid) {
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
return `${alias}|${sid}`;
|
|
149
|
+
}
|
|
150
|
+
function getLatestSignatureMap() {
|
|
151
|
+
const g = globalThis;
|
|
152
|
+
const existing = g[GLOBAL_LATEST_SIGNATURE_BY_ALIAS_KEY];
|
|
153
|
+
if (existing instanceof Map) {
|
|
154
|
+
return existing;
|
|
155
|
+
}
|
|
156
|
+
const created = new Map();
|
|
157
|
+
g[GLOBAL_LATEST_SIGNATURE_BY_ALIAS_KEY] = created;
|
|
158
|
+
return created;
|
|
159
|
+
}
|
|
160
|
+
const latestSignaturesByAlias = getLatestSignatureMap();
|
|
161
|
+
function getPinnedAliasBySessionMap() {
|
|
162
|
+
const g = globalThis;
|
|
163
|
+
const existing = g[GLOBAL_PINNED_ALIAS_BY_SESSION_KEY];
|
|
164
|
+
if (existing instanceof Map) {
|
|
165
|
+
return existing;
|
|
166
|
+
}
|
|
167
|
+
const created = new Map();
|
|
168
|
+
g[GLOBAL_PINNED_ALIAS_BY_SESSION_KEY] = created;
|
|
169
|
+
return created;
|
|
170
|
+
}
|
|
171
|
+
function getPinnedSessionByAliasMap() {
|
|
172
|
+
const g = globalThis;
|
|
173
|
+
const existing = g[GLOBAL_PINNED_SESSION_BY_ALIAS_KEY];
|
|
174
|
+
if (existing instanceof Map) {
|
|
175
|
+
return existing;
|
|
176
|
+
}
|
|
177
|
+
const created = new Map();
|
|
178
|
+
g[GLOBAL_PINNED_SESSION_BY_ALIAS_KEY] = created;
|
|
179
|
+
return created;
|
|
180
|
+
}
|
|
181
|
+
const pinnedAliasBySession = getPinnedAliasBySessionMap();
|
|
182
|
+
const pinnedSessionByAlias = getPinnedSessionByAliasMap();
|
|
183
|
+
export function getAntigravityThoughtSignatureSentinel() {
|
|
184
|
+
return DUMMY_THOUGHT_SIGNATURE_SENTINEL;
|
|
185
|
+
}
|
|
186
|
+
function shouldAllowAliasLatestFallback(aliasKey) {
|
|
187
|
+
const normalized = normalizeAliasKey(aliasKey);
|
|
188
|
+
// Never fall back when alias is unknown: avoids cross-alias mixing and enforces isolation.
|
|
189
|
+
return normalized !== 'antigravity.unknown';
|
|
190
|
+
}
|
|
191
|
+
function getLatestSignatureEntry(aliasKey) {
|
|
192
|
+
const key = normalizeAliasKey(aliasKey);
|
|
193
|
+
const existing = latestSignaturesByAlias.get(key);
|
|
194
|
+
if (existing && typeof existing === 'object') {
|
|
195
|
+
const signature = typeof existing.signature === 'string' ? existing.signature : '';
|
|
196
|
+
const messageCount = typeof existing.messageCount === 'number' ? existing.messageCount : 1;
|
|
197
|
+
const timestamp = typeof existing.timestamp === 'number' ? existing.timestamp : 0;
|
|
198
|
+
const sessionId = typeof existing.sessionId === 'string'
|
|
199
|
+
? String(existing.sessionId)
|
|
200
|
+
: '';
|
|
201
|
+
if (signature && timestamp > 0) {
|
|
202
|
+
return { signature, messageCount, timestamp, ...(sessionId.trim().length ? { sessionId: sessionId.trim() } : {}) };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
function setLatestSignatureEntry(aliasKey, entry) {
|
|
208
|
+
const key = normalizeAliasKey(aliasKey);
|
|
209
|
+
if (!entry) {
|
|
210
|
+
latestSignaturesByAlias.delete(key);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
latestSignaturesByAlias.set(key, entry);
|
|
214
|
+
}
|
|
31
215
|
function nowMs() {
|
|
32
216
|
return Date.now();
|
|
33
217
|
}
|
|
218
|
+
function isTimeExpiryEnabled() {
|
|
219
|
+
return typeof SIGNATURE_TTL_MS === 'number' && Number.isFinite(SIGNATURE_TTL_MS) && SIGNATURE_TTL_MS > 0;
|
|
220
|
+
}
|
|
221
|
+
export function getAntigravityLatestSignatureSessionIdForAlias(aliasKeyInput, options) {
|
|
222
|
+
const allowHydrate = options?.hydrate !== false;
|
|
223
|
+
const aliasKey = normalizeAliasKey(aliasKeyInput);
|
|
224
|
+
if (!shouldAllowAliasLatestFallback(aliasKey)) {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
if (allowHydrate) {
|
|
228
|
+
hydrateSignaturesFromDiskIfNeeded(true);
|
|
229
|
+
}
|
|
230
|
+
const latest = getLatestSignatureEntry(aliasKey);
|
|
231
|
+
if (!latest) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
if (isTimeExpiryEnabled()) {
|
|
235
|
+
const ts = nowMs();
|
|
236
|
+
if (ts - latest.timestamp > SIGNATURE_TTL_MS) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const sessionId = typeof latest.sessionId === 'string' ? latest.sessionId.trim() : '';
|
|
241
|
+
return sessionId || undefined;
|
|
242
|
+
}
|
|
243
|
+
function extractSessionIdFromCacheKey(key) {
|
|
244
|
+
if (typeof key !== 'string' || !key)
|
|
245
|
+
return '';
|
|
246
|
+
const idx = key.indexOf('|');
|
|
247
|
+
if (idx < 0)
|
|
248
|
+
return '';
|
|
249
|
+
return key.slice(idx + 1).trim();
|
|
250
|
+
}
|
|
34
251
|
function isRecord(value) {
|
|
35
252
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
36
253
|
}
|
|
@@ -63,18 +280,74 @@ function sha256Hex(value) {
|
|
|
63
280
|
return createHash('sha256').update(value).digest('hex');
|
|
64
281
|
}
|
|
65
282
|
function isExpired(entry, ts) {
|
|
283
|
+
if (!isTimeExpiryEnabled()) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
return ts - entry.timestamp > SIGNATURE_TTL_MS;
|
|
287
|
+
}
|
|
288
|
+
function isPinnedExpired(entry, ts) {
|
|
289
|
+
if (!isTimeExpiryEnabled()) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
66
292
|
return ts - entry.timestamp > SIGNATURE_TTL_MS;
|
|
67
293
|
}
|
|
68
294
|
function isRequestSessionExpired(entry, ts) {
|
|
295
|
+
if (!isTimeExpiryEnabled()) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
69
298
|
return ts - entry.timestamp > SIGNATURE_TTL_MS;
|
|
70
299
|
}
|
|
300
|
+
function touchSessionSignature(aliasKey, cacheKey, entry, ts) {
|
|
301
|
+
if (ts - entry.timestamp < SIGNATURE_TOUCH_INTERVAL_MS) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
sessionSignatures.set(cacheKey, { signature: entry.signature, messageCount: entry.messageCount, timestamp: ts });
|
|
305
|
+
const latest = getLatestSignatureEntry(aliasKey);
|
|
306
|
+
if (!latest || ts >= latest.timestamp) {
|
|
307
|
+
const sessionId = extractSessionIdFromCacheKey(cacheKey);
|
|
308
|
+
setLatestSignatureEntry(aliasKey, {
|
|
309
|
+
signature: entry.signature,
|
|
310
|
+
messageCount: entry.messageCount,
|
|
311
|
+
timestamp: ts,
|
|
312
|
+
...(sessionId ? { sessionId } : {})
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
schedulePersistenceFlush();
|
|
316
|
+
}
|
|
71
317
|
function pruneExpired() {
|
|
318
|
+
if (!isTimeExpiryEnabled()) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
72
321
|
const ts = nowMs();
|
|
73
322
|
for (const [key, entry] of sessionSignatures.entries()) {
|
|
74
323
|
if (isExpired(entry, ts)) {
|
|
75
324
|
sessionSignatures.delete(key);
|
|
76
325
|
}
|
|
77
326
|
}
|
|
327
|
+
for (const [sessionId, entry] of pinnedAliasBySession.entries()) {
|
|
328
|
+
if (isPinnedExpired(entry, ts)) {
|
|
329
|
+
pinnedAliasBySession.delete(sessionId);
|
|
330
|
+
const aliasKey = typeof entry.aliasKey === 'string' ? entry.aliasKey : '';
|
|
331
|
+
if (aliasKey) {
|
|
332
|
+
const backref = pinnedSessionByAlias.get(aliasKey);
|
|
333
|
+
if (backref?.sessionId === sessionId) {
|
|
334
|
+
pinnedSessionByAlias.delete(aliasKey);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
for (const [aliasKey, entry] of pinnedSessionByAlias.entries()) {
|
|
340
|
+
if (isPinnedExpired(entry, ts)) {
|
|
341
|
+
pinnedSessionByAlias.delete(aliasKey);
|
|
342
|
+
const sid = typeof entry.sessionId === 'string' ? entry.sessionId : '';
|
|
343
|
+
if (sid) {
|
|
344
|
+
const backref = pinnedAliasBySession.get(sid);
|
|
345
|
+
if (backref?.aliasKey === aliasKey) {
|
|
346
|
+
pinnedAliasBySession.delete(sid);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
78
351
|
}
|
|
79
352
|
function ensureCacheLimit() {
|
|
80
353
|
if (sessionSignatures.size <= SESSION_CACHE_LIMIT) {
|
|
@@ -94,14 +367,308 @@ function ensureCacheLimit() {
|
|
|
94
367
|
}
|
|
95
368
|
}
|
|
96
369
|
}
|
|
97
|
-
|
|
370
|
+
function resolvePersistFilePath(config) {
|
|
371
|
+
return path.join(config.stateDir, config.fileName);
|
|
372
|
+
}
|
|
373
|
+
function isValidPersistedEntry(value) {
|
|
374
|
+
if (!isRecord(value))
|
|
375
|
+
return false;
|
|
376
|
+
const sig = typeof value.signature === 'string' ? value.signature.trim() : '';
|
|
377
|
+
if (!sig || sig.length < MIN_SIGNATURE_LENGTH)
|
|
378
|
+
return false;
|
|
379
|
+
if (sig === DUMMY_THOUGHT_SIGNATURE_SENTINEL)
|
|
380
|
+
return false;
|
|
381
|
+
const messageCount = typeof value.messageCount === 'number' && Number.isFinite(value.messageCount) && value.messageCount > 0
|
|
382
|
+
? Math.floor(value.messageCount)
|
|
383
|
+
: 1;
|
|
384
|
+
const timestamp = typeof value.timestamp === 'number' && Number.isFinite(value.timestamp) && value.timestamp > 0
|
|
385
|
+
? Math.floor(value.timestamp)
|
|
386
|
+
: 0;
|
|
387
|
+
if (!timestamp)
|
|
388
|
+
return false;
|
|
389
|
+
value.signature = sig;
|
|
390
|
+
value.messageCount = messageCount;
|
|
391
|
+
value.timestamp = timestamp;
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
function hydrateSignaturesFromDiskIfNeeded(force = false) {
|
|
395
|
+
const state = getPersistenceState();
|
|
396
|
+
if (!state) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const filePath = resolvePersistFilePath(state.config);
|
|
400
|
+
let stat = null;
|
|
401
|
+
try {
|
|
402
|
+
stat = fs.statSync(filePath);
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
state.loadedOnce = true;
|
|
406
|
+
state.loadedMtimeMs = null;
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const mtimeMs = typeof stat.mtimeMs === 'number' && Number.isFinite(stat.mtimeMs) ? stat.mtimeMs : stat.mtime.getTime();
|
|
410
|
+
if (!force && state.loadedOnce && state.loadedMtimeMs === mtimeMs) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
let parsed;
|
|
414
|
+
try {
|
|
415
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
state.loadedOnce = true;
|
|
419
|
+
state.loadedMtimeMs = mtimeMs;
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const latestRaw = isRecord(parsed) ? parsed.latest : undefined;
|
|
423
|
+
const latestPersisted = isValidPersistedEntry(latestRaw)
|
|
424
|
+
? { ...latestRaw }
|
|
425
|
+
: null;
|
|
426
|
+
const latestByAliasRaw = isRecord(parsed) ? parsed.latestByAlias : undefined;
|
|
427
|
+
const latestByAlias = isRecord(latestByAliasRaw) ? latestByAliasRaw : undefined;
|
|
428
|
+
const sessionsRaw = isRecord(parsed) ? parsed.sessions : undefined;
|
|
429
|
+
const sessions = isRecord(sessionsRaw) ? sessionsRaw : undefined;
|
|
430
|
+
if (!sessions) {
|
|
431
|
+
state.loadedOnce = true;
|
|
432
|
+
state.loadedMtimeMs = mtimeMs;
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const tsNow = nowMs();
|
|
436
|
+
const pinnedBySessionRaw = isRecord(parsed) ? parsed.pinnedBySession : undefined;
|
|
437
|
+
const pinnedBySession = isRecord(pinnedBySessionRaw) ? pinnedBySessionRaw : undefined;
|
|
438
|
+
if (pinnedBySession) {
|
|
439
|
+
for (const [sessionIdRaw, entryRaw] of Object.entries(pinnedBySession)) {
|
|
440
|
+
const sessionId = typeof sessionIdRaw === 'string' ? sessionIdRaw.trim() : '';
|
|
441
|
+
if (!sessionId)
|
|
442
|
+
continue;
|
|
443
|
+
if (!isRecord(entryRaw))
|
|
444
|
+
continue;
|
|
445
|
+
const aliasKeyRaw = entryRaw.aliasKey;
|
|
446
|
+
const aliasKey = typeof aliasKeyRaw === 'string' ? aliasKeyRaw.trim() : '';
|
|
447
|
+
const timestampRaw = entryRaw.timestamp;
|
|
448
|
+
const timestamp = typeof timestampRaw === 'number' && Number.isFinite(timestampRaw) ? Math.floor(timestampRaw) : 0;
|
|
449
|
+
if (!aliasKey || !timestamp)
|
|
450
|
+
continue;
|
|
451
|
+
if (isPinnedExpired({ timestamp }, tsNow))
|
|
452
|
+
continue;
|
|
453
|
+
const normalizedAlias = normalizeAliasKey(aliasKey);
|
|
454
|
+
const existing = pinnedAliasBySession.get(sessionId);
|
|
455
|
+
if (existing && !isPinnedExpired(existing, tsNow) && existing.timestamp >= timestamp) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
pinnedAliasBySession.set(sessionId, { aliasKey: normalizedAlias, timestamp });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const pinnedByAliasRaw = isRecord(parsed) ? parsed.pinnedByAlias : undefined;
|
|
462
|
+
const pinnedByAlias = isRecord(pinnedByAliasRaw) ? pinnedByAliasRaw : undefined;
|
|
463
|
+
if (pinnedByAlias) {
|
|
464
|
+
for (const [aliasKeyRaw, entryRaw] of Object.entries(pinnedByAlias)) {
|
|
465
|
+
const aliasKey = typeof aliasKeyRaw === 'string' ? aliasKeyRaw.trim() : '';
|
|
466
|
+
if (!aliasKey)
|
|
467
|
+
continue;
|
|
468
|
+
if (!isRecord(entryRaw))
|
|
469
|
+
continue;
|
|
470
|
+
const sessionIdRaw = entryRaw.sessionId;
|
|
471
|
+
const sessionId = typeof sessionIdRaw === 'string' ? sessionIdRaw.trim() : '';
|
|
472
|
+
const timestampRaw = entryRaw.timestamp;
|
|
473
|
+
const timestamp = typeof timestampRaw === 'number' && Number.isFinite(timestampRaw) ? Math.floor(timestampRaw) : 0;
|
|
474
|
+
if (!sessionId || !timestamp)
|
|
475
|
+
continue;
|
|
476
|
+
if (isPinnedExpired({ timestamp }, tsNow))
|
|
477
|
+
continue;
|
|
478
|
+
const normalizedAlias = normalizeAliasKey(aliasKey);
|
|
479
|
+
const existing = pinnedSessionByAlias.get(normalizedAlias);
|
|
480
|
+
if (existing && !isPinnedExpired(existing, tsNow) && existing.timestamp >= timestamp) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
pinnedSessionByAlias.set(normalizedAlias, { sessionId, timestamp });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
for (const [persistedKey, entry] of Object.entries(sessions)) {
|
|
487
|
+
if (typeof persistedKey !== 'string' || !persistedKey.trim())
|
|
488
|
+
continue;
|
|
489
|
+
if (!isValidPersistedEntry(entry))
|
|
490
|
+
continue;
|
|
491
|
+
if (isExpired(entry, tsNow))
|
|
492
|
+
continue;
|
|
493
|
+
const keyTrimmed = persistedKey.trim();
|
|
494
|
+
const isScopedKey = keyTrimmed.includes('|');
|
|
495
|
+
const normalizedKeys = isScopedKey
|
|
496
|
+
? [keyTrimmed]
|
|
497
|
+
: [
|
|
498
|
+
// Legacy v1 persistence stored signatures keyed only by sid-*.
|
|
499
|
+
// Treat them as session-global so they can be used for any alias (v2 global store).
|
|
500
|
+
buildSignatureCacheKey(ANTIGRAVITY_GLOBAL_ALIAS_KEY, keyTrimmed),
|
|
501
|
+
buildSignatureCacheKey('antigravity.unknown', keyTrimmed)
|
|
502
|
+
].filter(Boolean);
|
|
503
|
+
for (const normalizedKey of normalizedKeys) {
|
|
504
|
+
if (!normalizedKey)
|
|
505
|
+
continue;
|
|
506
|
+
const existing = sessionSignatures.get(normalizedKey);
|
|
507
|
+
if (existing && !isExpired(existing, tsNow) && existing.timestamp >= entry.timestamp) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
sessionSignatures.set(normalizedKey, entry);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
latestSignaturesByAlias.clear();
|
|
514
|
+
if (latestByAlias) {
|
|
515
|
+
for (const [aliasKeyRaw, entryRaw] of Object.entries(latestByAlias)) {
|
|
516
|
+
if (typeof aliasKeyRaw !== 'string' || !aliasKeyRaw.trim())
|
|
517
|
+
continue;
|
|
518
|
+
if (!isValidPersistedEntry(entryRaw))
|
|
519
|
+
continue;
|
|
520
|
+
if (isExpired(entryRaw, tsNow))
|
|
521
|
+
continue;
|
|
522
|
+
const sessionIdCandidate = isRecord(entryRaw) && typeof entryRaw.sessionId === 'string'
|
|
523
|
+
? String(entryRaw.sessionId).trim()
|
|
524
|
+
: '';
|
|
525
|
+
setLatestSignatureEntry(aliasKeyRaw, {
|
|
526
|
+
signature: entryRaw.signature,
|
|
527
|
+
messageCount: entryRaw.messageCount,
|
|
528
|
+
timestamp: entryRaw.timestamp,
|
|
529
|
+
...(sessionIdCandidate ? { sessionId: sessionIdCandidate } : {})
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Legacy v1 persistence ("latest" without alias) is kept only under "unknown" to avoid cross-alias mixing.
|
|
534
|
+
if (latestSignaturesByAlias.size === 0 && latestPersisted && !isExpired(latestPersisted, tsNow)) {
|
|
535
|
+
setLatestSignatureEntry('antigravity.unknown', {
|
|
536
|
+
signature: latestPersisted.signature,
|
|
537
|
+
messageCount: latestPersisted.messageCount,
|
|
538
|
+
timestamp: latestPersisted.timestamp
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
// Recompute per-alias latest values from session cache as a final best-effort pass.
|
|
542
|
+
for (const [key, entry] of sessionSignatures.entries()) {
|
|
543
|
+
if (isExpired(entry, tsNow))
|
|
544
|
+
continue;
|
|
545
|
+
const alias = key.split('|')[0] ?? 'unknown';
|
|
546
|
+
const existing = getLatestSignatureEntry(alias);
|
|
547
|
+
if (!existing || entry.timestamp > existing.timestamp) {
|
|
548
|
+
const sessionId = extractSessionIdFromCacheKey(key);
|
|
549
|
+
setLatestSignatureEntry(alias, {
|
|
550
|
+
signature: entry.signature,
|
|
551
|
+
messageCount: entry.messageCount,
|
|
552
|
+
timestamp: entry.timestamp,
|
|
553
|
+
...(sessionId ? { sessionId } : {})
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
ensureCacheLimit();
|
|
558
|
+
state.loadedOnce = true;
|
|
559
|
+
state.loadedMtimeMs = mtimeMs;
|
|
560
|
+
}
|
|
561
|
+
function flushPersistedSignaturesSync(state) {
|
|
562
|
+
const filePath = resolvePersistFilePath(state.config);
|
|
563
|
+
// Avoid wiping existing persisted signatures when this process hasn't cached anything yet.
|
|
564
|
+
// Always re-hydrate before writing.
|
|
565
|
+
hydrateSignaturesFromDiskIfNeeded(true);
|
|
566
|
+
const tsNow = nowMs();
|
|
567
|
+
pruneExpired();
|
|
568
|
+
ensureCacheLimit();
|
|
569
|
+
const sessions = {};
|
|
570
|
+
for (const [sid, entry] of sessionSignatures.entries()) {
|
|
571
|
+
if (isExpired(entry, tsNow))
|
|
572
|
+
continue;
|
|
573
|
+
if (!entry.signature || entry.signature.trim().length < MIN_SIGNATURE_LENGTH)
|
|
574
|
+
continue;
|
|
575
|
+
if (entry.signature.trim() === DUMMY_THOUGHT_SIGNATURE_SENTINEL)
|
|
576
|
+
continue;
|
|
577
|
+
sessions[sid] = entry;
|
|
578
|
+
}
|
|
579
|
+
const latestByAliasPersist = {};
|
|
580
|
+
for (const [aliasKey, entry] of latestSignaturesByAlias.entries()) {
|
|
581
|
+
if (!entry.signature || entry.signature.trim().length < MIN_SIGNATURE_LENGTH)
|
|
582
|
+
continue;
|
|
583
|
+
if (entry.signature.trim() === DUMMY_THOUGHT_SIGNATURE_SENTINEL)
|
|
584
|
+
continue;
|
|
585
|
+
latestByAliasPersist[aliasKey] = entry;
|
|
586
|
+
}
|
|
587
|
+
const pinnedBySessionPersist = {};
|
|
588
|
+
for (const [sessionId, entry] of pinnedAliasBySession.entries()) {
|
|
589
|
+
const sid = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
590
|
+
if (!sid)
|
|
591
|
+
continue;
|
|
592
|
+
const aliasKey = typeof entry?.aliasKey === 'string' ? entry.aliasKey.trim() : '';
|
|
593
|
+
const timestamp = typeof entry?.timestamp === 'number' && Number.isFinite(entry.timestamp) ? Math.floor(entry.timestamp) : 0;
|
|
594
|
+
if (!aliasKey || !timestamp)
|
|
595
|
+
continue;
|
|
596
|
+
if (isPinnedExpired({ timestamp }, tsNow))
|
|
597
|
+
continue;
|
|
598
|
+
pinnedBySessionPersist[sid] = { aliasKey, timestamp };
|
|
599
|
+
}
|
|
600
|
+
const pinnedByAliasPersist = {};
|
|
601
|
+
for (const [aliasKey, entry] of pinnedSessionByAlias.entries()) {
|
|
602
|
+
const a = typeof aliasKey === 'string' ? aliasKey.trim() : '';
|
|
603
|
+
if (!a)
|
|
604
|
+
continue;
|
|
605
|
+
const sessionId = typeof entry?.sessionId === 'string' ? entry.sessionId.trim() : '';
|
|
606
|
+
const timestamp = typeof entry?.timestamp === 'number' && Number.isFinite(entry.timestamp) ? Math.floor(entry.timestamp) : 0;
|
|
607
|
+
if (!sessionId || !timestamp)
|
|
608
|
+
continue;
|
|
609
|
+
if (isPinnedExpired({ timestamp }, tsNow))
|
|
610
|
+
continue;
|
|
611
|
+
pinnedByAliasPersist[a] = { sessionId, timestamp };
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
615
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${tsNow}`;
|
|
616
|
+
fs.writeFileSync(tmpPath, JSON.stringify({
|
|
617
|
+
version: 3,
|
|
618
|
+
updatedAt: tsNow,
|
|
619
|
+
sessions,
|
|
620
|
+
...(Object.keys(latestByAliasPersist).length ? { latestByAlias: latestByAliasPersist } : {}),
|
|
621
|
+
...(Object.keys(pinnedBySessionPersist).length ? { pinnedBySession: pinnedBySessionPersist } : {}),
|
|
622
|
+
...(Object.keys(pinnedByAliasPersist).length ? { pinnedByAlias: pinnedByAliasPersist } : {})
|
|
623
|
+
}, null, 2), 'utf8');
|
|
624
|
+
fs.renameSync(tmpPath, filePath);
|
|
625
|
+
try {
|
|
626
|
+
const stat = fs.statSync(filePath);
|
|
627
|
+
const mtimeMs = typeof stat.mtimeMs === 'number' && Number.isFinite(stat.mtimeMs) ? stat.mtimeMs : stat.mtime.getTime();
|
|
628
|
+
state.loadedOnce = true;
|
|
629
|
+
state.loadedMtimeMs = mtimeMs;
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
// ignore
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
// best-effort persistence: must not affect runtime
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function schedulePersistenceFlush() {
|
|
640
|
+
const state = getPersistenceState();
|
|
641
|
+
if (!state) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (state.flushTimer) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
state.flushTimer = setTimeout(() => {
|
|
648
|
+
state.flushTimer = null;
|
|
649
|
+
flushPersistedSignaturesSync(state);
|
|
650
|
+
}, 250);
|
|
651
|
+
state.flushTimer.unref?.();
|
|
652
|
+
}
|
|
653
|
+
export function cacheAntigravityRequestSessionId(requestId, a, b) {
|
|
654
|
+
if (typeof b === 'string') {
|
|
655
|
+
cacheAntigravityRequestSessionMeta(requestId, { aliasKey: a, sessionId: b });
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
cacheAntigravityRequestSessionMeta(requestId, { sessionId: a });
|
|
659
|
+
}
|
|
660
|
+
export function cacheAntigravityRequestSessionMeta(requestId, meta) {
|
|
98
661
|
const rid = typeof requestId === 'string' ? requestId.trim() : '';
|
|
99
|
-
const sid = typeof sessionId === 'string' ? sessionId.trim() : '';
|
|
662
|
+
const sid = typeof meta?.sessionId === 'string' ? meta.sessionId.trim() : '';
|
|
100
663
|
if (!rid || !sid) {
|
|
101
664
|
return;
|
|
102
665
|
}
|
|
666
|
+
const aliasKey = normalizeAliasKey(meta?.aliasKey);
|
|
667
|
+
const messageCount = typeof meta?.messageCount === 'number' && Number.isFinite(meta.messageCount) && meta.messageCount > 0
|
|
668
|
+
? Math.floor(meta.messageCount)
|
|
669
|
+
: 1;
|
|
103
670
|
const ts = nowMs();
|
|
104
|
-
requestSessionIds.set(rid, { sessionId: sid, timestamp: ts });
|
|
671
|
+
requestSessionIds.set(rid, { aliasKey, sessionId: sid, messageCount, timestamp: ts });
|
|
105
672
|
if (requestSessionIds.size <= SESSION_CACHE_LIMIT) {
|
|
106
673
|
return;
|
|
107
674
|
}
|
|
@@ -116,6 +683,10 @@ export function cacheAntigravityRequestSessionId(requestId, sessionId) {
|
|
|
116
683
|
}
|
|
117
684
|
}
|
|
118
685
|
export function getAntigravityRequestSessionId(requestId) {
|
|
686
|
+
const meta = getAntigravityRequestSessionMeta(requestId);
|
|
687
|
+
return meta?.sessionId;
|
|
688
|
+
}
|
|
689
|
+
export function getAntigravityRequestSessionMeta(requestId) {
|
|
119
690
|
const rid = typeof requestId === 'string' ? requestId.trim() : '';
|
|
120
691
|
if (!rid) {
|
|
121
692
|
return undefined;
|
|
@@ -129,7 +700,7 @@ export function getAntigravityRequestSessionId(requestId) {
|
|
|
129
700
|
requestSessionIds.delete(rid);
|
|
130
701
|
return undefined;
|
|
131
702
|
}
|
|
132
|
-
return entry.sessionId;
|
|
703
|
+
return { aliasKey: entry.aliasKey, sessionId: entry.sessionId, messageCount: entry.messageCount };
|
|
133
704
|
}
|
|
134
705
|
function findGeminiContentsNode(payload) {
|
|
135
706
|
if (!isRecord(payload)) {
|
|
@@ -176,7 +747,9 @@ export function extractAntigravityGeminiSessionId(payload) {
|
|
|
176
747
|
}
|
|
177
748
|
}
|
|
178
749
|
const combined = texts.join(' ').trim();
|
|
179
|
-
|
|
750
|
+
// Antigravity-Manager alignment: always derive a stable session_id from the FIRST user message text,
|
|
751
|
+
// even if the prompt is short (avoid JSON fallback instability across tool loops / followups).
|
|
752
|
+
if (combined.length > 0) {
|
|
180
753
|
seed = combined;
|
|
181
754
|
break;
|
|
182
755
|
}
|
|
@@ -187,14 +760,23 @@ export function extractAntigravityGeminiSessionId(payload) {
|
|
|
187
760
|
const hash = sha256Hex(seed);
|
|
188
761
|
return `sid-${hash.slice(0, 16)}`;
|
|
189
762
|
}
|
|
190
|
-
export function cacheAntigravitySessionSignature(
|
|
191
|
-
|
|
763
|
+
export function cacheAntigravitySessionSignature(a, b, c, d = 1) {
|
|
764
|
+
const isNewSignature = typeof c === 'string';
|
|
765
|
+
const aliasKey = isNewSignature ? a : 'antigravity.unknown';
|
|
766
|
+
const sessionId = isNewSignature ? b : a;
|
|
767
|
+
const signature = isNewSignature ? c : b;
|
|
768
|
+
const messageCount = typeof c === 'number' ? c : typeof d === 'number' ? d : 1;
|
|
769
|
+
const key = buildSignatureCacheKey(aliasKey, sessionId);
|
|
770
|
+
if (!key) {
|
|
192
771
|
return;
|
|
193
772
|
}
|
|
194
|
-
if (typeof signature !== 'string'
|
|
773
|
+
if (typeof signature !== 'string') {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const trimmedSignature = signature.trim();
|
|
777
|
+
if (trimmedSignature.length < MIN_SIGNATURE_LENGTH || trimmedSignature === DUMMY_THOUGHT_SIGNATURE_SENTINEL) {
|
|
195
778
|
return;
|
|
196
779
|
}
|
|
197
|
-
const key = sessionId.trim();
|
|
198
780
|
const ts = nowMs();
|
|
199
781
|
const existing = sessionSignatures.get(key);
|
|
200
782
|
let shouldStore = false;
|
|
@@ -217,29 +799,259 @@ export function cacheAntigravitySessionSignature(sessionId, signature, messageCo
|
|
|
217
799
|
if (!shouldStore) {
|
|
218
800
|
return;
|
|
219
801
|
}
|
|
220
|
-
sessionSignatures.set(key, { signature, messageCount, timestamp: ts });
|
|
802
|
+
sessionSignatures.set(key, { signature: trimmedSignature, messageCount, timestamp: ts });
|
|
803
|
+
rewindBlocks.delete(key);
|
|
804
|
+
const latest = getLatestSignatureEntry(aliasKey);
|
|
805
|
+
if (!latest || ts >= latest.timestamp) {
|
|
806
|
+
setLatestSignatureEntry(aliasKey, { signature: trimmedSignature, messageCount, timestamp: ts, sessionId });
|
|
807
|
+
}
|
|
808
|
+
maybePinAntigravitySessionToAlias(aliasKey, sessionId, ts);
|
|
221
809
|
ensureCacheLimit();
|
|
810
|
+
schedulePersistenceFlush();
|
|
811
|
+
}
|
|
812
|
+
function maybePinAntigravitySessionToAlias(aliasKeyInput, sessionIdInput, ts) {
|
|
813
|
+
const aliasKey = normalizeAliasKey(aliasKeyInput);
|
|
814
|
+
const sessionId = normalizeSessionId(sessionIdInput);
|
|
815
|
+
if (!sessionId) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (aliasKey === 'antigravity.unknown' || aliasKey === ANTIGRAVITY_GLOBAL_ALIAS_KEY) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const existing = pinnedAliasBySession.get(sessionId);
|
|
822
|
+
if (existing && !isPinnedExpired(existing, ts)) {
|
|
823
|
+
if (existing.aliasKey === aliasKey && ts - existing.timestamp >= SIGNATURE_TOUCH_INTERVAL_MS) {
|
|
824
|
+
pinnedAliasBySession.set(sessionId, { aliasKey, timestamp: ts });
|
|
825
|
+
pinnedSessionByAlias.set(aliasKey, { sessionId, timestamp: ts });
|
|
826
|
+
schedulePersistenceFlush();
|
|
827
|
+
}
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const existingForAlias = pinnedSessionByAlias.get(aliasKey);
|
|
831
|
+
if (existingForAlias && !isPinnedExpired(existingForAlias, ts) && existingForAlias.sessionId !== sessionId) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
pinnedAliasBySession.set(sessionId, { aliasKey, timestamp: ts });
|
|
835
|
+
pinnedSessionByAlias.set(aliasKey, { sessionId, timestamp: ts });
|
|
836
|
+
schedulePersistenceFlush();
|
|
222
837
|
}
|
|
223
|
-
export function
|
|
224
|
-
|
|
838
|
+
export function lookupAntigravityPinnedAliasForSessionId(sessionIdInput, options) {
|
|
839
|
+
const sessionId = normalizeSessionId(sessionIdInput);
|
|
840
|
+
if (!sessionId) {
|
|
225
841
|
return undefined;
|
|
226
842
|
}
|
|
227
|
-
const
|
|
228
|
-
|
|
843
|
+
const allowHydrate = options?.hydrate !== false;
|
|
844
|
+
if (allowHydrate) {
|
|
845
|
+
hydrateSignaturesFromDiskIfNeeded(true);
|
|
846
|
+
}
|
|
847
|
+
const entry = pinnedAliasBySession.get(sessionId);
|
|
229
848
|
if (!entry) {
|
|
230
849
|
return undefined;
|
|
231
850
|
}
|
|
232
851
|
const ts = nowMs();
|
|
233
|
-
if (
|
|
234
|
-
|
|
852
|
+
if (isPinnedExpired(entry, ts)) {
|
|
853
|
+
pinnedAliasBySession.delete(sessionId);
|
|
235
854
|
return undefined;
|
|
236
855
|
}
|
|
237
|
-
|
|
856
|
+
if (ts - entry.timestamp >= SIGNATURE_TOUCH_INTERVAL_MS) {
|
|
857
|
+
pinnedAliasBySession.set(sessionId, { aliasKey: entry.aliasKey, timestamp: ts });
|
|
858
|
+
pinnedSessionByAlias.set(entry.aliasKey, { sessionId, timestamp: ts });
|
|
859
|
+
schedulePersistenceFlush();
|
|
860
|
+
}
|
|
861
|
+
return entry.aliasKey;
|
|
862
|
+
}
|
|
863
|
+
export function unpinAntigravitySessionAliasForSessionId(sessionIdInput) {
|
|
864
|
+
const sessionId = normalizeSessionId(sessionIdInput);
|
|
865
|
+
if (!sessionId) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
hydrateSignaturesFromDiskIfNeeded(true);
|
|
869
|
+
const existing = pinnedAliasBySession.get(sessionId);
|
|
870
|
+
if (!existing) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
pinnedAliasBySession.delete(sessionId);
|
|
874
|
+
const aliasKey = typeof existing.aliasKey === 'string' ? existing.aliasKey : '';
|
|
875
|
+
if (aliasKey) {
|
|
876
|
+
const backref = pinnedSessionByAlias.get(aliasKey);
|
|
877
|
+
if (backref?.sessionId === sessionId) {
|
|
878
|
+
pinnedSessionByAlias.delete(aliasKey);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
schedulePersistenceFlush();
|
|
882
|
+
}
|
|
883
|
+
export function getAntigravitySessionSignature(a, b) {
|
|
884
|
+
return getAntigravitySessionSignatureEntry(a, b)?.signature;
|
|
885
|
+
}
|
|
886
|
+
export function lookupAntigravitySessionSignatureEntry(aliasKeyInput, sessionIdInput, options) {
|
|
887
|
+
const allowHydrate = options?.hydrate !== false;
|
|
888
|
+
const aliasKey = normalizeAliasKey(aliasKeyInput);
|
|
889
|
+
const sessionId = normalizeSessionId(sessionIdInput);
|
|
890
|
+
const cacheKey = buildSignatureCacheKey(aliasKey, sessionId);
|
|
891
|
+
if (!cacheKey) {
|
|
892
|
+
return { aliasKey, sessionId, cacheKey, source: 'miss' };
|
|
893
|
+
}
|
|
894
|
+
let entry = sessionSignatures.get(cacheKey);
|
|
895
|
+
if (!entry && allowHydrate) {
|
|
896
|
+
hydrateSignaturesFromDiskIfNeeded(true);
|
|
897
|
+
entry = sessionSignatures.get(cacheKey);
|
|
898
|
+
}
|
|
899
|
+
if (entry) {
|
|
900
|
+
const ts = nowMs();
|
|
901
|
+
if (isExpired(entry, ts)) {
|
|
902
|
+
sessionSignatures.delete(cacheKey);
|
|
903
|
+
return { aliasKey, sessionId, cacheKey, source: 'expired' };
|
|
904
|
+
}
|
|
905
|
+
touchSessionSignature(aliasKey, cacheKey, entry, ts);
|
|
906
|
+
return {
|
|
907
|
+
aliasKey,
|
|
908
|
+
sessionId,
|
|
909
|
+
cacheKey,
|
|
910
|
+
source: 'session_cache',
|
|
911
|
+
signature: entry.signature,
|
|
912
|
+
messageCount: entry.messageCount,
|
|
913
|
+
sourceSessionId: sessionId,
|
|
914
|
+
sourceTimestamp: entry.timestamp
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
if (!shouldAllowAliasLatestFallback(aliasKey)) {
|
|
918
|
+
return { aliasKey, sessionId, cacheKey, source: 'blocked_unknown_alias' };
|
|
919
|
+
}
|
|
920
|
+
const ts = nowMs();
|
|
921
|
+
const rewind = rewindBlocks.get(cacheKey);
|
|
922
|
+
if (rewind && ts < rewind.until) {
|
|
923
|
+
return { aliasKey, sessionId, cacheKey, source: 'blocked_rewind' };
|
|
924
|
+
}
|
|
925
|
+
// Antigravity-Manager alignment: do NOT reuse signatures across sessions.
|
|
926
|
+
// If we miss the session cache, the caller must omit thoughtSignature and wait for upstream to provide a real one.
|
|
927
|
+
// (Persistence is still used to restore the same session across restarts, not to "heal" unrelated sessions.)
|
|
928
|
+
if (allowHydrate) {
|
|
929
|
+
hydrateSignaturesFromDiskIfNeeded(true);
|
|
930
|
+
const hydrated = sessionSignatures.get(cacheKey);
|
|
931
|
+
if (hydrated && !isExpired(hydrated, ts)) {
|
|
932
|
+
touchSessionSignature(aliasKey, cacheKey, hydrated, ts);
|
|
933
|
+
return {
|
|
934
|
+
aliasKey,
|
|
935
|
+
sessionId,
|
|
936
|
+
cacheKey,
|
|
937
|
+
source: 'session_cache',
|
|
938
|
+
signature: hydrated.signature,
|
|
939
|
+
messageCount: hydrated.messageCount,
|
|
940
|
+
sourceSessionId: sessionId,
|
|
941
|
+
sourceTimestamp: hydrated.timestamp
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return { aliasKey, sessionId, cacheKey, source: 'miss' };
|
|
946
|
+
}
|
|
947
|
+
export function getAntigravitySessionSignatureEntry(a, b, c) {
|
|
948
|
+
const hasAlias = typeof b === 'string';
|
|
949
|
+
const options = (hasAlias ? c : b);
|
|
950
|
+
const aliasKey = normalizeAliasKey(hasAlias ? a : 'antigravity.unknown');
|
|
951
|
+
const sessionId = hasAlias ? b : a;
|
|
952
|
+
const lookup = lookupAntigravitySessionSignatureEntry(aliasKey, sessionId, options);
|
|
953
|
+
if (lookup.signature && typeof lookup.messageCount === 'number') {
|
|
954
|
+
return { signature: lookup.signature, messageCount: lookup.messageCount };
|
|
955
|
+
}
|
|
956
|
+
return undefined;
|
|
957
|
+
}
|
|
958
|
+
export function clearAntigravitySessionSignature(a, b) {
|
|
959
|
+
const hasAlias = typeof b === 'string';
|
|
960
|
+
const aliasKey = hasAlias ? a : 'antigravity.unknown';
|
|
961
|
+
const sessionId = hasAlias ? b : a;
|
|
962
|
+
const key = buildSignatureCacheKey(aliasKey, sessionId);
|
|
963
|
+
if (!key) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
sessionSignatures.delete(key);
|
|
967
|
+
// Keep latest-by-alias coherent so leasing doesn't keep pointing at a cleared signature.
|
|
968
|
+
if (hasAlias) {
|
|
969
|
+
const latest = getLatestSignatureEntry(aliasKey);
|
|
970
|
+
if (latest?.sessionId && latest.sessionId.trim() === normalizeSessionId(sessionId)) {
|
|
971
|
+
setLatestSignatureEntry(aliasKey, null);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
schedulePersistenceFlush();
|
|
975
|
+
}
|
|
976
|
+
export function markAntigravitySessionSignatureRewind(aliasKey, sessionId, messageCount = 1) {
|
|
977
|
+
const key = buildSignatureCacheKey(aliasKey, sessionId);
|
|
978
|
+
if (!key) {
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
const ts = nowMs();
|
|
982
|
+
const mc = typeof messageCount === 'number' && Number.isFinite(messageCount) && messageCount > 0 ? Math.floor(messageCount) : 1;
|
|
983
|
+
rewindBlocks.set(key, { timestamp: ts, until: ts + REWIND_BLOCK_MS, messageCount: mc });
|
|
984
|
+
// Best-effort bound on memory usage.
|
|
985
|
+
if (rewindBlocks.size > SESSION_CACHE_LIMIT) {
|
|
986
|
+
const entries = Array.from(rewindBlocks.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
987
|
+
const overflow = rewindBlocks.size - SESSION_CACHE_LIMIT;
|
|
988
|
+
for (let i = 0; i < overflow; i++) {
|
|
989
|
+
const k = entries[i]?.[0];
|
|
990
|
+
if (k)
|
|
991
|
+
rewindBlocks.delete(k);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Clear thoughtSignature caches + pins for a specific (aliasKey, sessionId).
|
|
997
|
+
*
|
|
998
|
+
* Used for "Invalid signature / Corrupted thought signature / thinking.signature" style upstream errors,
|
|
999
|
+
* where keeping a persisted signature would cause repeated 400s after restart.
|
|
1000
|
+
*/
|
|
1001
|
+
export function invalidateAntigravitySessionSignature(aliasKeyInput, sessionIdInput) {
|
|
1002
|
+
const aliasKey = normalizeAliasKey(aliasKeyInput);
|
|
1003
|
+
const sessionId = normalizeSessionId(sessionIdInput);
|
|
1004
|
+
if (!sessionId) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
hydrateSignaturesFromDiskIfNeeded(true);
|
|
1008
|
+
const keys = [
|
|
1009
|
+
buildSignatureCacheKey(aliasKey, sessionId),
|
|
1010
|
+
buildSignatureCacheKey(ANTIGRAVITY_GLOBAL_ALIAS_KEY, sessionId)
|
|
1011
|
+
].filter((k) => typeof k === 'string' && k.trim().length > 0);
|
|
1012
|
+
keys.forEach((k) => sessionSignatures.delete(k));
|
|
1013
|
+
const latest = getLatestSignatureEntry(aliasKey);
|
|
1014
|
+
if (latest?.sessionId && latest.sessionId.trim() === sessionId) {
|
|
1015
|
+
setLatestSignatureEntry(aliasKey, null);
|
|
1016
|
+
}
|
|
1017
|
+
const globalLatest = getLatestSignatureEntry(ANTIGRAVITY_GLOBAL_ALIAS_KEY);
|
|
1018
|
+
if (globalLatest?.sessionId && globalLatest.sessionId.trim() === sessionId) {
|
|
1019
|
+
setLatestSignatureEntry(ANTIGRAVITY_GLOBAL_ALIAS_KEY, null);
|
|
1020
|
+
}
|
|
1021
|
+
// Release any pins so routing can rotate away from a broken tool loop.
|
|
1022
|
+
const pinnedAlias = pinnedAliasBySession.get(sessionId);
|
|
1023
|
+
if (pinnedAlias) {
|
|
1024
|
+
pinnedAliasBySession.delete(sessionId);
|
|
1025
|
+
if (typeof pinnedAlias.aliasKey === 'string' && pinnedAlias.aliasKey.trim()) {
|
|
1026
|
+
const backref = pinnedSessionByAlias.get(pinnedAlias.aliasKey);
|
|
1027
|
+
if (backref?.sessionId === sessionId) {
|
|
1028
|
+
pinnedSessionByAlias.delete(pinnedAlias.aliasKey);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
const pinnedSession = pinnedSessionByAlias.get(aliasKey);
|
|
1033
|
+
if (pinnedSession?.sessionId === sessionId) {
|
|
1034
|
+
pinnedSessionByAlias.delete(aliasKey);
|
|
1035
|
+
}
|
|
1036
|
+
schedulePersistenceFlush();
|
|
1037
|
+
}
|
|
1038
|
+
export function resetAntigravitySessionSignatureCachesForTests() {
|
|
1039
|
+
sessionSignatures.clear();
|
|
1040
|
+
requestSessionIds.clear();
|
|
1041
|
+
latestSignaturesByAlias.clear();
|
|
1042
|
+
pinnedAliasBySession.clear();
|
|
1043
|
+
pinnedSessionByAlias.clear();
|
|
1044
|
+
rewindBlocks.clear();
|
|
1045
|
+
const persistence = getPersistenceState();
|
|
1046
|
+
if (persistence) {
|
|
1047
|
+
persistence.loadedOnce = false;
|
|
1048
|
+
persistence.loadedMtimeMs = null;
|
|
1049
|
+
}
|
|
238
1050
|
}
|
|
239
1051
|
export function shouldTreatAsMissingThoughtSignature(value) {
|
|
240
1052
|
if (typeof value !== 'string') {
|
|
241
1053
|
return true;
|
|
242
1054
|
}
|
|
243
1055
|
const trimmed = value.trim();
|
|
244
|
-
return trimmed.length === 0 || trimmed ===
|
|
1056
|
+
return trimmed.length === 0 || trimmed === DUMMY_THOUGHT_SIGNATURE_SENTINEL;
|
|
245
1057
|
}
|