mobbdev 1.4.27 → 1.4.29
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.
|
@@ -221,13 +221,18 @@ type RepoState = {
|
|
|
221
221
|
* `GitService.getCurrentRepoState()` so detached-HEAD handling and SHA
|
|
222
222
|
* normalization stay in lockstep across the two clients.
|
|
223
223
|
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
224
|
+
* The repository URL is the canonical form (parseScmURL().canonicalUrl) for any
|
|
225
|
+
* SCM host, including self-hosted / enterprise remotes (scmType "Unknown"); only
|
|
226
|
+
* an absent or genuinely unparseable remote resolves to `repositoryUrl: null`.
|
|
227
|
+
*
|
|
228
|
+
* Never throws — a non-existent dir, missing git binary, or absent/unparseable
|
|
229
|
+
* remote resolves to null so the daemon hot path can rely on a value.
|
|
226
230
|
*/
|
|
227
231
|
declare function readRepoState(workingDir?: string): Promise<RepoState>;
|
|
228
232
|
/**
|
|
229
|
-
* Gets the
|
|
230
|
-
*
|
|
233
|
+
* Gets the canonical remote repository URL for the working directory's repo, for
|
|
234
|
+
* any SCM host (GitHub, GitLab, Azure DevOps, Bitbucket, and self-hosted /
|
|
235
|
+
* enterprise). Returns null when there is no remote or it can't be parsed.
|
|
231
236
|
*/
|
|
232
237
|
declare function getRepositoryUrl(workingDir?: string): Promise<string | null>;
|
|
233
238
|
/**
|
|
@@ -338,6 +338,7 @@ var init_client_generates = __esm({
|
|
|
338
338
|
IssueType_Enum2["Pt"] = "PT";
|
|
339
339
|
IssueType_Enum2["RaceConditionFormatFlaw"] = "RACE_CONDITION_FORMAT_FLAW";
|
|
340
340
|
IssueType_Enum2["Redos"] = "REDOS";
|
|
341
|
+
IssueType_Enum2["RedundantCondition"] = "REDUNDANT_CONDITION";
|
|
341
342
|
IssueType_Enum2["RedundantNilErrorCheck"] = "REDUNDANT_NIL_ERROR_CHECK";
|
|
342
343
|
IssueType_Enum2["RegexInjection"] = "REGEX_INJECTION";
|
|
343
344
|
IssueType_Enum2["RegexMissingTimeout"] = "REGEX_MISSING_TIMEOUT";
|
|
@@ -1919,6 +1920,7 @@ var init_getIssueType = __esm({
|
|
|
1919
1920
|
["RETURN_IN_INIT" /* ReturnInInit */]: "Return in Init",
|
|
1920
1921
|
["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: "Action Not Pinned to Commit Sha",
|
|
1921
1922
|
["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: "Django Blank Field Needs Null or Default",
|
|
1923
|
+
["REDUNDANT_CONDITION" /* RedundantCondition */]: "Insider Threat: Redundant Condition",
|
|
1922
1924
|
["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: "Redundant Nil Error Check",
|
|
1923
1925
|
["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: "Missing Workflow Permissions",
|
|
1924
1926
|
["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: "Excessive Secrets Exposure",
|
|
@@ -5168,6 +5170,7 @@ var fixDetailsData = {
|
|
|
5168
5170
|
["RETURN_IN_INIT" /* ReturnInInit */]: void 0,
|
|
5169
5171
|
["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: void 0,
|
|
5170
5172
|
["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: void 0,
|
|
5173
|
+
["REDUNDANT_CONDITION" /* RedundantCondition */]: void 0,
|
|
5171
5174
|
["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: void 0,
|
|
5172
5175
|
["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: void 0,
|
|
5173
5176
|
["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: void 0,
|
|
@@ -5514,9 +5517,19 @@ Provide the absolute directory that contains the executable (e.g. \`/usr/bin\`);
|
|
|
5514
5517
|
}
|
|
5515
5518
|
};
|
|
5516
5519
|
|
|
5520
|
+
// src/features/analysis/scm/shared/src/storedQuestionData/cpp/pathManipulation.ts
|
|
5521
|
+
var pathManipulation = {
|
|
5522
|
+
baseDirectory: {
|
|
5523
|
+
content: ({ expression }) => `Which directory must \`${expression}\` stay within? Enter the absolute base directory file access is restricted to.`,
|
|
5524
|
+
description: ({ expression }) => `The fix canonicalizes \`${expression}\` (resolving \`.\`, \`..\`, and symlinks) and verifies the result stays inside this base directory. Provide the **absolute** root the application is allowed to read or write under, for example \`/var/app/data\`. The directory must exist at runtime. A path that resolves outside it is rejected at runtime.`,
|
|
5525
|
+
guidance: (_) => "You **must** set this to an absolute path that exists at runtime and that your application confines file access to. If you leave it unset (or give a relative path), the generated fix is emitted with a placeholder identifier (`MOBB_SET_ALLOWED_BASE_DIR`) that **deliberately does not compile**, so a missing security boundary cannot be overlooked \u2014 replace it with your absolute base directory. A relative path would otherwise resolve against the process working directory (moving the boundary). At runtime the guard aborts (C: `exit(EXIT_FAILURE)`) / throws (C++: `std::runtime_error`) on a path that escapes the base. Pick the narrowest directory that still contains every legitimate file the code opens."
|
|
5526
|
+
}
|
|
5527
|
+
};
|
|
5528
|
+
|
|
5517
5529
|
// src/features/analysis/scm/shared/src/storedQuestionData/cpp/index.ts
|
|
5518
5530
|
var vulnerabilities11 = {
|
|
5519
|
-
["CMDi" /* CmDi */]: commandInjection
|
|
5531
|
+
["CMDi" /* CmDi */]: commandInjection,
|
|
5532
|
+
["PT" /* Pt */]: pathManipulation
|
|
5520
5533
|
};
|
|
5521
5534
|
var cpp_default = vulnerabilities11;
|
|
5522
5535
|
|
|
@@ -7829,6 +7842,19 @@ import Debug8 from "debug";
|
|
|
7829
7842
|
import Debug7 from "debug";
|
|
7830
7843
|
import fetch3, { File, fileFrom, FormData } from "node-fetch";
|
|
7831
7844
|
var debug8 = Debug7("mobbdev:upload-file");
|
|
7845
|
+
var MIN_UPLOAD_TIMEOUT_MS = 1e4;
|
|
7846
|
+
var MAX_UPLOAD_TIMEOUT_MS = 6e4;
|
|
7847
|
+
var SLOW_LINK_BYTES_PER_MS = 512;
|
|
7848
|
+
function computeUploadTimeoutMs(file) {
|
|
7849
|
+
if (typeof file === "string") {
|
|
7850
|
+
return MAX_UPLOAD_TIMEOUT_MS;
|
|
7851
|
+
}
|
|
7852
|
+
const sizeAllowanceMs = Math.ceil(file.byteLength / SLOW_LINK_BYTES_PER_MS);
|
|
7853
|
+
return Math.min(
|
|
7854
|
+
MAX_UPLOAD_TIMEOUT_MS,
|
|
7855
|
+
MIN_UPLOAD_TIMEOUT_MS + sizeAllowanceMs
|
|
7856
|
+
);
|
|
7857
|
+
}
|
|
7832
7858
|
var S3UploadError = class extends Error {
|
|
7833
7859
|
constructor(status, s3Code, s3Message) {
|
|
7834
7860
|
super(`Failed to upload the file: ${status}`);
|
|
@@ -7887,11 +7913,12 @@ async function uploadFile({
|
|
|
7887
7913
|
);
|
|
7888
7914
|
}
|
|
7889
7915
|
const agent = getProxyAgent(url);
|
|
7916
|
+
const effectiveSignal = signal ?? AbortSignal.timeout(computeUploadTimeoutMs(file));
|
|
7890
7917
|
const response = await fetch3(url, {
|
|
7891
7918
|
method: "POST",
|
|
7892
7919
|
body: form,
|
|
7893
7920
|
agent,
|
|
7894
|
-
signal
|
|
7921
|
+
signal: effectiveSignal
|
|
7895
7922
|
});
|
|
7896
7923
|
if (!response.ok) {
|
|
7897
7924
|
let bodyText = "";
|
|
@@ -8468,8 +8495,7 @@ async function readRepoState(workingDir) {
|
|
|
8468
8495
|
const repoStatePromise = gitService.getCurrentRepoState().catch(() => ({ branch: null, commitSha: null }));
|
|
8469
8496
|
const repositoryUrlPromise = gitService.getRemoteUrl().then((url) => {
|
|
8470
8497
|
if (!url) return null;
|
|
8471
|
-
|
|
8472
|
-
return parsed?.scmType && parsed.scmType !== "Unknown" ? url : null;
|
|
8498
|
+
return parseScmURL(url)?.canonicalUrl ?? null;
|
|
8473
8499
|
}).catch(() => null);
|
|
8474
8500
|
const [{ branch, commitSha }, repositoryUrl] = await Promise.all([
|
|
8475
8501
|
repoStatePromise,
|
package/dist/index.mjs
CHANGED
|
@@ -338,6 +338,7 @@ var init_client_generates = __esm({
|
|
|
338
338
|
IssueType_Enum2["Pt"] = "PT";
|
|
339
339
|
IssueType_Enum2["RaceConditionFormatFlaw"] = "RACE_CONDITION_FORMAT_FLAW";
|
|
340
340
|
IssueType_Enum2["Redos"] = "REDOS";
|
|
341
|
+
IssueType_Enum2["RedundantCondition"] = "REDUNDANT_CONDITION";
|
|
341
342
|
IssueType_Enum2["RedundantNilErrorCheck"] = "REDUNDANT_NIL_ERROR_CHECK";
|
|
342
343
|
IssueType_Enum2["RegexInjection"] = "REGEX_INJECTION";
|
|
343
344
|
IssueType_Enum2["RegexMissingTimeout"] = "REGEX_MISSING_TIMEOUT";
|
|
@@ -1584,6 +1585,7 @@ var init_getIssueType = __esm({
|
|
|
1584
1585
|
["RETURN_IN_INIT" /* ReturnInInit */]: "Return in Init",
|
|
1585
1586
|
["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: "Action Not Pinned to Commit Sha",
|
|
1586
1587
|
["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: "Django Blank Field Needs Null or Default",
|
|
1588
|
+
["REDUNDANT_CONDITION" /* RedundantCondition */]: "Insider Threat: Redundant Condition",
|
|
1587
1589
|
["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: "Redundant Nil Error Check",
|
|
1588
1590
|
["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: "Missing Workflow Permissions",
|
|
1589
1591
|
["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: "Excessive Secrets Exposure",
|
|
@@ -4873,6 +4875,7 @@ var fixDetailsData = {
|
|
|
4873
4875
|
["RETURN_IN_INIT" /* ReturnInInit */]: void 0,
|
|
4874
4876
|
["ACTION_NOT_PINNED_TO_COMMIT_SHA" /* ActionNotPinnedToCommitSha */]: void 0,
|
|
4875
4877
|
["DJANGO_BLANK_FIELD_NEEDS_NULL_OR_DEFAULT" /* DjangoBlankFieldNeedsNullOrDefault */]: void 0,
|
|
4878
|
+
["REDUNDANT_CONDITION" /* RedundantCondition */]: void 0,
|
|
4876
4879
|
["REDUNDANT_NIL_ERROR_CHECK" /* RedundantNilErrorCheck */]: void 0,
|
|
4877
4880
|
["MISSING_WORKFLOW_PERMISSIONS" /* MissingWorkflowPermissions */]: void 0,
|
|
4878
4881
|
["EXCESSIVE_SECRETS_EXPOSURE" /* ExcessiveSecretsExposure */]: void 0,
|
|
@@ -5307,9 +5310,19 @@ Provide the absolute directory that contains the executable (e.g. \`/usr/bin\`);
|
|
|
5307
5310
|
}
|
|
5308
5311
|
};
|
|
5309
5312
|
|
|
5313
|
+
// src/features/analysis/scm/shared/src/storedQuestionData/cpp/pathManipulation.ts
|
|
5314
|
+
var pathManipulation = {
|
|
5315
|
+
baseDirectory: {
|
|
5316
|
+
content: ({ expression }) => `Which directory must \`${expression}\` stay within? Enter the absolute base directory file access is restricted to.`,
|
|
5317
|
+
description: ({ expression }) => `The fix canonicalizes \`${expression}\` (resolving \`.\`, \`..\`, and symlinks) and verifies the result stays inside this base directory. Provide the **absolute** root the application is allowed to read or write under, for example \`/var/app/data\`. The directory must exist at runtime. A path that resolves outside it is rejected at runtime.`,
|
|
5318
|
+
guidance: (_) => "You **must** set this to an absolute path that exists at runtime and that your application confines file access to. If you leave it unset (or give a relative path), the generated fix is emitted with a placeholder identifier (`MOBB_SET_ALLOWED_BASE_DIR`) that **deliberately does not compile**, so a missing security boundary cannot be overlooked \u2014 replace it with your absolute base directory. A relative path would otherwise resolve against the process working directory (moving the boundary). At runtime the guard aborts (C: `exit(EXIT_FAILURE)`) / throws (C++: `std::runtime_error`) on a path that escapes the base. Pick the narrowest directory that still contains every legitimate file the code opens."
|
|
5319
|
+
}
|
|
5320
|
+
};
|
|
5321
|
+
|
|
5310
5322
|
// src/features/analysis/scm/shared/src/storedQuestionData/cpp/index.ts
|
|
5311
5323
|
var vulnerabilities11 = {
|
|
5312
|
-
["CMDi" /* CmDi */]: commandInjection
|
|
5324
|
+
["CMDi" /* CmDi */]: commandInjection,
|
|
5325
|
+
["PT" /* Pt */]: pathManipulation
|
|
5313
5326
|
};
|
|
5314
5327
|
var cpp_default = vulnerabilities11;
|
|
5315
5328
|
|
|
@@ -14209,6 +14222,41 @@ init_urlParser2();
|
|
|
14209
14222
|
import Debug8 from "debug";
|
|
14210
14223
|
import fetch3, { File, fileFrom, FormData } from "node-fetch";
|
|
14211
14224
|
var debug9 = Debug8("mobbdev:upload-file");
|
|
14225
|
+
var MIN_UPLOAD_TIMEOUT_MS = 1e4;
|
|
14226
|
+
var MAX_UPLOAD_TIMEOUT_MS = 6e4;
|
|
14227
|
+
var SLOW_LINK_BYTES_PER_MS = 512;
|
|
14228
|
+
function computeUploadTimeoutMs(file) {
|
|
14229
|
+
if (typeof file === "string") {
|
|
14230
|
+
return MAX_UPLOAD_TIMEOUT_MS;
|
|
14231
|
+
}
|
|
14232
|
+
const sizeAllowanceMs = Math.ceil(file.byteLength / SLOW_LINK_BYTES_PER_MS);
|
|
14233
|
+
return Math.min(
|
|
14234
|
+
MAX_UPLOAD_TIMEOUT_MS,
|
|
14235
|
+
MIN_UPLOAD_TIMEOUT_MS + sizeAllowanceMs
|
|
14236
|
+
);
|
|
14237
|
+
}
|
|
14238
|
+
var TRANSIENT_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
14239
|
+
"ECONNRESET",
|
|
14240
|
+
"ETIMEDOUT",
|
|
14241
|
+
"ECONNREFUSED",
|
|
14242
|
+
"EPIPE",
|
|
14243
|
+
"EAI_AGAIN",
|
|
14244
|
+
"ENETUNREACH",
|
|
14245
|
+
"ENOTFOUND"
|
|
14246
|
+
]);
|
|
14247
|
+
function isTransientUploadError(err) {
|
|
14248
|
+
if (err instanceof S3UploadError) {
|
|
14249
|
+
return false;
|
|
14250
|
+
}
|
|
14251
|
+
if (typeof err !== "object" || err === null) {
|
|
14252
|
+
return false;
|
|
14253
|
+
}
|
|
14254
|
+
const e = err;
|
|
14255
|
+
if (e.code !== void 0 && TRANSIENT_ERROR_CODES.has(e.code)) {
|
|
14256
|
+
return true;
|
|
14257
|
+
}
|
|
14258
|
+
return e.type === "system" || e.name === "AbortError" || e.name === "TimeoutError";
|
|
14259
|
+
}
|
|
14212
14260
|
var S3UploadError = class extends Error {
|
|
14213
14261
|
constructor(status, s3Code, s3Message) {
|
|
14214
14262
|
super(`Failed to upload the file: ${status}`);
|
|
@@ -14267,11 +14315,12 @@ async function uploadFile({
|
|
|
14267
14315
|
);
|
|
14268
14316
|
}
|
|
14269
14317
|
const agent = getProxyAgent(url);
|
|
14318
|
+
const effectiveSignal = signal ?? AbortSignal.timeout(computeUploadTimeoutMs(file));
|
|
14270
14319
|
const response = await fetch3(url, {
|
|
14271
14320
|
method: "POST",
|
|
14272
14321
|
body: form,
|
|
14273
14322
|
agent,
|
|
14274
|
-
signal
|
|
14323
|
+
signal: effectiveSignal
|
|
14275
14324
|
});
|
|
14276
14325
|
if (!response.ok) {
|
|
14277
14326
|
let bodyText = "";
|
|
@@ -14653,8 +14702,7 @@ async function readRepoState(workingDir) {
|
|
|
14653
14702
|
const repoStatePromise = gitService.getCurrentRepoState().catch(() => ({ branch: null, commitSha: null }));
|
|
14654
14703
|
const repositoryUrlPromise = gitService.getRemoteUrl().then((url) => {
|
|
14655
14704
|
if (!url) return null;
|
|
14656
|
-
|
|
14657
|
-
return parsed?.scmType && parsed.scmType !== "Unknown" ? url : null;
|
|
14705
|
+
return parseScmURL(url)?.canonicalUrl ?? null;
|
|
14658
14706
|
}).catch(() => null);
|
|
14659
14707
|
const [{ branch, commitSha }, repositoryUrl] = await Promise.all([
|
|
14660
14708
|
repoStatePromise,
|
|
@@ -17865,7 +17913,7 @@ import { spawn } from "child_process";
|
|
|
17865
17913
|
import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
|
|
17866
17914
|
import * as os8 from "os";
|
|
17867
17915
|
import path24 from "path";
|
|
17868
|
-
import { setTimeout as
|
|
17916
|
+
import { setTimeout as sleep4 } from "timers/promises";
|
|
17869
17917
|
import Configstore3 from "configstore";
|
|
17870
17918
|
|
|
17871
17919
|
// src/features/analysis/skill_quarantine/runQuarantineCheck.ts
|
|
@@ -18144,23 +18192,61 @@ var SCAN_PATHS = {
|
|
|
18144
18192
|
// src/features/analysis/context_file_scanner.ts
|
|
18145
18193
|
var MAX_CONTEXT_FILE_SIZE = 20 * 1024 * 1024;
|
|
18146
18194
|
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
18195
|
+
var FAILURE_BACKOFF_BASE_MS = 60 * 1e3;
|
|
18196
|
+
var FAILURE_BACKOFF_MAX_MS = 60 * 60 * 1e3;
|
|
18147
18197
|
var sessionMtimes = /* @__PURE__ */ new Map();
|
|
18148
18198
|
function markContextFilesUploaded(sessionId, files, skills) {
|
|
18149
|
-
|
|
18150
|
-
if (!entry) {
|
|
18151
|
-
entry = { files: /* @__PURE__ */ new Map(), skills: /* @__PURE__ */ new Map(), lastUpdatedAt: Date.now() };
|
|
18152
|
-
sessionMtimes.set(sessionId, entry);
|
|
18153
|
-
}
|
|
18199
|
+
const entry = getOrCreateSessionEntry(sessionId);
|
|
18154
18200
|
for (const f of files) {
|
|
18155
18201
|
entry.files.set(f.path, f.mtimeMs);
|
|
18202
|
+
entry.failures.delete(f.path);
|
|
18156
18203
|
}
|
|
18157
18204
|
if (skills) {
|
|
18158
18205
|
for (const sg of skills) {
|
|
18159
18206
|
entry.skills.set(sg.sessionKey, sg.maxMtimeMs);
|
|
18207
|
+
entry.failures.delete(sg.sessionKey);
|
|
18160
18208
|
}
|
|
18161
18209
|
}
|
|
18162
18210
|
entry.lastUpdatedAt = Date.now();
|
|
18163
18211
|
}
|
|
18212
|
+
function markContextUploadFailed(sessionId, files, skills) {
|
|
18213
|
+
const entry = getOrCreateSessionEntry(sessionId);
|
|
18214
|
+
const now = Date.now();
|
|
18215
|
+
const recordFailure = (key) => {
|
|
18216
|
+
const attempts = (entry.failures.get(key)?.attempts ?? 0) + 1;
|
|
18217
|
+
const delay3 = Math.min(
|
|
18218
|
+
FAILURE_BACKOFF_BASE_MS * 2 ** (attempts - 1),
|
|
18219
|
+
FAILURE_BACKOFF_MAX_MS
|
|
18220
|
+
);
|
|
18221
|
+
entry.failures.set(key, { attempts, nextRetryAt: now + delay3 });
|
|
18222
|
+
};
|
|
18223
|
+
for (const f of files) {
|
|
18224
|
+
recordFailure(f.path);
|
|
18225
|
+
}
|
|
18226
|
+
if (skills) {
|
|
18227
|
+
for (const sg of skills) {
|
|
18228
|
+
recordFailure(sg.sessionKey);
|
|
18229
|
+
}
|
|
18230
|
+
}
|
|
18231
|
+
entry.lastUpdatedAt = now;
|
|
18232
|
+
}
|
|
18233
|
+
function getOrCreateSessionEntry(sessionId) {
|
|
18234
|
+
let entry = sessionMtimes.get(sessionId);
|
|
18235
|
+
if (!entry) {
|
|
18236
|
+
entry = {
|
|
18237
|
+
files: /* @__PURE__ */ new Map(),
|
|
18238
|
+
skills: /* @__PURE__ */ new Map(),
|
|
18239
|
+
failures: /* @__PURE__ */ new Map(),
|
|
18240
|
+
lastUpdatedAt: Date.now()
|
|
18241
|
+
};
|
|
18242
|
+
sessionMtimes.set(sessionId, entry);
|
|
18243
|
+
}
|
|
18244
|
+
return entry;
|
|
18245
|
+
}
|
|
18246
|
+
function isInUploadBackoff(entry, key, now) {
|
|
18247
|
+
const failure = entry.failures.get(key);
|
|
18248
|
+
return failure !== void 0 && failure.nextRetryAt > now;
|
|
18249
|
+
}
|
|
18164
18250
|
var COPILOT_CUSTOM_LOCATION_SETTINGS = [
|
|
18165
18251
|
{
|
|
18166
18252
|
key: "chat.agentSkillsLocations",
|
|
@@ -18520,6 +18606,9 @@ async function scanContextFiles(workspaceRoot, platform2, sessionId) {
|
|
|
18520
18606
|
if (prevMtime !== void 0 && fileStat.mtimeMs <= prevMtime) {
|
|
18521
18607
|
continue;
|
|
18522
18608
|
}
|
|
18609
|
+
if (sessionEntry && isInUploadBackoff(sessionEntry, filePath, now)) {
|
|
18610
|
+
continue;
|
|
18611
|
+
}
|
|
18523
18612
|
allFiles.push(fileEntry);
|
|
18524
18613
|
}
|
|
18525
18614
|
} catch {
|
|
@@ -18535,6 +18624,9 @@ async function scanContextFiles(workspaceRoot, platform2, sessionId) {
|
|
|
18535
18624
|
if (prevMtime !== void 0 && group.maxMtimeMs <= prevMtime) {
|
|
18536
18625
|
continue;
|
|
18537
18626
|
}
|
|
18627
|
+
if (isInUploadBackoff(sessionEntry, group.sessionKey, now)) {
|
|
18628
|
+
continue;
|
|
18629
|
+
}
|
|
18538
18630
|
}
|
|
18539
18631
|
allSkillGroups.push(group);
|
|
18540
18632
|
}
|
|
@@ -19123,6 +19215,7 @@ var lastRunAt = /* @__PURE__ */ new Map();
|
|
|
19123
19215
|
var lastDirsMtimeMs = /* @__PURE__ */ new Map();
|
|
19124
19216
|
var seenSkillMd5s = /* @__PURE__ */ new Set();
|
|
19125
19217
|
var killSwitchLogged = false;
|
|
19218
|
+
var optInDisabledLogged = false;
|
|
19126
19219
|
async function runQuarantineCheckIfNeeded(opts) {
|
|
19127
19220
|
const { sessionId, cwd, gqlClient, log: log2 } = opts;
|
|
19128
19221
|
if (process.env[KILL_SWITCH_ENV] === "1") {
|
|
@@ -19149,14 +19242,14 @@ async function runQuarantineCheckIfNeeded(opts) {
|
|
|
19149
19242
|
return;
|
|
19150
19243
|
}
|
|
19151
19244
|
lastDirsMtimeMs.set(sessionId, dirsMtime);
|
|
19152
|
-
log2.
|
|
19245
|
+
log2.debug(
|
|
19153
19246
|
{ sessionId, metric: Metric.CHECK_TRIGGERED, hasNewSkills },
|
|
19154
19247
|
"skill_quarantine: check start"
|
|
19155
19248
|
);
|
|
19156
19249
|
const t0 = Date.now();
|
|
19157
19250
|
try {
|
|
19158
19251
|
await reconcileAndSweep(log2);
|
|
19159
|
-
log2.
|
|
19252
|
+
log2.debug(
|
|
19160
19253
|
{ sessionId, count: installed.length, metric: Metric.SKILLS_CHECKED },
|
|
19161
19254
|
"skill_quarantine: skills enumerated"
|
|
19162
19255
|
);
|
|
@@ -19176,10 +19269,13 @@ async function runQuarantineCheckIfNeeded(opts) {
|
|
|
19176
19269
|
log2
|
|
19177
19270
|
);
|
|
19178
19271
|
if (!quarantineEnabled) {
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
|
|
19182
|
-
|
|
19272
|
+
if (!optInDisabledLogged) {
|
|
19273
|
+
log2.info(
|
|
19274
|
+
{ sessionId, metric: Metric.CHECK_DISABLED_ORG },
|
|
19275
|
+
"skill_quarantine: opt-in not enabled for any org of caller; skipping enforcement"
|
|
19276
|
+
);
|
|
19277
|
+
optInDisabledLogged = true;
|
|
19278
|
+
}
|
|
19183
19279
|
return;
|
|
19184
19280
|
}
|
|
19185
19281
|
for (const skill of installed) {
|
|
@@ -19204,7 +19300,7 @@ async function runQuarantineCheckIfNeeded(opts) {
|
|
|
19204
19300
|
}
|
|
19205
19301
|
}
|
|
19206
19302
|
} finally {
|
|
19207
|
-
log2.
|
|
19303
|
+
log2.debug(
|
|
19208
19304
|
{
|
|
19209
19305
|
sessionId,
|
|
19210
19306
|
duration_ms: Date.now() - t0,
|
|
@@ -19316,9 +19412,27 @@ import path21 from "path";
|
|
|
19316
19412
|
import { promisify } from "util";
|
|
19317
19413
|
|
|
19318
19414
|
// src/features/analysis/context_file_uploader.ts
|
|
19415
|
+
import { setTimeout as sleep3 } from "timers/promises";
|
|
19319
19416
|
import pLimit7 from "p-limit";
|
|
19320
19417
|
init_client_generates();
|
|
19321
19418
|
var UPLOAD_CONCURRENCY = 5;
|
|
19419
|
+
var UPLOAD_MAX_ATTEMPTS = 2;
|
|
19420
|
+
var UPLOAD_RETRY_BASE_MS = 250;
|
|
19421
|
+
async function uploadWithRetry(args) {
|
|
19422
|
+
for (let attempt = 1; attempt <= UPLOAD_MAX_ATTEMPTS; attempt++) {
|
|
19423
|
+
try {
|
|
19424
|
+
await uploadFile(args);
|
|
19425
|
+
return;
|
|
19426
|
+
} catch (err) {
|
|
19427
|
+
const isLastAttempt = attempt === UPLOAD_MAX_ATTEMPTS;
|
|
19428
|
+
if (isLastAttempt || !isTransientUploadError(err)) {
|
|
19429
|
+
throw err;
|
|
19430
|
+
}
|
|
19431
|
+
const backoff = UPLOAD_RETRY_BASE_MS * 2 ** (attempt - 1) * (1 + Math.random());
|
|
19432
|
+
await sleep3(backoff);
|
|
19433
|
+
}
|
|
19434
|
+
}
|
|
19435
|
+
}
|
|
19322
19436
|
async function uploadContextRecords(opts) {
|
|
19323
19437
|
const {
|
|
19324
19438
|
processedFiles,
|
|
@@ -19339,6 +19453,8 @@ async function uploadContextRecords(opts) {
|
|
|
19339
19453
|
const records = [];
|
|
19340
19454
|
const uploadedFiles = [];
|
|
19341
19455
|
const uploadedSkillGroups = [];
|
|
19456
|
+
const failedFiles = [];
|
|
19457
|
+
const failedSkillGroups = [];
|
|
19342
19458
|
const limit = pLimit7(UPLOAD_CONCURRENCY);
|
|
19343
19459
|
const extraFields = {
|
|
19344
19460
|
...repositoryUrl !== void 0 && { repositoryUrl },
|
|
@@ -19351,13 +19467,14 @@ async function uploadContextRecords(opts) {
|
|
|
19351
19467
|
(pf) => limit(async () => {
|
|
19352
19468
|
const s3Key = `${keyPrefix}ctx-${pf.md5}.bin`;
|
|
19353
19469
|
try {
|
|
19354
|
-
await
|
|
19470
|
+
await uploadWithRetry({
|
|
19355
19471
|
file: Buffer.from(pf.sanitizedContent, "utf-8"),
|
|
19356
19472
|
url,
|
|
19357
19473
|
uploadKey: s3Key,
|
|
19358
19474
|
uploadFields
|
|
19359
19475
|
});
|
|
19360
19476
|
} catch (err) {
|
|
19477
|
+
failedFiles.push(pf.entry);
|
|
19361
19478
|
onFileError?.(pf.entry.name, err);
|
|
19362
19479
|
return;
|
|
19363
19480
|
}
|
|
@@ -19385,13 +19502,14 @@ async function uploadContextRecords(opts) {
|
|
|
19385
19502
|
(ps) => limit(async () => {
|
|
19386
19503
|
const s3Key = `${keyPrefix}skill-${ps.md5}.zip`;
|
|
19387
19504
|
try {
|
|
19388
|
-
await
|
|
19505
|
+
await uploadWithRetry({
|
|
19389
19506
|
file: ps.zipBuffer,
|
|
19390
19507
|
url,
|
|
19391
19508
|
uploadKey: s3Key,
|
|
19392
19509
|
uploadFields
|
|
19393
19510
|
});
|
|
19394
19511
|
} catch (err) {
|
|
19512
|
+
failedSkillGroups.push(ps.group);
|
|
19395
19513
|
onSkillError?.(ps.group.name, err);
|
|
19396
19514
|
return;
|
|
19397
19515
|
}
|
|
@@ -19417,7 +19535,13 @@ async function uploadContextRecords(opts) {
|
|
|
19417
19535
|
)
|
|
19418
19536
|
];
|
|
19419
19537
|
await Promise.allSettled(tasks);
|
|
19420
|
-
return {
|
|
19538
|
+
return {
|
|
19539
|
+
records,
|
|
19540
|
+
uploadedFiles,
|
|
19541
|
+
uploadedSkillGroups,
|
|
19542
|
+
failedFiles,
|
|
19543
|
+
failedSkillGroups
|
|
19544
|
+
};
|
|
19421
19545
|
}
|
|
19422
19546
|
async function runContextFileUploadPipeline(opts) {
|
|
19423
19547
|
const {
|
|
@@ -19443,7 +19567,13 @@ async function runContextFileUploadPipeline(opts) {
|
|
|
19443
19567
|
return null;
|
|
19444
19568
|
}
|
|
19445
19569
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
19446
|
-
const {
|
|
19570
|
+
const {
|
|
19571
|
+
records,
|
|
19572
|
+
uploadedFiles,
|
|
19573
|
+
uploadedSkillGroups,
|
|
19574
|
+
failedFiles,
|
|
19575
|
+
failedSkillGroups
|
|
19576
|
+
} = await uploadContextRecords({
|
|
19447
19577
|
processedFiles,
|
|
19448
19578
|
processedSkills,
|
|
19449
19579
|
keyPrefix,
|
|
@@ -19459,6 +19589,9 @@ async function runContextFileUploadPipeline(opts) {
|
|
|
19459
19589
|
onFileError,
|
|
19460
19590
|
onSkillError
|
|
19461
19591
|
});
|
|
19592
|
+
if (failedFiles.length > 0 || failedSkillGroups.length > 0) {
|
|
19593
|
+
markContextUploadFailed(sessionId, failedFiles, failedSkillGroups);
|
|
19594
|
+
}
|
|
19462
19595
|
if (records.length === 0) {
|
|
19463
19596
|
return { fileCount: 0, skillCount: 0 };
|
|
19464
19597
|
}
|
|
@@ -19810,7 +19943,7 @@ function createLogger(config2) {
|
|
|
19810
19943
|
|
|
19811
19944
|
// src/features/claude_code/hook_logger.ts
|
|
19812
19945
|
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
19813
|
-
var CLI_VERSION = true ? "1.4.
|
|
19946
|
+
var CLI_VERSION = true ? "1.4.29" : "unknown";
|
|
19814
19947
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
19815
19948
|
var claudeCodeVersion;
|
|
19816
19949
|
function buildDdTags() {
|
|
@@ -19989,7 +20122,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
|
|
|
19989
20122
|
const stat5 = await fh.stat();
|
|
19990
20123
|
fileSize = stat5.size;
|
|
19991
20124
|
if (cursor.byteOffset >= stat5.size) {
|
|
19992
|
-
hookLog.
|
|
20125
|
+
hookLog.debug({ data: { sessionId } }, "No new data in transcript file");
|
|
19993
20126
|
return {
|
|
19994
20127
|
entries: [],
|
|
19995
20128
|
endByteOffset: fileSize,
|
|
@@ -20226,12 +20359,12 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
|
|
|
20226
20359
|
);
|
|
20227
20360
|
const cursorKey = getCursorKey(resolvedTranscriptPath);
|
|
20228
20361
|
if (rawEntries.length === 0) {
|
|
20229
|
-
log2.
|
|
20362
|
+
log2.debug("No new entries to upload");
|
|
20230
20363
|
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
20231
20364
|
}
|
|
20232
20365
|
const { filtered: entries, filteredOut } = filterEntries(rawEntries);
|
|
20233
20366
|
if (filteredOut > 0) {
|
|
20234
|
-
log2.
|
|
20367
|
+
log2.debug(
|
|
20235
20368
|
{ data: { filteredOut, remaining: entries.length } },
|
|
20236
20369
|
"Filtered out noise entries"
|
|
20237
20370
|
);
|
|
@@ -20914,7 +21047,7 @@ async function startDaemon() {
|
|
|
20914
21047
|
},
|
|
20915
21048
|
"daemon poll cycle"
|
|
20916
21049
|
);
|
|
20917
|
-
await
|
|
21050
|
+
await sleep4(DAEMON_POLL_INTERVAL_MS);
|
|
20918
21051
|
}
|
|
20919
21052
|
}
|
|
20920
21053
|
async function acquirePidFile() {
|