behavior-wrapped 0.5.10 → 0.5.12
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/assets/{index-DzSM-hPQ.js → index-DZsbqTc5.js} +2 -3
- package/dist/assets/{index-COnYUt6B.css → index-QstqPzUh.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server/analysis.mjs +1 -1
- package/server/discovery.mjs +8 -7
- package/server/launcher.mjs +3 -3
- package/server/workaround-evidence.mjs +65 -52
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-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-DZsbqTc5.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-QstqPzUh.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
package/package.json
CHANGED
package/server/analysis.mjs
CHANGED
|
@@ -479,7 +479,7 @@ function donationRedactionInventory(detections) {
|
|
|
479
479
|
}).sort((left, right) => right.count - left.count || left.label.localeCompare(right.label));
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
function localOpeningPrompt(value) {
|
|
482
|
+
export function localOpeningPrompt(value) {
|
|
483
483
|
let prompt = String(value || "");
|
|
484
484
|
const explicitRequest = prompt.match(/(?:^|\n)## My request:\s*([\s\S]*)$/i);
|
|
485
485
|
if (explicitRequest) prompt = explicitRequest[1];
|
package/server/discovery.mjs
CHANGED
|
@@ -414,7 +414,7 @@ function restrictionErrorSummary(value) {
|
|
|
414
414
|
return rules.find(([pattern]) => pattern.test(text))?.[1] || null;
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
function normalizeCodexRecords(records) {
|
|
417
|
+
function normalizeCodexRecords(records, { includePrivateToolDetails = false } = {}) {
|
|
418
418
|
const normalized = [];
|
|
419
419
|
const firstDeclaredModel = records.find((record) => record?.type === "turn_context" && typeof record?.payload?.model === "string" && record.payload.model)?.payload.model;
|
|
420
420
|
const isForkedSession = records.some((record) => record?.type === "session_meta" && (record?.payload?.forked_from_id || record?.payload?.parent_thread_id));
|
|
@@ -436,7 +436,8 @@ function normalizeCodexRecords(records) {
|
|
|
436
436
|
const semantics = semanticToolUse({ name: toolName, argumentsValue: payload.arguments, inputValue: payload.input });
|
|
437
437
|
if (typeof payload.call_id === "string" && payload.call_id) pendingTools.set(payload.call_id, semantics);
|
|
438
438
|
else anonymousTools.push(semantics);
|
|
439
|
-
|
|
439
|
+
const privateInput = payload.arguments ?? payload.input;
|
|
440
|
+
normalized.push({ type: "assistant", timestamp: record.timestamp, message: { model: currentModel, content: [{ type: "tool_use", name: toolName, action_hint: semantics.action, method_hint: semantics.method, ...(includePrivateToolDetails && privateInput !== undefined ? { input: privateInput } : {}) }] } });
|
|
440
441
|
} else if (record.type === "response_item" && (payload.type === "function_call_output" || payload.type === "custom_tool_call_output")) {
|
|
441
442
|
const callId = typeof payload.call_id === "string" && payload.call_id ? payload.call_id : null;
|
|
442
443
|
const semantics = callId ? pendingTools.get(callId) : anonymousTools.shift();
|
|
@@ -446,7 +447,7 @@ function normalizeCodexRecords(records) {
|
|
|
446
447
|
const unwrappedFailure = !status.wrapped && /(?:^|\b)(?:error|failed|failure)(?:\b|:)/i.test(output);
|
|
447
448
|
const canSummarizeRestriction = status.failed || (!status.wrapped && restrictionEligibleActions.has(semantics?.action));
|
|
448
449
|
const errorSummary = canSummarizeRestriction ? restrictionErrorSummary(output) : null;
|
|
449
|
-
normalized.push({ type: "user", isMeta: true, timestamp: record.timestamp, message: { content: [{ type: "tool_result", is_error: Boolean(errorSummary) || status.failed || unwrappedFailure, error_summary: errorSummary }] } });
|
|
450
|
+
normalized.push({ type: "user", isMeta: true, timestamp: record.timestamp, message: { content: [{ type: "tool_result", is_error: Boolean(errorSummary) || status.failed || unwrappedFailure, error_summary: errorSummary, ...(includePrivateToolDetails && output ? { content: output } : {}) }] } });
|
|
450
451
|
} else if (record.type === "event_msg" && payload.type === "turn_aborted") {
|
|
451
452
|
normalized.push({ type: "system", subtype: "interrupt", timestamp: record.timestamp, content: "interrupt" });
|
|
452
453
|
} else if (record.type === "event_msg" && payload.type === "token_count" && payload.info?.total_token_usage) {
|
|
@@ -506,16 +507,16 @@ function normalizeCoworkRecords(records) {
|
|
|
506
507
|
return normalized;
|
|
507
508
|
}
|
|
508
509
|
|
|
509
|
-
export function readRecords(file, agent = "claude") {
|
|
510
|
+
export function readRecords(file, agent = "claude", options = {}) {
|
|
510
511
|
const { records } = recordsFromFileSync(file);
|
|
511
|
-
if (agent === "codex") return normalizeCodexRecords(records);
|
|
512
|
+
if (agent === "codex") return normalizeCodexRecords(records, options);
|
|
512
513
|
if (agent === "cowork") return normalizeCoworkRecords(records);
|
|
513
514
|
return records;
|
|
514
515
|
}
|
|
515
516
|
|
|
516
|
-
export async function readRecordsAsync(file, agent = "claude") {
|
|
517
|
+
export async function readRecordsAsync(file, agent = "claude", options = {}) {
|
|
517
518
|
const { records } = await recordsFromFile(file);
|
|
518
|
-
if (agent === "codex") return normalizeCodexRecords(records);
|
|
519
|
+
if (agent === "codex") return normalizeCodexRecords(records, options);
|
|
519
520
|
if (agent === "cowork") return normalizeCoworkRecords(records);
|
|
520
521
|
return records;
|
|
521
522
|
}
|
package/server/launcher.mjs
CHANGED
|
@@ -67,11 +67,11 @@ function readBody(request, maximumBytes = 1_000_000) {
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
async function chosenRecords(ids) {
|
|
70
|
+
async function chosenRecords(ids, options = {}) {
|
|
71
71
|
const selected = [];
|
|
72
72
|
for (const id of ids) {
|
|
73
73
|
const session = catalog.index.get(id);
|
|
74
|
-
if (session) selected.push({ sessionId: id, agent: session.agent, records: await readRecordsAsync(session.file, session.agent) });
|
|
74
|
+
if (session) selected.push({ sessionId: id, agent: session.agent, records: await readRecordsAsync(session.file, session.agent, options) });
|
|
75
75
|
}
|
|
76
76
|
return selected;
|
|
77
77
|
}
|
|
@@ -118,7 +118,7 @@ const server = http.createServer(async (request, response) => {
|
|
|
118
118
|
if (!report) return json(response, 404, { error: "Saved report not found" });
|
|
119
119
|
const allowed = new Set(report.sessionIds || []);
|
|
120
120
|
const ids = [...new Set((report.workaroundReview?.occurrences || []).map((occurrence) => occurrence?.location?.sessionId).filter((id) => allowed.has(id) && catalog.index.has(id)))].slice(0, 100);
|
|
121
|
-
const records = await chosenRecords(ids);
|
|
121
|
+
const records = await chosenRecords(ids, { includePrivateToolDetails: true });
|
|
122
122
|
const labels = new Map(publicCatalog().sessions.map((session) => [session.id, session]));
|
|
123
123
|
return json(response, 200, makeWorkaroundEvidencePreview(report, records, labels));
|
|
124
124
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { makeDonationPreview } from "./analysis.mjs";
|
|
2
|
-
import { redactText } from "./privacy.mjs";
|
|
1
|
+
import { localOpeningPrompt, makeDonationPreview } from "./analysis.mjs";
|
|
3
2
|
|
|
4
|
-
const DEFAULT_CONTEXT_TURNS = 2;
|
|
5
3
|
const MAX_OCCURRENCES = 100;
|
|
6
4
|
|
|
7
5
|
function finiteRecordIndex(value, length) {
|
|
@@ -19,6 +17,10 @@ function visibleText(record) {
|
|
|
19
17
|
return contentBlocks(record).filter((block) => block?.type === "text").map((block) => block.text || "").join("\n").trim();
|
|
20
18
|
}
|
|
21
19
|
|
|
20
|
+
function exactLocalText(value) {
|
|
21
|
+
return String(value || "").trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
22
24
|
function toolResultText(record) {
|
|
23
25
|
return contentBlocks(record).filter((block) => block?.type === "tool_result").map((block) => {
|
|
24
26
|
const content = typeof block.content === "string"
|
|
@@ -26,19 +28,54 @@ function toolResultText(record) {
|
|
|
26
28
|
: Array.isArray(block.content)
|
|
27
29
|
? block.content.map((part) => typeof part === "string" ? part : part?.text || "").filter(Boolean).join("\n")
|
|
28
30
|
: "";
|
|
29
|
-
return
|
|
31
|
+
return content || block.error_summary || "";
|
|
30
32
|
}).filter(Boolean).join("\n").trim();
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
function
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
function parsedJson(value) {
|
|
36
|
+
try { return JSON.parse(value); } catch { return null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function exactCommandFromWrapper(value) {
|
|
40
|
+
const match = String(value).match(/(?:\b(?:cmd|command)|"(?:cmd|command)")\s*:\s*("(?:\\.|[^"\\])*")/s);
|
|
41
|
+
return match ? parsedJson(match[1]) : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function formattedToolInput(value) {
|
|
45
|
+
if (value && typeof value === "object") {
|
|
46
|
+
const direct = value.cmd ?? value.command;
|
|
47
|
+
return typeof direct === "string" && direct.trim() ? direct.trim() : JSON.stringify(value, null, 2);
|
|
48
|
+
}
|
|
49
|
+
const text = String(value || "").trim();
|
|
50
|
+
if (!text) return "";
|
|
51
|
+
const decoded = parsedJson(text);
|
|
52
|
+
if (decoded && typeof decoded === "object") return formattedToolInput(decoded);
|
|
53
|
+
return exactCommandFromWrapper(text) || text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toolAction(record, fallbackText) {
|
|
57
|
+
const block = contentBlocks(record).find((item) => item?.type === "tool_use");
|
|
58
|
+
if (block) return {
|
|
59
|
+
toolName: String(block.name || "Tool"),
|
|
60
|
+
details: exactLocalText(formattedToolInput(block.input) || fallbackText),
|
|
61
|
+
timestamp: record?.timestamp || null,
|
|
62
|
+
};
|
|
63
|
+
return {
|
|
64
|
+
toolName: "Agent message",
|
|
65
|
+
details: exactLocalText(visibleText(record) || fallbackText),
|
|
66
|
+
timestamp: record?.timestamp || null,
|
|
67
|
+
};
|
|
38
68
|
}
|
|
39
69
|
|
|
40
|
-
function
|
|
41
|
-
return
|
|
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;
|
|
42
79
|
}
|
|
43
80
|
|
|
44
81
|
function matchingEvidenceText(occurrence, kind) {
|
|
@@ -56,69 +93,45 @@ function occurrenceRecordIndex(occurrence, records, field, kind) {
|
|
|
56
93
|
return finiteRecordIndex(occurrence?.location?.[field], records.length) ?? fallbackRecordIndex(occurrence, records, kind);
|
|
57
94
|
}
|
|
58
95
|
|
|
59
|
-
function
|
|
60
|
-
const text = locallyRedacted(visibleText(record));
|
|
61
|
-
if (!text) return null;
|
|
62
|
-
return { role: record.type, timestamp: record.timestamp || null, text, kind };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function contextAroundBlocker(records, blockerIndex, alternativeIndex, contextTurns, blockerText) {
|
|
66
|
-
if (blockerIndex === null) return [];
|
|
67
|
-
const conversationIndexes = records.flatMap((record, index) => !record?.isMeta && (record?.type === "user" || record?.type === "assistant") && visibleText(record) ? [index] : []);
|
|
68
|
-
const selectedIndexes = [
|
|
69
|
-
...conversationIndexes.filter((index) => index < blockerIndex).slice(-contextTurns),
|
|
70
|
-
blockerIndex,
|
|
71
|
-
...conversationIndexes.filter((index) => index > blockerIndex).slice(0, contextTurns),
|
|
72
|
-
];
|
|
73
|
-
return selectedIndexes.flatMap((recordIndex) => {
|
|
74
|
-
if (recordIndex === blockerIndex) return [{ role: "tool", timestamp: records[recordIndex]?.timestamp || null, text: blockerText, kind: "blocker" }];
|
|
75
|
-
const message = transcriptMessage(records[recordIndex], recordIndex === alternativeIndex ? "workaround" : "context");
|
|
76
|
-
return message ? [message] : [];
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function safeFallbackMessages(occurrence) {
|
|
81
|
-
return (occurrence?.evidence || []).flatMap((event) => {
|
|
82
|
-
if (!["user", "assistant", "tool"].includes(event?.role)) return [];
|
|
83
|
-
const text = locallyRedacted(event?.text);
|
|
84
|
-
return text ? [{ role: event.role, timestamp: event.timestamp || null, text, kind: event.kind === "tool_result" ? "blocker" : "context" }] : [];
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function makeWorkaroundEvidencePreview(report, sessionRecords, metadataById, { contextTurns = DEFAULT_CONTEXT_TURNS } = {}) {
|
|
89
|
-
const boundedContext = Number.isInteger(contextTurns) ? Math.min(4, Math.max(1, contextTurns)) : DEFAULT_CONTEXT_TURNS;
|
|
96
|
+
export function makeWorkaroundEvidencePreview(report, sessionRecords, metadataById) {
|
|
90
97
|
const recordsById = new Map(sessionRecords.map((session) => [session.sessionId, session.records]));
|
|
91
98
|
const occurrences = (report?.workaroundReview?.occurrences || []).slice(0, MAX_OCCURRENCES).flatMap((occurrence, index) => {
|
|
92
99
|
const sessionId = occurrence?.location?.sessionId;
|
|
93
100
|
const records = recordsById.get(sessionId);
|
|
94
101
|
if (!records) return [];
|
|
95
102
|
const metadata = metadataById.get(sessionId);
|
|
96
|
-
const donationSession = makeDonationPreview([{ sessionId, records }], metadataById).sessions[0];
|
|
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)
|
|
105
|
+
|| donationSession?.summary
|
|
106
|
+
|| "Opening message unavailable";
|
|
107
|
+
const originalIndex = occurrenceRecordIndex(occurrence, records, "originalRecordIndex", "tool_use");
|
|
97
108
|
const blockerIndex = occurrenceRecordIndex(occurrence, records, "blockerRecordIndex", "tool_result");
|
|
98
109
|
const alternativeIndex = occurrenceRecordIndex(occurrence, records, "alternativeRecordIndex", ["assistant_text", "tool_use"]);
|
|
110
|
+
const originalRecord = originalIndex === null ? null : records[originalIndex];
|
|
99
111
|
const blockerRecord = blockerIndex === null ? null : records[blockerIndex];
|
|
100
112
|
const alternativeRecord = alternativeIndex === null ? null : records[alternativeIndex];
|
|
101
|
-
const blockerText =
|
|
102
|
-
const workaroundText = locallyRedacted(visibleText(alternativeRecord) || occurrence.alternativeMethod || toolUseText(alternativeRecord) || matchingEvidenceText(occurrence, "assistant_text") || "The agent tried another method.");
|
|
103
|
-
const context = contextAroundBlocker(records, blockerIndex, alternativeIndex, boundedContext, blockerText);
|
|
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: {
|
|
107
117
|
label: metadata?.label || `Session ${index + 1}`,
|
|
108
118
|
agentName: metadata?.agentName || "AI agent",
|
|
109
119
|
startedAt: metadata?.startedAt || records.find((record) => record?.timestamp)?.timestamp || null,
|
|
110
|
-
openingMessage:
|
|
120
|
+
openingMessage: {
|
|
121
|
+
preview: donationSession?.summary || fullOpeningMessage,
|
|
122
|
+
full: fullOpeningMessage,
|
|
123
|
+
},
|
|
124
|
+
turnsBeforeWorkaround: turnsBetweenOpeningAndAction(records, originalIndex),
|
|
111
125
|
},
|
|
112
|
-
|
|
126
|
+
originalAction: toolAction(originalRecord, occurrence.originalMethod || matchingEvidenceText(occurrence, "tool_use") || "Original tool call unavailable"),
|
|
113
127
|
blocker: { text: blockerText, timestamp: blockerRecord?.timestamp || null },
|
|
114
|
-
|
|
115
|
-
contextTurns: boundedContext,
|
|
128
|
+
workaroundAction: toolAction(alternativeRecord, occurrence.alternativeMethod || matchingEvidenceText(occurrence, "assistant_text") || "Workaround action unavailable"),
|
|
116
129
|
}];
|
|
117
130
|
});
|
|
118
131
|
return {
|
|
119
|
-
format: "behavior-wrapped-workaround-evidence-
|
|
132
|
+
format: "behavior-wrapped-workaround-evidence-v4",
|
|
120
133
|
localPrivate: true,
|
|
121
|
-
standardRedactionsApplied:
|
|
134
|
+
standardRedactionsApplied: false,
|
|
122
135
|
reportId: report?.id,
|
|
123
136
|
occurrences,
|
|
124
137
|
};
|