mobbdev 1.4.26 → 1.4.28

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
- * Never throws non-existent dirs, missing git binaries, and unrecognized
225
- * remotes all resolve to nulls so the daemon hot path can rely on a value.
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 normalized GitHub repository URL from the current working directory.
230
- * Returns null if not in a git repository or if not a GitHub repository.
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
  /**
@@ -357,6 +357,7 @@ var init_client_generates = __esm({
357
357
  IssueType_Enum2["TrustBoundaryViolation"] = "TRUST_BOUNDARY_VIOLATION";
358
358
  IssueType_Enum2["TypeConfusion"] = "TYPE_CONFUSION";
359
359
  IssueType_Enum2["UncheckedLoopCondition"] = "UNCHECKED_LOOP_CONDITION";
360
+ IssueType_Enum2["UncheckedReturnValue"] = "UNCHECKED_RETURN_VALUE";
360
361
  IssueType_Enum2["UnencryptedAwsSqsQueue"] = "UNENCRYPTED_AWS_SQS_QUEUE";
361
362
  IssueType_Enum2["UnnecessaryImports"] = "UNNECESSARY_IMPORTS";
362
363
  IssueType_Enum2["UnsafeDeserialization"] = "UNSAFE_DESERIALIZATION";
@@ -1930,7 +1931,8 @@ var init_getIssueType = __esm({
1930
1931
  ["UNENCRYPTED_AWS_SQS_QUEUE" /* UnencryptedAwsSqsQueue */]: "AWS SQS Queue Unencrypted",
1931
1932
  ["INSECURE_DESERIALIZATION" /* InsecureDeserialization */]: "Insecure Deserialization",
1932
1933
  ["AWS_DYNAMODB_POINT_IN_TIME_RECOVERY_DISABLED" /* AwsDynamodbPointInTimeRecoveryDisabled */]: "AWS DynamoDB Point-in-Time Recovery Disabled",
1933
- ["JWT_DECODE_WITHOUT_VERIFY" /* JwtDecodeWithoutVerify */]: "JWT Decoded Without Signature Verification"
1934
+ ["JWT_DECODE_WITHOUT_VERIFY" /* JwtDecodeWithoutVerify */]: "JWT Decoded Without Signature Verification",
1935
+ ["UNCHECKED_RETURN_VALUE" /* UncheckedReturnValue */]: "Unchecked Return Value"
1934
1936
  };
1935
1937
  issueTypeZ = z5.nativeEnum(IssueType_Enum);
1936
1938
  getIssueTypeFriendlyString = (issueType) => {
@@ -5188,7 +5190,8 @@ var fixDetailsData = {
5188
5190
  ["JWT_DECODE_WITHOUT_VERIFY" /* JwtDecodeWithoutVerify */]: {
5189
5191
  issueDescription: "Decoding a JWT with `JWT.decode()` only base64-decodes the token without checking its signature, so an attacker can forge a token with arbitrary claims (identity, roles, expiration) and have it trusted. CWE-345, OWASP A08:2021 Software and Data Integrity Failures.",
5190
5192
  fixInstructions: "Verify the signature before trusting any claims: build a verifier with the expected algorithm and secret/key (e.g. `JWT.require(Algorithm.HMAC256(secret)).build().verify(token)`) instead of calling `JWT.decode(token)`. After merging, confirm the verifier is configured with the same algorithm and secret/key used to sign your tokens \u2014 an incorrect or placeholder secret will make verification throw `JWTVerificationException` at runtime and reject legitimate tokens."
5191
- }
5193
+ },
5194
+ ["UNCHECKED_RETURN_VALUE" /* UncheckedReturnValue */]: void 0
5192
5195
  };
5193
5196
 
5194
5197
  // src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
@@ -7826,6 +7829,19 @@ import Debug8 from "debug";
7826
7829
  import Debug7 from "debug";
7827
7830
  import fetch3, { File, fileFrom, FormData } from "node-fetch";
7828
7831
  var debug8 = Debug7("mobbdev:upload-file");
