@darkhunt-security/endpoint-codex 0.9.10 → 0.9.11
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/.codex-plugin/plugin.json +1 -1
- package/dist/bin/backfill.mjs +245 -12
- package/dist/bin/forwarder.mjs +245 -12
- package/dist/bin/status.mjs +181 -5
- package/package.json +3 -3
package/dist/bin/backfill.mjs
CHANGED
|
@@ -18308,6 +18308,7 @@ function attachmentLabel(record) {
|
|
|
18308
18308
|
return `[${record.mediaType ?? "attachment"}${size}]`;
|
|
18309
18309
|
}
|
|
18310
18310
|
var RESUMED_USER_TEXT_LIMIT = 4e3;
|
|
18311
|
+
var USAGE_WIRE_NAMES = { thinking_tokens: "reasoning" };
|
|
18311
18312
|
var SessionEmitter = class {
|
|
18312
18313
|
client;
|
|
18313
18314
|
options;
|
|
@@ -18331,6 +18332,8 @@ var SessionEmitter = class {
|
|
|
18331
18332
|
/** Attachments seen before the trace could be opened knowing the prompt. */
|
|
18332
18333
|
deferred = [];
|
|
18333
18334
|
pendingUsage;
|
|
18335
|
+
/** Set when the model call this generation stands for failed. Cleared with it. */
|
|
18336
|
+
generationError;
|
|
18334
18337
|
outputText = [];
|
|
18335
18338
|
/** The last thing the assistant said in this pass — the trace's result. */
|
|
18336
18339
|
lastAssistantText;
|
|
@@ -18386,9 +18389,10 @@ var SessionEmitter = class {
|
|
|
18386
18389
|
this.count++;
|
|
18387
18390
|
if (record.kind === "attachment")
|
|
18388
18391
|
this.pendingAttachments.push(attachmentLabel(record));
|
|
18389
|
-
if (record.kind === "user_message")
|
|
18392
|
+
if (record.kind === "user_message" && !record.isError && !record.synthetic) {
|
|
18390
18393
|
this.absorbUserTurn(record);
|
|
18391
|
-
|
|
18394
|
+
}
|
|
18395
|
+
if (!this.trace && (record.kind === "attachment" || record.synthetic === true)) {
|
|
18392
18396
|
this.deferred.push(record);
|
|
18393
18397
|
return;
|
|
18394
18398
|
}
|
|
@@ -18426,6 +18430,15 @@ var SessionEmitter = class {
|
|
|
18426
18430
|
this.owners.set(record.uuid, owner);
|
|
18427
18431
|
switch (record.kind) {
|
|
18428
18432
|
case "user_message":
|
|
18433
|
+
if (record.synthetic) {
|
|
18434
|
+
this.seeded(["injected", record.uuid], () => owner.span("context_injection", {
|
|
18435
|
+
startTime: record.ts,
|
|
18436
|
+
...record.text !== void 0 ? { output: summarize(record.text) } : {}
|
|
18437
|
+
})).end({ endTime: record.ts });
|
|
18438
|
+
break;
|
|
18439
|
+
}
|
|
18440
|
+
if (record.isError)
|
|
18441
|
+
this.generationError = record.errorKind ?? "error";
|
|
18429
18442
|
this.endGeneration();
|
|
18430
18443
|
break;
|
|
18431
18444
|
case "attachment": {
|
|
@@ -18442,7 +18455,9 @@ var SessionEmitter = class {
|
|
|
18442
18455
|
}
|
|
18443
18456
|
case "assistant_message":
|
|
18444
18457
|
this.openGeneration(record, owner);
|
|
18445
|
-
if (record.
|
|
18458
|
+
if (record.isError)
|
|
18459
|
+
this.generationError = record.errorKind ?? "error";
|
|
18460
|
+
else if (record.text)
|
|
18446
18461
|
this.outputText.push(record.text);
|
|
18447
18462
|
break;
|
|
18448
18463
|
case "thinking": {
|
|
@@ -18471,15 +18486,44 @@ var SessionEmitter = class {
|
|
|
18471
18486
|
case "tool_result": {
|
|
18472
18487
|
const span = record.toolCallId ? this.toolSpans.get(record.toolCallId) : void 0;
|
|
18473
18488
|
if (span) {
|
|
18474
|
-
span.end({
|
|
18489
|
+
span.end({
|
|
18490
|
+
output: record.output,
|
|
18491
|
+
endTime: record.ts,
|
|
18492
|
+
...record.isError ? { level: "ERROR" } : {},
|
|
18493
|
+
...record.errorKind !== void 0 ? { statusMessage: record.errorKind } : {}
|
|
18494
|
+
});
|
|
18475
18495
|
this.toolSpans.delete(record.toolCallId);
|
|
18476
18496
|
}
|
|
18477
18497
|
this.pendingToolResults.push(summarize(record.output));
|
|
18478
18498
|
break;
|
|
18479
18499
|
}
|
|
18500
|
+
case "compaction":
|
|
18501
|
+
this.endGeneration();
|
|
18502
|
+
this.seeded(["compaction", record.uuid], () => owner.span("compaction", {
|
|
18503
|
+
startTime: record.ts,
|
|
18504
|
+
...record.text ? { output: record.text } : {}
|
|
18505
|
+
})).end({ endTime: record.ts });
|
|
18506
|
+
break;
|
|
18507
|
+
case "permission_grant": {
|
|
18508
|
+
const snapshot = record.input && typeof record.input === "object" ? record.input : void 0;
|
|
18509
|
+
const steps = Array.isArray(snapshot?.steps) ? snapshot.steps : [];
|
|
18510
|
+
this.seeded(["permission", record.uuid], () => owner.span("permission_grant", {
|
|
18511
|
+
startTime: record.ts,
|
|
18512
|
+
// `input`, not `output`: the list is what this record IS, not a result it
|
|
18513
|
+
// produced.
|
|
18514
|
+
input: steps,
|
|
18515
|
+
metadata: {
|
|
18516
|
+
// The true count, which is larger than `steps.length` when the mapper
|
|
18517
|
+
// truncated the snapshot to its byte budget.
|
|
18518
|
+
count: String(snapshot?.count ?? steps.length),
|
|
18519
|
+
full: String(snapshot?.full === true)
|
|
18520
|
+
}
|
|
18521
|
+
})).end({ endTime: record.ts });
|
|
18522
|
+
break;
|
|
18523
|
+
}
|
|
18480
18524
|
case "usage":
|
|
18481
18525
|
if (record.usage) {
|
|
18482
|
-
this.pendingUsage = Object.fromEntries(Object.entries(record.usage).filter(([, v]) => typeof v === "number"));
|
|
18526
|
+
this.pendingUsage = Object.fromEntries(Object.entries(record.usage).filter(([, v]) => typeof v === "number").map(([key, value]) => [USAGE_WIRE_NAMES[key] ?? key, value]));
|
|
18483
18527
|
}
|
|
18484
18528
|
if (record.model)
|
|
18485
18529
|
this.model = record.model;
|
|
@@ -18636,7 +18680,18 @@ var SessionEmitter = class {
|
|
|
18636
18680
|
this.model = record.model;
|
|
18637
18681
|
this.generation = this.seeded(["generation", id], () => owner.generation("assistant", {
|
|
18638
18682
|
startTime: record.ts,
|
|
18639
|
-
...this.model !== void 0 ? { model: this.model } : {}
|
|
18683
|
+
...this.model !== void 0 ? { model: this.model } : {},
|
|
18684
|
+
// What drove this model call — an installed skill, an MCP server's output, a
|
|
18685
|
+
// plugin. A flat scalar like every other metadata value emitted here, because
|
|
18686
|
+
// trace-hub lifts each key onto the span unchanged and a nested value would
|
|
18687
|
+
// arrive as a blob nothing can group or filter on. One lowercase word, so unlike
|
|
18688
|
+
// `agent_id` there is no camelCase to convert.
|
|
18689
|
+
//
|
|
18690
|
+
// Set once, at open, and never through update(): this method early-returns when
|
|
18691
|
+
// the generation is already open, so the value has to be constant across a
|
|
18692
|
+
// message group. Measured over the claude-code corpus, 0 of 1,381 attributed
|
|
18693
|
+
// groups carry two different tokens.
|
|
18694
|
+
...record.attribution !== void 0 ? { metadata: { attribution: record.attribution } } : {}
|
|
18640
18695
|
}));
|
|
18641
18696
|
const input = this.buildInputMessages();
|
|
18642
18697
|
if (input.inputMessages)
|
|
@@ -18676,12 +18731,14 @@ var SessionEmitter = class {
|
|
|
18676
18731
|
this.generation.end({
|
|
18677
18732
|
...output ? { outputMessages: [{ role: "assistant", content: output }] } : {},
|
|
18678
18733
|
...this.model !== void 0 ? { model: this.model } : {},
|
|
18679
|
-
...this.pendingUsage !== void 0 ? { usage: this.pendingUsage } : {}
|
|
18734
|
+
...this.pendingUsage !== void 0 ? { usage: this.pendingUsage } : {},
|
|
18735
|
+
...this.generationError !== void 0 ? { level: "ERROR", statusMessage: this.generationError } : {}
|
|
18680
18736
|
});
|
|
18681
18737
|
this.generation = void 0;
|
|
18682
18738
|
this.generationId = void 0;
|
|
18683
18739
|
this.outputText = [];
|
|
18684
18740
|
this.pendingUsage = void 0;
|
|
18741
|
+
this.generationError = void 0;
|
|
18685
18742
|
}
|
|
18686
18743
|
};
|
|
18687
18744
|
|
|
@@ -21939,6 +21996,49 @@ function textOf(content) {
|
|
|
21939
21996
|
const parts = content.map((b) => b && typeof b === "object" ? str(b["text"]) : void 0).filter((t) => !!t);
|
|
21940
21997
|
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
21941
21998
|
}
|
|
21999
|
+
var CODEX_APPROVAL_MODES = {
|
|
22000
|
+
never: "auto",
|
|
22001
|
+
"on-request": "ask"
|
|
22002
|
+
};
|
|
22003
|
+
var CODEX_SANDBOX_MODES = {
|
|
22004
|
+
"workspace-write": "workspace-write",
|
|
22005
|
+
"read-only": "read-only",
|
|
22006
|
+
"danger-full-access": "danger-full-access"
|
|
22007
|
+
};
|
|
22008
|
+
var PERMISSION_SNAPSHOT_LIMIT = 32e3;
|
|
22009
|
+
function budgeted(steps) {
|
|
22010
|
+
const kept = [];
|
|
22011
|
+
let bytes = 0;
|
|
22012
|
+
for (const step of steps) {
|
|
22013
|
+
bytes += JSON.stringify(step ?? null).length + 1;
|
|
22014
|
+
if (bytes > PERMISSION_SNAPSHOT_LIMIT)
|
|
22015
|
+
break;
|
|
22016
|
+
kept.push(step);
|
|
22017
|
+
}
|
|
22018
|
+
return kept;
|
|
22019
|
+
}
|
|
22020
|
+
function unwrapExecOutput(value) {
|
|
22021
|
+
if (typeof value !== "string")
|
|
22022
|
+
return { output: value };
|
|
22023
|
+
let parsed;
|
|
22024
|
+
try {
|
|
22025
|
+
parsed = JSON.parse(value);
|
|
22026
|
+
} catch {
|
|
22027
|
+
return { output: value };
|
|
22028
|
+
}
|
|
22029
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
22030
|
+
return { output: value };
|
|
22031
|
+
const bag = parsed;
|
|
22032
|
+
if (!Object.prototype.hasOwnProperty.call(bag, "output"))
|
|
22033
|
+
return { output: value };
|
|
22034
|
+
const metadata = bag["metadata"];
|
|
22035
|
+
if (!metadata || typeof metadata !== "object")
|
|
22036
|
+
return { output: value };
|
|
22037
|
+
const exitCode = num(metadata["exit_code"]);
|
|
22038
|
+
if (exitCode === void 0)
|
|
22039
|
+
return { output: value };
|
|
22040
|
+
return { output: bag["output"], exitCode };
|
|
22041
|
+
}
|
|
21942
22042
|
function jwtClaims(token) {
|
|
21943
22043
|
const parts = token.split(".");
|
|
21944
22044
|
if (parts.length !== 3 || !parts[1])
|
|
@@ -21965,6 +22065,29 @@ function emailFromAuth(raw) {
|
|
|
21965
22065
|
const email = jwtClaims(token)?.["email"];
|
|
21966
22066
|
return typeof email === "string" && email !== "" ? email : void 0;
|
|
21967
22067
|
}
|
|
22068
|
+
function repositoryIdentity(url) {
|
|
22069
|
+
if (url === void 0)
|
|
22070
|
+
return void 0;
|
|
22071
|
+
const trimmed = url.trim();
|
|
22072
|
+
if (trimmed === "")
|
|
22073
|
+
return void 0;
|
|
22074
|
+
const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(\S+)$/.exec(trimmed);
|
|
22075
|
+
if (scp)
|
|
22076
|
+
return `${scp[1]}/${stripSuffix(scp[2])}`;
|
|
22077
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `ssh://${trimmed}`;
|
|
22078
|
+
try {
|
|
22079
|
+
const parsed = new URL(withScheme);
|
|
22080
|
+
if (!parsed.hostname)
|
|
22081
|
+
return trimmed;
|
|
22082
|
+
const path = stripSuffix(parsed.pathname.replace(/^\/+/, ""));
|
|
22083
|
+
return path === "" ? parsed.hostname : `${parsed.hostname}/${path}`;
|
|
22084
|
+
} catch {
|
|
22085
|
+
return trimmed;
|
|
22086
|
+
}
|
|
22087
|
+
}
|
|
22088
|
+
function stripSuffix(path) {
|
|
22089
|
+
return path.replace(/\/+$/, "").replace(/\.git$/i, "");
|
|
22090
|
+
}
|
|
21968
22091
|
var codexTranscript = {
|
|
21969
22092
|
vendor: "codex",
|
|
21970
22093
|
sessionRoots() {
|
|
@@ -22017,12 +22140,20 @@ var codexTranscript = {
|
|
|
22017
22140
|
const uuid = `codex:${offset}`;
|
|
22018
22141
|
const base = { vendor: "codex", ts, uuid };
|
|
22019
22142
|
if (raw.type === "session_meta") {
|
|
22143
|
+
const rawGit = payload["git"];
|
|
22144
|
+
const git = rawGit && typeof rawGit === "object" ? rawGit : void 0;
|
|
22145
|
+
const gitBranch = str(git?.["branch"]);
|
|
22146
|
+
const gitRepositoryUrl = repositoryIdentity(str(git?.["repository_url"]));
|
|
22020
22147
|
return [
|
|
22021
22148
|
{
|
|
22022
22149
|
...base,
|
|
22023
22150
|
kind: "session_meta",
|
|
22024
22151
|
meta: {
|
|
22025
22152
|
...str(payload["cwd"]) !== void 0 ? { cwd: str(payload["cwd"]) } : {},
|
|
22153
|
+
// Conditional spread rather than a plain assignment: the 7 `{}` blocks and
|
|
22154
|
+
// the 92 absent ones must leave the key absent, not present-and-undefined.
|
|
22155
|
+
...gitBranch !== void 0 ? { gitBranch } : {},
|
|
22156
|
+
...gitRepositoryUrl !== void 0 ? { gitRepositoryUrl } : {},
|
|
22026
22157
|
...str(payload["cli_version"]) !== void 0 ? { version: str(payload["cli_version"]) } : {},
|
|
22027
22158
|
...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {},
|
|
22028
22159
|
// A Codex subagent is a rollout of its own, a peer of its parent in the
|
|
@@ -22038,15 +22169,61 @@ var codexTranscript = {
|
|
|
22038
22169
|
}
|
|
22039
22170
|
if (raw.type === "turn_context") {
|
|
22040
22171
|
const model = str(payload["model"]);
|
|
22041
|
-
|
|
22172
|
+
const approval = str(payload["approval_policy"]);
|
|
22173
|
+
const sandbox = payload["sandbox_policy"];
|
|
22174
|
+
const sandboxMode = (() => {
|
|
22175
|
+
if (!sandbox || typeof sandbox !== "object")
|
|
22176
|
+
return void 0;
|
|
22177
|
+
const bag = sandbox;
|
|
22178
|
+
return str(bag["mode"]) ?? str(bag["type"]);
|
|
22179
|
+
})();
|
|
22180
|
+
const meta = {
|
|
22181
|
+
...model !== void 0 ? { model } : {},
|
|
22182
|
+
...approval !== void 0 ? { approvalMode: CODEX_APPROVAL_MODES[approval] ?? approval } : {},
|
|
22183
|
+
...sandboxMode !== void 0 ? { sandboxMode: CODEX_SANDBOX_MODES[sandboxMode] ?? sandboxMode } : {}
|
|
22184
|
+
};
|
|
22185
|
+
if (Object.keys(meta).length === 0)
|
|
22042
22186
|
return [];
|
|
22043
|
-
return [{ ...base, kind: "session_meta", meta
|
|
22187
|
+
return [{ ...base, kind: "session_meta", meta }];
|
|
22044
22188
|
}
|
|
22045
22189
|
const kind = str(payload["type"]);
|
|
22046
22190
|
if (raw.type === "event_msg" && kind === "token_count") {
|
|
22047
22191
|
const usage = mapTokenCount(payload["info"]);
|
|
22048
22192
|
return usage ? [{ ...base, kind: "usage", usage }] : [];
|
|
22049
22193
|
}
|
|
22194
|
+
if (raw.type === "compacted") {
|
|
22195
|
+
const note = str(payload["message"]);
|
|
22196
|
+
return [{ ...base, kind: "compaction", ...note ? { text: note } : {} }];
|
|
22197
|
+
}
|
|
22198
|
+
if (raw.type === "world_state") {
|
|
22199
|
+
const state = payload["state"];
|
|
22200
|
+
const perms = state && typeof state === "object" ? state["permissions"] : void 0;
|
|
22201
|
+
if (!perms || typeof perms !== "object")
|
|
22202
|
+
return [];
|
|
22203
|
+
const list = perms["approved_command_prefixes"];
|
|
22204
|
+
const steps = Array.isArray(list) ? list : [];
|
|
22205
|
+
return [
|
|
22206
|
+
{
|
|
22207
|
+
...base,
|
|
22208
|
+
kind: "permission_grant",
|
|
22209
|
+
input: {
|
|
22210
|
+
steps: budgeted(steps),
|
|
22211
|
+
count: steps.length,
|
|
22212
|
+
...payload["full"] === true ? { full: true } : {}
|
|
22213
|
+
}
|
|
22214
|
+
}
|
|
22215
|
+
];
|
|
22216
|
+
}
|
|
22217
|
+
if (raw.type === "event_msg" && kind === "turn_aborted") {
|
|
22218
|
+
return [
|
|
22219
|
+
{
|
|
22220
|
+
...base,
|
|
22221
|
+
kind: "user_message",
|
|
22222
|
+
isError: true,
|
|
22223
|
+
errorKind: `aborted:${str(payload["reason"]) ?? "unknown"}`
|
|
22224
|
+
}
|
|
22225
|
+
];
|
|
22226
|
+
}
|
|
22050
22227
|
if (raw.type !== "response_item")
|
|
22051
22228
|
return [];
|
|
22052
22229
|
switch (kind) {
|
|
@@ -22060,18 +22237,74 @@ var codexTranscript = {
|
|
|
22060
22237
|
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
22061
22238
|
}
|
|
22062
22239
|
];
|
|
22063
|
-
case "function_call_output":
|
|
22240
|
+
case "function_call_output": {
|
|
22241
|
+
const flattened = textOf(payload["output"]) ?? payload["output"];
|
|
22242
|
+
const { output, exitCode } = unwrapExecOutput(flattened);
|
|
22064
22243
|
return [
|
|
22065
22244
|
{
|
|
22066
22245
|
...base,
|
|
22067
22246
|
kind: "tool_result",
|
|
22068
|
-
output
|
|
22247
|
+
output,
|
|
22248
|
+
// Only where an exit status was actually found. On the 993 string outputs
|
|
22249
|
+
// that are not envelopes and the 144 array ones the key stays ABSENT rather
|
|
22250
|
+
// than `false`: the emitter tests `record.isError`, and "this tool reported
|
|
22251
|
+
// success" is a different claim from "nothing here reported an outcome".
|
|
22252
|
+
...exitCode !== void 0 ? { isError: exitCode !== 0 } : {},
|
|
22069
22253
|
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
22070
22254
|
}
|
|
22071
22255
|
];
|
|
22256
|
+
}
|
|
22257
|
+
// Codex's OTHER tool-call shape, and the one it actually uses: `exec` and
|
|
22258
|
+
// `apply_patch` arrive as `custom_tool_call`, never as `function_call`. Measured
|
|
22259
|
+
// across 251 rollouts, 2179 of 5382 tool calls — 40% — took this shape, so
|
|
22260
|
+
// handling only `function_call` left every shell command and every file write
|
|
22261
|
+
// this endpoint made invisible to both the trace and anything reading it.
|
|
22262
|
+
case "custom_tool_call":
|
|
22263
|
+
return [
|
|
22264
|
+
{
|
|
22265
|
+
...base,
|
|
22266
|
+
kind: "tool_call",
|
|
22267
|
+
toolName: str(payload["name"]) ?? "tool",
|
|
22268
|
+
input: payload["input"],
|
|
22269
|
+
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
22270
|
+
}
|
|
22271
|
+
];
|
|
22272
|
+
case "custom_tool_call_output": {
|
|
22273
|
+
const flattened = textOf(payload["output"]) ?? payload["output"];
|
|
22274
|
+
const { output, exitCode } = unwrapExecOutput(flattened);
|
|
22275
|
+
return [
|
|
22276
|
+
{
|
|
22277
|
+
...base,
|
|
22278
|
+
kind: "tool_result",
|
|
22279
|
+
output,
|
|
22280
|
+
...exitCode !== void 0 ? { isError: exitCode !== 0 } : {},
|
|
22281
|
+
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
22282
|
+
}
|
|
22283
|
+
];
|
|
22284
|
+
}
|
|
22285
|
+
// The model's own web reads. 49 across the corpus in 9 rollouts — `search` 31,
|
|
22286
|
+
// `open_page` 16, `find_in_page` 2 — every one of which mapped to nothing, so a
|
|
22287
|
+
// session where the agent searched and opened pages rendered as reasoning
|
|
22288
|
+
// followed by an answer with no source anywhere in it. The queries and the URLs
|
|
22289
|
+
// are the whole point of the record: they are the evidence of what the agent
|
|
22290
|
+
// read from outside the machine.
|
|
22291
|
+
//
|
|
22292
|
+
// `action` is passed through whole. It is already a vendor-neutral argument bag
|
|
22293
|
+
// ({type, query?, queries?, url?, pattern?}) and it is small — 322 bytes at the
|
|
22294
|
+
// worst, 121 on average across all 49.
|
|
22295
|
+
//
|
|
22296
|
+
// Deliberately unpaired: no `toolCallId`. Only 1 of the 49 lines carries an
|
|
22297
|
+
// `id`, `call_id` is the pairing key everywhere else in this stream, and no
|
|
22298
|
+
// response_item output line ever pairs with a search — so setting one would
|
|
22299
|
+
// register a span in the emitter's pairing table that nothing ever closes, and
|
|
22300
|
+
// `finish()` would end it at pass end and stamp a multi-minute duration on a
|
|
22301
|
+
// search that took seconds. Left unpaired, the emitter closes it at the call
|
|
22302
|
+
// timestamp, which is the truthful reading.
|
|
22303
|
+
case "web_search_call":
|
|
22304
|
+
return [{ ...base, kind: "tool_call", toolName: "web_search", input: payload["action"] }];
|
|
22072
22305
|
case "reasoning": {
|
|
22073
22306
|
const text = textOf(payload["summary"]) ?? textOf(payload["content"]);
|
|
22074
|
-
return
|
|
22307
|
+
return [{ ...base, kind: "thinking", ...text !== void 0 ? { text } : {} }];
|
|
22075
22308
|
}
|
|
22076
22309
|
case "message": {
|
|
22077
22310
|
const text = textOf(payload["content"]);
|
package/dist/bin/forwarder.mjs
CHANGED
|
@@ -18308,6 +18308,7 @@ function attachmentLabel(record) {
|
|
|
18308
18308
|
return `[${record.mediaType ?? "attachment"}${size}]`;
|
|
18309
18309
|
}
|
|
18310
18310
|
var RESUMED_USER_TEXT_LIMIT = 4e3;
|
|
18311
|
+
var USAGE_WIRE_NAMES = { thinking_tokens: "reasoning" };
|
|
18311
18312
|
var SessionEmitter = class {
|
|
18312
18313
|
client;
|
|
18313
18314
|
options;
|
|
@@ -18331,6 +18332,8 @@ var SessionEmitter = class {
|
|
|
18331
18332
|
/** Attachments seen before the trace could be opened knowing the prompt. */
|
|
18332
18333
|
deferred = [];
|
|
18333
18334
|
pendingUsage;
|
|
18335
|
+
/** Set when the model call this generation stands for failed. Cleared with it. */
|
|
18336
|
+
generationError;
|
|
18334
18337
|
outputText = [];
|
|
18335
18338
|
/** The last thing the assistant said in this pass — the trace's result. */
|
|
18336
18339
|
lastAssistantText;
|
|
@@ -18386,9 +18389,10 @@ var SessionEmitter = class {
|
|
|
18386
18389
|
this.count++;
|
|
18387
18390
|
if (record.kind === "attachment")
|
|
18388
18391
|
this.pendingAttachments.push(attachmentLabel(record));
|
|
18389
|
-
if (record.kind === "user_message")
|
|
18392
|
+
if (record.kind === "user_message" && !record.isError && !record.synthetic) {
|
|
18390
18393
|
this.absorbUserTurn(record);
|
|
18391
|
-
|
|
18394
|
+
}
|
|
18395
|
+
if (!this.trace && (record.kind === "attachment" || record.synthetic === true)) {
|
|
18392
18396
|
this.deferred.push(record);
|
|
18393
18397
|
return;
|
|
18394
18398
|
}
|
|
@@ -18426,6 +18430,15 @@ var SessionEmitter = class {
|
|
|
18426
18430
|
this.owners.set(record.uuid, owner);
|
|
18427
18431
|
switch (record.kind) {
|
|
18428
18432
|
case "user_message":
|
|
18433
|
+
if (record.synthetic) {
|
|
18434
|
+
this.seeded(["injected", record.uuid], () => owner.span("context_injection", {
|
|
18435
|
+
startTime: record.ts,
|
|
18436
|
+
...record.text !== void 0 ? { output: summarize(record.text) } : {}
|
|
18437
|
+
})).end({ endTime: record.ts });
|
|
18438
|
+
break;
|
|
18439
|
+
}
|
|
18440
|
+
if (record.isError)
|
|
18441
|
+
this.generationError = record.errorKind ?? "error";
|
|
18429
18442
|
this.endGeneration();
|
|
18430
18443
|
break;
|
|
18431
18444
|
case "attachment": {
|
|
@@ -18442,7 +18455,9 @@ var SessionEmitter = class {
|
|
|
18442
18455
|
}
|
|
18443
18456
|
case "assistant_message":
|
|
18444
18457
|
this.openGeneration(record, owner);
|
|
18445
|
-
if (record.
|
|
18458
|
+
if (record.isError)
|
|
18459
|
+
this.generationError = record.errorKind ?? "error";
|
|
18460
|
+
else if (record.text)
|
|
18446
18461
|
this.outputText.push(record.text);
|
|
18447
18462
|
break;
|
|
18448
18463
|
case "thinking": {
|
|
@@ -18471,15 +18486,44 @@ var SessionEmitter = class {
|
|
|
18471
18486
|
case "tool_result": {
|
|
18472
18487
|
const span = record.toolCallId ? this.toolSpans.get(record.toolCallId) : void 0;
|
|
18473
18488
|
if (span) {
|
|
18474
|
-
span.end({
|
|
18489
|
+
span.end({
|
|
18490
|
+
output: record.output,
|
|
18491
|
+
endTime: record.ts,
|
|
18492
|
+
...record.isError ? { level: "ERROR" } : {},
|
|
18493
|
+
...record.errorKind !== void 0 ? { statusMessage: record.errorKind } : {}
|
|
18494
|
+
});
|
|
18475
18495
|
this.toolSpans.delete(record.toolCallId);
|
|
18476
18496
|
}
|
|
18477
18497
|
this.pendingToolResults.push(summarize(record.output));
|
|
18478
18498
|
break;
|
|
18479
18499
|
}
|
|
18500
|
+
case "compaction":
|
|
18501
|
+
this.endGeneration();
|
|
18502
|
+
this.seeded(["compaction", record.uuid], () => owner.span("compaction", {
|
|
18503
|
+
startTime: record.ts,
|
|
18504
|
+
...record.text ? { output: record.text } : {}
|
|
18505
|
+
})).end({ endTime: record.ts });
|
|
18506
|
+
break;
|
|
18507
|
+
case "permission_grant": {
|
|
18508
|
+
const snapshot = record.input && typeof record.input === "object" ? record.input : void 0;
|
|
18509
|
+
const steps = Array.isArray(snapshot?.steps) ? snapshot.steps : [];
|
|
18510
|
+
this.seeded(["permission", record.uuid], () => owner.span("permission_grant", {
|
|
18511
|
+
startTime: record.ts,
|
|
18512
|
+
// `input`, not `output`: the list is what this record IS, not a result it
|
|
18513
|
+
// produced.
|
|
18514
|
+
input: steps,
|
|
18515
|
+
metadata: {
|
|
18516
|
+
// The true count, which is larger than `steps.length` when the mapper
|
|
18517
|
+
// truncated the snapshot to its byte budget.
|
|
18518
|
+
count: String(snapshot?.count ?? steps.length),
|
|
18519
|
+
full: String(snapshot?.full === true)
|
|
18520
|
+
}
|
|
18521
|
+
})).end({ endTime: record.ts });
|
|
18522
|
+
break;
|
|
18523
|
+
}
|
|
18480
18524
|
case "usage":
|
|
18481
18525
|
if (record.usage) {
|
|
18482
|
-
this.pendingUsage = Object.fromEntries(Object.entries(record.usage).filter(([, v]) => typeof v === "number"));
|
|
18526
|
+
this.pendingUsage = Object.fromEntries(Object.entries(record.usage).filter(([, v]) => typeof v === "number").map(([key, value]) => [USAGE_WIRE_NAMES[key] ?? key, value]));
|
|
18483
18527
|
}
|
|
18484
18528
|
if (record.model)
|
|
18485
18529
|
this.model = record.model;
|
|
@@ -18636,7 +18680,18 @@ var SessionEmitter = class {
|
|
|
18636
18680
|
this.model = record.model;
|
|
18637
18681
|
this.generation = this.seeded(["generation", id], () => owner.generation("assistant", {
|
|
18638
18682
|
startTime: record.ts,
|
|
18639
|
-
...this.model !== void 0 ? { model: this.model } : {}
|
|
18683
|
+
...this.model !== void 0 ? { model: this.model } : {},
|
|
18684
|
+
// What drove this model call — an installed skill, an MCP server's output, a
|
|
18685
|
+
// plugin. A flat scalar like every other metadata value emitted here, because
|
|
18686
|
+
// trace-hub lifts each key onto the span unchanged and a nested value would
|
|
18687
|
+
// arrive as a blob nothing can group or filter on. One lowercase word, so unlike
|
|
18688
|
+
// `agent_id` there is no camelCase to convert.
|
|
18689
|
+
//
|
|
18690
|
+
// Set once, at open, and never through update(): this method early-returns when
|
|
18691
|
+
// the generation is already open, so the value has to be constant across a
|
|
18692
|
+
// message group. Measured over the claude-code corpus, 0 of 1,381 attributed
|
|
18693
|
+
// groups carry two different tokens.
|
|
18694
|
+
...record.attribution !== void 0 ? { metadata: { attribution: record.attribution } } : {}
|
|
18640
18695
|
}));
|
|
18641
18696
|
const input = this.buildInputMessages();
|
|
18642
18697
|
if (input.inputMessages)
|
|
@@ -18676,12 +18731,14 @@ var SessionEmitter = class {
|
|
|
18676
18731
|
this.generation.end({
|
|
18677
18732
|
...output ? { outputMessages: [{ role: "assistant", content: output }] } : {},
|
|
18678
18733
|
...this.model !== void 0 ? { model: this.model } : {},
|
|
18679
|
-
...this.pendingUsage !== void 0 ? { usage: this.pendingUsage } : {}
|
|
18734
|
+
...this.pendingUsage !== void 0 ? { usage: this.pendingUsage } : {},
|
|
18735
|
+
...this.generationError !== void 0 ? { level: "ERROR", statusMessage: this.generationError } : {}
|
|
18680
18736
|
});
|
|
18681
18737
|
this.generation = void 0;
|
|
18682
18738
|
this.generationId = void 0;
|
|
18683
18739
|
this.outputText = [];
|
|
18684
18740
|
this.pendingUsage = void 0;
|
|
18741
|
+
this.generationError = void 0;
|
|
18685
18742
|
}
|
|
18686
18743
|
};
|
|
18687
18744
|
|
|
@@ -21926,6 +21983,49 @@ function textOf(content) {
|
|
|
21926
21983
|
const parts = content.map((b) => b && typeof b === "object" ? str(b["text"]) : void 0).filter((t) => !!t);
|
|
21927
21984
|
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
21928
21985
|
}
|
|
21986
|
+
var CODEX_APPROVAL_MODES = {
|
|
21987
|
+
never: "auto",
|
|
21988
|
+
"on-request": "ask"
|
|
21989
|
+
};
|
|
21990
|
+
var CODEX_SANDBOX_MODES = {
|
|
21991
|
+
"workspace-write": "workspace-write",
|
|
21992
|
+
"read-only": "read-only",
|
|
21993
|
+
"danger-full-access": "danger-full-access"
|
|
21994
|
+
};
|
|
21995
|
+
var PERMISSION_SNAPSHOT_LIMIT = 32e3;
|
|
21996
|
+
function budgeted(steps) {
|
|
21997
|
+
const kept = [];
|
|
21998
|
+
let bytes = 0;
|
|
21999
|
+
for (const step of steps) {
|
|
22000
|
+
bytes += JSON.stringify(step ?? null).length + 1;
|
|
22001
|
+
if (bytes > PERMISSION_SNAPSHOT_LIMIT)
|
|
22002
|
+
break;
|
|
22003
|
+
kept.push(step);
|
|
22004
|
+
}
|
|
22005
|
+
return kept;
|
|
22006
|
+
}
|
|
22007
|
+
function unwrapExecOutput(value) {
|
|
22008
|
+
if (typeof value !== "string")
|
|
22009
|
+
return { output: value };
|
|
22010
|
+
let parsed;
|
|
22011
|
+
try {
|
|
22012
|
+
parsed = JSON.parse(value);
|
|
22013
|
+
} catch {
|
|
22014
|
+
return { output: value };
|
|
22015
|
+
}
|
|
22016
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
22017
|
+
return { output: value };
|
|
22018
|
+
const bag = parsed;
|
|
22019
|
+
if (!Object.prototype.hasOwnProperty.call(bag, "output"))
|
|
22020
|
+
return { output: value };
|
|
22021
|
+
const metadata = bag["metadata"];
|
|
22022
|
+
if (!metadata || typeof metadata !== "object")
|
|
22023
|
+
return { output: value };
|
|
22024
|
+
const exitCode = num(metadata["exit_code"]);
|
|
22025
|
+
if (exitCode === void 0)
|
|
22026
|
+
return { output: value };
|
|
22027
|
+
return { output: bag["output"], exitCode };
|
|
22028
|
+
}
|
|
21929
22029
|
function jwtClaims(token) {
|
|
21930
22030
|
const parts = token.split(".");
|
|
21931
22031
|
if (parts.length !== 3 || !parts[1])
|
|
@@ -21952,6 +22052,29 @@ function emailFromAuth(raw) {
|
|
|
21952
22052
|
const email = jwtClaims(token)?.["email"];
|
|
21953
22053
|
return typeof email === "string" && email !== "" ? email : void 0;
|
|
21954
22054
|
}
|
|
22055
|
+
function repositoryIdentity(url) {
|
|
22056
|
+
if (url === void 0)
|
|
22057
|
+
return void 0;
|
|
22058
|
+
const trimmed = url.trim();
|
|
22059
|
+
if (trimmed === "")
|
|
22060
|
+
return void 0;
|
|
22061
|
+
const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(\S+)$/.exec(trimmed);
|
|
22062
|
+
if (scp)
|
|
22063
|
+
return `${scp[1]}/${stripSuffix(scp[2])}`;
|
|
22064
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `ssh://${trimmed}`;
|
|
22065
|
+
try {
|
|
22066
|
+
const parsed = new URL(withScheme);
|
|
22067
|
+
if (!parsed.hostname)
|
|
22068
|
+
return trimmed;
|
|
22069
|
+
const path = stripSuffix(parsed.pathname.replace(/^\/+/, ""));
|
|
22070
|
+
return path === "" ? parsed.hostname : `${parsed.hostname}/${path}`;
|
|
22071
|
+
} catch {
|
|
22072
|
+
return trimmed;
|
|
22073
|
+
}
|
|
22074
|
+
}
|
|
22075
|
+
function stripSuffix(path) {
|
|
22076
|
+
return path.replace(/\/+$/, "").replace(/\.git$/i, "");
|
|
22077
|
+
}
|
|
21955
22078
|
var codexTranscript = {
|
|
21956
22079
|
vendor: "codex",
|
|
21957
22080
|
sessionRoots() {
|
|
@@ -22004,12 +22127,20 @@ var codexTranscript = {
|
|
|
22004
22127
|
const uuid = `codex:${offset}`;
|
|
22005
22128
|
const base = { vendor: "codex", ts, uuid };
|
|
22006
22129
|
if (raw.type === "session_meta") {
|
|
22130
|
+
const rawGit = payload["git"];
|
|
22131
|
+
const git = rawGit && typeof rawGit === "object" ? rawGit : void 0;
|
|
22132
|
+
const gitBranch = str(git?.["branch"]);
|
|
22133
|
+
const gitRepositoryUrl = repositoryIdentity(str(git?.["repository_url"]));
|
|
22007
22134
|
return [
|
|
22008
22135
|
{
|
|
22009
22136
|
...base,
|
|
22010
22137
|
kind: "session_meta",
|
|
22011
22138
|
meta: {
|
|
22012
22139
|
...str(payload["cwd"]) !== void 0 ? { cwd: str(payload["cwd"]) } : {},
|
|
22140
|
+
// Conditional spread rather than a plain assignment: the 7 `{}` blocks and
|
|
22141
|
+
// the 92 absent ones must leave the key absent, not present-and-undefined.
|
|
22142
|
+
...gitBranch !== void 0 ? { gitBranch } : {},
|
|
22143
|
+
...gitRepositoryUrl !== void 0 ? { gitRepositoryUrl } : {},
|
|
22013
22144
|
...str(payload["cli_version"]) !== void 0 ? { version: str(payload["cli_version"]) } : {},
|
|
22014
22145
|
...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {},
|
|
22015
22146
|
// A Codex subagent is a rollout of its own, a peer of its parent in the
|
|
@@ -22025,15 +22156,61 @@ var codexTranscript = {
|
|
|
22025
22156
|
}
|
|
22026
22157
|
if (raw.type === "turn_context") {
|
|
22027
22158
|
const model = str(payload["model"]);
|
|
22028
|
-
|
|
22159
|
+
const approval = str(payload["approval_policy"]);
|
|
22160
|
+
const sandbox = payload["sandbox_policy"];
|
|
22161
|
+
const sandboxMode = (() => {
|
|
22162
|
+
if (!sandbox || typeof sandbox !== "object")
|
|
22163
|
+
return void 0;
|
|
22164
|
+
const bag = sandbox;
|
|
22165
|
+
return str(bag["mode"]) ?? str(bag["type"]);
|
|
22166
|
+
})();
|
|
22167
|
+
const meta = {
|
|
22168
|
+
...model !== void 0 ? { model } : {},
|
|
22169
|
+
...approval !== void 0 ? { approvalMode: CODEX_APPROVAL_MODES[approval] ?? approval } : {},
|
|
22170
|
+
...sandboxMode !== void 0 ? { sandboxMode: CODEX_SANDBOX_MODES[sandboxMode] ?? sandboxMode } : {}
|
|
22171
|
+
};
|
|
22172
|
+
if (Object.keys(meta).length === 0)
|
|
22029
22173
|
return [];
|
|
22030
|
-
return [{ ...base, kind: "session_meta", meta
|
|
22174
|
+
return [{ ...base, kind: "session_meta", meta }];
|
|
22031
22175
|
}
|
|
22032
22176
|
const kind = str(payload["type"]);
|
|
22033
22177
|
if (raw.type === "event_msg" && kind === "token_count") {
|
|
22034
22178
|
const usage = mapTokenCount(payload["info"]);
|
|
22035
22179
|
return usage ? [{ ...base, kind: "usage", usage }] : [];
|
|
22036
22180
|
}
|
|
22181
|
+
if (raw.type === "compacted") {
|
|
22182
|
+
const note = str(payload["message"]);
|
|
22183
|
+
return [{ ...base, kind: "compaction", ...note ? { text: note } : {} }];
|
|
22184
|
+
}
|
|
22185
|
+
if (raw.type === "world_state") {
|
|
22186
|
+
const state = payload["state"];
|
|
22187
|
+
const perms = state && typeof state === "object" ? state["permissions"] : void 0;
|
|
22188
|
+
if (!perms || typeof perms !== "object")
|
|
22189
|
+
return [];
|
|
22190
|
+
const list = perms["approved_command_prefixes"];
|
|
22191
|
+
const steps = Array.isArray(list) ? list : [];
|
|
22192
|
+
return [
|
|
22193
|
+
{
|
|
22194
|
+
...base,
|
|
22195
|
+
kind: "permission_grant",
|
|
22196
|
+
input: {
|
|
22197
|
+
steps: budgeted(steps),
|
|
22198
|
+
count: steps.length,
|
|
22199
|
+
...payload["full"] === true ? { full: true } : {}
|
|
22200
|
+
}
|
|
22201
|
+
}
|
|
22202
|
+
];
|
|
22203
|
+
}
|
|
22204
|
+
if (raw.type === "event_msg" && kind === "turn_aborted") {
|
|
22205
|
+
return [
|
|
22206
|
+
{
|
|
22207
|
+
...base,
|
|
22208
|
+
kind: "user_message",
|
|
22209
|
+
isError: true,
|
|
22210
|
+
errorKind: `aborted:${str(payload["reason"]) ?? "unknown"}`
|
|
22211
|
+
}
|
|
22212
|
+
];
|
|
22213
|
+
}
|
|
22037
22214
|
if (raw.type !== "response_item")
|
|
22038
22215
|
return [];
|
|
22039
22216
|
switch (kind) {
|
|
@@ -22047,18 +22224,74 @@ var codexTranscript = {
|
|
|
22047
22224
|
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
22048
22225
|
}
|
|
22049
22226
|
];
|
|
22050
|
-
case "function_call_output":
|
|
22227
|
+
case "function_call_output": {
|
|
22228
|
+
const flattened = textOf(payload["output"]) ?? payload["output"];
|
|
22229
|
+
const { output, exitCode } = unwrapExecOutput(flattened);
|
|
22051
22230
|
return [
|
|
22052
22231
|
{
|
|
22053
22232
|
...base,
|
|
22054
22233
|
kind: "tool_result",
|
|
22055
|
-
output
|
|
22234
|
+
output,
|
|
22235
|
+
// Only where an exit status was actually found. On the 993 string outputs
|
|
22236
|
+
// that are not envelopes and the 144 array ones the key stays ABSENT rather
|
|
22237
|
+
// than `false`: the emitter tests `record.isError`, and "this tool reported
|
|
22238
|
+
// success" is a different claim from "nothing here reported an outcome".
|
|
22239
|
+
...exitCode !== void 0 ? { isError: exitCode !== 0 } : {},
|
|
22056
22240
|
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
22057
22241
|
}
|
|
22058
22242
|
];
|
|
22243
|
+
}
|
|
22244
|
+
// Codex's OTHER tool-call shape, and the one it actually uses: `exec` and
|
|
22245
|
+
// `apply_patch` arrive as `custom_tool_call`, never as `function_call`. Measured
|
|
22246
|
+
// across 251 rollouts, 2179 of 5382 tool calls — 40% — took this shape, so
|
|
22247
|
+
// handling only `function_call` left every shell command and every file write
|
|
22248
|
+
// this endpoint made invisible to both the trace and anything reading it.
|
|
22249
|
+
case "custom_tool_call":
|
|
22250
|
+
return [
|
|
22251
|
+
{
|
|
22252
|
+
...base,
|
|
22253
|
+
kind: "tool_call",
|
|
22254
|
+
toolName: str(payload["name"]) ?? "tool",
|
|
22255
|
+
input: payload["input"],
|
|
22256
|
+
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
22257
|
+
}
|
|
22258
|
+
];
|
|
22259
|
+
case "custom_tool_call_output": {
|
|
22260
|
+
const flattened = textOf(payload["output"]) ?? payload["output"];
|
|
22261
|
+
const { output, exitCode } = unwrapExecOutput(flattened);
|
|
22262
|
+
return [
|
|
22263
|
+
{
|
|
22264
|
+
...base,
|
|
22265
|
+
kind: "tool_result",
|
|
22266
|
+
output,
|
|
22267
|
+
...exitCode !== void 0 ? { isError: exitCode !== 0 } : {},
|
|
22268
|
+
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
22269
|
+
}
|
|
22270
|
+
];
|
|
22271
|
+
}
|
|
22272
|
+
// The model's own web reads. 49 across the corpus in 9 rollouts — `search` 31,
|
|
22273
|
+
// `open_page` 16, `find_in_page` 2 — every one of which mapped to nothing, so a
|
|
22274
|
+
// session where the agent searched and opened pages rendered as reasoning
|
|
22275
|
+
// followed by an answer with no source anywhere in it. The queries and the URLs
|
|
22276
|
+
// are the whole point of the record: they are the evidence of what the agent
|
|
22277
|
+
// read from outside the machine.
|
|
22278
|
+
//
|
|
22279
|
+
// `action` is passed through whole. It is already a vendor-neutral argument bag
|
|
22280
|
+
// ({type, query?, queries?, url?, pattern?}) and it is small — 322 bytes at the
|
|
22281
|
+
// worst, 121 on average across all 49.
|
|
22282
|
+
//
|
|
22283
|
+
// Deliberately unpaired: no `toolCallId`. Only 1 of the 49 lines carries an
|
|
22284
|
+
// `id`, `call_id` is the pairing key everywhere else in this stream, and no
|
|
22285
|
+
// response_item output line ever pairs with a search — so setting one would
|
|
22286
|
+
// register a span in the emitter's pairing table that nothing ever closes, and
|
|
22287
|
+
// `finish()` would end it at pass end and stamp a multi-minute duration on a
|
|
22288
|
+
// search that took seconds. Left unpaired, the emitter closes it at the call
|
|
22289
|
+
// timestamp, which is the truthful reading.
|
|
22290
|
+
case "web_search_call":
|
|
22291
|
+
return [{ ...base, kind: "tool_call", toolName: "web_search", input: payload["action"] }];
|
|
22059
22292
|
case "reasoning": {
|
|
22060
22293
|
const text = textOf(payload["summary"]) ?? textOf(payload["content"]);
|
|
22061
|
-
return
|
|
22294
|
+
return [{ ...base, kind: "thinking", ...text !== void 0 ? { text } : {} }];
|
|
22062
22295
|
}
|
|
22063
22296
|
case "message": {
|
|
22064
22297
|
const text = textOf(payload["content"]);
|
package/dist/bin/status.mjs
CHANGED
|
@@ -1265,6 +1265,49 @@ function textOf(content) {
|
|
|
1265
1265
|
const parts = content.map((b) => b && typeof b === "object" ? str(b["text"]) : void 0).filter((t) => !!t);
|
|
1266
1266
|
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
1267
1267
|
}
|
|
1268
|
+
var CODEX_APPROVAL_MODES = {
|
|
1269
|
+
never: "auto",
|
|
1270
|
+
"on-request": "ask"
|
|
1271
|
+
};
|
|
1272
|
+
var CODEX_SANDBOX_MODES = {
|
|
1273
|
+
"workspace-write": "workspace-write",
|
|
1274
|
+
"read-only": "read-only",
|
|
1275
|
+
"danger-full-access": "danger-full-access"
|
|
1276
|
+
};
|
|
1277
|
+
var PERMISSION_SNAPSHOT_LIMIT = 32e3;
|
|
1278
|
+
function budgeted(steps) {
|
|
1279
|
+
const kept = [];
|
|
1280
|
+
let bytes = 0;
|
|
1281
|
+
for (const step of steps) {
|
|
1282
|
+
bytes += JSON.stringify(step ?? null).length + 1;
|
|
1283
|
+
if (bytes > PERMISSION_SNAPSHOT_LIMIT)
|
|
1284
|
+
break;
|
|
1285
|
+
kept.push(step);
|
|
1286
|
+
}
|
|
1287
|
+
return kept;
|
|
1288
|
+
}
|
|
1289
|
+
function unwrapExecOutput(value) {
|
|
1290
|
+
if (typeof value !== "string")
|
|
1291
|
+
return { output: value };
|
|
1292
|
+
let parsed;
|
|
1293
|
+
try {
|
|
1294
|
+
parsed = JSON.parse(value);
|
|
1295
|
+
} catch {
|
|
1296
|
+
return { output: value };
|
|
1297
|
+
}
|
|
1298
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
1299
|
+
return { output: value };
|
|
1300
|
+
const bag = parsed;
|
|
1301
|
+
if (!Object.prototype.hasOwnProperty.call(bag, "output"))
|
|
1302
|
+
return { output: value };
|
|
1303
|
+
const metadata = bag["metadata"];
|
|
1304
|
+
if (!metadata || typeof metadata !== "object")
|
|
1305
|
+
return { output: value };
|
|
1306
|
+
const exitCode = num(metadata["exit_code"]);
|
|
1307
|
+
if (exitCode === void 0)
|
|
1308
|
+
return { output: value };
|
|
1309
|
+
return { output: bag["output"], exitCode };
|
|
1310
|
+
}
|
|
1268
1311
|
function jwtClaims(token) {
|
|
1269
1312
|
const parts = token.split(".");
|
|
1270
1313
|
if (parts.length !== 3 || !parts[1])
|
|
@@ -1291,6 +1334,29 @@ function emailFromAuth(raw) {
|
|
|
1291
1334
|
const email = jwtClaims(token)?.["email"];
|
|
1292
1335
|
return typeof email === "string" && email !== "" ? email : void 0;
|
|
1293
1336
|
}
|
|
1337
|
+
function repositoryIdentity(url) {
|
|
1338
|
+
if (url === void 0)
|
|
1339
|
+
return void 0;
|
|
1340
|
+
const trimmed = url.trim();
|
|
1341
|
+
if (trimmed === "")
|
|
1342
|
+
return void 0;
|
|
1343
|
+
const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(\S+)$/.exec(trimmed);
|
|
1344
|
+
if (scp)
|
|
1345
|
+
return `${scp[1]}/${stripSuffix(scp[2])}`;
|
|
1346
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `ssh://${trimmed}`;
|
|
1347
|
+
try {
|
|
1348
|
+
const parsed = new URL(withScheme);
|
|
1349
|
+
if (!parsed.hostname)
|
|
1350
|
+
return trimmed;
|
|
1351
|
+
const path = stripSuffix(parsed.pathname.replace(/^\/+/, ""));
|
|
1352
|
+
return path === "" ? parsed.hostname : `${parsed.hostname}/${path}`;
|
|
1353
|
+
} catch {
|
|
1354
|
+
return trimmed;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
function stripSuffix(path) {
|
|
1358
|
+
return path.replace(/\/+$/, "").replace(/\.git$/i, "");
|
|
1359
|
+
}
|
|
1294
1360
|
var codexTranscript = {
|
|
1295
1361
|
vendor: "codex",
|
|
1296
1362
|
sessionRoots() {
|
|
@@ -1343,12 +1409,20 @@ var codexTranscript = {
|
|
|
1343
1409
|
const uuid = `codex:${offset}`;
|
|
1344
1410
|
const base = { vendor: "codex", ts, uuid };
|
|
1345
1411
|
if (raw.type === "session_meta") {
|
|
1412
|
+
const rawGit = payload["git"];
|
|
1413
|
+
const git = rawGit && typeof rawGit === "object" ? rawGit : void 0;
|
|
1414
|
+
const gitBranch = str(git?.["branch"]);
|
|
1415
|
+
const gitRepositoryUrl = repositoryIdentity(str(git?.["repository_url"]));
|
|
1346
1416
|
return [
|
|
1347
1417
|
{
|
|
1348
1418
|
...base,
|
|
1349
1419
|
kind: "session_meta",
|
|
1350
1420
|
meta: {
|
|
1351
1421
|
...str(payload["cwd"]) !== void 0 ? { cwd: str(payload["cwd"]) } : {},
|
|
1422
|
+
// Conditional spread rather than a plain assignment: the 7 `{}` blocks and
|
|
1423
|
+
// the 92 absent ones must leave the key absent, not present-and-undefined.
|
|
1424
|
+
...gitBranch !== void 0 ? { gitBranch } : {},
|
|
1425
|
+
...gitRepositoryUrl !== void 0 ? { gitRepositoryUrl } : {},
|
|
1352
1426
|
...str(payload["cli_version"]) !== void 0 ? { version: str(payload["cli_version"]) } : {},
|
|
1353
1427
|
...str(payload["originator"]) !== void 0 ? { entrypoint: str(payload["originator"]) } : {},
|
|
1354
1428
|
// A Codex subagent is a rollout of its own, a peer of its parent in the
|
|
@@ -1364,15 +1438,61 @@ var codexTranscript = {
|
|
|
1364
1438
|
}
|
|
1365
1439
|
if (raw.type === "turn_context") {
|
|
1366
1440
|
const model = str(payload["model"]);
|
|
1367
|
-
|
|
1441
|
+
const approval = str(payload["approval_policy"]);
|
|
1442
|
+
const sandbox = payload["sandbox_policy"];
|
|
1443
|
+
const sandboxMode = (() => {
|
|
1444
|
+
if (!sandbox || typeof sandbox !== "object")
|
|
1445
|
+
return void 0;
|
|
1446
|
+
const bag = sandbox;
|
|
1447
|
+
return str(bag["mode"]) ?? str(bag["type"]);
|
|
1448
|
+
})();
|
|
1449
|
+
const meta = {
|
|
1450
|
+
...model !== void 0 ? { model } : {},
|
|
1451
|
+
...approval !== void 0 ? { approvalMode: CODEX_APPROVAL_MODES[approval] ?? approval } : {},
|
|
1452
|
+
...sandboxMode !== void 0 ? { sandboxMode: CODEX_SANDBOX_MODES[sandboxMode] ?? sandboxMode } : {}
|
|
1453
|
+
};
|
|
1454
|
+
if (Object.keys(meta).length === 0)
|
|
1368
1455
|
return [];
|
|
1369
|
-
return [{ ...base, kind: "session_meta", meta
|
|
1456
|
+
return [{ ...base, kind: "session_meta", meta }];
|
|
1370
1457
|
}
|
|
1371
1458
|
const kind = str(payload["type"]);
|
|
1372
1459
|
if (raw.type === "event_msg" && kind === "token_count") {
|
|
1373
1460
|
const usage = mapTokenCount(payload["info"]);
|
|
1374
1461
|
return usage ? [{ ...base, kind: "usage", usage }] : [];
|
|
1375
1462
|
}
|
|
1463
|
+
if (raw.type === "compacted") {
|
|
1464
|
+
const note = str(payload["message"]);
|
|
1465
|
+
return [{ ...base, kind: "compaction", ...note ? { text: note } : {} }];
|
|
1466
|
+
}
|
|
1467
|
+
if (raw.type === "world_state") {
|
|
1468
|
+
const state = payload["state"];
|
|
1469
|
+
const perms = state && typeof state === "object" ? state["permissions"] : void 0;
|
|
1470
|
+
if (!perms || typeof perms !== "object")
|
|
1471
|
+
return [];
|
|
1472
|
+
const list = perms["approved_command_prefixes"];
|
|
1473
|
+
const steps = Array.isArray(list) ? list : [];
|
|
1474
|
+
return [
|
|
1475
|
+
{
|
|
1476
|
+
...base,
|
|
1477
|
+
kind: "permission_grant",
|
|
1478
|
+
input: {
|
|
1479
|
+
steps: budgeted(steps),
|
|
1480
|
+
count: steps.length,
|
|
1481
|
+
...payload["full"] === true ? { full: true } : {}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
];
|
|
1485
|
+
}
|
|
1486
|
+
if (raw.type === "event_msg" && kind === "turn_aborted") {
|
|
1487
|
+
return [
|
|
1488
|
+
{
|
|
1489
|
+
...base,
|
|
1490
|
+
kind: "user_message",
|
|
1491
|
+
isError: true,
|
|
1492
|
+
errorKind: `aborted:${str(payload["reason"]) ?? "unknown"}`
|
|
1493
|
+
}
|
|
1494
|
+
];
|
|
1495
|
+
}
|
|
1376
1496
|
if (raw.type !== "response_item")
|
|
1377
1497
|
return [];
|
|
1378
1498
|
switch (kind) {
|
|
@@ -1386,18 +1506,74 @@ var codexTranscript = {
|
|
|
1386
1506
|
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
1387
1507
|
}
|
|
1388
1508
|
];
|
|
1389
|
-
case "function_call_output":
|
|
1509
|
+
case "function_call_output": {
|
|
1510
|
+
const flattened = textOf(payload["output"]) ?? payload["output"];
|
|
1511
|
+
const { output, exitCode } = unwrapExecOutput(flattened);
|
|
1512
|
+
return [
|
|
1513
|
+
{
|
|
1514
|
+
...base,
|
|
1515
|
+
kind: "tool_result",
|
|
1516
|
+
output,
|
|
1517
|
+
// Only where an exit status was actually found. On the 993 string outputs
|
|
1518
|
+
// that are not envelopes and the 144 array ones the key stays ABSENT rather
|
|
1519
|
+
// than `false`: the emitter tests `record.isError`, and "this tool reported
|
|
1520
|
+
// success" is a different claim from "nothing here reported an outcome".
|
|
1521
|
+
...exitCode !== void 0 ? { isError: exitCode !== 0 } : {},
|
|
1522
|
+
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
1523
|
+
}
|
|
1524
|
+
];
|
|
1525
|
+
}
|
|
1526
|
+
// Codex's OTHER tool-call shape, and the one it actually uses: `exec` and
|
|
1527
|
+
// `apply_patch` arrive as `custom_tool_call`, never as `function_call`. Measured
|
|
1528
|
+
// across 251 rollouts, 2179 of 5382 tool calls — 40% — took this shape, so
|
|
1529
|
+
// handling only `function_call` left every shell command and every file write
|
|
1530
|
+
// this endpoint made invisible to both the trace and anything reading it.
|
|
1531
|
+
case "custom_tool_call":
|
|
1532
|
+
return [
|
|
1533
|
+
{
|
|
1534
|
+
...base,
|
|
1535
|
+
kind: "tool_call",
|
|
1536
|
+
toolName: str(payload["name"]) ?? "tool",
|
|
1537
|
+
input: payload["input"],
|
|
1538
|
+
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
1539
|
+
}
|
|
1540
|
+
];
|
|
1541
|
+
case "custom_tool_call_output": {
|
|
1542
|
+
const flattened = textOf(payload["output"]) ?? payload["output"];
|
|
1543
|
+
const { output, exitCode } = unwrapExecOutput(flattened);
|
|
1390
1544
|
return [
|
|
1391
1545
|
{
|
|
1392
1546
|
...base,
|
|
1393
1547
|
kind: "tool_result",
|
|
1394
|
-
output
|
|
1548
|
+
output,
|
|
1549
|
+
...exitCode !== void 0 ? { isError: exitCode !== 0 } : {},
|
|
1395
1550
|
...str(payload["call_id"]) !== void 0 ? { toolCallId: str(payload["call_id"]) } : {}
|
|
1396
1551
|
}
|
|
1397
1552
|
];
|
|
1553
|
+
}
|
|
1554
|
+
// The model's own web reads. 49 across the corpus in 9 rollouts — `search` 31,
|
|
1555
|
+
// `open_page` 16, `find_in_page` 2 — every one of which mapped to nothing, so a
|
|
1556
|
+
// session where the agent searched and opened pages rendered as reasoning
|
|
1557
|
+
// followed by an answer with no source anywhere in it. The queries and the URLs
|
|
1558
|
+
// are the whole point of the record: they are the evidence of what the agent
|
|
1559
|
+
// read from outside the machine.
|
|
1560
|
+
//
|
|
1561
|
+
// `action` is passed through whole. It is already a vendor-neutral argument bag
|
|
1562
|
+
// ({type, query?, queries?, url?, pattern?}) and it is small — 322 bytes at the
|
|
1563
|
+
// worst, 121 on average across all 49.
|
|
1564
|
+
//
|
|
1565
|
+
// Deliberately unpaired: no `toolCallId`. Only 1 of the 49 lines carries an
|
|
1566
|
+
// `id`, `call_id` is the pairing key everywhere else in this stream, and no
|
|
1567
|
+
// response_item output line ever pairs with a search — so setting one would
|
|
1568
|
+
// register a span in the emitter's pairing table that nothing ever closes, and
|
|
1569
|
+
// `finish()` would end it at pass end and stamp a multi-minute duration on a
|
|
1570
|
+
// search that took seconds. Left unpaired, the emitter closes it at the call
|
|
1571
|
+
// timestamp, which is the truthful reading.
|
|
1572
|
+
case "web_search_call":
|
|
1573
|
+
return [{ ...base, kind: "tool_call", toolName: "web_search", input: payload["action"] }];
|
|
1398
1574
|
case "reasoning": {
|
|
1399
1575
|
const text = textOf(payload["summary"]) ?? textOf(payload["content"]);
|
|
1400
|
-
return
|
|
1576
|
+
return [{ ...base, kind: "thinking", ...text !== void 0 ? { text } : {} }];
|
|
1401
1577
|
}
|
|
1402
1578
|
case "message": {
|
|
1403
1579
|
const text = textOf(payload["content"]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@darkhunt-security/endpoint-codex",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Darkhunt endpoint adapter for Codex CLI: hook codec, rollout transcript mapper, plugin manifest.",
|
|
6
6
|
"bin": {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
".codex-plugin"
|
|
14
14
|
],
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@darkhunt-security/endpoint-contracts": "0.9.
|
|
17
|
-
"@darkhunt-security/endpoint-core": "0.9.
|
|
16
|
+
"@darkhunt-security/endpoint-contracts": "0.9.3",
|
|
17
|
+
"@darkhunt-security/endpoint-core": "0.9.3"
|
|
18
18
|
},
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public",
|