mobbdev 1.4.43 → 1.4.45
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/args/commands/upload_ai_blame.mjs +58 -19
- package/dist/index.mjs +435 -115
- package/package.json +1 -1
|
@@ -1274,9 +1274,11 @@ var init_shared = __esm({
|
|
|
1274
1274
|
z.object({
|
|
1275
1275
|
scmSubmitFixRequest: z.object({
|
|
1276
1276
|
submitFixRequest: z.object({
|
|
1277
|
+
// note: nullable at runtime (e.g. deleted or permission-filtered user)
|
|
1278
|
+
// even though the generated schema says otherwise
|
|
1277
1279
|
createdByUser: z.object({
|
|
1278
1280
|
email: z.string()
|
|
1279
|
-
}),
|
|
1281
|
+
}).nullable().transform((user) => user ?? { email: "" }),
|
|
1280
1282
|
targetBranchName: z.string().default("")
|
|
1281
1283
|
}),
|
|
1282
1284
|
prUrl: z.string().nullable(),
|
|
@@ -4403,6 +4405,8 @@ var ScanContext = {
|
|
|
4403
4405
|
};
|
|
4404
4406
|
|
|
4405
4407
|
// src/utils/proxy.ts
|
|
4408
|
+
import { Agent as HttpAgent } from "http";
|
|
4409
|
+
import { Agent as HttpsAgent } from "https";
|
|
4406
4410
|
import fetchOrig from "cross-fetch";
|
|
4407
4411
|
import Debug2 from "debug";
|
|
4408
4412
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
@@ -4416,31 +4420,33 @@ function httpToWsUrl(url) {
|
|
|
4416
4420
|
|
|
4417
4421
|
// src/utils/proxy.ts
|
|
4418
4422
|
var debug2 = Debug2("mobbdev:proxy");
|
|
4423
|
+
var UPLOAD_MAX_SOCKETS = 8;
|
|
4424
|
+
var uploadHttpAgent;
|
|
4425
|
+
var uploadHttpsAgent;
|
|
4426
|
+
var uploadProxyAgents = /* @__PURE__ */ new Map();
|
|
4419
4427
|
function getHttpProxy() {
|
|
4420
4428
|
return process.env["HTTPS_PROXY"] || process.env["HTTP_PROXY"] || "";
|
|
4421
4429
|
}
|
|
4422
4430
|
function getHttpProxyOnly() {
|
|
4423
4431
|
return process.env["HTTP_PROXY"] || "";
|
|
4424
4432
|
}
|
|
4433
|
+
function resolveProxyForUrl(url) {
|
|
4434
|
+
const parsedUrl = new URL(url);
|
|
4435
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
4436
|
+
const isHttp = parsedUrl.protocol === "http:";
|
|
4437
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
4438
|
+
const isLocal = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
4439
|
+
const noProxy = process.env["NO_PROXY"] || process.env["no_proxy"];
|
|
4440
|
+
const noProxyMatch = noProxy?.split(",").map((h) => h.trim().toLowerCase()).some((h) => h === hostname || h === "*") ?? false;
|
|
4441
|
+
if (isLocal || noProxyMatch) {
|
|
4442
|
+
return { proxy: "", isHttp, isHttps };
|
|
4443
|
+
}
|
|
4444
|
+
const proxy = isHttps ? getHttpProxy() : isHttp ? getHttpProxyOnly() : "";
|
|
4445
|
+
return { proxy, isHttp, isHttps };
|
|
4446
|
+
}
|
|
4425
4447
|
function getProxyAgent(url) {
|
|
4426
4448
|
try {
|
|
4427
|
-
const
|
|
4428
|
-
const hostname = parsedUrl.hostname.toLowerCase();
|
|
4429
|
-
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]") {
|
|
4430
|
-
debug2("Skipping proxy for localhost URL: %s", url);
|
|
4431
|
-
return void 0;
|
|
4432
|
-
}
|
|
4433
|
-
const noProxy = process.env["NO_PROXY"] || process.env["no_proxy"];
|
|
4434
|
-
if (noProxy) {
|
|
4435
|
-
const noProxyList = noProxy.split(",").map((h) => h.trim().toLowerCase());
|
|
4436
|
-
if (noProxyList.includes(hostname) || noProxyList.includes("*")) {
|
|
4437
|
-
debug2("Skipping proxy due to NO_PROXY for: %s", url);
|
|
4438
|
-
return void 0;
|
|
4439
|
-
}
|
|
4440
|
-
}
|
|
4441
|
-
const isHttp = parsedUrl.protocol === "http:";
|
|
4442
|
-
const isHttps = parsedUrl.protocol === "https:";
|
|
4443
|
-
const proxy = isHttps ? getHttpProxy() : isHttp ? getHttpProxyOnly() : null;
|
|
4449
|
+
const { proxy } = resolveProxyForUrl(url);
|
|
4444
4450
|
if (proxy) {
|
|
4445
4451
|
debug2("Using proxy %s for %s", proxy, url);
|
|
4446
4452
|
return new HttpsProxyAgent(proxy);
|
|
@@ -4450,6 +4456,39 @@ function getProxyAgent(url) {
|
|
|
4450
4456
|
}
|
|
4451
4457
|
return void 0;
|
|
4452
4458
|
}
|
|
4459
|
+
function getUploadAgent(url) {
|
|
4460
|
+
try {
|
|
4461
|
+
const { proxy, isHttp, isHttps } = resolveProxyForUrl(url);
|
|
4462
|
+
if (proxy) {
|
|
4463
|
+
let agent = uploadProxyAgents.get(proxy);
|
|
4464
|
+
if (!agent) {
|
|
4465
|
+
agent = new HttpsProxyAgent(proxy, {
|
|
4466
|
+
keepAlive: true,
|
|
4467
|
+
maxSockets: UPLOAD_MAX_SOCKETS
|
|
4468
|
+
});
|
|
4469
|
+
uploadProxyAgents.set(proxy, agent);
|
|
4470
|
+
}
|
|
4471
|
+
return agent;
|
|
4472
|
+
}
|
|
4473
|
+
if (isHttps) {
|
|
4474
|
+
uploadHttpsAgent ?? (uploadHttpsAgent = new HttpsAgent({
|
|
4475
|
+
keepAlive: true,
|
|
4476
|
+
maxSockets: UPLOAD_MAX_SOCKETS
|
|
4477
|
+
}));
|
|
4478
|
+
return uploadHttpsAgent;
|
|
4479
|
+
}
|
|
4480
|
+
if (isHttp) {
|
|
4481
|
+
uploadHttpAgent ?? (uploadHttpAgent = new HttpAgent({
|
|
4482
|
+
keepAlive: true,
|
|
4483
|
+
maxSockets: UPLOAD_MAX_SOCKETS
|
|
4484
|
+
}));
|
|
4485
|
+
return uploadHttpAgent;
|
|
4486
|
+
}
|
|
4487
|
+
} catch (err) {
|
|
4488
|
+
debug2(`No upload agent for ${url}. Reason: ${err.message}`);
|
|
4489
|
+
}
|
|
4490
|
+
return void 0;
|
|
4491
|
+
}
|
|
4453
4492
|
var fetchWithProxy = (url, options = {}) => {
|
|
4454
4493
|
try {
|
|
4455
4494
|
const agent = getProxyAgent(url.toString());
|
|
@@ -5657,7 +5696,7 @@ async function uploadFile({
|
|
|
5657
5696
|
)
|
|
5658
5697
|
);
|
|
5659
5698
|
}
|
|
5660
|
-
const agent =
|
|
5699
|
+
const agent = getUploadAgent(url);
|
|
5661
5700
|
const effectiveSignal = signal ?? AbortSignal.timeout(computeUploadTimeoutMs(file));
|
|
5662
5701
|
const response = await fetch3(url, {
|
|
5663
5702
|
method: "POST",
|
package/dist/index.mjs
CHANGED
|
@@ -1376,9 +1376,11 @@ var init_shared = __esm({
|
|
|
1376
1376
|
z2.object({
|
|
1377
1377
|
scmSubmitFixRequest: z2.object({
|
|
1378
1378
|
submitFixRequest: z2.object({
|
|
1379
|
+
// note: nullable at runtime (e.g. deleted or permission-filtered user)
|
|
1380
|
+
// even though the generated schema says otherwise
|
|
1379
1381
|
createdByUser: z2.object({
|
|
1380
1382
|
email: z2.string()
|
|
1381
|
-
}),
|
|
1383
|
+
}).nullable().transform((user) => user ?? { email: "" }),
|
|
1382
1384
|
targetBranchName: z2.string().default("")
|
|
1383
1385
|
}),
|
|
1384
1386
|
prUrl: z2.string().nullable(),
|
|
@@ -11369,6 +11371,8 @@ __publicField(_ReportDigestError, "defaultMessage", "\u{1F575}\uFE0F\u200D\u2642
|
|
|
11369
11371
|
var ReportDigestError = _ReportDigestError;
|
|
11370
11372
|
|
|
11371
11373
|
// src/utils/proxy.ts
|
|
11374
|
+
import { Agent as HttpAgent } from "http";
|
|
11375
|
+
import { Agent as HttpsAgent } from "https";
|
|
11372
11376
|
import fetchOrig from "cross-fetch";
|
|
11373
11377
|
import Debug5 from "debug";
|
|
11374
11378
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
@@ -11382,31 +11386,33 @@ function httpToWsUrl(url) {
|
|
|
11382
11386
|
|
|
11383
11387
|
// src/utils/proxy.ts
|
|
11384
11388
|
var debug6 = Debug5("mobbdev:proxy");
|
|
11389
|
+
var UPLOAD_MAX_SOCKETS = 8;
|
|
11390
|
+
var uploadHttpAgent;
|
|
11391
|
+
var uploadHttpsAgent;
|
|
11392
|
+
var uploadProxyAgents = /* @__PURE__ */ new Map();
|
|
11385
11393
|
function getHttpProxy() {
|
|
11386
11394
|
return process.env["HTTPS_PROXY"] || process.env["HTTP_PROXY"] || "";
|
|
11387
11395
|
}
|
|
11388
11396
|
function getHttpProxyOnly() {
|
|
11389
11397
|
return process.env["HTTP_PROXY"] || "";
|
|
11390
11398
|
}
|
|
11399
|
+
function resolveProxyForUrl(url) {
|
|
11400
|
+
const parsedUrl = new URL(url);
|
|
11401
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
11402
|
+
const isHttp = parsedUrl.protocol === "http:";
|
|
11403
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
11404
|
+
const isLocal = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
11405
|
+
const noProxy = process.env["NO_PROXY"] || process.env["no_proxy"];
|
|
11406
|
+
const noProxyMatch = noProxy?.split(",").map((h) => h.trim().toLowerCase()).some((h) => h === hostname || h === "*") ?? false;
|
|
11407
|
+
if (isLocal || noProxyMatch) {
|
|
11408
|
+
return { proxy: "", isHttp, isHttps };
|
|
11409
|
+
}
|
|
11410
|
+
const proxy = isHttps ? getHttpProxy() : isHttp ? getHttpProxyOnly() : "";
|
|
11411
|
+
return { proxy, isHttp, isHttps };
|
|
11412
|
+
}
|
|
11391
11413
|
function getProxyAgent(url) {
|
|
11392
11414
|
try {
|
|
11393
|
-
const
|
|
11394
|
-
const hostname = parsedUrl.hostname.toLowerCase();
|
|
11395
|
-
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]") {
|
|
11396
|
-
debug6("Skipping proxy for localhost URL: %s", url);
|
|
11397
|
-
return void 0;
|
|
11398
|
-
}
|
|
11399
|
-
const noProxy = process.env["NO_PROXY"] || process.env["no_proxy"];
|
|
11400
|
-
if (noProxy) {
|
|
11401
|
-
const noProxyList = noProxy.split(",").map((h) => h.trim().toLowerCase());
|
|
11402
|
-
if (noProxyList.includes(hostname) || noProxyList.includes("*")) {
|
|
11403
|
-
debug6("Skipping proxy due to NO_PROXY for: %s", url);
|
|
11404
|
-
return void 0;
|
|
11405
|
-
}
|
|
11406
|
-
}
|
|
11407
|
-
const isHttp = parsedUrl.protocol === "http:";
|
|
11408
|
-
const isHttps = parsedUrl.protocol === "https:";
|
|
11409
|
-
const proxy = isHttps ? getHttpProxy() : isHttp ? getHttpProxyOnly() : null;
|
|
11415
|
+
const { proxy } = resolveProxyForUrl(url);
|
|
11410
11416
|
if (proxy) {
|
|
11411
11417
|
debug6("Using proxy %s for %s", proxy, url);
|
|
11412
11418
|
return new HttpsProxyAgent(proxy);
|
|
@@ -11416,6 +11422,39 @@ function getProxyAgent(url) {
|
|
|
11416
11422
|
}
|
|
11417
11423
|
return void 0;
|
|
11418
11424
|
}
|
|
11425
|
+
function getUploadAgent(url) {
|
|
11426
|
+
try {
|
|
11427
|
+
const { proxy, isHttp, isHttps } = resolveProxyForUrl(url);
|
|
11428
|
+
if (proxy) {
|
|
11429
|
+
let agent = uploadProxyAgents.get(proxy);
|
|
11430
|
+
if (!agent) {
|
|
11431
|
+
agent = new HttpsProxyAgent(proxy, {
|
|
11432
|
+
keepAlive: true,
|
|
11433
|
+
maxSockets: UPLOAD_MAX_SOCKETS
|
|
11434
|
+
});
|
|
11435
|
+
uploadProxyAgents.set(proxy, agent);
|
|
11436
|
+
}
|
|
11437
|
+
return agent;
|
|
11438
|
+
}
|
|
11439
|
+
if (isHttps) {
|
|
11440
|
+
uploadHttpsAgent ?? (uploadHttpsAgent = new HttpsAgent({
|
|
11441
|
+
keepAlive: true,
|
|
11442
|
+
maxSockets: UPLOAD_MAX_SOCKETS
|
|
11443
|
+
}));
|
|
11444
|
+
return uploadHttpsAgent;
|
|
11445
|
+
}
|
|
11446
|
+
if (isHttp) {
|
|
11447
|
+
uploadHttpAgent ?? (uploadHttpAgent = new HttpAgent({
|
|
11448
|
+
keepAlive: true,
|
|
11449
|
+
maxSockets: UPLOAD_MAX_SOCKETS
|
|
11450
|
+
}));
|
|
11451
|
+
return uploadHttpAgent;
|
|
11452
|
+
}
|
|
11453
|
+
} catch (err) {
|
|
11454
|
+
debug6(`No upload agent for ${url}. Reason: ${err.message}`);
|
|
11455
|
+
}
|
|
11456
|
+
return void 0;
|
|
11457
|
+
}
|
|
11419
11458
|
var fetchWithProxy = (url, options = {}) => {
|
|
11420
11459
|
try {
|
|
11421
11460
|
const agent = getProxyAgent(url.toString());
|
|
@@ -12299,6 +12338,16 @@ var S3UploadError = class extends Error {
|
|
|
12299
12338
|
this.name = "S3UploadError";
|
|
12300
12339
|
}
|
|
12301
12340
|
};
|
|
12341
|
+
var ACTIONABLE_S3_CODES = /* @__PURE__ */ new Set([
|
|
12342
|
+
"AccessDenied",
|
|
12343
|
+
"ExpiredToken",
|
|
12344
|
+
"InvalidAccessKeyId",
|
|
12345
|
+
"SignatureDoesNotMatch",
|
|
12346
|
+
"TokenRefreshRequired"
|
|
12347
|
+
]);
|
|
12348
|
+
function isActionableS3Error(err) {
|
|
12349
|
+
return err instanceof S3UploadError && err.s3Code !== void 0 && ACTIONABLE_S3_CODES.has(err.s3Code);
|
|
12350
|
+
}
|
|
12302
12351
|
function parseS3ErrorBody(body) {
|
|
12303
12352
|
return {
|
|
12304
12353
|
code: body.match(/<Code>([^<]+)<\/Code>/)?.[1],
|
|
@@ -12347,7 +12396,7 @@ async function uploadFile({
|
|
|
12347
12396
|
)
|
|
12348
12397
|
);
|
|
12349
12398
|
}
|
|
12350
|
-
const agent =
|
|
12399
|
+
const agent = getUploadAgent(url);
|
|
12351
12400
|
const effectiveSignal = signal ?? AbortSignal.timeout(computeUploadTimeoutMs(file));
|
|
12352
12401
|
const response = await fetch3(url, {
|
|
12353
12402
|
method: "POST",
|
|
@@ -15948,7 +15997,179 @@ import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
|
15948
15997
|
import * as os8 from "os";
|
|
15949
15998
|
import path24 from "path";
|
|
15950
15999
|
import { setTimeout as sleep4 } from "timers/promises";
|
|
15951
|
-
import
|
|
16000
|
+
import Configstore4 from "configstore";
|
|
16001
|
+
|
|
16002
|
+
// src/features/analysis/context_upload_ledger.ts
|
|
16003
|
+
import Configstore2 from "configstore";
|
|
16004
|
+
var UPLOADED_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
16005
|
+
var FAILURE_BACKOFF_BASE_MS = 60 * 1e3;
|
|
16006
|
+
var FAILURE_BACKOFF_MAX_MS = 60 * 60 * 1e3;
|
|
16007
|
+
var FAILURE_LOG_WINDOW_MS = 30 * 60 * 1e3;
|
|
16008
|
+
var BREAKER_FAILURE_THRESHOLD = 8;
|
|
16009
|
+
var BREAKER_FAILURE_WINDOW_MS = 5 * 60 * 1e3;
|
|
16010
|
+
var BREAKER_BASE_COOLDOWN_MS = 30 * 1e3;
|
|
16011
|
+
var BREAKER_MAX_COOLDOWN_MS = 10 * 60 * 1e3;
|
|
16012
|
+
var MAX_LEDGER_ENTRIES = 5e3;
|
|
16013
|
+
var LEDGER_NAMESPACE = "mobbdev-context-upload-ledger";
|
|
16014
|
+
var ENTRIES_KEY = "entries";
|
|
16015
|
+
var BREAKER_KEY = "breaker";
|
|
16016
|
+
var NOOP_UPLOAD_LEDGER = {
|
|
16017
|
+
isUploaded: () => false,
|
|
16018
|
+
isBackedOff: () => false,
|
|
16019
|
+
isBreakerOpen: () => false,
|
|
16020
|
+
onSuccess: () => {
|
|
16021
|
+
},
|
|
16022
|
+
onFailure: () => true,
|
|
16023
|
+
flush: () => {
|
|
16024
|
+
}
|
|
16025
|
+
};
|
|
16026
|
+
var ConfigstoreUploadLedger = class {
|
|
16027
|
+
constructor(store) {
|
|
16028
|
+
__publicField(this, "store");
|
|
16029
|
+
__publicField(this, "entries");
|
|
16030
|
+
__publicField(this, "breaker");
|
|
16031
|
+
__publicField(this, "dirty", false);
|
|
16032
|
+
this.store = store ?? new Configstore2(LEDGER_NAMESPACE);
|
|
16033
|
+
this.entries = loadEntries(this.store);
|
|
16034
|
+
this.breaker = loadBreaker(this.store);
|
|
16035
|
+
}
|
|
16036
|
+
isUploaded(md5, now) {
|
|
16037
|
+
const entry = this.entries.get(md5);
|
|
16038
|
+
if (!entry?.uploadedAt) {
|
|
16039
|
+
return false;
|
|
16040
|
+
}
|
|
16041
|
+
if (now - entry.uploadedAt > UPLOADED_TTL_MS) {
|
|
16042
|
+
return false;
|
|
16043
|
+
}
|
|
16044
|
+
entry.touchedAt = now;
|
|
16045
|
+
this.dirty = true;
|
|
16046
|
+
return true;
|
|
16047
|
+
}
|
|
16048
|
+
isBackedOff(md5, now) {
|
|
16049
|
+
const entry = this.entries.get(md5);
|
|
16050
|
+
return entry?.nextRetryAt !== void 0 && entry.nextRetryAt > now;
|
|
16051
|
+
}
|
|
16052
|
+
isBreakerOpen(now) {
|
|
16053
|
+
return this.breaker.openUntil > now;
|
|
16054
|
+
}
|
|
16055
|
+
onSuccess(md5, now) {
|
|
16056
|
+
const entry = this.getOrCreate(md5, now);
|
|
16057
|
+
entry.uploadedAt = now;
|
|
16058
|
+
entry.touchedAt = now;
|
|
16059
|
+
delete entry.attempts;
|
|
16060
|
+
delete entry.nextRetryAt;
|
|
16061
|
+
delete entry.lastLoggedAt;
|
|
16062
|
+
this.breaker.consecutiveFailures = 0;
|
|
16063
|
+
this.breaker.lastFailureAt = 0;
|
|
16064
|
+
this.breaker.trips = 0;
|
|
16065
|
+
this.breaker.openUntil = 0;
|
|
16066
|
+
this.dirty = true;
|
|
16067
|
+
}
|
|
16068
|
+
onFailure(md5, now) {
|
|
16069
|
+
const entry = this.getOrCreate(md5, now);
|
|
16070
|
+
const attempts = (entry.attempts ?? 0) + 1;
|
|
16071
|
+
const delay3 = Math.min(
|
|
16072
|
+
FAILURE_BACKOFF_BASE_MS * 2 ** (attempts - 1),
|
|
16073
|
+
FAILURE_BACKOFF_MAX_MS
|
|
16074
|
+
);
|
|
16075
|
+
entry.attempts = attempts;
|
|
16076
|
+
entry.nextRetryAt = now + delay3;
|
|
16077
|
+
entry.touchedAt = now;
|
|
16078
|
+
if (now - this.breaker.lastFailureAt > BREAKER_FAILURE_WINDOW_MS) {
|
|
16079
|
+
this.breaker.consecutiveFailures = 0;
|
|
16080
|
+
}
|
|
16081
|
+
this.breaker.lastFailureAt = now;
|
|
16082
|
+
this.breaker.consecutiveFailures += 1;
|
|
16083
|
+
if (this.breaker.consecutiveFailures >= BREAKER_FAILURE_THRESHOLD && this.breaker.openUntil <= now) {
|
|
16084
|
+
const cooldown = Math.min(
|
|
16085
|
+
BREAKER_BASE_COOLDOWN_MS * 2 ** this.breaker.trips,
|
|
16086
|
+
BREAKER_MAX_COOLDOWN_MS
|
|
16087
|
+
);
|
|
16088
|
+
this.breaker.trips += 1;
|
|
16089
|
+
this.breaker.openUntil = now + cooldown;
|
|
16090
|
+
}
|
|
16091
|
+
const shouldLog = entry.lastLoggedAt === void 0 || now - entry.lastLoggedAt >= FAILURE_LOG_WINDOW_MS;
|
|
16092
|
+
if (shouldLog) {
|
|
16093
|
+
entry.lastLoggedAt = now;
|
|
16094
|
+
}
|
|
16095
|
+
this.dirty = true;
|
|
16096
|
+
return shouldLog;
|
|
16097
|
+
}
|
|
16098
|
+
flush() {
|
|
16099
|
+
if (!this.dirty) {
|
|
16100
|
+
return;
|
|
16101
|
+
}
|
|
16102
|
+
this.evictIfNeeded();
|
|
16103
|
+
try {
|
|
16104
|
+
this.store.set(ENTRIES_KEY, Object.fromEntries(this.entries));
|
|
16105
|
+
this.store.set(BREAKER_KEY, this.breaker);
|
|
16106
|
+
this.dirty = false;
|
|
16107
|
+
} catch {
|
|
16108
|
+
}
|
|
16109
|
+
}
|
|
16110
|
+
getOrCreate(md5, now) {
|
|
16111
|
+
let entry = this.entries.get(md5);
|
|
16112
|
+
if (!entry) {
|
|
16113
|
+
entry = { touchedAt: now };
|
|
16114
|
+
this.entries.set(md5, entry);
|
|
16115
|
+
}
|
|
16116
|
+
return entry;
|
|
16117
|
+
}
|
|
16118
|
+
/** Evict least-recently-touched entries when over the cap. */
|
|
16119
|
+
evictIfNeeded() {
|
|
16120
|
+
if (this.entries.size <= MAX_LEDGER_ENTRIES) {
|
|
16121
|
+
return;
|
|
16122
|
+
}
|
|
16123
|
+
const sorted = [...this.entries.entries()].sort(
|
|
16124
|
+
(a, b) => a[1].touchedAt - b[1].touchedAt
|
|
16125
|
+
);
|
|
16126
|
+
const removeCount = this.entries.size - MAX_LEDGER_ENTRIES;
|
|
16127
|
+
for (let i = 0; i < removeCount; i++) {
|
|
16128
|
+
this.entries.delete(sorted[i][0]);
|
|
16129
|
+
}
|
|
16130
|
+
}
|
|
16131
|
+
};
|
|
16132
|
+
function finiteOrUndefined(value) {
|
|
16133
|
+
const n = Number(value);
|
|
16134
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
16135
|
+
}
|
|
16136
|
+
function loadEntries(store) {
|
|
16137
|
+
const map = /* @__PURE__ */ new Map();
|
|
16138
|
+
try {
|
|
16139
|
+
const raw = store.get(ENTRIES_KEY);
|
|
16140
|
+
if (raw && typeof raw === "object") {
|
|
16141
|
+
for (const [md5, entry] of Object.entries(raw)) {
|
|
16142
|
+
if (!entry || typeof entry !== "object") {
|
|
16143
|
+
continue;
|
|
16144
|
+
}
|
|
16145
|
+
map.set(md5, {
|
|
16146
|
+
uploadedAt: finiteOrUndefined(entry.uploadedAt),
|
|
16147
|
+
attempts: finiteOrUndefined(entry.attempts),
|
|
16148
|
+
nextRetryAt: finiteOrUndefined(entry.nextRetryAt),
|
|
16149
|
+
lastLoggedAt: finiteOrUndefined(entry.lastLoggedAt),
|
|
16150
|
+
touchedAt: finiteOrUndefined(entry.touchedAt) ?? 0
|
|
16151
|
+
});
|
|
16152
|
+
}
|
|
16153
|
+
}
|
|
16154
|
+
} catch {
|
|
16155
|
+
}
|
|
16156
|
+
return map;
|
|
16157
|
+
}
|
|
16158
|
+
function loadBreaker(store) {
|
|
16159
|
+
try {
|
|
16160
|
+
const raw = store.get(BREAKER_KEY);
|
|
16161
|
+
if (raw && typeof raw === "object") {
|
|
16162
|
+
return {
|
|
16163
|
+
consecutiveFailures: finiteOrUndefined(raw.consecutiveFailures) ?? 0,
|
|
16164
|
+
lastFailureAt: finiteOrUndefined(raw.lastFailureAt) ?? 0,
|
|
16165
|
+
trips: finiteOrUndefined(raw.trips) ?? 0,
|
|
16166
|
+
openUntil: finiteOrUndefined(raw.openUntil) ?? 0
|
|
16167
|
+
};
|
|
16168
|
+
}
|
|
16169
|
+
} catch {
|
|
16170
|
+
}
|
|
16171
|
+
return { consecutiveFailures: 0, lastFailureAt: 0, trips: 0, openUntil: 0 };
|
|
16172
|
+
}
|
|
15952
16173
|
|
|
15953
16174
|
// src/features/analysis/skill_quarantine/runQuarantineCheck.ts
|
|
15954
16175
|
import { stat as stat3 } from "fs/promises";
|
|
@@ -16226,8 +16447,8 @@ var SCAN_PATHS = {
|
|
|
16226
16447
|
// src/features/analysis/context_file_scanner.ts
|
|
16227
16448
|
var MAX_CONTEXT_FILE_SIZE = 20 * 1024 * 1024;
|
|
16228
16449
|
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
16229
|
-
var
|
|
16230
|
-
var
|
|
16450
|
+
var FAILURE_BACKOFF_BASE_MS2 = 60 * 1e3;
|
|
16451
|
+
var FAILURE_BACKOFF_MAX_MS2 = 60 * 60 * 1e3;
|
|
16231
16452
|
var sessionMtimes = /* @__PURE__ */ new Map();
|
|
16232
16453
|
function markContextFilesUploaded(sessionId, files, skills) {
|
|
16233
16454
|
const entry = getOrCreateSessionEntry(sessionId);
|
|
@@ -16249,8 +16470,8 @@ function markContextUploadFailed(sessionId, files, skills) {
|
|
|
16249
16470
|
const recordFailure = (key) => {
|
|
16250
16471
|
const attempts = (entry.failures.get(key)?.attempts ?? 0) + 1;
|
|
16251
16472
|
const delay3 = Math.min(
|
|
16252
|
-
|
|
16253
|
-
|
|
16473
|
+
FAILURE_BACKOFF_BASE_MS2 * 2 ** (attempts - 1),
|
|
16474
|
+
FAILURE_BACKOFF_MAX_MS2
|
|
16254
16475
|
);
|
|
16255
16476
|
entry.failures.set(key, { attempts, nextRetryAt: now + delay3 });
|
|
16256
16477
|
};
|
|
@@ -17365,7 +17586,12 @@ var DAEMON_POLL_INTERVAL_MS = (() => {
|
|
|
17365
17586
|
var HEARTBEAT_STALE_MS = 3e4;
|
|
17366
17587
|
var TRANSCRIPT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
17367
17588
|
var DAEMON_CHUNK_SIZE = 50;
|
|
17368
|
-
var CONTEXT_SCAN_INTERVAL_MS =
|
|
17589
|
+
var CONTEXT_SCAN_INTERVAL_MS = (() => {
|
|
17590
|
+
const raw = Number(process.env["MOBB_CONTEXT_SCAN_INTERVAL_MS"]);
|
|
17591
|
+
if (!Number.isFinite(raw) || raw <= 0) return 15 * 6e4;
|
|
17592
|
+
return Math.min(Math.max(raw, 1e4), 60 * 6e4);
|
|
17593
|
+
})();
|
|
17594
|
+
var DAEMON_SHUTDOWN_DRAIN_MS = 6e3;
|
|
17369
17595
|
|
|
17370
17596
|
// src/features/claude_code/daemon_pid_file.ts
|
|
17371
17597
|
function getMobbdevDir() {
|
|
@@ -17449,7 +17675,7 @@ import { promisify } from "util";
|
|
|
17449
17675
|
import { setTimeout as sleep3 } from "timers/promises";
|
|
17450
17676
|
import pLimit6 from "p-limit";
|
|
17451
17677
|
init_client_generates();
|
|
17452
|
-
var UPLOAD_CONCURRENCY =
|
|
17678
|
+
var UPLOAD_CONCURRENCY = 1;
|
|
17453
17679
|
var UPLOAD_MAX_ATTEMPTS = 2;
|
|
17454
17680
|
var UPLOAD_RETRY_BASE_MS = 250;
|
|
17455
17681
|
async function uploadWithRetry(args) {
|
|
@@ -17467,6 +17693,29 @@ async function uploadWithRetry(args) {
|
|
|
17467
17693
|
}
|
|
17468
17694
|
}
|
|
17469
17695
|
}
|
|
17696
|
+
var uploadLimit = pLimit6(UPLOAD_CONCURRENCY);
|
|
17697
|
+
var inFlightUploads = /* @__PURE__ */ new Map();
|
|
17698
|
+
async function coalescedUpload(s3Key, run2) {
|
|
17699
|
+
const existing = inFlightUploads.get(s3Key);
|
|
17700
|
+
if (existing) {
|
|
17701
|
+
try {
|
|
17702
|
+
await existing;
|
|
17703
|
+
return { leader: false, ok: true };
|
|
17704
|
+
} catch (err) {
|
|
17705
|
+
return { leader: false, ok: false, err };
|
|
17706
|
+
}
|
|
17707
|
+
}
|
|
17708
|
+
const p = uploadLimit(run2);
|
|
17709
|
+
inFlightUploads.set(s3Key, p);
|
|
17710
|
+
try {
|
|
17711
|
+
await p;
|
|
17712
|
+
return { leader: true, ok: true };
|
|
17713
|
+
} catch (err) {
|
|
17714
|
+
return { leader: true, ok: false, err };
|
|
17715
|
+
} finally {
|
|
17716
|
+
inFlightUploads.delete(s3Key);
|
|
17717
|
+
}
|
|
17718
|
+
}
|
|
17470
17719
|
async function uploadContextRecords(opts) {
|
|
17471
17720
|
const {
|
|
17472
17721
|
processedFiles,
|
|
@@ -17481,6 +17730,7 @@ async function uploadContextRecords(opts) {
|
|
|
17481
17730
|
branch,
|
|
17482
17731
|
commitSha,
|
|
17483
17732
|
clientVersion,
|
|
17733
|
+
ledger = NOOP_UPLOAD_LEDGER,
|
|
17484
17734
|
onFileError,
|
|
17485
17735
|
onSkillError
|
|
17486
17736
|
} = opts;
|
|
@@ -17489,82 +17739,97 @@ async function uploadContextRecords(opts) {
|
|
|
17489
17739
|
const uploadedSkillGroups = [];
|
|
17490
17740
|
const failedFiles = [];
|
|
17491
17741
|
const failedSkillGroups = [];
|
|
17492
|
-
|
|
17742
|
+
let dedupedCount = 0;
|
|
17743
|
+
let suppressedCount = 0;
|
|
17493
17744
|
const extraFields = {
|
|
17494
17745
|
...repositoryUrl !== void 0 && { repositoryUrl },
|
|
17495
17746
|
...branch !== void 0 && { branch },
|
|
17496
17747
|
...commitSha !== void 0 && { commitSha },
|
|
17497
17748
|
...clientVersion !== void 0 && { clientVersion }
|
|
17498
17749
|
};
|
|
17750
|
+
const processItem = async (item) => {
|
|
17751
|
+
const nowMs = Date.now();
|
|
17752
|
+
if (ledger.isUploaded(item.md5, nowMs)) {
|
|
17753
|
+
dedupedCount++;
|
|
17754
|
+
} else if (ledger.isBreakerOpen(nowMs) || ledger.isBackedOff(item.md5, nowMs)) {
|
|
17755
|
+
suppressedCount++;
|
|
17756
|
+
return;
|
|
17757
|
+
} else {
|
|
17758
|
+
const { leader, ok, err } = await coalescedUpload(
|
|
17759
|
+
item.s3Key,
|
|
17760
|
+
() => uploadWithRetry({
|
|
17761
|
+
file: item.getPayload(),
|
|
17762
|
+
url,
|
|
17763
|
+
uploadKey: item.s3Key,
|
|
17764
|
+
uploadFields
|
|
17765
|
+
})
|
|
17766
|
+
);
|
|
17767
|
+
if (!ok) {
|
|
17768
|
+
item.markFailed();
|
|
17769
|
+
if (leader) {
|
|
17770
|
+
const logByWindow = ledger.onFailure(item.md5, Date.now());
|
|
17771
|
+
if (logByWindow || isActionableS3Error(err)) {
|
|
17772
|
+
item.onError?.(item.errorName, err);
|
|
17773
|
+
}
|
|
17774
|
+
}
|
|
17775
|
+
return;
|
|
17776
|
+
}
|
|
17777
|
+
if (leader) {
|
|
17778
|
+
ledger.onSuccess(item.md5, Date.now());
|
|
17779
|
+
}
|
|
17780
|
+
}
|
|
17781
|
+
records.push({
|
|
17782
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17783
|
+
platform: platform2,
|
|
17784
|
+
recordId: `ctx:${sessionId}:${item.md5}`,
|
|
17785
|
+
recordTimestamp: now,
|
|
17786
|
+
blameType: "CHAT" /* Chat */,
|
|
17787
|
+
rawDataS3Key: item.s3Key,
|
|
17788
|
+
...extraFields,
|
|
17789
|
+
context: {
|
|
17790
|
+
md5: item.md5,
|
|
17791
|
+
category: item.context.category,
|
|
17792
|
+
name: item.context.name,
|
|
17793
|
+
sizeBytes: item.context.sizeBytes,
|
|
17794
|
+
filePath: item.context.filePath,
|
|
17795
|
+
sessionId
|
|
17796
|
+
}
|
|
17797
|
+
});
|
|
17798
|
+
item.markUploaded();
|
|
17799
|
+
};
|
|
17499
17800
|
const tasks = [
|
|
17500
17801
|
...processedFiles.map(
|
|
17501
|
-
(pf) =>
|
|
17502
|
-
|
|
17503
|
-
|
|
17504
|
-
|
|
17505
|
-
|
|
17506
|
-
|
|
17507
|
-
|
|
17508
|
-
|
|
17509
|
-
|
|
17510
|
-
}
|
|
17511
|
-
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
|
|
17515
|
-
records.push({
|
|
17516
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17517
|
-
platform: platform2,
|
|
17518
|
-
recordId: `ctx:${sessionId}:${pf.md5}`,
|
|
17519
|
-
recordTimestamp: now,
|
|
17520
|
-
blameType: "CHAT" /* Chat */,
|
|
17521
|
-
rawDataS3Key: s3Key,
|
|
17522
|
-
...extraFields,
|
|
17523
|
-
context: {
|
|
17524
|
-
md5: pf.md5,
|
|
17525
|
-
category: pf.entry.category,
|
|
17526
|
-
name: pf.entry.name,
|
|
17527
|
-
sizeBytes: pf.sizeBytes,
|
|
17528
|
-
filePath: pf.entry.path,
|
|
17529
|
-
sessionId
|
|
17530
|
-
}
|
|
17531
|
-
});
|
|
17532
|
-
uploadedFiles.push(pf.entry);
|
|
17802
|
+
(pf) => processItem({
|
|
17803
|
+
md5: pf.md5,
|
|
17804
|
+
s3Key: `${keyPrefix}ctx-${pf.md5}.bin`,
|
|
17805
|
+
getPayload: () => Buffer.from(pf.sanitizedContent, "utf-8"),
|
|
17806
|
+
context: {
|
|
17807
|
+
category: pf.entry.category,
|
|
17808
|
+
name: pf.entry.name,
|
|
17809
|
+
sizeBytes: pf.sizeBytes,
|
|
17810
|
+
filePath: pf.entry.path
|
|
17811
|
+
},
|
|
17812
|
+
errorName: pf.entry.name,
|
|
17813
|
+
onError: onFileError,
|
|
17814
|
+
markUploaded: () => uploadedFiles.push(pf.entry),
|
|
17815
|
+
markFailed: () => failedFiles.push(pf.entry)
|
|
17533
17816
|
})
|
|
17534
17817
|
),
|
|
17535
17818
|
...processedSkills.map(
|
|
17536
|
-
(ps) =>
|
|
17537
|
-
|
|
17538
|
-
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
}
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
records.push({
|
|
17551
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17552
|
-
platform: platform2,
|
|
17553
|
-
recordId: `ctx:${sessionId}:${ps.md5}`,
|
|
17554
|
-
recordTimestamp: now,
|
|
17555
|
-
blameType: "CHAT" /* Chat */,
|
|
17556
|
-
rawDataS3Key: s3Key,
|
|
17557
|
-
...extraFields,
|
|
17558
|
-
context: {
|
|
17559
|
-
md5: ps.md5,
|
|
17560
|
-
category: SKILL_CATEGORY,
|
|
17561
|
-
name: ps.group.name,
|
|
17562
|
-
sizeBytes: ps.sizeBytes,
|
|
17563
|
-
filePath: ps.group.skillPath,
|
|
17564
|
-
sessionId
|
|
17565
|
-
}
|
|
17566
|
-
});
|
|
17567
|
-
uploadedSkillGroups.push(ps.group);
|
|
17819
|
+
(ps) => processItem({
|
|
17820
|
+
md5: ps.md5,
|
|
17821
|
+
s3Key: `${keyPrefix}skill-${ps.md5}.zip`,
|
|
17822
|
+
getPayload: () => ps.zipBuffer,
|
|
17823
|
+
context: {
|
|
17824
|
+
category: SKILL_CATEGORY,
|
|
17825
|
+
name: ps.group.name,
|
|
17826
|
+
sizeBytes: ps.sizeBytes,
|
|
17827
|
+
filePath: ps.group.skillPath
|
|
17828
|
+
},
|
|
17829
|
+
errorName: ps.group.name,
|
|
17830
|
+
onError: onSkillError,
|
|
17831
|
+
markUploaded: () => uploadedSkillGroups.push(ps.group),
|
|
17832
|
+
markFailed: () => failedSkillGroups.push(ps.group)
|
|
17568
17833
|
})
|
|
17569
17834
|
)
|
|
17570
17835
|
];
|
|
@@ -17574,7 +17839,10 @@ async function uploadContextRecords(opts) {
|
|
|
17574
17839
|
uploadedFiles,
|
|
17575
17840
|
uploadedSkillGroups,
|
|
17576
17841
|
failedFiles,
|
|
17577
|
-
failedSkillGroups
|
|
17842
|
+
failedSkillGroups,
|
|
17843
|
+
dedupedCount,
|
|
17844
|
+
suppressedCount,
|
|
17845
|
+
breakerOpen: ledger.isBreakerOpen(Date.now())
|
|
17578
17846
|
};
|
|
17579
17847
|
}
|
|
17580
17848
|
async function runContextFileUploadPipeline(opts) {
|
|
@@ -17590,6 +17858,7 @@ async function runContextFileUploadPipeline(opts) {
|
|
|
17590
17858
|
branch,
|
|
17591
17859
|
commitSha,
|
|
17592
17860
|
clientVersion,
|
|
17861
|
+
ledger,
|
|
17593
17862
|
submitRecords,
|
|
17594
17863
|
onFileError,
|
|
17595
17864
|
onSkillError
|
|
@@ -17606,7 +17875,10 @@ async function runContextFileUploadPipeline(opts) {
|
|
|
17606
17875
|
uploadedFiles,
|
|
17607
17876
|
uploadedSkillGroups,
|
|
17608
17877
|
failedFiles,
|
|
17609
|
-
failedSkillGroups
|
|
17878
|
+
failedSkillGroups,
|
|
17879
|
+
dedupedCount,
|
|
17880
|
+
suppressedCount,
|
|
17881
|
+
breakerOpen
|
|
17610
17882
|
} = await uploadContextRecords({
|
|
17611
17883
|
processedFiles,
|
|
17612
17884
|
processedSkills,
|
|
@@ -17620,20 +17892,28 @@ async function runContextFileUploadPipeline(opts) {
|
|
|
17620
17892
|
branch,
|
|
17621
17893
|
commitSha,
|
|
17622
17894
|
clientVersion,
|
|
17895
|
+
ledger,
|
|
17623
17896
|
onFileError,
|
|
17624
17897
|
onSkillError
|
|
17625
17898
|
});
|
|
17626
17899
|
if (failedFiles.length > 0 || failedSkillGroups.length > 0) {
|
|
17627
17900
|
markContextUploadFailed(sessionId, failedFiles, failedSkillGroups);
|
|
17628
17901
|
}
|
|
17902
|
+
const summary = {
|
|
17903
|
+
dedupedCount,
|
|
17904
|
+
suppressedCount,
|
|
17905
|
+
failedCount: failedFiles.length + failedSkillGroups.length,
|
|
17906
|
+
breakerOpen
|
|
17907
|
+
};
|
|
17629
17908
|
if (records.length === 0) {
|
|
17630
|
-
return { fileCount: 0, skillCount: 0 };
|
|
17909
|
+
return { fileCount: 0, skillCount: 0, ...summary };
|
|
17631
17910
|
}
|
|
17632
17911
|
await submitRecords(records);
|
|
17633
17912
|
markContextFilesUploaded(sessionId, uploadedFiles, uploadedSkillGroups);
|
|
17634
17913
|
return {
|
|
17635
17914
|
fileCount: uploadedFiles.length,
|
|
17636
|
-
skillCount: uploadedSkillGroups.length
|
|
17915
|
+
skillCount: uploadedSkillGroups.length,
|
|
17916
|
+
...summary
|
|
17637
17917
|
};
|
|
17638
17918
|
}
|
|
17639
17919
|
|
|
@@ -17641,7 +17921,7 @@ async function runContextFileUploadPipeline(opts) {
|
|
|
17641
17921
|
init_client_generates();
|
|
17642
17922
|
|
|
17643
17923
|
// src/utils/shared-logger/create-logger.ts
|
|
17644
|
-
import
|
|
17924
|
+
import Configstore3 from "configstore";
|
|
17645
17925
|
import pino from "pino";
|
|
17646
17926
|
|
|
17647
17927
|
// src/utils/shared-logger/configstore-stream.ts
|
|
@@ -17868,7 +18148,7 @@ function createLogger(config2) {
|
|
|
17868
18148
|
} = config2;
|
|
17869
18149
|
let csStream = null;
|
|
17870
18150
|
if (enableConfigstore) {
|
|
17871
|
-
const store = new
|
|
18151
|
+
const store = new Configstore3(namespace, {});
|
|
17872
18152
|
csStream = createConfigstoreStream(store, {
|
|
17873
18153
|
buffered,
|
|
17874
18154
|
scopePath,
|
|
@@ -17977,7 +18257,7 @@ function createLogger(config2) {
|
|
|
17977
18257
|
|
|
17978
18258
|
// src/features/claude_code/hook_logger.ts
|
|
17979
18259
|
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
17980
|
-
var CLI_VERSION = true ? "1.4.
|
|
18260
|
+
var CLI_VERSION = true ? "1.4.45" : "unknown";
|
|
17981
18261
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
17982
18262
|
var claudeCodeVersion;
|
|
17983
18263
|
function buildDdTags() {
|
|
@@ -18548,7 +18828,7 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
|
|
|
18548
18828
|
errors: entries.length
|
|
18549
18829
|
};
|
|
18550
18830
|
}
|
|
18551
|
-
async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2) {
|
|
18831
|
+
async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2, ledger) {
|
|
18552
18832
|
const { regularFiles, skillGroups } = await scanContextFiles(
|
|
18553
18833
|
cwd,
|
|
18554
18834
|
"claude-code",
|
|
@@ -18570,6 +18850,7 @@ async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2) {
|
|
|
18570
18850
|
);
|
|
18571
18851
|
return;
|
|
18572
18852
|
}
|
|
18853
|
+
const uploadFailures = [];
|
|
18573
18854
|
const pipelineResult = await runContextFileUploadPipeline({
|
|
18574
18855
|
processedFiles,
|
|
18575
18856
|
processedSkills,
|
|
@@ -18578,20 +18859,15 @@ async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2) {
|
|
|
18578
18859
|
url,
|
|
18579
18860
|
uploadFieldsJSON,
|
|
18580
18861
|
keyPrefix,
|
|
18862
|
+
ledger,
|
|
18581
18863
|
submitRecords: async (records) => {
|
|
18582
18864
|
const r = await prepareAndSendTracyRecords(gqlClient, records, cwd);
|
|
18583
18865
|
if (!r.ok) {
|
|
18584
18866
|
throw new Error(r.errors?.join(", ") ?? "batch upload failed");
|
|
18585
18867
|
}
|
|
18586
18868
|
},
|
|
18587
|
-
onFileError: (name, err) =>
|
|
18588
|
-
|
|
18589
|
-
"Failed to upload context file to S3"
|
|
18590
|
-
),
|
|
18591
|
-
onSkillError: (name, err) => log2.error(
|
|
18592
|
-
{ data: { sessionId, name, err } },
|
|
18593
|
-
"Failed to upload skill zip to S3"
|
|
18594
|
-
)
|
|
18869
|
+
onFileError: (name, err) => uploadFailures.push({ name, err }),
|
|
18870
|
+
onSkillError: (name, err) => uploadFailures.push({ name, err })
|
|
18595
18871
|
});
|
|
18596
18872
|
if (pipelineResult === null) {
|
|
18597
18873
|
log2.error(
|
|
@@ -18600,19 +18876,48 @@ async function uploadContextFilesIfNeeded(sessionId, cwd, gqlClient, log2) {
|
|
|
18600
18876
|
);
|
|
18601
18877
|
return;
|
|
18602
18878
|
}
|
|
18879
|
+
if (uploadFailures.length > 0) {
|
|
18880
|
+
const hasActionableS3Error = uploadFailures.some(
|
|
18881
|
+
(f) => isActionableS3Error(f.err)
|
|
18882
|
+
);
|
|
18883
|
+
const payload = {
|
|
18884
|
+
sessionId,
|
|
18885
|
+
failedCount: pipelineResult.failedCount,
|
|
18886
|
+
suppressedCount: pipelineResult.suppressedCount,
|
|
18887
|
+
dedupedCount: pipelineResult.dedupedCount,
|
|
18888
|
+
breakerOpen: pipelineResult.breakerOpen,
|
|
18889
|
+
errTypes: summarizeUploadErrTypes(uploadFailures.map((f) => f.err)),
|
|
18890
|
+
sample: uploadFailures.slice(0, 3).map((f) => f.name)
|
|
18891
|
+
};
|
|
18892
|
+
if (hasActionableS3Error) {
|
|
18893
|
+
log2.error({ data: payload }, "Context/skill S3 upload failures");
|
|
18894
|
+
} else {
|
|
18895
|
+
log2.warn({ data: payload }, "Context/skill upload failures (transient)");
|
|
18896
|
+
}
|
|
18897
|
+
}
|
|
18603
18898
|
if (pipelineResult.fileCount > 0 || pipelineResult.skillCount > 0) {
|
|
18604
18899
|
log2.info(
|
|
18605
18900
|
{
|
|
18606
18901
|
data: {
|
|
18607
18902
|
sessionId,
|
|
18608
18903
|
fileCount: pipelineResult.fileCount,
|
|
18609
|
-
skillCount: pipelineResult.skillCount
|
|
18904
|
+
skillCount: pipelineResult.skillCount,
|
|
18905
|
+
dedupedCount: pipelineResult.dedupedCount
|
|
18610
18906
|
}
|
|
18611
18907
|
},
|
|
18612
18908
|
"Uploaded context files and skills for session"
|
|
18613
18909
|
);
|
|
18614
18910
|
}
|
|
18615
18911
|
}
|
|
18912
|
+
function summarizeUploadErrTypes(errs) {
|
|
18913
|
+
const counts = {};
|
|
18914
|
+
for (const err of errs) {
|
|
18915
|
+
const e = err;
|
|
18916
|
+
const key = e?.type ?? e?.code ?? e?.name ?? "unknown";
|
|
18917
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
18918
|
+
}
|
|
18919
|
+
return counts;
|
|
18920
|
+
}
|
|
18616
18921
|
|
|
18617
18922
|
// src/features/claude_code/install_hook.ts
|
|
18618
18923
|
import fs14 from "fs";
|
|
@@ -18951,9 +19256,18 @@ async function startDaemon() {
|
|
|
18951
19256
|
hookLog.info("Daemon starting");
|
|
18952
19257
|
pruneHookLogFile();
|
|
18953
19258
|
const pidFile = await acquirePidFile();
|
|
19259
|
+
const uploadLedger = new ConfigstoreUploadLedger();
|
|
19260
|
+
const inFlightContextUploads = /* @__PURE__ */ new Set();
|
|
18954
19261
|
async function gracefulExit(code, reason) {
|
|
18955
19262
|
hookLog.info({ data: { code } }, `Daemon exiting: ${reason}`);
|
|
18956
19263
|
pidFile.remove();
|
|
19264
|
+
if (inFlightContextUploads.size > 0) {
|
|
19265
|
+
await Promise.race([
|
|
19266
|
+
Promise.allSettled([...inFlightContextUploads]),
|
|
19267
|
+
sleep4(DAEMON_SHUTDOWN_DRAIN_MS)
|
|
19268
|
+
]);
|
|
19269
|
+
}
|
|
19270
|
+
uploadLedger.flush();
|
|
18957
19271
|
await flushDdLogs();
|
|
18958
19272
|
process.exit(code);
|
|
18959
19273
|
}
|
|
@@ -19043,14 +19357,20 @@ async function startDaemon() {
|
|
|
19043
19357
|
}
|
|
19044
19358
|
}
|
|
19045
19359
|
const now = Date.now();
|
|
19046
|
-
if (now - lastContextScanMs >= CONTEXT_SCAN_INTERVAL_MS) {
|
|
19360
|
+
if (!shuttingDown && now - lastContextScanMs >= CONTEXT_SCAN_INTERVAL_MS) {
|
|
19047
19361
|
lastContextScanMs = now;
|
|
19048
19362
|
for (const { sessionId, cwd } of sessionCwdCache.values()) {
|
|
19049
19363
|
const log2 = createScopedHookLog(cwd, { daemonMode: true });
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
|
|
19364
|
+
const p = uploadContextFilesIfNeeded(
|
|
19365
|
+
sessionId,
|
|
19366
|
+
cwd,
|
|
19367
|
+
gqlClient,
|
|
19368
|
+
log2,
|
|
19369
|
+
uploadLedger
|
|
19370
|
+
).catch((err) => log2.warn({ err }, "Context file scan failed")).finally(() => inFlightContextUploads.delete(p));
|
|
19371
|
+
inFlightContextUploads.add(p);
|
|
19053
19372
|
}
|
|
19373
|
+
uploadLedger.flush();
|
|
19054
19374
|
}
|
|
19055
19375
|
if (cleanupConfigDir) {
|
|
19056
19376
|
await cleanupStaleSessions(cleanupConfigDir);
|
|
@@ -19220,7 +19540,7 @@ async function tryAutoUpgradeHooks() {
|
|
|
19220
19540
|
var HOOK_LOG_MAX_SCOPE_KEYS = 20;
|
|
19221
19541
|
var HOOK_LOG_MAX_ENTRIES_PER_KEY = 200;
|
|
19222
19542
|
function pruneHookLogFile() {
|
|
19223
|
-
const logFilePath = new
|
|
19543
|
+
const logFilePath = new Configstore4("mobbdev-claude-code-hook-logs").path;
|
|
19224
19544
|
try {
|
|
19225
19545
|
const raw = readFileSync(logFilePath, "utf-8");
|
|
19226
19546
|
const data = JSON.parse(raw);
|
|
@@ -19352,7 +19672,7 @@ import {
|
|
|
19352
19672
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
19353
19673
|
|
|
19354
19674
|
// src/mcp/Logger.ts
|
|
19355
|
-
import
|
|
19675
|
+
import Configstore5 from "configstore";
|
|
19356
19676
|
|
|
19357
19677
|
// src/mcp/services/WorkspaceService.ts
|
|
19358
19678
|
var WorkspaceService = class {
|
|
@@ -19438,7 +19758,7 @@ var Logger = class {
|
|
|
19438
19758
|
__publicField(this, "lastKnownPath", null);
|
|
19439
19759
|
this.host = WorkspaceService.getHost();
|
|
19440
19760
|
this.unknownPathSuffix = Math.floor(1e3 + Math.random() * 9e3).toString();
|
|
19441
|
-
this.mobbConfigStore = new
|
|
19761
|
+
this.mobbConfigStore = new Configstore5("mobb-logs", {});
|
|
19442
19762
|
this.mobbConfigStore.set("version", packageJson.version);
|
|
19443
19763
|
}
|
|
19444
19764
|
/**
|