dsh-agy 0.4.0 → 0.4.2
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/README.md +21 -10
- package/lib/{accounts-BN4h0AU4.mjs → accounts-D605JE2i.mjs} +186 -19
- package/lib/accounts-D605JE2i.mjs.map +1 -0
- package/lib/cli/index.d.mts.map +1 -1
- package/lib/cli/index.mjs +9 -7
- package/lib/cli/index.mjs.map +1 -1
- package/lib/client.js +402 -51
- package/lib/{constants-BpyRirvk.mjs → constants-CmA9wXNe.mjs} +3 -3
- package/lib/{constants-BpyRirvk.mjs.map → constants-CmA9wXNe.mjs.map} +1 -1
- package/lib/{exchange-LzEyxve1.mjs → exchange-C-tndv7j.mjs} +3 -3
- package/lib/{exchange-LzEyxve1.mjs.map → exchange-C-tndv7j.mjs.map} +1 -1
- package/lib/{import-CotRHNDk.mjs → import-SxPDjH3u.mjs} +4 -4
- package/lib/{import-CotRHNDk.mjs.map → import-SxPDjH3u.mjs.map} +1 -1
- package/lib/index.mjs +2 -2
- package/lib/{models-Qn0-sh6X.mjs → models-B6bD7zPi.mjs} +3 -3
- package/lib/{models-Qn0-sh6X.mjs.map → models-B6bD7zPi.mjs.map} +1 -1
- package/lib/{plugin-common-N7p9Cm-3.mjs → plugin-common-kSBwIaFi.mjs} +303 -17
- package/lib/plugin-common-kSBwIaFi.mjs.map +1 -0
- package/lib/{proxy-DfaL73mL.mjs → proxy-BbuQX5U9.mjs} +28 -8
- package/lib/proxy-BbuQX5U9.mjs.map +1 -0
- package/lib/{quota-summary-adID82I2.mjs → quota-summary-DEA3P3Zh.mjs} +3 -3
- package/lib/{quota-summary-adID82I2.mjs.map → quota-summary-DEA3P3Zh.mjs.map} +1 -1
- package/lib/web/plugin.mjs +40 -18
- package/lib/web/plugin.mjs.map +1 -1
- package/package.json +1 -4
- package/lib/accounts-BN4h0AU4.mjs.map +0 -1
- package/lib/plugin-common-N7p9Cm-3.mjs.map +0 -1
- package/lib/proxy-DfaL73mL.mjs.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { m as fetchAgyFirstOk, v as AgyAuthError, y as AgyPoolBlockedError } from "./constants-
|
|
2
|
-
import { a as probeFetch, o as proxiedFetch, t as accountFetch } from "./proxy-
|
|
3
|
-
import { _ as
|
|
1
|
+
import { m as fetchAgyFirstOk, v as AgyAuthError, y as AgyPoolBlockedError } from "./constants-CmA9wXNe.mjs";
|
|
2
|
+
import { a as probeFetch, o as proxiedFetch, t as accountFetch } from "./proxy-BbuQX5U9.mjs";
|
|
3
|
+
import { S as isSessionAccumulationOverflow, _ as resolveDshHome, b as classifyHttpError, d as migrateToAgyDir, f as MASTER_KEY_REF, g as persistMasterKey, h as loadMasterKey, m as deriveKey, p as createAesGcmCodec, r as AgySessionManager, s as resolveAntigravityVersion, t as JsonAccountStore, u as pickProbeProxyUrl, v as resolveMasterKeyCodec, x as describeFetchError, y as classifyFetchError } from "./accounts-D605JE2i.mjs";
|
|
4
4
|
import { i as generateAntigravityRequestId, n as currentSessionGeneration, r as deriveAntigravitySessionId, t as bumpSessionGeneration } from "./identity-Ci8EGc1F.mjs";
|
|
5
5
|
import { i as setThoughtSignature, r as resolveMultimodalFiles, t as toAgyRequestBody } from "./translate-YhGDUuVa.mjs";
|
|
6
6
|
import { t as parseAgySse } from "./parse-Bs4rPkHA.mjs";
|
|
7
|
-
import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-
|
|
7
|
+
import { i as resolveAgyModel, n as listAgyModels, t as catalogModelList } from "./models-B6bD7zPi.mjs";
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
9
9
|
import { LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE } from "@deepseek-ai/dsh-llm";
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { dirname
|
|
11
|
+
import { dirname } from "node:path";
|
|
12
12
|
import lockfile from "proper-lockfile";
|
|
13
13
|
//#region src/adapter/adapter.ts
|
|
14
14
|
/**
|
|
@@ -261,7 +261,10 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
261
261
|
} catch (error) {
|
|
262
262
|
const classified = classifyFetchError(error, { proxyUrl: session.account.proxy });
|
|
263
263
|
await this.options.reportFailure(classified.kind, session);
|
|
264
|
-
this.recordUsage(session, options.model, {
|
|
264
|
+
this.recordUsage(session, options.model, {
|
|
265
|
+
ok: false,
|
|
266
|
+
reason: "network-error"
|
|
267
|
+
}, startedAt);
|
|
265
268
|
throw new LlmError(classified.message ?? "agy fetch failed", "TRANSPORT", { cause: error });
|
|
266
269
|
}
|
|
267
270
|
if (response.ok) return { response };
|
|
@@ -289,7 +292,8 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
289
292
|
});
|
|
290
293
|
this.recordUsage(session, options.model, {
|
|
291
294
|
ok: false,
|
|
292
|
-
rateLimited: classified.kind === "rate-limit"
|
|
295
|
+
rateLimited: classified.kind === "rate-limit",
|
|
296
|
+
reason: classified.kind
|
|
293
297
|
}, startedAt);
|
|
294
298
|
if (classified.kind === "rate-limit") {
|
|
295
299
|
if (classified.rateLimitCategory === "quota_exhausted") throw new LlmError(`agy daily quota exhausted (${response.status}): ${classified.message ?? ""}`, QUOTA_EXCEEDED_CODE);
|
|
@@ -337,7 +341,10 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
337
341
|
} catch (error) {
|
|
338
342
|
if (error instanceof DOMException && error.name === "AbortError") throw new LlmError("agy stream aborted", "ABORTED", { cause: error });
|
|
339
343
|
await this.options.reportFailure("network-error", session);
|
|
340
|
-
this.recordUsage(session, options.model, {
|
|
344
|
+
this.recordUsage(session, options.model, {
|
|
345
|
+
ok: false,
|
|
346
|
+
reason: "network-error"
|
|
347
|
+
}, startedAt);
|
|
341
348
|
throw new LlmError(error instanceof Error ? describeFetchError(error) : "agy stream parse failed", UPSTREAM_ERROR_CODE, { cause: error });
|
|
342
349
|
}
|
|
343
350
|
}
|
|
@@ -356,6 +363,7 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
356
363
|
...model === void 0 ? {} : { model },
|
|
357
364
|
ok: result.ok,
|
|
358
365
|
...result.rateLimited === true ? { rateLimited: true } : {},
|
|
366
|
+
...result.reason === void 0 ? {} : { reason: result.reason },
|
|
359
367
|
...result.usage === void 0 ? {} : { usage: result.usage },
|
|
360
368
|
...result.ttftMs === void 0 ? {} : { ttftMs: result.ttftMs },
|
|
361
369
|
latencyMs: Math.max(0, Date.now() - startedAt)
|
|
@@ -417,7 +425,7 @@ var ModelVisibility = class ModelVisibility {
|
|
|
417
425
|
/** Reusable frozen empty set, so the common "nothing disabled" read allocates nothing. */
|
|
418
426
|
sets = /* @__PURE__ */ new Map();
|
|
419
427
|
constructor(options = {}) {
|
|
420
|
-
this.file = options.file ??
|
|
428
|
+
this.file = options.file ?? migrateToAgyDir("agy-models.json").file;
|
|
421
429
|
const initial = this.readWithRaw();
|
|
422
430
|
this.disabled = initial.disabled;
|
|
423
431
|
this.raw = initial.raw;
|
|
@@ -651,7 +659,7 @@ var ThinkingBudgetStore = class ThinkingBudgetStore {
|
|
|
651
659
|
/** When `reloadNow` last ran, for the hot-path throttle. */
|
|
652
660
|
checkedAt;
|
|
653
661
|
constructor(options = {}) {
|
|
654
|
-
this.file = options.file ??
|
|
662
|
+
this.file = options.file ?? migrateToAgyDir("agy-thinking.json").file;
|
|
655
663
|
this.revalidateIntervalMs = options.revalidateIntervalMs ?? 1e3;
|
|
656
664
|
const initial = this.readWithRaw();
|
|
657
665
|
this.doc = initial.doc;
|
|
@@ -1086,6 +1094,7 @@ var UsageStats = class {
|
|
|
1086
1094
|
flushIntervalMs;
|
|
1087
1095
|
maxPending;
|
|
1088
1096
|
onFlushError;
|
|
1097
|
+
persistRecent;
|
|
1089
1098
|
/** This process's un-flushed records. */
|
|
1090
1099
|
pending = [];
|
|
1091
1100
|
timer;
|
|
@@ -1096,13 +1105,14 @@ var UsageStats = class {
|
|
|
1096
1105
|
*/
|
|
1097
1106
|
flushFailedSince;
|
|
1098
1107
|
constructor(options = {}) {
|
|
1099
|
-
this.file = options.file ??
|
|
1108
|
+
this.file = options.file ?? migrateToAgyDir("agy-stats.json").file;
|
|
1100
1109
|
this.lock = options.lock ?? properStatsLock;
|
|
1101
1110
|
this.now = options.now ?? (() => Date.now());
|
|
1102
1111
|
this.flushEvery = options.flushEvery ?? 50;
|
|
1103
1112
|
this.flushIntervalMs = options.flushIntervalMs ?? 5e3;
|
|
1104
1113
|
this.maxPending = options.maxPending ?? 500;
|
|
1105
1114
|
this.onFlushError = options.onFlushError;
|
|
1115
|
+
this.persistRecent = options.persistRecent;
|
|
1106
1116
|
}
|
|
1107
1117
|
/** Path of the backing file. */
|
|
1108
1118
|
get path() {
|
|
@@ -1137,6 +1147,7 @@ var UsageStats = class {
|
|
|
1137
1147
|
}
|
|
1138
1148
|
/** Record one request. Hot path: no I/O. */
|
|
1139
1149
|
record(record) {
|
|
1150
|
+
this.noteRecent(record);
|
|
1140
1151
|
this.pending.push(record);
|
|
1141
1152
|
if (this.maxPending > 0 && this.pending.length > this.maxPending) this.pending.splice(0, this.pending.length - this.maxPending);
|
|
1142
1153
|
if (this.flushFailedSince === void 0 && this.flushEvery > 0 && this.pending.length >= this.flushEvery) {
|
|
@@ -1145,6 +1156,51 @@ var UsageStats = class {
|
|
|
1145
1156
|
}
|
|
1146
1157
|
this.armTimer();
|
|
1147
1158
|
}
|
|
1159
|
+
/**
|
|
1160
|
+
* The in-memory recent-activity ring, persisted by an injected
|
|
1161
|
+
* `RecentActivityStore` when one is supplied.
|
|
1162
|
+
*
|
|
1163
|
+
* The ring itself stays in-memory and I/O-free — `noteRecent` only flattens —
|
|
1164
|
+
* while the OPTIONAL `persistRecent` seam hands each flattened record to
|
|
1165
|
+
* `recent-store.ts`, which owns the file, the triggers, the lock, and the
|
|
1166
|
+
* failure story. When no store is injected (tests, the CLI) the ring behaves
|
|
1167
|
+
* exactly as before: per-process, never persisted.
|
|
1168
|
+
*
|
|
1169
|
+
* An earlier revision recorded why persistence was rejected outright (file
|
|
1170
|
+
* growth, a versioned migration, a wider stored surface); `recent-store.ts`
|
|
1171
|
+
* answers each — a bounded list in its own file, no version field, emails
|
|
1172
|
+
* and model ids only — which is what turned this from a bad idea into a
|
|
1173
|
+
* spec.
|
|
1174
|
+
*/
|
|
1175
|
+
recent = [];
|
|
1176
|
+
noteRecent(record) {
|
|
1177
|
+
const entry = {
|
|
1178
|
+
at: this.now(),
|
|
1179
|
+
account: record.account ?? null,
|
|
1180
|
+
model: record.model ?? null,
|
|
1181
|
+
kind: record.poolEvent === true ? "rotation" : record.source,
|
|
1182
|
+
ok: record.ok === true,
|
|
1183
|
+
rateLimited: record.rateLimited === true,
|
|
1184
|
+
latencyMs: record.latencyMs ?? null,
|
|
1185
|
+
ttftMs: record.ttftMs ?? null,
|
|
1186
|
+
output: record.usage?.output ?? null,
|
|
1187
|
+
reason: record.ok === true ? null : record.reason ?? null
|
|
1188
|
+
};
|
|
1189
|
+
this.recent.push(entry);
|
|
1190
|
+
if (this.recent.length > 200) this.recent.splice(0, this.recent.length - 200);
|
|
1191
|
+
try {
|
|
1192
|
+
this.persistRecent?.(entry);
|
|
1193
|
+
} catch {}
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Newest-first copy of the ring, for the `pool.recent` RPC.
|
|
1197
|
+
*
|
|
1198
|
+
* A copy, not the live array: the RPC layer may hold it across awaits while
|
|
1199
|
+
* `record()` keeps pushing.
|
|
1200
|
+
*/
|
|
1201
|
+
recentRequests() {
|
|
1202
|
+
return [...this.recent].reverse();
|
|
1203
|
+
}
|
|
1148
1204
|
armTimer() {
|
|
1149
1205
|
if (this.timer !== void 0 || this.flushIntervalMs <= 0) return;
|
|
1150
1206
|
this.timer = setTimeout(() => {
|
|
@@ -1229,6 +1285,204 @@ var UsageStats = class {
|
|
|
1229
1285
|
return this.flushFailedSince !== void 0;
|
|
1230
1286
|
}
|
|
1231
1287
|
};
|
|
1288
|
+
/** Capture-key dedupe: the fields two processes writing the same capture share. */
|
|
1289
|
+
function captureKey(entry) {
|
|
1290
|
+
return `${entry.at}|${entry.account ?? ""}|${entry.model ?? ""}|${entry.kind}|${entry.output ?? ""}`;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Coerce one parsed JSON value into a `RecentActivity`, or null when it is not
|
|
1294
|
+
* one. No version field: a record this build cannot parse is dropped, which
|
|
1295
|
+
* for a bounded diagnostics list is the whole migration story.
|
|
1296
|
+
*/
|
|
1297
|
+
function parseRecentActivity(raw) {
|
|
1298
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
1299
|
+
const r = raw;
|
|
1300
|
+
const kinds = [
|
|
1301
|
+
"chat",
|
|
1302
|
+
"cli",
|
|
1303
|
+
"verify",
|
|
1304
|
+
"test",
|
|
1305
|
+
"rotation"
|
|
1306
|
+
];
|
|
1307
|
+
if (typeof r.at !== "number" || !Number.isFinite(r.at)) return null;
|
|
1308
|
+
if (!kinds.includes(r.kind)) return null;
|
|
1309
|
+
if (typeof r.ok !== "boolean") return null;
|
|
1310
|
+
const nullableString = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
1311
|
+
const nullableNumber = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : null;
|
|
1312
|
+
return {
|
|
1313
|
+
at: r.at,
|
|
1314
|
+
account: nullableString(r.account),
|
|
1315
|
+
model: nullableString(r.model),
|
|
1316
|
+
kind: r.kind,
|
|
1317
|
+
ok: r.ok,
|
|
1318
|
+
rateLimited: r.rateLimited === true,
|
|
1319
|
+
latencyMs: nullableNumber(r.latencyMs),
|
|
1320
|
+
ttftMs: nullableNumber(r.ttftMs),
|
|
1321
|
+
output: nullableNumber(r.output),
|
|
1322
|
+
reason: nullableString(r.reason)
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
/** Merge one batch into an on-disk list (pure; the caller owns the lock and the write). */
|
|
1326
|
+
function mergeRecentEntries(onDisk, batch, cap = 200) {
|
|
1327
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
1328
|
+
for (const entry of [...onDisk, ...batch]) byKey.set(captureKey(entry), entry);
|
|
1329
|
+
return [...byKey.values()].sort((a, b) => a.at - b.at).slice(-cap);
|
|
1330
|
+
}
|
|
1331
|
+
const properRecentLock = { withLock(file, fn) {
|
|
1332
|
+
const release = lockfile.lockSync(file, {
|
|
1333
|
+
stale: 8e3,
|
|
1334
|
+
update: 4e3,
|
|
1335
|
+
realpath: false
|
|
1336
|
+
});
|
|
1337
|
+
try {
|
|
1338
|
+
return fn();
|
|
1339
|
+
} finally {
|
|
1340
|
+
release();
|
|
1341
|
+
}
|
|
1342
|
+
} };
|
|
1343
|
+
/**
|
|
1344
|
+
* Persists the recent-activity ring across processes and restarts.
|
|
1345
|
+
*
|
|
1346
|
+
* The plugin constructs it BEFORE any record can arrive, seeds the in-memory
|
|
1347
|
+
* ring from the file, and hands it to `UsageStats` as the persistence half;
|
|
1348
|
+
* `record()` in `UsageStats` stays I/O-free — it appends to this store's
|
|
1349
|
+
* pending list, and flushing happens on the triggers above.
|
|
1350
|
+
*/
|
|
1351
|
+
var RecentActivityStore = class {
|
|
1352
|
+
file;
|
|
1353
|
+
lock;
|
|
1354
|
+
now;
|
|
1355
|
+
flushEvery;
|
|
1356
|
+
flushIntervalMs;
|
|
1357
|
+
onFlushError;
|
|
1358
|
+
/** The seeded ring plus this process's un-flushed captures. */
|
|
1359
|
+
entries = [];
|
|
1360
|
+
pending = [];
|
|
1361
|
+
timer;
|
|
1362
|
+
flushFailedSince;
|
|
1363
|
+
lastFlushError;
|
|
1364
|
+
constructor(options = {}) {
|
|
1365
|
+
this.file = options.file ?? migrateToAgyDir("agy-recent.json").file;
|
|
1366
|
+
this.lock = options.lock ?? properRecentLock;
|
|
1367
|
+
this.now = options.now ?? (() => Date.now());
|
|
1368
|
+
this.flushEvery = options.flushEvery ?? 25;
|
|
1369
|
+
this.flushIntervalMs = options.flushIntervalMs ?? 3e4;
|
|
1370
|
+
this.onFlushError = options.onFlushError;
|
|
1371
|
+
try {
|
|
1372
|
+
const parsed = JSON.parse(readFileSync(this.file, "utf8"));
|
|
1373
|
+
if (Array.isArray(parsed)) {
|
|
1374
|
+
for (const raw of parsed) {
|
|
1375
|
+
const entry = parseRecentActivity(raw);
|
|
1376
|
+
if (entry) this.entries.push(entry);
|
|
1377
|
+
}
|
|
1378
|
+
if (this.entries.length > 200) this.entries.splice(0, this.entries.length - 200);
|
|
1379
|
+
}
|
|
1380
|
+
} catch {}
|
|
1381
|
+
}
|
|
1382
|
+
/** Path of the backing file. */
|
|
1383
|
+
get path() {
|
|
1384
|
+
return this.file;
|
|
1385
|
+
}
|
|
1386
|
+
/** Whether the ring is currently failing to persist (a run of failures). */
|
|
1387
|
+
get flushFailing() {
|
|
1388
|
+
return this.flushFailedSince !== void 0;
|
|
1389
|
+
}
|
|
1390
|
+
/** Number of records awaiting flush. */
|
|
1391
|
+
get pendingCount() {
|
|
1392
|
+
return this.pending.length;
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Capture one flattened record and the I/O-free hot-path append.
|
|
1396
|
+
*
|
|
1397
|
+
* `UsageStats.record()` calls this after folding into its own ring; keeping
|
|
1398
|
+
* the write-behind state HERE (not in UsageStats) is what lets the file have
|
|
1399
|
+
* its own triggers, its own lock, and its own failure story without widening
|
|
1400
|
+
* the ledger's.
|
|
1401
|
+
*/
|
|
1402
|
+
capture(entry) {
|
|
1403
|
+
this.entries.push(entry);
|
|
1404
|
+
if (this.entries.length > 200) this.entries.splice(0, this.entries.length - 200);
|
|
1405
|
+
this.pending.push(entry);
|
|
1406
|
+
if (this.flushEvery > 0 && this.pending.length >= this.flushEvery) {
|
|
1407
|
+
this.flush();
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
this.armTimer();
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Newest-first copy of the ring, for the `pool.recent` RPC: the seeded
|
|
1414
|
+
* history plus everything this process captured since boot.
|
|
1415
|
+
*/
|
|
1416
|
+
recentRequests() {
|
|
1417
|
+
return [...this.entries].reverse();
|
|
1418
|
+
}
|
|
1419
|
+
armTimer() {
|
|
1420
|
+
if (this.timer !== void 0 || this.flushIntervalMs <= 0) return;
|
|
1421
|
+
this.timer = setTimeout(() => {
|
|
1422
|
+
this.timer = void 0;
|
|
1423
|
+
this.flush();
|
|
1424
|
+
}, this.flushIntervalMs);
|
|
1425
|
+
this.timer.unref?.();
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Merge pending captures into the persisted file.
|
|
1429
|
+
*
|
|
1430
|
+
* Same merge discipline as the ledger: re-read under the lock, merge by
|
|
1431
|
+
* capture key (two processes replaying the same window must not duplicate
|
|
1432
|
+
* rows), sort by time, cap. A failed flush puts the batch back — a
|
|
1433
|
+
* diagnostics ring never breaks a generation — and reports once per run.
|
|
1434
|
+
*/
|
|
1435
|
+
flush() {
|
|
1436
|
+
if (this.timer !== void 0) {
|
|
1437
|
+
clearTimeout(this.timer);
|
|
1438
|
+
this.timer = void 0;
|
|
1439
|
+
}
|
|
1440
|
+
const batch = this.pending;
|
|
1441
|
+
if (batch.length === 0) return;
|
|
1442
|
+
this.pending = [];
|
|
1443
|
+
const now = this.now();
|
|
1444
|
+
try {
|
|
1445
|
+
if (!existsSync(this.file)) {
|
|
1446
|
+
mkdirSync(dirname(this.file), { recursive: true });
|
|
1447
|
+
writeFileSync(this.file, "[]\n", { mode: 384 });
|
|
1448
|
+
}
|
|
1449
|
+
this.lock.withLock(this.file, () => {
|
|
1450
|
+
const onDisk = [];
|
|
1451
|
+
try {
|
|
1452
|
+
const parsed = JSON.parse(readFileSync(this.file, "utf8"));
|
|
1453
|
+
if (Array.isArray(parsed)) for (const raw of parsed) {
|
|
1454
|
+
const entry = parseRecentActivity(raw);
|
|
1455
|
+
if (entry) onDisk.push(entry);
|
|
1456
|
+
}
|
|
1457
|
+
} catch {}
|
|
1458
|
+
const merged = mergeRecentEntries(onDisk, batch);
|
|
1459
|
+
const tmp = `${this.file}.tmp`;
|
|
1460
|
+
writeFileSync(tmp, JSON.stringify(merged, null, 2) + "\n", { mode: 384 });
|
|
1461
|
+
renameSync(tmp, this.file);
|
|
1462
|
+
});
|
|
1463
|
+
this.lastFlushError = void 0;
|
|
1464
|
+
this.flushFailedSince = void 0;
|
|
1465
|
+
} catch (error) {
|
|
1466
|
+
this.pending = [...batch, ...this.pending];
|
|
1467
|
+
this.lastFlushError = error;
|
|
1468
|
+
if (this.flushFailedSince === void 0) {
|
|
1469
|
+
this.flushFailedSince = now;
|
|
1470
|
+
try {
|
|
1471
|
+
this.onFlushError?.(error);
|
|
1472
|
+
} catch {}
|
|
1473
|
+
}
|
|
1474
|
+
this.armTimer();
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
/** Flush if anything is pending; called on process exit. */
|
|
1478
|
+
flushSync() {
|
|
1479
|
+
this.flush();
|
|
1480
|
+
}
|
|
1481
|
+
/** The most recent flush failure, if any (diagnostics). */
|
|
1482
|
+
get flushError() {
|
|
1483
|
+
return this.lastFlushError;
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1232
1486
|
//#endregion
|
|
1233
1487
|
//#region src/plugin-common.ts
|
|
1234
1488
|
/**
|
|
@@ -1291,18 +1545,48 @@ async function warmVersionCache(store) {
|
|
|
1291
1545
|
} catch {}
|
|
1292
1546
|
await resolveAntigravityVersion(fetchImpl).catch(() => {});
|
|
1293
1547
|
}
|
|
1294
|
-
/**
|
|
1548
|
+
/**
|
|
1549
|
+
* The process-wide runtime promise, shared by both entry points.
|
|
1550
|
+
*
|
|
1551
|
+
* Splitting it was a real defect, not a stylistic one: the ledger FILE merges
|
|
1552
|
+
* across instances, so counters looked fine, but every in-memory surface is
|
|
1553
|
+
* per instance — the web entry's ring and in-flight map could never see the
|
|
1554
|
+
* main plugin's chat traffic (same process, two rings), and `account.activate`
|
|
1555
|
+
* cleared affinity pins on the WRONG session manager while the serving one
|
|
1556
|
+
* kept its stale pin. Both entries call this in one process; one memo here is
|
|
1557
|
+
* what makes that "shared runtime" claim true. Separate profiles are separate
|
|
1558
|
+
* processes, so the memo never bridges compositions.
|
|
1559
|
+
*/
|
|
1560
|
+
let sharedRuntime;
|
|
1561
|
+
/** Build (once per process) and return the runtime both entries share. */
|
|
1295
1562
|
async function createAgyRuntime(ctx) {
|
|
1563
|
+
if (sharedRuntime === void 0) sharedRuntime = buildAgyRuntime(ctx).catch((error) => {
|
|
1564
|
+
sharedRuntime = void 0;
|
|
1565
|
+
throw error;
|
|
1566
|
+
});
|
|
1567
|
+
return sharedRuntime;
|
|
1568
|
+
}
|
|
1569
|
+
async function buildAgyRuntime(ctx) {
|
|
1296
1570
|
const { codec } = await resolveCodec(ctx);
|
|
1297
1571
|
const dshHome = resolveDshHome();
|
|
1572
|
+
const accountsMigrated = migrateToAgyDir("agy-accounts.json", dshHome);
|
|
1573
|
+
if (accountsMigrated.skew) ctx.logger.warn("[dsh-agy] legacy data file detected next to the agy folder — an older dsh-agy process may still be writing the pre-0.5 layout; its writes will not be seen until every dsh surface is restarted");
|
|
1298
1574
|
const store = new JsonAccountStore({
|
|
1299
|
-
file:
|
|
1575
|
+
file: accountsMigrated.file,
|
|
1300
1576
|
codec
|
|
1301
1577
|
});
|
|
1302
1578
|
warmVersionCache(store);
|
|
1303
|
-
const
|
|
1304
|
-
ctx.logger.warn(`[dsh-agy]
|
|
1579
|
+
const recentStore = new RecentActivityStore({ onFlushError: (error) => {
|
|
1580
|
+
ctx.logger.warn(`[dsh-agy] recent-activity file could not be written (${recentStore.path}): ${error instanceof Error ? error.message : String(error)} — entries are buffered and will retry`);
|
|
1305
1581
|
} });
|
|
1582
|
+
const stats = new UsageStats({
|
|
1583
|
+
persistRecent: (entry) => {
|
|
1584
|
+
recentStore.capture(entry);
|
|
1585
|
+
},
|
|
1586
|
+
onFlushError: (error) => {
|
|
1587
|
+
ctx.logger.warn(`[dsh-agy] usage ledger could not be written (${stats.path}): ${error instanceof Error ? error.message : String(error)} — counts are buffered and will retry`);
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1306
1590
|
const modelVisibility = new ModelVisibility();
|
|
1307
1591
|
const thinkingBudget = new ThinkingBudgetStore();
|
|
1308
1592
|
let invalidateModelCache = () => {};
|
|
@@ -1341,17 +1625,19 @@ async function createAgyRuntime(ctx) {
|
|
|
1341
1625
|
adapter.listAllModels().catch(() => {});
|
|
1342
1626
|
process.once("exit", () => {
|
|
1343
1627
|
stats.flushSync();
|
|
1628
|
+
recentStore.flushSync();
|
|
1344
1629
|
});
|
|
1345
1630
|
return {
|
|
1346
1631
|
store,
|
|
1347
1632
|
sessions,
|
|
1348
1633
|
adapter,
|
|
1349
1634
|
stats,
|
|
1635
|
+
recentStore,
|
|
1350
1636
|
modelVisibility,
|
|
1351
1637
|
thinkingBudget
|
|
1352
1638
|
};
|
|
1353
1639
|
}
|
|
1354
1640
|
//#endregion
|
|
1355
|
-
export {
|
|
1641
|
+
export { CLAUDE_BUDGET_MIN as a, CLAUDE_BUDGET_MAX as i, dayKey as n, THINKING_BUDGET_MAX as o, foldWindowBreakdown as r, createAgyRuntime as t };
|
|
1356
1642
|
|
|
1357
|
-
//# sourceMappingURL=plugin-common-
|
|
1643
|
+
//# sourceMappingURL=plugin-common-kSBwIaFi.mjs.map
|