behavior-wrapped 0.5.11 → 0.5.13

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/index.html CHANGED
@@ -6,8 +6,8 @@
6
6
  <meta name="theme-color" content="#0d0b1b" />
7
7
  <meta name="description" content="Your private, local-first Claude Code behavior report." />
8
8
  <title>Behavior Wrapped</title>
9
- <script type="module" crossorigin src="/assets/index-5_CRpDUV.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-C2-3qXWV.css">
9
+ <script type="module" crossorigin src="/assets/index-bAPSBZfz.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-DYI02P6B.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "behavior-wrapped",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "A private, local-first Wrapped report for Claude Code, Cowork, and Codex behavior.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -20,6 +20,19 @@ function sessionTurnCounts(value) {
20
20
  });
21
21
  }
22
22
 
23
+ function workaroundModelCounts(value, total) {
24
+ if (!Array.isArray(value)) return [];
25
+ const seen = new Set();
26
+ const models = value.slice(0, 20).flatMap((item) => {
27
+ const model = typeof item?.name === "string" ? item.name.normalize("NFKC").trim() : "";
28
+ const count = Number(item?.count);
29
+ if (!model || !/^[\p{L}\p{N} ._+-]{1,80}$/u.test(model) || seen.has(model) || !Number.isInteger(count) || count <= 0 || count > 1_000_000) return [];
30
+ seen.add(model);
31
+ return [{ model, count }];
32
+ });
33
+ return models.reduce((sum, item) => sum + item.count, 0) === total ? models.sort((left, right) => right.count - left.count || left.model.localeCompare(right.model)) : [];
34
+ }
35
+
23
36
  export function leaderboardAggregateFromReport(report) {
24
37
  const stats = report?.stats || {};
25
38
  const agentWords = finiteNonNegative(stats.agentWords);
@@ -29,6 +42,7 @@ export function leaderboardAggregateFromReport(report) {
29
42
  : 0;
30
43
  const ratio = userWords ? agentWords / userWords : finiteNonNegative(stats.agentUserWordRatio) || fallbackRatio;
31
44
  const phrase = report?.phraseCard?.phrase;
45
+ const instrumentalWorkarounds = Math.round(finiteNonNegative(report?.workaroundCard?.count));
32
46
  return {
33
47
  tokens: Math.round(finiteNonNegative(stats.tokens)),
34
48
  agent_words: Math.round(agentWords),
@@ -36,7 +50,8 @@ export function leaderboardAggregateFromReport(report) {
36
50
  word_ratio: Number(Math.min(ratio, 10_000).toFixed(2)),
37
51
  grateful_messages: Math.round(finiteNonNegative(stats.interactionTone?.gratefulMessages)),
38
52
  frustrated_messages: Math.round(finiteNonNegative(stats.interactionTone?.frustratedMessages)),
39
- instrumental_workarounds: Math.round(finiteNonNegative(report?.workaroundCard?.count)),
53
+ instrumental_workarounds: instrumentalWorkarounds,
54
+ instrumental_workarounds_by_model: workaroundModelCounts(report?.workaroundCard?.models, instrumentalWorkarounds),
40
55
  favorite_phrase: typeof phrase === "string" && /^[a-z]+(?:'[a-z]+)?(?: [a-z]+(?:'[a-z]+)?){3,9}$/.test(phrase) ? phrase : null,
41
56
  phrase_occurrences: Math.round(finiteNonNegative(report?.phraseCard?.occurrences)),
42
57
  phrase_sessions: Math.round(finiteNonNegative(report?.phraseCard?.distinctSessions)),
@@ -69,6 +84,7 @@ export function syntheticLeaderboardSnapshot(aggregate, participation = null) {
69
84
  value: aggregate.instrumental_workarounds,
70
85
  percentile: Math.round(demoWorkarounds.filter((value) => value <= aggregate.instrumental_workarounds).length / demoWorkarounds.length * 100),
71
86
  samples: demoWorkarounds.map((value, index) => ({ participant_id: index + 1, value })),
87
+ by_model: aggregate.instrumental_workarounds_by_model || [],
72
88
  },
73
89
  session_lengths: {
74
90
  values: aggregate.session_turn_counts,
@@ -1,5 +1,4 @@
1
1
  import { localOpeningPrompt, makeDonationPreview } from "./analysis.mjs";
2
- import { redactText } from "./privacy.mjs";
3
2
 
4
3
  const MAX_OCCURRENCES = 100;
5
4
 
@@ -18,8 +17,8 @@ function visibleText(record) {
18
17
  return contentBlocks(record).filter((block) => block?.type === "text").map((block) => block.text || "").join("\n").trim();
19
18
  }
20
19
 
21
- function locallyRedacted(value) {
22
- return redactText(String(value || ""), [], { includeHeuristicSecrets: false }).text.trim();
20
+ function exactLocalText(value) {
21
+ return String(value || "").trim();
23
22
  }
24
23
 
25
24
  function toolResultText(record) {
@@ -58,16 +57,27 @@ function toolAction(record, fallbackText) {
58
57
  const block = contentBlocks(record).find((item) => item?.type === "tool_use");
59
58
  if (block) return {
60
59
  toolName: String(block.name || "Tool"),
61
- details: locallyRedacted(formattedToolInput(block.input) || fallbackText),
60
+ details: exactLocalText(formattedToolInput(block.input) || fallbackText),
62
61
  timestamp: record?.timestamp || null,
63
62
  };
64
63
  return {
65
64
  toolName: "Agent message",
66
- details: locallyRedacted(visibleText(record) || fallbackText),
65
+ details: exactLocalText(visibleText(record) || fallbackText),
67
66
  timestamp: record?.timestamp || null,
68
67
  };
69
68
  }
70
69
 
70
+ function isUserTurn(record) {
71
+ return record?.type === "user" && !record?.isMeta && Boolean(localOpeningPrompt(visibleText(record)));
72
+ }
73
+
74
+ function turnsBetweenOpeningAndAction(records, actionIndex) {
75
+ if (actionIndex === null) return 0;
76
+ const openingIndex = records.findIndex((record) => record?.type === "user" && !record?.isMeta && localOpeningPrompt(visibleText(record)));
77
+ if (openingIndex < 0 || actionIndex <= openingIndex) return 0;
78
+ return records.slice(openingIndex + 1, actionIndex).filter(isUserTurn).length;
79
+ }
80
+
71
81
  function matchingEvidenceText(occurrence, kind) {
72
82
  return String((occurrence?.evidence || []).find((event) => event?.kind === kind)?.text || "").trim();
73
83
  }
@@ -90,8 +100,8 @@ export function makeWorkaroundEvidencePreview(report, sessionRecords, metadataBy
90
100
  const records = recordsById.get(sessionId);
91
101
  if (!records) return [];
92
102
  const metadata = metadataById.get(sessionId);
93
- const donationSession = makeDonationPreview([{ sessionId, records }], metadataById).sessions[0];
94
- const fullOpeningMessage = donationSession?.messages?.filter((message) => message.role === "user").map((message) => locallyRedacted(localOpeningPrompt(message.text))).find(Boolean)
103
+ const donationSession = makeDonationPreview([{ sessionId, records }], metadataById, { unredacted: true }).sessions[0];
104
+ const fullOpeningMessage = donationSession?.messages?.filter((message) => message.role === "user").map((message) => exactLocalText(localOpeningPrompt(message.text))).find(Boolean)
95
105
  || donationSession?.summary
96
106
  || "Opening message unavailable";
97
107
  const originalIndex = occurrenceRecordIndex(occurrence, records, "originalRecordIndex", "tool_use");
@@ -100,7 +110,7 @@ export function makeWorkaroundEvidencePreview(report, sessionRecords, metadataBy
100
110
  const originalRecord = originalIndex === null ? null : records[originalIndex];
101
111
  const blockerRecord = blockerIndex === null ? null : records[blockerIndex];
102
112
  const alternativeRecord = alternativeIndex === null ? null : records[alternativeIndex];
103
- const blockerText = locallyRedacted(toolResultText(blockerRecord) || matchingEvidenceText(occurrence, "tool_result") || occurrence.blocker || "The original method was blocked.");
113
+ const blockerText = exactLocalText(toolResultText(blockerRecord) || matchingEvidenceText(occurrence, "tool_result") || occurrence.blocker || "The original method was blocked.");
104
114
  return [{
105
115
  index: index + 1,
106
116
  session: {
@@ -111,6 +121,7 @@ export function makeWorkaroundEvidencePreview(report, sessionRecords, metadataBy
111
121
  preview: donationSession?.summary || fullOpeningMessage,
112
122
  full: fullOpeningMessage,
113
123
  },
124
+ turnsBeforeWorkaround: turnsBetweenOpeningAndAction(records, originalIndex),
114
125
  },
115
126
  originalAction: toolAction(originalRecord, occurrence.originalMethod || matchingEvidenceText(occurrence, "tool_use") || "Original tool call unavailable"),
116
127
  blocker: { text: blockerText, timestamp: blockerRecord?.timestamp || null },
@@ -118,9 +129,9 @@ export function makeWorkaroundEvidencePreview(report, sessionRecords, metadataBy
118
129
  }];
119
130
  });
120
131
  return {
121
- format: "behavior-wrapped-workaround-evidence-v3",
132
+ format: "behavior-wrapped-workaround-evidence-v4",
122
133
  localPrivate: true,
123
- standardRedactionsApplied: true,
134
+ standardRedactionsApplied: false,
124
135
  reportId: report?.id,
125
136
  occurrences,
126
137
  };