7832
+ var MIN_UPLOAD_TIMEOUT_MS = 1e4;
7833
+ var MAX_UPLOAD_TIMEOUT_MS = 6e4;
7834
+ var SLOW_LINK_BYTES_PER_MS = 512;
7835
+ function computeUploadTimeoutMs(file) {
7836
+ if (typeof file === "string") {
7837
+ return MAX_UPLOAD_TIMEOUT_MS;
7838
+ }
7839
+ const sizeAllowanceMs = Math.ceil(file.byteLength / SLOW_LINK_BYTES_PER_MS);
7840
+ return Math.min(
7841
+ MAX_UPLOAD_TIMEOUT_MS,
7842
+ MIN_UPLOAD_TIMEOUT_MS + sizeAllowanceMs
7843
+ );
7844
+ }
7829
7845
  var S3UploadError = class extends Error {
7830
7846
  constructor(status, s3Code, s3Message) {
7831
7847
  super(`Failed to upload the file: ${status}`);
@@ -7884,11 +7900,12 @@ async function uploadFile({
7884
7900
  );
7885
7901
  }
7886
7902
  const agent = getProxyAgent(url);
7903
+ const effectiveSignal = signal ?? AbortSignal.timeout(computeUploadTimeoutMs(file));
7887
7904
  const response = await fetch3(url, {
7888
7905
  method: "POST",
7889
7906
  body: form,
7890
7907
  agent,
7891
- signal
7908
+ signal: effectiveSignal
7892
7909
  });
7893
7910
  if (!response.ok) {
7894
7911
  let bodyText = "";
@@ -8465,8 +8482,7 @@ async function readRepoState(workingDir) {
8465
8482
  const repoStatePromise = gitService.getCurrentRepoState().catch(() => ({ branch: null, commitSha: null }));
8466
8483
  const repositoryUrlPromise = gitService.getRemoteUrl().then((url) => {
8467
8484
  if (!url) return null;
8468
- const parsed = parseScmURL(url);
8469
- return parsed?.scmType && parsed.scmType !== "Unknown" ? url : null;
8485
+ return parseScmURL(url)?.canonicalUrl ?? null;
8470
8486
  }).catch(() => null);
8471
8487
  const [{ branch, commitSha }, repositoryUrl] = await Promise.all([
8472
8488
  repoStatePromise,
package/dist/index.mjs CHANGED
@@ -357,6 +357,7 @@ var init_client_generates = __esm({
357
357
  IssueType_Enum2["TrustBoundaryViolation"] = "TRUST_BOUNDARY_VIOLATION";
358
358
  IssueType_Enum2["TypeConfusion"] = "TYPE_CONFUSION";
359
359
  IssueType_Enum2["UncheckedLoopCondition"] = "UNCHECKED_LOOP_CONDITION";
360
+ IssueType_Enum2["UncheckedReturnValue"] = "UNCHECKED_RETURN_VALUE";
360
361
  IssueType_Enum2["UnencryptedAwsSqsQueue"] = "UNENCRYPTED_AWS_SQS_QUEUE";
361
362
  IssueType_Enum2["UnnecessaryImports"] = "UNNECESSARY_IMPORTS";
362
363
  IssueType_Enum2["UnsafeDeserialization"] = "UNSAFE_DESERIALIZATION";
@@ -1595,7 +1596,8 @@ var init_getIssueType = __esm({
1595
1596
  ["UNENCRYPTED_AWS_SQS_QUEUE" /* UnencryptedAwsSqsQueue */]: "AWS SQS Queue Unencrypted",
1596
1597
  ["INSECURE_DESERIALIZATION" /* InsecureDeserialization */]: "Insecure Deserialization",
1597
1598
  ["AWS_DYNAMODB_POINT_IN_TIME_RECOVERY_DISABLED" /* AwsDynamodbPointInTimeRecoveryDisabled */]: "AWS DynamoDB Point-in-Time Recovery Disabled",
1598
- ["JWT_DECODE_WITHOUT_VERIFY" /* JwtDecodeWithoutVerify */]: "JWT Decoded Without Signature Verification"
1599
+ ["JWT_DECODE_WITHOUT_VERIFY" /* JwtDecodeWithoutVerify */]: "JWT Decoded Without Signature Verification",
1600
+ ["UNCHECKED_RETURN_VALUE" /* UncheckedReturnValue */]: "Unchecked Return Value"
1599
1601
  };
1600
1602
  issueTypeZ = z.nativeEnum(IssueType_Enum);
1601
1603
  getIssueTypeFriendlyString = (issueType) => {
@@ -4893,7 +4895,8 @@ var fixDetailsData = {
4893
4895
  ["JWT_DECODE_WITHOUT_VERIFY" /* JwtDecodeWithoutVerify */]: {
4894
4896
  issueDescription: "Decoding a JWT with `JWT.decode()` only base64-decodes the token without checking its signature, so an attacker can forge a token with arbitrary claims (identity, roles, expiration) and have it trusted. CWE-345, OWASP A08:2021 Software and Data Integrity Failures.",
4895
4897
  fixInstructions: "Verify the signature before trusting any claims: build a verifier with the expected algorithm and secret/key (e.g. `JWT.require(Algorithm.HMAC256(secret)).build().verify(token)`) instead of calling `JWT.decode(token)`. After merging, confirm the verifier is configured with the same algorithm and secret/key used to sign your tokens \u2014 an incorrect or placeholder secret will make verification throw `JWTVerificationException` at runtime and reject legitimate tokens."
4896
- }
4898
+ },
4899
+ ["UNCHECKED_RETURN_VALUE" /* UncheckedReturnValue */]: void 0
4897
4900
  };
4898
4901
 
4899
4902
  // src/features/analysis/scm/shared/src/commitDescriptionMarkup.ts
@@ -14206,6 +14209,41 @@ init_urlParser2();
14206
14209
  import Debug8 from "debug";
14207
14210
  import fetch3, { File, fileFrom, FormData } from "node-fetch";
14208
14211
  var debug9 = Debug8("mobbdev:upload-file");
14212
+ var MIN_UPLOAD_TIMEOUT_MS = 1e4;
14213
+ var MAX_UPLOAD_TIMEOUT_MS = 6e4;
14214
+ var SLOW_LINK_BYTES_PER_MS = 512;
14215
+ function computeUploadTimeoutMs(file) {
14216
+ if (typeof file === "string") {
14217
+ return MAX_UPLOAD_TIMEOUT_MS;
14218
+ }
14219
+ const sizeAllowanceMs = Math.ceil(file.byteLength / SLOW_LINK_BYTES_PER_MS);
14220
+ return Math.min(
14221
+ MAX_UPLOAD_TIMEOUT_MS,
14222
+ MIN_UPLOAD_TIMEOUT_MS + sizeAllowanceMs
14223
+ );
14224
+ }
14225
+ var TRANSIENT_ERROR_CODES = /* @__PURE__ */ new Set([
14226
+ "ECONNRESET",
14227
+ "ETIMEDOUT",
14228
+ "ECONNREFUSED",
14229
+ "EPIPE",
14230
+ "EAI_AGAIN",
14231
+ "ENETUNREACH",
14232
+ "ENOTFOUND"
14233
+ ]);
14234
+ function isTransientUploadError(err) {
14235
+ if (err instanceof S3UploadError) {
14236
+ return false;
14237
+ }
14238
+ if (typeof err !== "object" || err === null) {
14239
+ return false;
14240
+ }
14241
+ const e = err;
14242
+ if (e.code !== void 0 && TRANSIENT_ERROR_CODES.has(e.code)) {
14243
+ return true;
14244
+ }
14245
+ return e.type === "system" || e.name === "AbortError" || e.name === "TimeoutError";
14246
+ }
14209
14247
  var S3UploadError = class extends Error {
14210
14248
  constructor(status, s3Code, s3Message) {
14211
14249
  super(`Failed to upload the file: ${status}`);
@@ -14264,11 +14302,12 @@ async function uploadFile({
14264
14302
  );
14265
14303
  }
14266
14304
  const agent = getProxyAgent(url);
14305
+ const effectiveSignal = signal ?? AbortSignal.timeout(computeUploadTimeoutMs(file));
14267
14306
  const response = await fetch3(url, {
14268
14307
  method: "POST",
14269
14308
  body: form,
14270
14309
  agent,
14271
- signal
14310
+ signal: effectiveSignal
14272
14311
  });
14273
14312
  if (!response.ok) {
14274
14313
  let bodyText = "";
@@ -14650,8 +14689,7 @@ async function readRepoState(workingDir) {
14650
14689
  const repoStatePromise = gitService.getCurrentRepoState().catch(() => ({ branch: null, commitSha: null }));
14651
14690
  const repositoryUrlPromise = gitService.getRemoteUrl().then((url) => {
14652
14691
  if (!url) return null;
14653
- const parsed = parseScmURL(url);
14654
- return parsed?.scmType && parsed.scmType !== "Unknown" ? url : null;
14692
+ return parseScmURL(url)?.canonicalUrl ?? null;
14655
14693
  }).catch(() => null);
14656
14694
  const [{ branch, commitSha }, repositoryUrl] = await Promise.all([
14657
14695
  repoStatePromise,
@@ -17862,7 +17900,7 @@ import { spawn } from "child_process";
17862
17900
  import { readFileSync, writeFileSync as writeFileSync2 } from "fs";
17863
17901
  import * as os8 from "os";
17864
17902
  import path24 from "path";
17865
- import { setTimeout as sleep3 } from "timers/promises";
17903
+ import { setTimeout as sleep4 } from "timers/promises";
17866
17904
  import Configstore3 from "configstore";
17867
17905
 
17868
17906
  // src/features/analysis/skill_quarantine/runQuarantineCheck.ts
@@ -18141,23 +18179,61 @@ var SCAN_PATHS = {
18141
18179
  // src/features/analysis/context_file_scanner.ts
18142
18180
  var MAX_CONTEXT_FILE_SIZE = 20 * 1024 * 1024;
18143
18181
  var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
18182
+ var FAILURE_BACKOFF_BASE_MS = 60 * 1e3;
18183
+ var FAILURE_BACKOFF_MAX_MS = 60 * 60 * 1e3;
18144
18184
  var sessionMtimes = /* @__PURE__ */ new Map();
18145
18185
  function markContextFilesUploaded(sessionId, files, skills) {
18146
- let entry = sessionMtimes.get(sessionId);
18147
- if (!entry) {
18148
- entry = { files: /* @__PURE__ */ new Map(), skills: /* @__PURE__ */ new Map(), lastUpdatedAt: Date.now() };
18149
- sessionMtimes.set(sessionId, entry);
18150
- }
18186
+ const entry = getOrCreateSessionEntry(sessionId);
18151
18187
  for (const f of files) {
18152
18188
  entry.files.set(f.path, f.mtimeMs);
18189
+ entry.failures.delete(f.path);
18153
18190
  }
18154
18191
  if (skills) {
18155
18192
  for (const sg of skills) {
18156
18193
  entry.skills.set(sg.sessionKey, sg.maxMtimeMs);
18194
+ entry.failures.delete(sg.sessionKey);
18157
18195
  }
18158
18196
  }
18159
18197
  entry.lastUpdatedAt = Date.now();
18160
18198
  }
18199
+ function markContextUploadFailed(sessionId, files, skills) {
18200
+ const entry = getOrCreateSessionEntry(sessionId);
18201
+ const now = Date.now();
18202
+ const recordFailure = (key) => {
18203
+ const attempts = (entry.failures.get(key)?.attempts ?? 0) + 1;
18204
+ const delay3 = Math.min(
18205
+ FAILURE_BACKOFF_BASE_MS * 2 ** (attempts - 1),
18206
+ FAILURE_BACKOFF_MAX_MS
18207
+ );
18208
+ entry.failures.set(key, { attempts, nextRetryAt: now + delay3 });
18209
+ };
18210
+ for (const f of files) {
18211
+ recordFailure(f.path);
18212
+ }
18213
+ if (skills) {
18214
+ for (const sg of skills) {
18215
+ recordFailure(sg.sessionKey);
18216
+ }
18217
+ }
18218
+ entry.lastUpdatedAt = now;
18219
+ }
18220
+ function getOrCreateSessionEntry(sessionId) {
18221
+ let entry = sessionMtimes.get(sessionId);
18222
+ if (!entry) {
18223
+ entry = {
18224
+ files: /* @__PURE__ */ new Map(),
18225
+ skills: /* @__PURE__ */ new Map(),
18226
+ failures: /* @__PURE__ */ new Map(),
18227
+ lastUpdatedAt: Date.now()
18228
+ };
18229
+ sessionMtimes.set(sessionId, entry);
18230
+ }
18231
+ return entry;
18232
+ }
18233
+ function isInUploadBackoff(entry, key, now) {
18234
+ const failure = entry.failures.get(key);
18235
+ return failure !== void 0 && failure.nextRetryAt > now;
18236
+ }
18161
18237
  var COPILOT_CUSTOM_LOCATION_SETTINGS = [
18162
18238
  {
18163
18239
  key: "chat.agentSkillsLocations",
@@ -18517,6 +18593,9 @@ async function scanContextFiles(workspaceRoot, platform2, sessionId) {
18517
18593
  if (prevMtime !== void 0 && fileStat.mtimeMs <= prevMtime) {
18518
18594
  continue;
18519
18595
  }
18596
+ if (sessionEntry && isInUploadBackoff(sessionEntry, filePath, now)) {
18597
+ continue;
18598
+ }
18520
18599
  allFiles.push(fileEntry);
18521
18600
  }
18522
18601
  } catch {
@@ -18532,6 +18611,9 @@ async function scanContextFiles(workspaceRoot, platform2, sessionId) {
18532
18611
  if (prevMtime !== void 0 && group.maxMtimeMs <= prevMtime) {
18533
18612
  continue;
18534
18613
  }
18614
+ if (isInUploadBackoff(sessionEntry, group.sessionKey, now)) {
18615
+ continue;
18616
+ }
18535
18617
  }
18536
18618
  allSkillGroups.push(group);
18537
18619
  }
@@ -19120,6 +19202,7 @@ var lastRunAt = /* @__PURE__ */ new Map();
19120
19202
  var lastDirsMtimeMs = /* @__PURE__ */ new Map();
19121
19203
  var seenSkillMd5s = /* @__PURE__ */ new Set();
19122
19204
  var killSwitchLogged = false;
19205
+ var optInDisabledLogged = false;
19123
19206
  async function runQuarantineCheckIfNeeded(opts) {
19124
19207
  const { sessionId, cwd, gqlClient, log: log2 } = opts;
19125
19208
  if (process.env[KILL_SWITCH_ENV] === "1") {
@@ -19146,14 +19229,14 @@ async function runQuarantineCheckIfNeeded(opts) {
19146
19229
  return;
19147
19230
  }
19148
19231
  lastDirsMtimeMs.set(sessionId, dirsMtime);
19149
- log2.info(
19232
+ log2.debug(
19150
19233
  { sessionId, metric: Metric.CHECK_TRIGGERED, hasNewSkills },
19151
19234
  "skill_quarantine: check start"
19152
19235
  );
19153
19236
  const t0 = Date.now();
19154
19237
  try {
19155
19238
  await reconcileAndSweep(log2);
19156
- log2.info(
19239
+ log2.debug(
19157
19240
  { sessionId, count: installed.length, metric: Metric.SKILLS_CHECKED },
19158
19241
  "skill_quarantine: skills enumerated"
19159
19242
  );
@@ -19173,10 +19256,13 @@ async function runQuarantineCheckIfNeeded(opts) {
19173
19256
  log2
19174
19257
  );
19175
19258
  if (!quarantineEnabled) {
19176
- log2.info(
19177
- { sessionId, metric: Metric.CHECK_DISABLED_ORG },
19178
- "skill_quarantine: opt-in not enabled for any org of caller; skipping enforcement"
19179
- );
19259
+ if (!optInDisabledLogged) {
19260
+ log2.info(
19261
+ { sessionId, metric: Metric.CHECK_DISABLED_ORG },
19262
+ "skill_quarantine: opt-in not enabled for any org of caller; skipping enforcement"
19263
+ );
19264
+ optInDisabledLogged = true;
19265
+ }
19180
19266
  return;
19181
19267
  }
19182
19268
  for (const skill of installed) {
@@ -19201,7 +19287,7 @@ async function runQuarantineCheckIfNeeded(opts) {
19201
19287
  }
19202
19288
  }
19203
19289
  } finally {
19204
- log2.info(
19290
+ log2.debug(
19205
19291
  {
19206
19292
  sessionId,
19207
19293
  duration_ms: Date.now() - t0,
@@ -19313,9 +19399,27 @@ import path21 from "path";
19313
19399
  import { promisify } from "util";
19314
19400
 
19315
19401
  // src/features/analysis/context_file_uploader.ts
19402
+ import { setTimeout as sleep3 } from "timers/promises";
19316
19403
  import pLimit7 from "p-limit";
19317
19404
  init_client_generates();
19318
19405
  var UPLOAD_CONCURRENCY = 5;
19406
+ var UPLOAD_MAX_ATTEMPTS = 2;
19407
+ var UPLOAD_RETRY_BASE_MS = 250;
19408
+ async function uploadWithRetry(args) {
19409
+ for (let attempt = 1; attempt <= UPLOAD_MAX_ATTEMPTS; attempt++) {
19410
+ try {
19411
+ await uploadFile(args);
19412
+ return;
19413
+ } catch (err) {
19414
+ const isLastAttempt = attempt === UPLOAD_MAX_ATTEMPTS;
19415
+ if (isLastAttempt || !isTransientUploadError(err)) {
19416
+ throw err;
19417
+ }
19418
+ const backoff = UPLOAD_RETRY_BASE_MS * 2 ** (attempt - 1) * (1 + Math.random());
19419
+ await sleep3(backoff);
19420
+ }
19421
+ }
19422
+ }
19319
19423
  async function uploadContextRecords(opts) {
19320
19424
  const {
19321
19425
  processedFiles,
@@ -19336,6 +19440,8 @@ async function uploadContextRecords(opts) {
19336
19440
  const records = [];
19337
19441
  const uploadedFiles = [];
19338
19442
  const uploadedSkillGroups = [];
19443
+ const failedFiles = [];
19444
+ const failedSkillGroups = [];
19339
19445
  const limit = pLimit7(UPLOAD_CONCURRENCY);
19340
19446
  const extraFields = {
19341
19447
  ...repositoryUrl !== void 0 && { repositoryUrl },
@@ -19348,13 +19454,14 @@ async function uploadContextRecords(opts) {
19348
19454
  (pf) => limit(async () => {
19349
19455
  const s3Key = `${keyPrefix}ctx-${pf.md5}.bin`;
19350
19456
  try {
19351
- await uploadFile({
19457
+ await uploadWithRetry({
19352
19458
  file: Buffer.from(pf.sanitizedContent, "utf-8"),
19353
19459
  url,
19354
19460
  uploadKey: s3Key,
19355
19461
  uploadFields
19356
19462
  });
19357
19463
  } catch (err) {
19464
+ failedFiles.push(pf.entry);
19358
19465
  onFileError?.(pf.entry.name, err);
19359
19466
  return;
19360
19467
  }
@@ -19382,13 +19489,14 @@ async function uploadContextRecords(opts) {
19382
19489
  (ps) => limit(async () => {
19383
19490
  const s3Key = `${keyPrefix}skill-${ps.md5}.zip`;
19384
19491
  try {
19385
- await uploadFile({
19492
+ await uploadWithRetry({
19386
19493
  file: ps.zipBuffer,
19387
19494
  url,
19388
19495
  uploadKey: s3Key,
19389
19496
  uploadFields
19390
19497
  });
19391
19498
  } catch (err) {
19499
+ failedSkillGroups.push(ps.group);
19392
19500
  onSkillError?.(ps.group.name, err);
19393
19501
  return;
19394
19502
  }
@@ -19414,7 +19522,13 @@ async function uploadContextRecords(opts) {
19414
19522
  )
19415
19523
  ];
19416
19524
  await Promise.allSettled(tasks);
19417
- return { records, uploadedFiles, uploadedSkillGroups };
19525
+ return {
19526
+ records,
19527
+ uploadedFiles,
19528
+ uploadedSkillGroups,
19529
+ failedFiles,
19530
+ failedSkillGroups
19531
+ };
19418
19532
  }
19419
19533
  async function runContextFileUploadPipeline(opts) {
19420
19534
  const {
@@ -19440,7 +19554,13 @@ async function runContextFileUploadPipeline(opts) {
19440
19554
  return null;
19441
19555
  }
19442
19556
  const now = (/* @__PURE__ */ new Date()).toISOString();
19443
- const { records, uploadedFiles, uploadedSkillGroups } = await uploadContextRecords({
19557
+ const {
19558
+ records,
19559
+ uploadedFiles,
19560
+ uploadedSkillGroups,
19561
+ failedFiles,
19562
+ failedSkillGroups
19563
+ } = await uploadContextRecords({
19444
19564
  processedFiles,
19445
19565
  processedSkills,
19446
19566
  keyPrefix,
@@ -19456,6 +19576,9 @@ async function runContextFileUploadPipeline(opts) {
19456
19576
  onFileError,
19457
19577
  onSkillError
19458
19578
  });
19579
+ if (failedFiles.length > 0 || failedSkillGroups.length > 0) {
19580
+ markContextUploadFailed(sessionId, failedFiles, failedSkillGroups);
19581
+ }
19459
19582
  if (records.length === 0) {
19460
19583
  return { fileCount: 0, skillCount: 0 };
19461
19584
  }
@@ -19807,7 +19930,7 @@ function createLogger(config2) {
19807
19930
 
19808
19931
  // src/features/claude_code/hook_logger.ts
19809
19932
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
19810
- var CLI_VERSION = true ? "1.4.26" : "unknown";
19933
+ var CLI_VERSION = true ? "1.4.28" : "unknown";
19811
19934
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
19812
19935
  var claudeCodeVersion;
19813
19936
  function buildDdTags() {
@@ -19986,7 +20109,7 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore,
19986
20109
  const stat5 = await fh.stat();
19987
20110
  fileSize = stat5.size;
19988
20111
  if (cursor.byteOffset >= stat5.size) {
19989
- hookLog.info({ data: { sessionId } }, "No new data in transcript file");
20112
+ hookLog.debug({ data: { sessionId } }, "No new data in transcript file");
19990
20113
  return {
19991
20114
  entries: [],
19992
20115
  endByteOffset: fileSize,
@@ -20223,12 +20346,12 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
20223
20346
  );
20224
20347
  const cursorKey = getCursorKey(resolvedTranscriptPath);
20225
20348
  if (rawEntries.length === 0) {
20226
- log2.info("No new entries to upload");
20349
+ log2.debug("No new entries to upload");
20227
20350
  return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
20228
20351
  }
20229
20352
  const { filtered: entries, filteredOut } = filterEntries(rawEntries);
20230
20353
  if (filteredOut > 0) {
20231
- log2.info(
20354
+ log2.debug(
20232
20355
  { data: { filteredOut, remaining: entries.length } },
20233
20356
  "Filtered out noise entries"
20234
20357
  );
@@ -20911,7 +21034,7 @@ async function startDaemon() {
20911
21034
  },
20912
21035
  "daemon poll cycle"
20913
21036
  );
20914
- await sleep3(DAEMON_POLL_INTERVAL_MS);
21037
+ await sleep4(DAEMON_POLL_INTERVAL_MS);
20915
21038
  }
20916
21039
  }
20917
21040
  async function acquirePidFile() {
@@ -28770,7 +28893,7 @@ var _FetchAvailableFixesService = class _FetchAvailableFixesService {
28770
28893
  interactiveFixes: fixReport.interactiveFixes ?? [],
28771
28894
  repositoryPath
28772
28895
  });
28773
- this.currentOffset = effectiveOffset + (fixReport.fixes?.length || 0);
28896
+ this.currentOffset = effectiveOffset + (fixReport.fixes?.length || 0) + (fixReport.interactiveFixes?.length || 0);
28774
28897
  return prompt;
28775
28898
  } catch (error) {
28776
28899
  logError("Failed to check for available fixes", {
@@ -29128,7 +29251,7 @@ var _ScanAndFixVulnerabilitiesService = class _ScanAndFixVulnerabilitiesService
29128
29251
  interactiveFixes: fixes.interactiveFixes,
29129
29252
  repositoryPath
29130
29253
  });
29131
- this.currentOffset = effectiveOffset + (fixes.fixes?.length || 0);
29254
+ this.currentOffset = effectiveOffset + (fixes.fixes?.length || 0) + (fixes.interactiveFixes?.length || 0);
29132
29255
  return prompt;
29133
29256
  } catch (error) {
29134
29257
  const message = error.message;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.4.26",
3
+ "version": "1.4.28",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.mjs",