mobbdev 1.4.17 → 1.4.19
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 +33 -2
- package/dist/index.mjs +113 -11
- package/package.json +1 -1
|
@@ -233,6 +233,7 @@ var init_client_generates = __esm({
|
|
|
233
233
|
IssueType_Enum2["AutoEscapeFalse"] = "AUTO_ESCAPE_FALSE";
|
|
234
234
|
IssueType_Enum2["AvoidBuiltinShadowing"] = "AVOID_BUILTIN_SHADOWING";
|
|
235
235
|
IssueType_Enum2["AvoidIdentityComparisonCachedTypes"] = "AVOID_IDENTITY_COMPARISON_CACHED_TYPES";
|
|
236
|
+
IssueType_Enum2["AwsDynamodbPointInTimeRecoveryDisabled"] = "AWS_DYNAMODB_POINT_IN_TIME_RECOVERY_DISABLED";
|
|
236
237
|
IssueType_Enum2["BufferOverflow"] = "BUFFER_OVERFLOW";
|
|
237
238
|
IssueType_Enum2["ClientDomStoredCodeInjection"] = "CLIENT_DOM_STORED_CODE_INJECTION";
|
|
238
239
|
IssueType_Enum2["CmDi"] = "CMDi";
|
|
@@ -282,6 +283,7 @@ var init_client_generates = __esm({
|
|
|
282
283
|
IssueType_Enum2["InformationExposureViaHeaders"] = "INFORMATION_EXPOSURE_VIA_HEADERS";
|
|
283
284
|
IssueType_Enum2["InsecureBinderConfiguration"] = "INSECURE_BINDER_CONFIGURATION";
|
|
284
285
|
IssueType_Enum2["InsecureCookie"] = "INSECURE_COOKIE";
|
|
286
|
+
IssueType_Enum2["InsecureDeserialization"] = "INSECURE_DESERIALIZATION";
|
|
285
287
|
IssueType_Enum2["InsecurePostmessage"] = "INSECURE_POSTMESSAGE";
|
|
286
288
|
IssueType_Enum2["InsecureRandomness"] = "INSECURE_RANDOMNESS";
|
|
287
289
|
IssueType_Enum2["InsecureTmpFile"] = "INSECURE_TMP_FILE";
|
|
@@ -1888,7 +1890,9 @@ var init_getIssueType = __esm({
|
|
|
1888
1890
|
["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: "Incorrect Integer Conversion",
|
|
1889
1891
|
["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: "Improper Certificate Validation",
|
|
1890
1892
|
["OFTEN_MISUSED_BOOLEAN_GET_BOOLEAN" /* OftenMisusedBooleanGetBoolean */]: "Often Misused: Boolean.getBoolean()",
|
|
1891
|
-
["UNENCRYPTED_AWS_SQS_QUEUE" /* UnencryptedAwsSqsQueue */]: "AWS SQS Queue Unencrypted"
|
|
1893
|
+
["UNENCRYPTED_AWS_SQS_QUEUE" /* UnencryptedAwsSqsQueue */]: "AWS SQS Queue Unencrypted",
|
|
1894
|
+
["INSECURE_DESERIALIZATION" /* InsecureDeserialization */]: "Insecure Deserialization",
|
|
1895
|
+
["AWS_DYNAMODB_POINT_IN_TIME_RECOVERY_DISABLED" /* AwsDynamodbPointInTimeRecoveryDisabled */]: "AWS DynamoDB Point-in-Time Recovery Disabled"
|
|
1892
1896
|
};
|
|
1893
1897
|
issueTypeZ = z5.nativeEnum(IssueType_Enum);
|
|
1894
1898
|
getIssueTypeFriendlyString = (issueType) => {
|
|
@@ -2867,7 +2871,23 @@ var init_gitBlameTypes = __esm({
|
|
|
2867
2871
|
"use strict";
|
|
2868
2872
|
PrepareGitBlameMessageZ = z19.object({
|
|
2869
2873
|
reportId: z19.string(),
|
|
2870
|
-
repoArchivePath: z19.string()
|
|
2874
|
+
repoArchivePath: z19.string(),
|
|
2875
|
+
// Optional list of file paths to blame. Producers must pick one of two modes:
|
|
2876
|
+
//
|
|
2877
|
+
// - **Omit `filePaths`** = "blame every file in the archive". Used by the
|
|
2878
|
+
// legacy side-effect producer in scm_agent's PrepareRepository handler
|
|
2879
|
+
// (consumers/scm_agent/src/index.ts) where the archive is already
|
|
2880
|
+
// sparse-checkout-narrowed to the report's file set.
|
|
2881
|
+
//
|
|
2882
|
+
// - **Provide `filePaths`** = "filter the walked tree to this set". Used by
|
|
2883
|
+
// report_init's `_ensure_git_blame_enqueued` (Python) when the archive is
|
|
2884
|
+
// a full-repo scan-mode zip that wasn't pre-narrowed.
|
|
2885
|
+
//
|
|
2886
|
+
// Entries should be repo-root-relative paths, but basename-only forms (e.g.
|
|
2887
|
+
// "Login.java" when the actual file is "src/main/java/Login.java") are
|
|
2888
|
+
// tolerated — scm_agent's filter matches exact-or-trailing-basename. Empty
|
|
2889
|
+
// strings are filtered out defensively.
|
|
2890
|
+
filePaths: z19.array(z19.string()).optional()
|
|
2871
2891
|
});
|
|
2872
2892
|
PrepareGitBlameResponseMessageZ = z19.object({
|
|
2873
2893
|
reportId: z19.string()
|
|
@@ -5117,6 +5137,11 @@ var fixDetailsData = {
|
|
|
5117
5137
|
["UNENCRYPTED_AWS_SQS_QUEUE" /* UnencryptedAwsSqsQueue */]: {
|
|
5118
5138
|
issueDescription: "AWS SQS queue contents are unencrypted; data could be read if the queue is compromised.",
|
|
5119
5139
|
fixInstructions: "Enable server-side encryption by setting sqs_managed_sse_enabled = true, or supply a KMS key via kms_master_key_id."
|
|
5140
|
+
},
|
|
5141
|
+
["INSECURE_DESERIALIZATION" /* InsecureDeserialization */]: void 0,
|
|
5142
|
+
["AWS_DYNAMODB_POINT_IN_TIME_RECOVERY_DISABLED" /* AwsDynamodbPointInTimeRecoveryDisabled */]: {
|
|
5143
|
+
issueDescription: "AWS DynamoDB table has point-in-time recovery disabled; accidental or malicious writes/deletes cannot be rolled back from a known-good snapshot.",
|
|
5144
|
+
fixInstructions: "Enable point-in-time recovery by adding `point_in_time_recovery { enabled = true }` to the aws_dynamodb_table resource."
|
|
5120
5145
|
}
|
|
5121
5146
|
};
|
|
5122
5147
|
|
|
@@ -5198,6 +5223,11 @@ var hcl_default = vulnerabilities4;
|
|
|
5198
5223
|
// src/features/analysis/scm/shared/src/storedFixData/java/index.ts
|
|
5199
5224
|
init_client_generates();
|
|
5200
5225
|
|
|
5226
|
+
// src/features/analysis/scm/shared/src/storedFixData/java/insecureDeserialization.ts
|
|
5227
|
+
var insecureDeserialization = {
|
|
5228
|
+
guidance: () => "Added a `@Consumes` annotation restricting the endpoint to common safe media types (JSON, XML, form, multipart, octet-stream, plain text). Requests with `Content-Type: application/x-java-serialized-object` are no longer routed to the RESTEasy `SerializableProvider`. If your endpoint legitimately accepts a content type not in this allowlist (e.g. `image/png`, a custom JSON variant), expect HTTP 415 from those clients and extend the `@Consumes` list to include it."
|
|
5229
|
+
};
|
|
5230
|
+
|
|
5201
5231
|
// src/features/analysis/scm/shared/src/storedFixData/java/j2eeGetConnection.ts
|
|
5202
5232
|
var j2eeGetConnection = {
|
|
5203
5233
|
guidance: () => `This fix replaces direct \`DriverManager.getConnection(...)\` calls with a container-managed JNDI \`DataSource\` lookup. The new code expects the app server (Tomcat / WildFly / WebSphere / etc.) to expose a configured connection pool under the JNDI name you specified.
|
|
@@ -5250,6 +5280,7 @@ var systemInformationLeak = {
|
|
|
5250
5280
|
// src/features/analysis/scm/shared/src/storedFixData/java/index.ts
|
|
5251
5281
|
var vulnerabilities5 = {
|
|
5252
5282
|
["PASSWORD_IN_COMMENT" /* PasswordInComment */]: passwordInComment,
|
|
5283
|
+
["INSECURE_DESERIALIZATION" /* InsecureDeserialization */]: insecureDeserialization,
|
|
5253
5284
|
["J2EE_GET_CONNECTION" /* J2EeGetConnection */]: j2eeGetConnection,
|
|
5254
5285
|
["SQL_Injection" /* SqlInjection */]: sqlInjection,
|
|
5255
5286
|
["SYSTEM_INFORMATION_LEAK" /* SystemInformationLeak */]: systemInformationLeak
|
package/dist/index.mjs
CHANGED
|
@@ -233,6 +233,7 @@ var init_client_generates = __esm({
|
|
|
233
233
|
IssueType_Enum2["AutoEscapeFalse"] = "AUTO_ESCAPE_FALSE";
|
|
234
234
|
IssueType_Enum2["AvoidBuiltinShadowing"] = "AVOID_BUILTIN_SHADOWING";
|
|
235
235
|
IssueType_Enum2["AvoidIdentityComparisonCachedTypes"] = "AVOID_IDENTITY_COMPARISON_CACHED_TYPES";
|
|
236
|
+
IssueType_Enum2["AwsDynamodbPointInTimeRecoveryDisabled"] = "AWS_DYNAMODB_POINT_IN_TIME_RECOVERY_DISABLED";
|
|
236
237
|
IssueType_Enum2["BufferOverflow"] = "BUFFER_OVERFLOW";
|
|
237
238
|
IssueType_Enum2["ClientDomStoredCodeInjection"] = "CLIENT_DOM_STORED_CODE_INJECTION";
|
|
238
239
|
IssueType_Enum2["CmDi"] = "CMDi";
|
|
@@ -282,6 +283,7 @@ var init_client_generates = __esm({
|
|
|
282
283
|
IssueType_Enum2["InformationExposureViaHeaders"] = "INFORMATION_EXPOSURE_VIA_HEADERS";
|
|
283
284
|
IssueType_Enum2["InsecureBinderConfiguration"] = "INSECURE_BINDER_CONFIGURATION";
|
|
284
285
|
IssueType_Enum2["InsecureCookie"] = "INSECURE_COOKIE";
|
|
286
|
+
IssueType_Enum2["InsecureDeserialization"] = "INSECURE_DESERIALIZATION";
|
|
285
287
|
IssueType_Enum2["InsecurePostmessage"] = "INSECURE_POSTMESSAGE";
|
|
286
288
|
IssueType_Enum2["InsecureRandomness"] = "INSECURE_RANDOMNESS";
|
|
287
289
|
IssueType_Enum2["InsecureTmpFile"] = "INSECURE_TMP_FILE";
|
|
@@ -1554,7 +1556,9 @@ var init_getIssueType = __esm({
|
|
|
1554
1556
|
["INCORRECT_INTEGER_CONVERSION" /* IncorrectIntegerConversion */]: "Incorrect Integer Conversion",
|
|
1555
1557
|
["IMPROPER_CERTIFICATE_VALIDATION" /* ImproperCertificateValidation */]: "Improper Certificate Validation",
|
|
1556
1558
|
["OFTEN_MISUSED_BOOLEAN_GET_BOOLEAN" /* OftenMisusedBooleanGetBoolean */]: "Often Misused: Boolean.getBoolean()",
|
|
1557
|
-
["UNENCRYPTED_AWS_SQS_QUEUE" /* UnencryptedAwsSqsQueue */]: "AWS SQS Queue Unencrypted"
|
|
1559
|
+
["UNENCRYPTED_AWS_SQS_QUEUE" /* UnencryptedAwsSqsQueue */]: "AWS SQS Queue Unencrypted",
|
|
1560
|
+
["INSECURE_DESERIALIZATION" /* InsecureDeserialization */]: "Insecure Deserialization",
|
|
1561
|
+
["AWS_DYNAMODB_POINT_IN_TIME_RECOVERY_DISABLED" /* AwsDynamodbPointInTimeRecoveryDisabled */]: "AWS DynamoDB Point-in-Time Recovery Disabled"
|
|
1558
1562
|
};
|
|
1559
1563
|
issueTypeZ = z.nativeEnum(IssueType_Enum);
|
|
1560
1564
|
getIssueTypeFriendlyString = (issueType) => {
|
|
@@ -2919,7 +2923,23 @@ var init_gitBlameTypes = __esm({
|
|
|
2919
2923
|
"use strict";
|
|
2920
2924
|
PrepareGitBlameMessageZ = z18.object({
|
|
2921
2925
|
reportId: z18.string(),
|
|
2922
|
-
repoArchivePath: z18.string()
|
|
2926
|
+
repoArchivePath: z18.string(),
|
|
2927
|
+
// Optional list of file paths to blame. Producers must pick one of two modes:
|
|
2928
|
+
//
|
|
2929
|
+
// - **Omit `filePaths`** = "blame every file in the archive". Used by the
|
|
2930
|
+
// legacy side-effect producer in scm_agent's PrepareRepository handler
|
|
2931
|
+
// (consumers/scm_agent/src/index.ts) where the archive is already
|
|
2932
|
+
// sparse-checkout-narrowed to the report's file set.
|
|
2933
|
+
//
|
|
2934
|
+
// - **Provide `filePaths`** = "filter the walked tree to this set". Used by
|
|
2935
|
+
// report_init's `_ensure_git_blame_enqueued` (Python) when the archive is
|
|
2936
|
+
// a full-repo scan-mode zip that wasn't pre-narrowed.
|
|
2937
|
+
//
|
|
2938
|
+
// Entries should be repo-root-relative paths, but basename-only forms (e.g.
|
|
2939
|
+
// "Login.java" when the actual file is "src/main/java/Login.java") are
|
|
2940
|
+
// tolerated — scm_agent's filter matches exact-or-trailing-basename. Empty
|
|
2941
|
+
// strings are filtered out defensively.
|
|
2942
|
+
filePaths: z18.array(z18.string()).optional()
|
|
2923
2943
|
});
|
|
2924
2944
|
PrepareGitBlameResponseMessageZ = z18.object({
|
|
2925
2945
|
reportId: z18.string()
|
|
@@ -4821,6 +4841,11 @@ var fixDetailsData = {
|
|
|
4821
4841
|
["UNENCRYPTED_AWS_SQS_QUEUE" /* UnencryptedAwsSqsQueue */]: {
|
|
4822
4842
|
issueDescription: "AWS SQS queue contents are unencrypted; data could be read if the queue is compromised.",
|
|
4823
4843
|
fixInstructions: "Enable server-side encryption by setting sqs_managed_sse_enabled = true, or supply a KMS key via kms_master_key_id."
|
|
4844
|
+
},
|
|
4845
|
+
["INSECURE_DESERIALIZATION" /* InsecureDeserialization */]: void 0,
|
|
4846
|
+
["AWS_DYNAMODB_POINT_IN_TIME_RECOVERY_DISABLED" /* AwsDynamodbPointInTimeRecoveryDisabled */]: {
|
|
4847
|
+
issueDescription: "AWS DynamoDB table has point-in-time recovery disabled; accidental or malicious writes/deletes cannot be rolled back from a known-good snapshot.",
|
|
4848
|
+
fixInstructions: "Enable point-in-time recovery by adding `point_in_time_recovery { enabled = true }` to the aws_dynamodb_table resource."
|
|
4824
4849
|
}
|
|
4825
4850
|
};
|
|
4826
4851
|
|
|
@@ -4990,6 +5015,11 @@ var hcl_default = vulnerabilities4;
|
|
|
4990
5015
|
// src/features/analysis/scm/shared/src/storedFixData/java/index.ts
|
|
4991
5016
|
init_client_generates();
|
|
4992
5017
|
|
|
5018
|
+
// src/features/analysis/scm/shared/src/storedFixData/java/insecureDeserialization.ts
|
|
5019
|
+
var insecureDeserialization = {
|
|
5020
|
+
guidance: () => "Added a `@Consumes` annotation restricting the endpoint to common safe media types (JSON, XML, form, multipart, octet-stream, plain text). Requests with `Content-Type: application/x-java-serialized-object` are no longer routed to the RESTEasy `SerializableProvider`. If your endpoint legitimately accepts a content type not in this allowlist (e.g. `image/png`, a custom JSON variant), expect HTTP 415 from those clients and extend the `@Consumes` list to include it."
|
|
5021
|
+
};
|
|
5022
|
+
|
|
4993
5023
|
// src/features/analysis/scm/shared/src/storedFixData/java/j2eeGetConnection.ts
|
|
4994
5024
|
var j2eeGetConnection = {
|
|
4995
5025
|
guidance: () => `This fix replaces direct \`DriverManager.getConnection(...)\` calls with a container-managed JNDI \`DataSource\` lookup. The new code expects the app server (Tomcat / WildFly / WebSphere / etc.) to expose a configured connection pool under the JNDI name you specified.
|
|
@@ -5042,6 +5072,7 @@ var systemInformationLeak = {
|
|
|
5042
5072
|
// src/features/analysis/scm/shared/src/storedFixData/java/index.ts
|
|
5043
5073
|
var vulnerabilities5 = {
|
|
5044
5074
|
["PASSWORD_IN_COMMENT" /* PasswordInComment */]: passwordInComment,
|
|
5075
|
+
["INSECURE_DESERIALIZATION" /* InsecureDeserialization */]: insecureDeserialization,
|
|
5045
5076
|
["J2EE_GET_CONNECTION" /* J2EeGetConnection */]: j2eeGetConnection,
|
|
5046
5077
|
["SQL_Injection" /* SqlInjection */]: sqlInjection,
|
|
5047
5078
|
["SYSTEM_INFORMATION_LEAK" /* SystemInformationLeak */]: systemInformationLeak
|
|
@@ -19417,7 +19448,7 @@ function createLogger(config2) {
|
|
|
19417
19448
|
|
|
19418
19449
|
// src/features/claude_code/hook_logger.ts
|
|
19419
19450
|
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
19420
|
-
var CLI_VERSION = true ? "1.4.
|
|
19451
|
+
var CLI_VERSION = true ? "1.4.19" : "unknown";
|
|
19421
19452
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
19422
19453
|
var claudeCodeVersion;
|
|
19423
19454
|
function buildDdTags() {
|
|
@@ -19909,7 +19940,12 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
|
|
|
19909
19940
|
rawData: rawEntry,
|
|
19910
19941
|
repositoryUrl: sampledRepoState.repositoryUrl ?? void 0,
|
|
19911
19942
|
branch: sampledRepoState.branch,
|
|
19912
|
-
commitSha: sampledRepoState.commitSha
|
|
19943
|
+
commitSha: sampledRepoState.commitSha,
|
|
19944
|
+
// T-510: sub-agent attribution stamped per record. Defaults preserve
|
|
19945
|
+
// main-session semantics so the upload schema's `isSubagent` defaults
|
|
19946
|
+
// to false on the wire for older clients / main-transcript files.
|
|
19947
|
+
isSubagent: input.isSubagent ?? false,
|
|
19948
|
+
agentType: input.agentType ?? null
|
|
19913
19949
|
};
|
|
19914
19950
|
});
|
|
19915
19951
|
let totalRawDataBytes = 0;
|
|
@@ -19949,9 +19985,16 @@ async function processTranscript(input, sessionStore, log2, maxEntries = DAEMON_
|
|
|
19949
19985
|
lastModel: lastSeenModel ?? void 0
|
|
19950
19986
|
};
|
|
19951
19987
|
sessionStore.set(cursorKey, cursor);
|
|
19988
|
+
const isSubagentBatch = input.isSubagent === true;
|
|
19952
19989
|
log2.heartbeat("Upload ok", {
|
|
19953
19990
|
entriesUploaded: entries.length,
|
|
19954
19991
|
entriesSkipped: filteredOut,
|
|
19992
|
+
subagentEntriesUploaded: isSubagentBatch ? entries.length : 0,
|
|
19993
|
+
// Renamed for clarity: this counts rows uploaded with no agent_type
|
|
19994
|
+
// because the sibling .meta.json was missing/unreadable at scan
|
|
19995
|
+
// time. Should be ~0 in steady state; a non-zero rate is a signal
|
|
19996
|
+
// that Claude Code's meta convention may have changed.
|
|
19997
|
+
subagentEntriesUploadedMissingMeta: isSubagentBatch && input.agentType == null ? entries.length : 0,
|
|
19955
19998
|
claudeCodeVersion: getClaudeCodeVersion()
|
|
19956
19999
|
});
|
|
19957
20000
|
return {
|
|
@@ -20193,7 +20236,7 @@ async function installMobbHooks(options = {}) {
|
|
|
20193
20236
|
}
|
|
20194
20237
|
|
|
20195
20238
|
// src/features/claude_code/transcript_scanner.ts
|
|
20196
|
-
import { open as open5, readdir as readdir4, stat as stat4 } from "fs/promises";
|
|
20239
|
+
import { lstat as lstat2, open as open5, readdir as readdir4, stat as stat4 } from "fs/promises";
|
|
20197
20240
|
import os7 from "os";
|
|
20198
20241
|
import path23 from "path";
|
|
20199
20242
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -20207,11 +20250,59 @@ function getClaudeProjectsDirs() {
|
|
|
20207
20250
|
dirs.push(path23.join(os7.homedir(), ".claude", "projects"));
|
|
20208
20251
|
return dirs;
|
|
20209
20252
|
}
|
|
20210
|
-
|
|
20253
|
+
var META_MAX_BYTES = 4096;
|
|
20254
|
+
var AGENT_TYPE_RE = /^[A-Za-z0-9_.\- ]+$/;
|
|
20255
|
+
var agentTypeCache = /* @__PURE__ */ new Map();
|
|
20256
|
+
var knownAbsentSubagentDirs = /* @__PURE__ */ new Set();
|
|
20257
|
+
async function readAgentType(jsonlPath, jsonlMtimeMs) {
|
|
20258
|
+
const cached = agentTypeCache.get(jsonlPath);
|
|
20259
|
+
if (cached && cached.mtimeMs === jsonlMtimeMs) {
|
|
20260
|
+
return cached.value;
|
|
20261
|
+
}
|
|
20262
|
+
const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
|
|
20263
|
+
let fh;
|
|
20264
|
+
let value = null;
|
|
20265
|
+
try {
|
|
20266
|
+
const lst = await lstat2(metaPath);
|
|
20267
|
+
if (!lst.isFile() || lst.size > META_MAX_BYTES) {
|
|
20268
|
+
agentTypeCache.set(jsonlPath, { mtimeMs: jsonlMtimeMs, value: null });
|
|
20269
|
+
return null;
|
|
20270
|
+
}
|
|
20271
|
+
fh = await open5(metaPath, "r");
|
|
20272
|
+
const buf = Buffer.alloc(Math.min(lst.size, META_MAX_BYTES));
|
|
20273
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
20274
|
+
const raw = buf.toString("utf8", 0, bytesRead);
|
|
20275
|
+
const parsed = JSON.parse(raw);
|
|
20276
|
+
if (parsed && typeof parsed === "object") {
|
|
20277
|
+
const candidate = parsed.agentType;
|
|
20278
|
+
if (typeof candidate === "string" && candidate.length > 0 && candidate.length <= 128 && AGENT_TYPE_RE.test(candidate.trim())) {
|
|
20279
|
+
value = candidate.trim();
|
|
20280
|
+
}
|
|
20281
|
+
}
|
|
20282
|
+
} catch {
|
|
20283
|
+
} finally {
|
|
20284
|
+
if (fh) {
|
|
20285
|
+
await fh.close().catch(() => void 0);
|
|
20286
|
+
}
|
|
20287
|
+
}
|
|
20288
|
+
agentTypeCache.set(jsonlPath, { mtimeMs: jsonlMtimeMs, value });
|
|
20289
|
+
return value;
|
|
20290
|
+
}
|
|
20291
|
+
async function collectJsonlFiles(files, dir, projectDir, seen, now, results, override) {
|
|
20211
20292
|
for (const file of files) {
|
|
20212
20293
|
if (!file.endsWith(".jsonl")) continue;
|
|
20213
|
-
|
|
20214
|
-
|
|
20294
|
+
let sessionId;
|
|
20295
|
+
let isSubagent;
|
|
20296
|
+
if (override) {
|
|
20297
|
+
if (!/^agent-[A-Za-z0-9_-]+\.jsonl$/.test(file)) continue;
|
|
20298
|
+
sessionId = override.parentUuid;
|
|
20299
|
+
isSubagent = true;
|
|
20300
|
+
} else {
|
|
20301
|
+
const basename2 = file.replace(".jsonl", "");
|
|
20302
|
+
if (!UUID_RE.test(basename2)) continue;
|
|
20303
|
+
sessionId = basename2;
|
|
20304
|
+
isSubagent = false;
|
|
20305
|
+
}
|
|
20215
20306
|
const filePath = path23.join(dir, file);
|
|
20216
20307
|
if (seen.has(filePath)) continue;
|
|
20217
20308
|
seen.add(filePath);
|
|
@@ -20222,12 +20313,15 @@ async function collectJsonlFiles(files, dir, projectDir, seen, now, results) {
|
|
|
20222
20313
|
continue;
|
|
20223
20314
|
}
|
|
20224
20315
|
if (now - fileStat.mtimeMs > TRANSCRIPT_MAX_AGE_MS) continue;
|
|
20316
|
+
const agentType = isSubagent ? await readAgentType(filePath, fileStat.mtimeMs) : null;
|
|
20225
20317
|
results.push({
|
|
20226
20318
|
filePath,
|
|
20227
20319
|
sessionId,
|
|
20228
20320
|
projectDir,
|
|
20229
20321
|
mtimeMs: fileStat.mtimeMs,
|
|
20230
|
-
size: fileStat.size
|
|
20322
|
+
size: fileStat.size,
|
|
20323
|
+
isSubagent,
|
|
20324
|
+
agentType
|
|
20231
20325
|
});
|
|
20232
20326
|
}
|
|
20233
20327
|
}
|
|
@@ -20261,6 +20355,7 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
|
|
|
20261
20355
|
for (const entry of files) {
|
|
20262
20356
|
if (!UUID_RE.test(entry)) continue;
|
|
20263
20357
|
const subagentsDir = path23.join(projPath, entry, "subagents");
|
|
20358
|
+
if (knownAbsentSubagentDirs.has(subagentsDir)) continue;
|
|
20264
20359
|
try {
|
|
20265
20360
|
const s = await stat4(subagentsDir);
|
|
20266
20361
|
if (!s.isDirectory()) continue;
|
|
@@ -20271,9 +20366,11 @@ async function scanForTranscripts(projectsDirs = getClaudeProjectsDirs()) {
|
|
|
20271
20366
|
projPath,
|
|
20272
20367
|
seen,
|
|
20273
20368
|
now,
|
|
20274
|
-
results
|
|
20369
|
+
results,
|
|
20370
|
+
{ parentUuid: entry }
|
|
20275
20371
|
);
|
|
20276
20372
|
} catch {
|
|
20373
|
+
knownAbsentSubagentDirs.add(subagentsDir);
|
|
20277
20374
|
}
|
|
20278
20375
|
}
|
|
20279
20376
|
}
|
|
@@ -20504,7 +20601,12 @@ async function drainTranscript(transcript, sessionStore, gqlClient, sessionCwdCa
|
|
|
20504
20601
|
{
|
|
20505
20602
|
session_id: transcript.sessionId,
|
|
20506
20603
|
transcript_path: transcript.filePath,
|
|
20507
|
-
cwd
|
|
20604
|
+
cwd,
|
|
20605
|
+
// T-510: forward sub-agent attribution from transcript_scanner.ts.
|
|
20606
|
+
// For main-session files these are false / null and behave
|
|
20607
|
+
// identically to the pre-T-510 wire.
|
|
20608
|
+
isSubagent: transcript.isSubagent,
|
|
20609
|
+
agentType: transcript.agentType
|
|
20508
20610
|
},
|
|
20509
20611
|
sessionStore,
|
|
20510
20612
|
log2,
|