modelstat 0.0.46 → 0.0.47

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/cli.mjs CHANGED
@@ -78,7 +78,7 @@ var init_git = __esm({
78
78
  });
79
79
 
80
80
  // ../../packages/core/src/enums.ts
81
- var TOOLS, PROVIDERS, IDENTITY_OWNER_SCOPES, INSTALL_METHODS, OS_FAMILIES, EVENT_KINDS, COMPANION_PHASES, CLASSIFICATION_CONFIDENCE;
81
+ var TOOLS, PROVIDERS, IDENTITY_OWNER_SCOPES, INSTALL_METHODS, OS_FAMILIES, EVENT_KINDS, TOOL_CALL_STATUSES, COMPANION_PHASES, CLASSIFICATION_CONFIDENCE;
82
82
  var init_enums = __esm({
83
83
  "../../packages/core/src/enums.ts"() {
84
84
  "use strict";
@@ -162,6 +162,13 @@ var init_enums = __esm({
162
162
  "tool_result",
163
163
  "summary"
164
164
  ];
165
+ TOOL_CALL_STATUSES = [
166
+ "success",
167
+ "error",
168
+ "denied",
169
+ "timeout",
170
+ "unknown"
171
+ ];
165
172
  COMPANION_PHASES = [
166
173
  "starting",
167
174
  "discovering",
@@ -4304,7 +4311,7 @@ var init_zod = __esm({
4304
4311
  });
4305
4312
 
4306
4313
  // ../../packages/core/src/schemas.ts
4307
- var TokenUsage, GitContext, RawEvent, RedactionReport, TaxonomyHintRooted, Segment, IngestBatch, HeartbeatPayload, DeviceEnrollment, DeviceSelfRegister, DeviceClaimRequest, ProcessingMetadata, RedactionPolicy, DetectedInstallation, DetectedIdentity, DiscoveryReport, ClassificationConfidenceEnum;
4314
+ var TokenUsage, GitContext, RawEvent, RedactionReport, TaxonomyHintRooted, Segment, ToolCallWire, IngestBatch, HeartbeatPayload, DeviceEnrollment, DeviceSelfRegister, DeviceClaimRequest, ProcessingMetadata, RedactionPolicy, DetectedInstallation, DetectedIdentity, DiscoveryReport, ClassificationConfidenceEnum;
4308
4315
  var init_schemas = __esm({
4309
4316
  "../../packages/core/src/schemas.ts"() {
4310
4317
  "use strict";
@@ -4409,6 +4416,49 @@ var init_schemas = __esm({
4409
4416
  * Reserved for server-side similarity / clustering. */
4410
4417
  abstract_embedding: external_exports.array(external_exports.number()).length(384).optional()
4411
4418
  });
4419
+ ToolCallWire = external_exports.object({
4420
+ /** tool_use block `id` / codex `call_id`; parsers fall back to a
4421
+ * deterministic `tc_<djb2-base36>` of `${source_event_id}|${call_index}`
4422
+ * when the source line carries no id. */
4423
+ external_call_id: external_exports.string().max(120),
4424
+ /** Tool-local session id — same id space as RawEvent.session_id. */
4425
+ session_id: external_exports.string().max(120),
4426
+ /** The RawEvent that contained the tool_use (dedupe/replay anchor). */
4427
+ source_event_id: external_exports.string(),
4428
+ /** Segment containing source_event_id — filled by the companion at
4429
+ * batch-build time when known, else null. */
4430
+ segment_id: external_exports.string().max(64).nullable().default(null),
4431
+ /** The agent that made the call (TOOLS enum — "tool" in legacy naming). */
4432
+ agent: external_exports.enum(TOOLS),
4433
+ /** `builtin` or `mcp:<server>`. */
4434
+ server: external_exports.string().max(120),
4435
+ /** Bare tool name (`Bash`, `create_pr`) — normalised vendor identifier. */
4436
+ name: external_exports.string().max(120),
4437
+ turn_index: external_exports.number().int().nonnegative().nullable(),
4438
+ /** Ordinal of the call within its source event (0-based). */
4439
+ call_index: external_exports.number().int().nonnegative(),
4440
+ /** ts of the line carrying the tool_use. */
4441
+ started_at: external_exports.string().datetime({ offset: true }),
4442
+ /** ts of the line carrying the matching tool_result; null if unmatched. */
4443
+ ended_at: external_exports.string().datetime({ offset: true }).nullable(),
4444
+ status: external_exports.enum(TOOL_CALL_STATUSES),
4445
+ /** Hex sha256 of JSON.stringify(input); `""` when the call had no input. */
4446
+ args_hash: external_exports.string().max(64),
4447
+ /** Sha256 of the sorted top-level arg key names joined by `,`; the
4448
+ * literal `none` when input is not a non-empty object. */
4449
+ signature_hash: external_exports.string().max(64),
4450
+ /** UTF-8 byte length of JSON.stringify(input); 0 if none. */
4451
+ args_bytes: external_exports.number().int().nonnegative(),
4452
+ /** UTF-8 byte length of JSON.stringify(tool_result content); 0 if
4453
+ * unmatched/empty. */
4454
+ result_bytes: external_exports.number().int().nonnegative(),
4455
+ /** Model of the assistant message that issued the call. `<synthetic>`
4456
+ * kept verbatim per the PR #12 attribution rules. */
4457
+ model: external_exports.string().max(120).nullable(),
4458
+ /** ONLY for shell-ish tools: command verbs from the fixed allowlist
4459
+ * (@modelstat/parsers/shell-families). Never raw command text. */
4460
+ command_families: external_exports.array(external_exports.string().max(40)).max(3).default([])
4461
+ });
4412
4462
  IngestBatch = external_exports.object({
4413
4463
  batch_id: external_exports.string(),
4414
4464
  // ULID
@@ -4416,6 +4466,10 @@ var init_schemas = __esm({
4416
4466
  agent_version: external_exports.string().max(40),
4417
4467
  events: external_exports.array(RawEvent).max(1e4),
4418
4468
  segments: external_exports.array(Segment).max(2e3).default([]),
4469
+ /** Per-call tool invocations (additive — old agents omit it, old
4470
+ * servers ignore it). See ToolCallWire for the privacy contract:
4471
+ * hashes / byte sizes / allowlisted verbs only, never payloads. */
4472
+ tool_calls: external_exports.array(ToolCallWire).max(2e4).default([]),
4419
4473
  /** Optional per-session metadata hint: which installation produced them, etc. */
4420
4474
  session_installs: external_exports.record(
4421
4475
  external_exports.string(),
@@ -4684,22 +4738,222 @@ var init_src = __esm({
4684
4738
  }
4685
4739
  });
4686
4740
 
4687
- // ../../packages/parsers/src/claude-code/index.ts
4741
+ // ../../packages/parsers/src/shell-families/index.ts
4742
+ function extractCommandFamilies(command) {
4743
+ const families = [];
4744
+ for (const part of splitCommandParts(command)) {
4745
+ const verb = leadingVerb(part);
4746
+ if (!verb || !ALLOWLIST.has(verb)) continue;
4747
+ if (!families.includes(verb)) families.push(verb);
4748
+ if (families.length >= MAX_COMMAND_FAMILIES) break;
4749
+ }
4750
+ return families;
4751
+ }
4752
+ function splitCommandParts(command) {
4753
+ const parts = [];
4754
+ let current = "";
4755
+ let inSingle = false;
4756
+ let inDouble = false;
4757
+ for (let i = 0; i < command.length; i++) {
4758
+ const ch = command[i];
4759
+ if (!inSingle && ch === "\\") {
4760
+ current += ch + (command[i + 1] ?? "");
4761
+ i++;
4762
+ continue;
4763
+ }
4764
+ if (ch === "'" && !inDouble) {
4765
+ inSingle = !inSingle;
4766
+ current += ch;
4767
+ continue;
4768
+ }
4769
+ if (ch === '"' && !inSingle) {
4770
+ inDouble = !inDouble;
4771
+ current += ch;
4772
+ continue;
4773
+ }
4774
+ if (!inSingle && !inDouble) {
4775
+ if (ch === ";" || ch === "|" || ch === "\n") {
4776
+ parts.push(current);
4777
+ current = "";
4778
+ continue;
4779
+ }
4780
+ if (ch === "&" && command[i + 1] === "&") {
4781
+ parts.push(current);
4782
+ current = "";
4783
+ i++;
4784
+ continue;
4785
+ }
4786
+ }
4787
+ current += ch;
4788
+ }
4789
+ parts.push(current);
4790
+ return parts;
4791
+ }
4792
+ function leadingVerb(part) {
4793
+ const tokens = part.trim().split(/\s+/);
4794
+ let i = 0;
4795
+ while (i < tokens.length) {
4796
+ const tok = stripQuotes(tokens[i] ?? "");
4797
+ if (tok === "") {
4798
+ i++;
4799
+ continue;
4800
+ }
4801
+ if (VAR_ASSIGNMENT.test(tok)) {
4802
+ i++;
4803
+ continue;
4804
+ }
4805
+ if (WRAPPERS.has(tok)) {
4806
+ i++;
4807
+ while (i < tokens.length && (tokens[i] ?? "").startsWith("-")) i++;
4808
+ continue;
4809
+ }
4810
+ const base = tok.split("/").pop() ?? tok;
4811
+ return base === "" ? null : base;
4812
+ }
4813
+ return null;
4814
+ }
4815
+ function stripQuotes(token) {
4816
+ if (token.length >= 2) {
4817
+ const first = token[0];
4818
+ const last = token[token.length - 1];
4819
+ if (first === "'" && last === "'" || first === '"' && last === '"') {
4820
+ return token.slice(1, -1);
4821
+ }
4822
+ }
4823
+ return token;
4824
+ }
4825
+ var SHELL_FAMILY_ALLOWLIST, MAX_COMMAND_FAMILIES, ALLOWLIST, WRAPPERS, VAR_ASSIGNMENT;
4826
+ var init_shell_families = __esm({
4827
+ "../../packages/parsers/src/shell-families/index.ts"() {
4828
+ "use strict";
4829
+ SHELL_FAMILY_ALLOWLIST = [
4830
+ "git",
4831
+ "npm",
4832
+ "pnpm",
4833
+ "npx",
4834
+ "yarn",
4835
+ "node",
4836
+ "python",
4837
+ "python3",
4838
+ "pytest",
4839
+ "pip",
4840
+ "pip3",
4841
+ "cargo",
4842
+ "rustc",
4843
+ "go",
4844
+ "make",
4845
+ "cmake",
4846
+ "docker",
4847
+ "docker-compose",
4848
+ "kubectl",
4849
+ "helm",
4850
+ "terraform",
4851
+ "gh",
4852
+ "aws",
4853
+ "gcloud",
4854
+ "az",
4855
+ "curl",
4856
+ "wget",
4857
+ "rg",
4858
+ "grep",
4859
+ "find",
4860
+ "sed",
4861
+ "awk",
4862
+ "jq",
4863
+ "psql",
4864
+ "mysql",
4865
+ "redis-cli",
4866
+ "brew",
4867
+ "apt",
4868
+ "tsx",
4869
+ "vitest",
4870
+ "jest",
4871
+ "playwright",
4872
+ "ruby",
4873
+ "bundle",
4874
+ "mvn",
4875
+ "gradle",
4876
+ "ls",
4877
+ "cat"
4878
+ ];
4879
+ MAX_COMMAND_FAMILIES = 3;
4880
+ ALLOWLIST = new Set(SHELL_FAMILY_ALLOWLIST);
4881
+ WRAPPERS = /* @__PURE__ */ new Set(["sudo", "env", "time", "nice"]);
4882
+ VAR_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
4883
+ }
4884
+ });
4885
+
4886
+ // ../../packages/parsers/src/tool-hash/index.ts
4688
4887
  import { createHash } from "crypto";
4888
+ function hashArgs(input) {
4889
+ if (input === void 0 || input === null) {
4890
+ return { args_hash: "", signature_hash: SIGNATURE_NONE, args_bytes: 0 };
4891
+ }
4892
+ const json = JSON.stringify(input);
4893
+ if (json === void 0) {
4894
+ return { args_hash: "", signature_hash: SIGNATURE_NONE, args_bytes: 0 };
4895
+ }
4896
+ const argsHash = createHash("sha256").update(json).digest("hex");
4897
+ let signatureHash = SIGNATURE_NONE;
4898
+ if (typeof input === "object" && !Array.isArray(input)) {
4899
+ const keys = Object.keys(input).sort();
4900
+ if (keys.length > 0) {
4901
+ signatureHash = createHash("sha256").update(keys.join(",")).digest("hex");
4902
+ }
4903
+ }
4904
+ return {
4905
+ args_hash: argsHash,
4906
+ signature_hash: signatureHash,
4907
+ args_bytes: Buffer.byteLength(json, "utf8")
4908
+ };
4909
+ }
4910
+ function jsonBytes(value) {
4911
+ if (value === void 0 || value === null || value === "") return 0;
4912
+ const json = JSON.stringify(value);
4913
+ return json === void 0 ? 0 : Buffer.byteLength(json, "utf8");
4914
+ }
4915
+ function normalizeToolName(raw) {
4916
+ const cleaned = raw.normalize("NFC").trim().replace(UUID_RE, "<dyn>").replace(HEX_TAIL_RE, "<dyn>");
4917
+ return cleaned.slice(0, 120);
4918
+ }
4919
+ function splitObservedToolName(observed) {
4920
+ const m = /^mcp__([^_].*?)__(.+)$/.exec(observed.trim());
4921
+ if (m?.[1] && m[2]) {
4922
+ return {
4923
+ server: `mcp:${normalizeToolName(m[1]).slice(0, 116)}`,
4924
+ name: normalizeToolName(m[2])
4925
+ };
4926
+ }
4927
+ return { server: "builtin", name: normalizeToolName(observed) };
4928
+ }
4929
+ function toolIdentity(server, name) {
4930
+ return server === "builtin" ? name : `${server}/${name}`;
4931
+ }
4932
+ function fallbackCallId(sourceEventId2, callIndex) {
4933
+ const s = `${sourceEventId2}|${callIndex}`;
4934
+ let h = 5381n;
4935
+ for (let i = 0; i < s.length; i++) {
4936
+ h = h * 33n ^ BigInt(s.charCodeAt(i));
4937
+ h &= 0xffffffffffffffffn;
4938
+ }
4939
+ return `tc_${h.toString(36)}`;
4940
+ }
4941
+ var SIGNATURE_NONE, UUID_RE, HEX_TAIL_RE;
4942
+ var init_tool_hash = __esm({
4943
+ "../../packages/parsers/src/tool-hash/index.ts"() {
4944
+ "use strict";
4945
+ SIGNATURE_NONE = "none";
4946
+ UUID_RE = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g;
4947
+ HEX_TAIL_RE = /[0-9a-fA-F]{8,}(?=$|[^0-9a-zA-Z])/g;
4948
+ }
4949
+ });
4950
+
4951
+ // ../../packages/parsers/src/claude-code/index.ts
4952
+ import { createHash as createHash2 } from "crypto";
4689
4953
  import { createReadStream, existsSync as existsSync2, readdirSync } from "fs";
4690
4954
  import { stat } from "fs/promises";
4691
4955
  import { dirname as dirname2, join } from "path";
4692
4956
  import { createInterface } from "readline";
4693
- function countToolCalls(content) {
4694
- const counts = {};
4695
- if (!Array.isArray(content)) return counts;
4696
- for (const block of content) {
4697
- if (block && block.type === "tool_use" && typeof block.name === "string" && block.name) {
4698
- counts[block.name] = (counts[block.name] ?? 0) + 1;
4699
- }
4700
- }
4701
- return counts;
4702
- }
4703
4957
  function extractExcerpt(content) {
4704
4958
  if (!content) return void 0;
4705
4959
  let text = "";
@@ -4722,8 +4976,41 @@ function extractExcerpt(content) {
4722
4976
  const truncated = cleaned.slice(0, 320);
4723
4977
  return truncated.length > 0 ? truncated : void 0;
4724
4978
  }
4979
+ function commandFamiliesFor(server, name, input) {
4980
+ if (server !== "builtin" || !SHELL_TOOL_NAMES.has(name)) return [];
4981
+ if (typeof input !== "object" || input === null) return [];
4982
+ const command = input.command;
4983
+ return typeof command === "string" ? extractCommandFamilies(command) : [];
4984
+ }
4985
+ function buildToolCallDraft(opts) {
4986
+ const { server, name } = splitObservedToolName(opts.observedName);
4987
+ const hashes = hashArgs(opts.input);
4988
+ return {
4989
+ external_call_id: typeof opts.rawCallId === "string" && opts.rawCallId.trim() !== "" ? opts.rawCallId.trim().slice(0, 120) : fallbackCallId(opts.sourceEventId, opts.callIndex),
4990
+ session_id: opts.sessionId,
4991
+ source_event_id: opts.sourceEventId,
4992
+ agent: "claude_code",
4993
+ server,
4994
+ name,
4995
+ // This parser never derives a turn index (events above carry
4996
+ // turn_index: null too), so per-call records can't either.
4997
+ turn_index: null,
4998
+ call_index: opts.callIndex,
4999
+ started_at: opts.startedAt,
5000
+ ended_at: null,
5001
+ status: "unknown",
5002
+ args_hash: hashes.args_hash,
5003
+ signature_hash: hashes.signature_hash,
5004
+ args_bytes: hashes.args_bytes,
5005
+ result_bytes: 0,
5006
+ model: opts.model,
5007
+ command_families: commandFamiliesFor(server, name, opts.input)
5008
+ };
5009
+ }
4725
5010
  async function parseClaudeCodeJsonl(ctx) {
4726
5011
  const events = [];
5012
+ const toolCalls = [];
5013
+ const pendingByCallId = /* @__PURE__ */ new Map();
4727
5014
  let chunk = [];
4728
5015
  let emitted = 0;
4729
5016
  const emit = async (e) => {
@@ -4826,6 +5113,32 @@ async function parseClaudeCodeJsonl(ctx) {
4826
5113
  }
4827
5114
  const slug = guessRepoSlugFromPath(cwd);
4828
5115
  const excerpt = extractExcerpt(a.message?.content);
5116
+ const aggregate = {};
5117
+ const blocks = Array.isArray(a.message?.content) ? a.message.content : [];
5118
+ let callIndex = 0;
5119
+ for (const block of blocks) {
5120
+ if (!block || block.type !== "tool_use") continue;
5121
+ const index = callIndex;
5122
+ callIndex += 1;
5123
+ const observed = typeof block.name === "string" ? block.name.trim() : "";
5124
+ if (!observed) continue;
5125
+ const draft = buildToolCallDraft({
5126
+ observedName: observed,
5127
+ rawCallId: block.id,
5128
+ input: block.input,
5129
+ sessionId,
5130
+ sourceEventId: eventId,
5131
+ callIndex: index,
5132
+ startedAt: a.timestamp,
5133
+ // Model verbatim from the issuing assistant message —
5134
+ // including "<synthetic>" (same rule as the event below).
5135
+ model: a.message?.model ?? null
5136
+ });
5137
+ const identity = toolIdentity(draft.server, draft.name);
5138
+ aggregate[identity] = (aggregate[identity] ?? 0) + 1;
5139
+ toolCalls.push(draft);
5140
+ if (typeof block.id === "string" && block.id) pendingByCallId.set(block.id, draft);
5141
+ }
4829
5142
  await emit({
4830
5143
  source_event_id: eventId,
4831
5144
  ts: a.timestamp,
@@ -4852,7 +5165,7 @@ async function parseClaudeCodeJsonl(ctx) {
4852
5165
  reasoning: 0
4853
5166
  },
4854
5167
  duration_ms: null,
4855
- tool_calls: countToolCalls(a.message?.content),
5168
+ tool_calls: aggregate,
4856
5169
  files_touched: [],
4857
5170
  ...excerpt ? { content_excerpt: excerpt } : {},
4858
5171
  source_file: ctx.sourceFile,
@@ -4865,6 +5178,20 @@ async function parseClaudeCodeJsonl(ctx) {
4865
5178
  });
4866
5179
  } else if (obj.type === "user") {
4867
5180
  const u = obj;
5181
+ const uContent = u.message?.content;
5182
+ if (Array.isArray(uContent)) {
5183
+ for (const block of uContent) {
5184
+ if (!block || block.type !== "tool_result") continue;
5185
+ const ref = block.tool_use_id;
5186
+ if (typeof ref !== "string") continue;
5187
+ const draft = pendingByCallId.get(ref);
5188
+ if (!draft) continue;
5189
+ pendingByCallId.delete(ref);
5190
+ draft.ended_at = u.timestamp;
5191
+ draft.status = block.is_error === true ? "error" : "success";
5192
+ draft.result_bytes = jsonBytes(block.content);
5193
+ }
5194
+ }
4868
5195
  if (!u.uuid || !sessionId) {
4869
5196
  skipped += 1;
4870
5197
  continue;
@@ -4896,6 +5223,30 @@ async function parseClaudeCodeJsonl(ctx) {
4896
5223
  source_byte_offset: offsetAtLineStart,
4897
5224
  billing: "subscription"
4898
5225
  });
5226
+ } else if (obj.type === "tool_use") {
5227
+ const t = obj;
5228
+ const observed = typeof t.name === "string" ? t.name.trim() : "";
5229
+ const ts = typeof t.timestamp === "string" ? t.timestamp : null;
5230
+ const sid = (typeof t.sessionId === "string" ? t.sessionId : null) ?? sessionId;
5231
+ if (!observed || !ts || !sid) {
5232
+ skipped += 1;
5233
+ continue;
5234
+ }
5235
+ const draft = buildToolCallDraft({
5236
+ observedName: observed,
5237
+ rawCallId: t.id,
5238
+ input: t.input,
5239
+ sessionId: sid,
5240
+ sourceEventId: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
5241
+ callIndex: 0,
5242
+ startedAt: ts,
5243
+ // No issuing assistant message on this line — attribute to the
5244
+ // session's last real model, same as user_message attribution
5245
+ // (lastModel never holds "<synthetic>", per the rule above).
5246
+ model: lastModel
5247
+ });
5248
+ toolCalls.push(draft);
5249
+ if (typeof t.id === "string" && t.id) pendingByCallId.set(t.id, draft);
4899
5250
  } else {
4900
5251
  skipped += 1;
4901
5252
  }
@@ -4903,6 +5254,7 @@ async function parseClaudeCodeJsonl(ctx) {
4903
5254
  if (ctx.onEvents && chunk.length > 0) await ctx.onEvents(chunk);
4904
5255
  return {
4905
5256
  events,
5257
+ toolCalls,
4906
5258
  stats: { rawLines, emittedEvents: emitted, skipped },
4907
5259
  sourceFile: ctx.sourceFile
4908
5260
  };
@@ -4917,16 +5269,26 @@ async function quickChecksum(path5) {
4917
5269
  start: Math.max(0, st.size - 4096),
4918
5270
  encoding: "utf8"
4919
5271
  });
4920
- const h = createHash("sha1");
5272
+ const h = createHash2("sha1");
4921
5273
  for await (const chunk of stream) h.update(chunk);
4922
5274
  return { size: st.size, mtime: st.mtimeMs, tailHash: h.digest("hex").slice(0, 16) };
4923
5275
  }
5276
+ var SHELL_TOOL_NAMES;
4924
5277
  var init_claude_code = __esm({
4925
5278
  "../../packages/parsers/src/claude-code/index.ts"() {
4926
5279
  "use strict";
4927
5280
  init_src();
4928
- init_types();
4929
5281
  init_git();
5282
+ init_shell_families();
5283
+ init_tool_hash();
5284
+ init_types();
5285
+ SHELL_TOOL_NAMES = /* @__PURE__ */ new Set([
5286
+ "Bash",
5287
+ "shell",
5288
+ "local_shell_call",
5289
+ "exec_command",
5290
+ "run_terminal_cmd"
5291
+ ]);
4930
5292
  }
4931
5293
  });
4932
5294
 
@@ -4939,8 +5301,90 @@ function deriveSessionIdFromRolloutPath(path5) {
4939
5301
  );
4940
5302
  return m ? m[1] ?? null : null;
4941
5303
  }
5304
+ function commandFieldToString(cmd) {
5305
+ if (typeof cmd === "string") return cmd || null;
5306
+ if (Array.isArray(cmd)) {
5307
+ const parts = cmd.filter((p) => typeof p === "string");
5308
+ if (parts.length === 0) return null;
5309
+ const head = (parts[0] ?? "").split("/").pop() ?? "";
5310
+ const flag = parts[1] ?? "";
5311
+ if (parts.length >= 3 && SHELL_WRAPPER_BINARIES.has(head) && /^-[a-z]*c[a-z]*$/i.test(flag)) {
5312
+ return parts.slice(2).join("\n");
5313
+ }
5314
+ return parts.join(" ");
5315
+ }
5316
+ return null;
5317
+ }
5318
+ function firstString(...values) {
5319
+ for (const v of values) {
5320
+ if (typeof v === "string" && v) return v;
5321
+ }
5322
+ return null;
5323
+ }
5324
+ function extractToolCallPayload(pt, p) {
5325
+ const callId = firstString(p.call_id, p.id);
5326
+ const failed = p.status === "failed";
5327
+ if (pt === "local_shell_call") {
5328
+ const action = p.action && typeof p.action === "object" ? p.action : null;
5329
+ const command = commandFieldToString(action?.command);
5330
+ return {
5331
+ callId,
5332
+ server: "builtin",
5333
+ name: "shell",
5334
+ input: action,
5335
+ commandFamilies: command ? extractCommandFamilies(command) : [],
5336
+ failed
5337
+ };
5338
+ }
5339
+ const observed = firstString(p.name, p.tool);
5340
+ if (!observed) return null;
5341
+ let input = pt === "custom_tool_call" ? p.input : p.arguments ?? p.input;
5342
+ if (typeof input === "string" && !input.trim()) input = void 0;
5343
+ if (pt !== "custom_tool_call" && typeof input === "string") {
5344
+ try {
5345
+ input = JSON.parse(input);
5346
+ } catch {
5347
+ }
5348
+ }
5349
+ if (SHELL_TOOL_NAMES2.has(observed)) {
5350
+ const rec = input && typeof input === "object" && !Array.isArray(input) ? input : null;
5351
+ const command = commandFieldToString(
5352
+ rec?.command ?? rec?.cmd ?? (typeof input === "string" ? input : null)
5353
+ );
5354
+ return {
5355
+ callId,
5356
+ server: "builtin",
5357
+ name: "shell",
5358
+ input,
5359
+ commandFamilies: command ? extractCommandFamilies(command) : [],
5360
+ failed
5361
+ };
5362
+ }
5363
+ if (pt === "mcp_tool_call" && typeof p.server === "string" && p.server) {
5364
+ return {
5365
+ callId,
5366
+ // Mirror splitObservedToolName's cap: 116 + the `mcp:` prefix ≤ 120.
5367
+ server: `mcp:${normalizeToolName(p.server).slice(0, 116)}`,
5368
+ name: normalizeToolName(observed),
5369
+ input,
5370
+ commandFamilies: [],
5371
+ failed
5372
+ };
5373
+ }
5374
+ const { server, name } = splitObservedToolName(observed);
5375
+ return { callId, server, name, input, commandFamilies: [], failed };
5376
+ }
5377
+ function outputIndicatesError(p) {
5378
+ const out = p.output ?? p.result;
5379
+ if (out && typeof out === "object" && !Array.isArray(out)) {
5380
+ const o = out;
5381
+ if (o.success === false || o.is_error === true) return true;
5382
+ }
5383
+ return false;
5384
+ }
4942
5385
  async function parseCodexRollout(ctx) {
4943
5386
  const events = [];
5387
+ const toolCalls = [];
4944
5388
  let chunk = [];
4945
5389
  let emitted = 0;
4946
5390
  const emit = async (e) => {
@@ -4969,6 +5413,9 @@ async function parseCodexRollout(ctx) {
4969
5413
  let cwd = null;
4970
5414
  let model = null;
4971
5415
  let turnIndex = 0;
5416
+ let lastTs = null;
5417
+ const openCalls = /* @__PURE__ */ new Map();
5418
+ let pendingToolAggregate = {};
4972
5419
  for await (const line of rl) {
4973
5420
  const byteLen = Buffer.byteLength(line, "utf8") + 1;
4974
5421
  const offsetAtLineStart = startOffset + bytePos;
@@ -4985,22 +5432,91 @@ async function parseCodexRollout(ctx) {
4985
5432
  skipped += 1;
4986
5433
  continue;
4987
5434
  }
5435
+ const lineTs = obj.timestamp;
5436
+ if (typeof lineTs === "string" && lineTs) lastTs = lineTs;
4988
5437
  if (obj.type === "session_meta") {
4989
5438
  const m = obj;
4990
- sessionId = m.id ?? sessionId;
5439
+ const id = m.id ?? m.payload?.id ?? null;
5440
+ if (id && id !== sessionId) {
5441
+ sessionId = id;
5442
+ pendingToolAggregate = {};
5443
+ openCalls.clear();
5444
+ }
4991
5445
  continue;
4992
5446
  }
4993
5447
  if (obj.type === "turn_context") {
4994
5448
  const t = obj;
4995
- cwd = t.cwd ?? cwd;
4996
- model = t.model ?? model;
5449
+ cwd = t.cwd ?? t.payload?.cwd ?? cwd;
5450
+ model = t.model ?? t.payload?.model ?? model;
5451
+ continue;
5452
+ }
5453
+ if (obj.type === "response_item") {
5454
+ const r = obj;
5455
+ const payload = r.payload;
5456
+ const pt = payload && typeof payload.type === "string" ? payload.type : null;
5457
+ if (pt && TOOL_CALL_PAYLOAD_TYPES.has(pt) && sessionId) {
5458
+ const extracted = extractToolCallPayload(pt, payload);
5459
+ if (!extracted) {
5460
+ skipped += 1;
5461
+ continue;
5462
+ }
5463
+ const ts = r.timestamp ?? lastTs;
5464
+ if (!ts) {
5465
+ skipped += 1;
5466
+ continue;
5467
+ }
5468
+ const srcId = sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart);
5469
+ const { args_hash, signature_hash, args_bytes } = hashArgs(extracted.input);
5470
+ const draft = {
5471
+ external_call_id: (extracted.callId ?? fallbackCallId(srcId, 0)).slice(0, 120),
5472
+ session_id: sessionId,
5473
+ source_event_id: srcId,
5474
+ agent: "codex_cli",
5475
+ server: extracted.server,
5476
+ name: extracted.name,
5477
+ turn_index: turnIndex,
5478
+ // Each response_item line carries exactly one call.
5479
+ call_index: 0,
5480
+ started_at: ts,
5481
+ ended_at: null,
5482
+ status: extracted.failed ? "error" : "unknown",
5483
+ args_hash,
5484
+ signature_hash,
5485
+ args_bytes,
5486
+ result_bytes: 0,
5487
+ model,
5488
+ command_families: extracted.commandFamilies
5489
+ };
5490
+ toolCalls.push(draft);
5491
+ if (extracted.callId) openCalls.set(extracted.callId, draft);
5492
+ const identity = toolIdentity(extracted.server, extracted.name);
5493
+ pendingToolAggregate[identity] = (pendingToolAggregate[identity] ?? 0) + 1;
5494
+ continue;
5495
+ }
5496
+ if (pt && TOOL_CALL_OUTPUT_PAYLOAD_TYPES.has(pt)) {
5497
+ const p = payload;
5498
+ const callId = firstString(p.call_id, p.id);
5499
+ const open2 = callId ? openCalls.get(callId) : void 0;
5500
+ if (!callId || !open2) {
5501
+ skipped += 1;
5502
+ continue;
5503
+ }
5504
+ openCalls.delete(callId);
5505
+ open2.ended_at = r.timestamp ?? lastTs ?? open2.started_at;
5506
+ open2.result_bytes = jsonBytes(p.output ?? p.result);
5507
+ if (open2.status === "unknown") {
5508
+ open2.status = outputIndicatesError(p) ? "error" : "success";
5509
+ }
5510
+ continue;
5511
+ }
5512
+ skipped += 1;
4997
5513
  continue;
4998
5514
  }
4999
5515
  if (obj.type === "event_msg") {
5000
5516
  const m = obj;
5001
5517
  const ts = m.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
5002
5518
  const payload = m.payload;
5003
- if (!payload || !payload.type) {
5519
+ if (!payload?.type) {
5004
5520
  skipped += 1;
5005
5521
  continue;
5006
5522
  }
@@ -5037,11 +5553,12 @@ async function parseCodexRollout(ctx) {
5037
5553
  reasoning: tk.reasoning_output_tokens ?? 0
5038
5554
  },
5039
5555
  duration_ms: null,
5040
- tool_calls: {},
5556
+ tool_calls: pendingToolAggregate,
5041
5557
  files_touched: [],
5042
5558
  source_file: ctx.sourceFile,
5043
5559
  source_byte_offset: offsetAtLineStart
5044
5560
  });
5561
+ pendingToolAggregate = {};
5045
5562
  turnIndex += 1;
5046
5563
  continue;
5047
5564
  }
@@ -5079,16 +5596,39 @@ async function parseCodexRollout(ctx) {
5079
5596
  if (ctx.onEvents && chunk.length > 0) await ctx.onEvents(chunk);
5080
5597
  return {
5081
5598
  events,
5599
+ toolCalls,
5082
5600
  stats: { rawLines, emittedEvents: emitted, skipped },
5083
5601
  sourceFile: ctx.sourceFile
5084
5602
  };
5085
5603
  }
5604
+ var TOOL_CALL_PAYLOAD_TYPES, TOOL_CALL_OUTPUT_PAYLOAD_TYPES, SHELL_TOOL_NAMES2, SHELL_WRAPPER_BINARIES;
5086
5605
  var init_codex = __esm({
5087
5606
  "../../packages/parsers/src/codex/index.ts"() {
5088
5607
  "use strict";
5089
5608
  init_src();
5090
- init_types();
5091
5609
  init_git();
5610
+ init_shell_families();
5611
+ init_tool_hash();
5612
+ init_types();
5613
+ TOOL_CALL_PAYLOAD_TYPES = /* @__PURE__ */ new Set([
5614
+ "function_call",
5615
+ "local_shell_call",
5616
+ "custom_tool_call",
5617
+ "mcp_tool_call"
5618
+ ]);
5619
+ TOOL_CALL_OUTPUT_PAYLOAD_TYPES = /* @__PURE__ */ new Set([
5620
+ "function_call_output",
5621
+ "local_shell_call_output",
5622
+ "custom_tool_call_output",
5623
+ "mcp_tool_call_output"
5624
+ ]);
5625
+ SHELL_TOOL_NAMES2 = /* @__PURE__ */ new Set([
5626
+ "shell",
5627
+ "local_shell_call",
5628
+ "exec_command",
5629
+ "run_terminal_cmd"
5630
+ ]);
5631
+ SHELL_WRAPPER_BINARIES = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish"]);
5092
5632
  }
5093
5633
  });
5094
5634
 
@@ -44392,6 +44932,16 @@ var init_ids2 = __esm({
44392
44932
  });
44393
44933
 
44394
44934
  // ../../packages/companion-core/src/queue/index.ts
44935
+ function attachSegmentIds(calls, segments) {
44936
+ const segmentByEvent = /* @__PURE__ */ new Map();
44937
+ for (const seg of segments) {
44938
+ for (const id of seg.source_event_ids) segmentByEvent.set(id, seg.segment_id);
44939
+ }
44940
+ return calls.map((c) => ({
44941
+ ...c,
44942
+ segment_id: segmentByEvent.get(c.source_event_id) ?? null
44943
+ }));
44944
+ }
44395
44945
  var init_queue = __esm({
44396
44946
  "../../packages/companion-core/src/queue/index.ts"() {
44397
44947
  "use strict";
@@ -44400,21 +44950,6 @@ var init_queue = __esm({
44400
44950
  }
44401
44951
  });
44402
44952
 
44403
- // ../../packages/companion-core/src/pipeline/prompts.ts
44404
- var OLLAMA_CHAT_MODEL, OLLAMA_EMBED_MODEL, SUMMARISER_SYSTEM_PROMPT, SUMMARISER_MAX_TOKENS, SUMMARISER_TEMPERATURE, QWEN_CHARS_PER_TOKEN, ABSTRACT_OUTPUT_MAX_CHARS;
44405
- var init_prompts = __esm({
44406
- "../../packages/companion-core/src/pipeline/prompts.ts"() {
44407
- "use strict";
44408
- OLLAMA_CHAT_MODEL = "qwen3:4b";
44409
- OLLAMA_EMBED_MODEL = "bge-small-en-v1.5";
44410
- SUMMARISER_SYSTEM_PROMPT = "You summarise an AI coding session in ONE sentence, \u2264 240 characters. If the user message includes sampled conversation excerpts, base your summary on what the developer was actually working on (the substance \u2014 what was being built, debugged, refactored, or designed). If only metadata is given, paraphrase the metadata. Never quote the excerpts verbatim. No PII, no code literals, no file paths, no API keys. Reply with only the sentence.";
44411
- SUMMARISER_MAX_TOKENS = 120;
44412
- SUMMARISER_TEMPERATURE = 0.2;
44413
- QWEN_CHARS_PER_TOKEN = 3.3;
44414
- ABSTRACT_OUTPUT_MAX_CHARS = 240;
44415
- }
44416
- });
44417
-
44418
44953
  // ../../packages/companion-core/src/pipeline/cognition.ts
44419
44954
  function buildCognitionUserPrompt(abstract) {
44420
44955
  return `Summary: "${abstract.replace(/\s+/g, " ").trim().slice(0, 480)}"
@@ -44497,6 +45032,21 @@ var init_cognition = __esm({
44497
45032
  }
44498
45033
  });
44499
45034
 
45035
+ // ../../packages/companion-core/src/pipeline/prompts.ts
45036
+ var OLLAMA_CHAT_MODEL, OLLAMA_EMBED_MODEL, SUMMARISER_SYSTEM_PROMPT, SUMMARISER_MAX_TOKENS, SUMMARISER_TEMPERATURE, QWEN_CHARS_PER_TOKEN, ABSTRACT_OUTPUT_MAX_CHARS;
45037
+ var init_prompts = __esm({
45038
+ "../../packages/companion-core/src/pipeline/prompts.ts"() {
45039
+ "use strict";
45040
+ OLLAMA_CHAT_MODEL = "qwen3:4b";
45041
+ OLLAMA_EMBED_MODEL = "bge-small-en-v1.5";
45042
+ SUMMARISER_SYSTEM_PROMPT = "You summarise an AI coding session in ONE sentence, \u2264 240 characters. If the user message includes sampled conversation excerpts, base your summary on what the developer was actually working on (the substance \u2014 what was being built, debugged, refactored, or designed). If only metadata is given, paraphrase the metadata. Never quote the excerpts verbatim. No PII, no code literals, no file paths, no API keys. Reply with only the sentence.";
45043
+ SUMMARISER_MAX_TOKENS = 120;
45044
+ SUMMARISER_TEMPERATURE = 0.2;
45045
+ QWEN_CHARS_PER_TOKEN = 3.3;
45046
+ ABSTRACT_OUTPUT_MAX_CHARS = 240;
45047
+ }
45048
+ });
45049
+
44500
45050
  // ../../packages/companion-core/src/pipeline/title.ts
44501
45051
  function buildTitleUserPrompt(input) {
44502
45052
  const lines = input.abstracts.map(
@@ -44674,9 +45224,7 @@ async function buildForOneSession(sessionId, events, adapters2, onSlice) {
44674
45224
  } catch (err) {
44675
45225
  failed += 1;
44676
45226
  lastError = err instanceof Error ? err.message : String(err);
44677
- console.warn(
44678
- `[modelstat] slice failed in session ${sessionId}: ${lastError}`
44679
- );
45227
+ console.warn(`[modelstat] slice failed in session ${sessionId}: ${lastError}`);
44680
45228
  }
44681
45229
  }
44682
45230
  if (failed > 0) {
@@ -44781,6 +45329,24 @@ Write the SHORTEST keyword-dense paragraph (1-3 sentences, \u2264${ABSTRACT_OUTP
44781
45329
  for (const c of [...components].slice(0, 8)) {
44782
45330
  tags.push({ root_key: "components", name: c, confidence: 0.6 });
44783
45331
  }
45332
+ const toolCallCounts = /* @__PURE__ */ new Map();
45333
+ let toolCallTotal = 0;
45334
+ for (const ev of slice) {
45335
+ for (const [identity, n] of Object.entries(ev.tool_calls ?? {})) {
45336
+ if (!(n > 0)) continue;
45337
+ toolCallCounts.set(identity, (toolCallCounts.get(identity) ?? 0) + n);
45338
+ toolCallTotal += n;
45339
+ }
45340
+ }
45341
+ const topToolCalls = [...toolCallCounts.entries()].filter(([identity]) => identity.length <= 120).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 8);
45342
+ for (const [identity, count] of topToolCalls) {
45343
+ const share = Math.round(count / toolCallTotal * 100) / 100;
45344
+ tags.push({
45345
+ root_key: "tool_calls",
45346
+ name: identity,
45347
+ confidence: Math.min(1, Math.max(0.05, share))
45348
+ });
45349
+ }
44784
45350
  let segmentEmbedding;
44785
45351
  try {
44786
45352
  const embedded = await adapters2.embed(redacted.text.slice(0, ABSTRACT_MAX_CHARS));
@@ -44868,13 +45434,13 @@ var SEGMENT_TIME_GAP_MS, SEGMENT_TOPIC_THRESHOLD, SEGMENT_MAX_TURNS, SEGMENT_MAX
44868
45434
  var init_pipeline = __esm({
44869
45435
  "../../packages/companion-core/src/pipeline/index.ts"() {
44870
45436
  "use strict";
44871
- init_redact();
44872
45437
  init_ids();
44873
- init_prompts();
44874
- init_cognition();
44875
45438
  init_redact();
45439
+ init_cognition();
44876
45440
  init_prompts();
45441
+ init_redact();
44877
45442
  init_cognition();
45443
+ init_prompts();
44878
45444
  init_title();
44879
45445
  SEGMENT_TIME_GAP_MS = 15 * 6e4;
44880
45446
  SEGMENT_TOPIC_THRESHOLD = 0.35;
@@ -45698,7 +46264,8 @@ async function scanAll(cb = {}) {
45698
46264
  jobs.push({
45699
46265
  path: full,
45700
46266
  parse: async (sink2) => {
45701
- await parseClaudeCodeJsonl({ deviceId, sourceFile: full, onEvents: sink2 });
46267
+ const r = await parseClaudeCodeJsonl({ deviceId, sourceFile: full, onEvents: sink2 });
46268
+ return { toolCalls: r.toolCalls ?? [] };
45702
46269
  }
45703
46270
  });
45704
46271
  }
@@ -45721,7 +46288,8 @@ async function scanAll(cb = {}) {
45721
46288
  jobs.push({
45722
46289
  path: full,
45723
46290
  parse: async (sink2) => {
45724
- await parseCodexRollout({ deviceId, sourceFile: full, onEvents: sink2 });
46291
+ const r = await parseCodexRollout({ deviceId, sourceFile: full, onEvents: sink2 });
46292
+ return { toolCalls: r.toolCalls ?? [] };
45725
46293
  }
45726
46294
  });
45727
46295
  }
@@ -45737,10 +46305,11 @@ async function scanAll(cb = {}) {
45737
46305
  let eventsUploaded = 0;
45738
46306
  let segmentsUploaded = 0;
45739
46307
  let buffer = [];
46308
+ let toolCallBuffer = [];
45740
46309
  let pendingCursors = [];
45741
46310
  const runSegmentsBySession = /* @__PURE__ */ new Map();
45742
46311
  async function flushBatch() {
45743
- if (!buffer.length) return;
46312
+ if (!buffer.length && !toolCallBuffer.length) return;
45744
46313
  const events = buffer.map(withNonNullTokens);
45745
46314
  const segments = await buildSegments(events, cb.onProgress);
45746
46315
  for (const seg of segments) {
@@ -45764,6 +46333,10 @@ async function scanAll(cb = {}) {
45764
46333
  agent_version: AGENT_VERSION,
45765
46334
  events,
45766
46335
  segments,
46336
+ // Per-call tool invocations: now that segments exist, attribute
46337
+ // each call to the segment covering its source event (null when
46338
+ // no segment covers it — e.g. codex response_item anchors).
46339
+ tool_calls: attachSegmentIds(toolCallBuffer, segments),
45767
46340
  ...Object.keys(sessionTitles).length ? { session_titles: sessionTitles } : {}
45768
46341
  };
45769
46342
  cb.onUpload?.({ events: events.length, segments: segments.length });
@@ -45774,6 +46347,7 @@ async function scanAll(cb = {}) {
45774
46347
  for (const pc of pendingCursors) state.setCursor(pc.path, pc.cs);
45775
46348
  pendingCursors = [];
45776
46349
  buffer = [];
46350
+ toolCallBuffer = [];
45777
46351
  cb.onUploaded?.({ events: res.accepted, segments: segments.length });
45778
46352
  }
45779
46353
  const sink = async (events) => {
@@ -45787,6 +46361,12 @@ async function scanAll(cb = {}) {
45787
46361
  );
45788
46362
  }
45789
46363
  };
46364
+ const bufferToolCalls = async (calls) => {
46365
+ for (const c of calls) {
46366
+ if (toolCallBuffer.length >= BATCH_MAX_TOOL_CALLS) await flushBatch();
46367
+ toolCallBuffer.push(c);
46368
+ }
46369
+ };
45790
46370
  for (let i = 0; i < jobs.length; i++) {
45791
46371
  const job = jobs[i];
45792
46372
  cb.onFile?.(job.path, i, jobs.length);
@@ -45798,7 +46378,8 @@ async function scanAll(cb = {}) {
45798
46378
  }
45799
46379
  filesScanned += 1;
45800
46380
  try {
45801
- await job.parse(sink);
46381
+ const r = await job.parse(sink);
46382
+ await bufferToolCalls(r.toolCalls);
45802
46383
  if (cs) pendingCursors.push({ path: job.path, cs });
45803
46384
  } catch (e) {
45804
46385
  console.warn(` ! parse failed for ${job.path}:`, e.message);
@@ -45807,17 +46388,19 @@ async function scanAll(cb = {}) {
45807
46388
  await flushBatch();
45808
46389
  return { filesScanned, filesUnchanged, batchesUploaded, eventsUploaded, segmentsUploaded };
45809
46390
  }
45810
- var AGENT_VERSION, BATCH_MAX_EVENTS, BATCH_BUFFER_HARD_CAP, ZERO_TOKENS;
46391
+ var AGENT_VERSION, BATCH_MAX_EVENTS, BATCH_MAX_TOOL_CALLS, BATCH_BUFFER_HARD_CAP, ZERO_TOKENS;
45811
46392
  var init_scan = __esm({
45812
46393
  "src/scan.ts"() {
45813
46394
  "use strict";
45814
- init_src2();
45815
46395
  init_src3();
45816
- init_pipeline2();
45817
- init_config2();
46396
+ init_queue();
46397
+ init_src2();
45818
46398
  init_api();
45819
- AGENT_VERSION = true ? "agent-0.0.46" : "agent-dev";
46399
+ init_config2();
46400
+ init_pipeline2();
46401
+ AGENT_VERSION = true ? "agent-0.0.47" : "agent-dev";
45820
46402
  BATCH_MAX_EVENTS = 2e3;
46403
+ BATCH_MAX_TOOL_CALLS = 2e4;
45821
46404
  BATCH_BUFFER_HARD_CAP = BATCH_MAX_EVENTS * 2;
45822
46405
  ZERO_TOKENS = {
45823
46406
  input: 0,
@@ -46028,7 +46611,7 @@ var PROCESSING_VERSION;
46028
46611
  var init_processing_version = __esm({
46029
46612
  "src/processing-version.ts"() {
46030
46613
  "use strict";
46031
- PROCESSING_VERSION = 4;
46614
+ PROCESSING_VERSION = 5;
46032
46615
  }
46033
46616
  });
46034
46617
 
@@ -48075,7 +48658,7 @@ var init_daemon = __esm({
48075
48658
  init_lock();
48076
48659
  init_scan();
48077
48660
  init_single_flight();
48078
- AGENT_VERSION2 = true ? "agent-0.0.46" : "agent-dev";
48661
+ AGENT_VERSION2 = true ? "agent-0.0.47" : "agent-dev";
48079
48662
  HEARTBEAT_INTERVAL_MS = 1e4;
48080
48663
  SCAN_INTERVAL_MS = 5 * 60 * 1e3;
48081
48664
  DISCOVERY_INTERVAL_MS = 6e4;
@@ -48640,7 +49223,7 @@ function tryOpenBrowser(url) {
48640
49223
  return false;
48641
49224
  }
48642
49225
  }
48643
- var AGENT_VERSION3 = true ? "agent-0.0.46" : "agent-dev";
49226
+ var AGENT_VERSION3 = true ? "agent-0.0.47" : "agent-dev";
48644
49227
  function osFamily() {
48645
49228
  const p = platform4();
48646
49229
  if (p === "darwin") return "macos";