@basou/core 0.34.0 → 0.35.1

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.d.ts CHANGED
@@ -857,11 +857,20 @@ type CodexRolloutToPayloadOptions = {
857
857
  * provenance-level events from the rollout's message-level records:
858
858
  *
859
859
  * - `session_started` / `session_ended` from the first / last timestamped record.
860
- * - `command_executed` from each `exec_command` function call, recorded as
861
- * `bash -c "<cmd>"`. The shell line and working directory come from the
862
- * call's JSON `arguments` (`{ cmd, workdir }`); the exit code and duration
863
- * are parsed from the paired `function_call_output` (matched by `call_id`),
864
- * whose text carries `Process exited with code N` and `Wall time: X seconds`.
860
+ * - `command_executed` from each shell execution, recorded as `bash -c "<cmd>"`.
861
+ * Codex has written these two different ways, and BOTH are read (an operator's
862
+ * `~/.codex/sessions` holds a mix across a CLI upgrade):
863
+ * - one `function_call` named `exec_command` per command, with JSON
864
+ * `arguments` (`{ cmd, workdir }`) and a `function_call_output` whose text
865
+ * carries `Process exited with code N` / `Wall time: X seconds`;
866
+ * - a scripted `custom_tool_call` (see
867
+ * {@link readExecCommandsFromScript}) whose `input` is a JS program that
868
+ * calls `tools.exec_command({ cmd, workdir })` — possibly several times in
869
+ * one call — and whose output reports only `Wall time X seconds` for the
870
+ * whole script, with no per-command exit code.
871
+ * Reading only the first shape made every session recorded by a CLI that had
872
+ * moved to the second derive ZERO commands, so the whole session was dropped
873
+ * as "no actions" — capture went silently blind rather than degrading.
865
874
  *
866
875
  * Per-session `metrics` are also derived: token totals from the cumulative
867
876
  * `token_count` events; active time from the real `task_started` ->
package/dist/index.js CHANGED
@@ -701,6 +701,32 @@ function codexRolloutToImportPayload(records, options) {
701
701
  continue;
702
702
  }
703
703
  if (readString4(record.type) !== "response_item") continue;
704
+ if (readString4(payload2.type) === "custom_tool_call") {
705
+ if (readString4(payload2.name) !== SCRIPT_TOOL_NAME) continue;
706
+ const scan = scanScript(readString4(payload2.input));
707
+ if (scan.commands.length === 0) continue;
708
+ const output2 = readCallId(payload2.call_id, outputsByCallId);
709
+ const durationMs = scan.toolCallCount === 1 ? parseWallTimeMs(output2) : 0;
710
+ const scriptTsMs = Date.parse(ts);
711
+ if (Number.isFinite(scriptTsMs)) engagementTsMs.push(scriptTsMs);
712
+ for (const command2 of scan.commands) {
713
+ derived.push(
714
+ // The per-command exit code is absent from this format (the script
715
+ // would have to print it), so it stays null — "unknown", not "0".
716
+ commandExecutedEvent2(
717
+ ts,
718
+ placeholderSessionId,
719
+ command2.cmd,
720
+ command2.workdir ?? workingDir ?? ".",
721
+ {
722
+ exitCode: null,
723
+ durationMs
724
+ }
725
+ )
726
+ );
727
+ }
728
+ continue;
729
+ }
704
730
  if (readString4(payload2.type) !== "function_call") continue;
705
731
  if (readString4(payload2.name) !== "exec_command") continue;
706
732
  const command = readExecCommand(payload2.arguments);
@@ -840,6 +866,250 @@ function readCallId(value, outputs) {
840
866
  const callId = readString4(value);
841
867
  return callId !== void 0 ? outputs.get(callId) : void 0;
842
868
  }
869
+ var SCRIPT_TOOL_NAME = "exec";
870
+ var TOOL_CALL_PREFIX = "tools.";
871
+ var EXEC_TOOL = "exec_command";
872
+ function scanScript(script) {
873
+ const scan = { commands: [], toolCallCount: 0 };
874
+ if (script === void 0) return scan;
875
+ try {
876
+ collectScriptCalls(script, scan);
877
+ } catch {
878
+ }
879
+ return scan;
880
+ }
881
+ function collectScriptCalls(script, scan) {
882
+ let i = 0;
883
+ while (i < script.length) {
884
+ const skipped = skipNonCode(script, i);
885
+ if (skipped !== i) {
886
+ if (skipped === -1) return;
887
+ i = skipped;
888
+ continue;
889
+ }
890
+ if (!script.startsWith(TOOL_CALL_PREFIX, i) || isIdentifierChar(script[i - 1])) {
891
+ i++;
892
+ continue;
893
+ }
894
+ const nameStart = i + TOOL_CALL_PREFIX.length;
895
+ const nameEnd = readIdentifierEnd(script, nameStart);
896
+ const open = skipWhitespaceAndComments(script, nameEnd);
897
+ if (nameEnd === nameStart || open === -1 || script[open] !== "(") {
898
+ i = nameStart;
899
+ continue;
900
+ }
901
+ scan.toolCallCount++;
902
+ const argsStart = skipWhitespaceAndComments(script, open + 1);
903
+ i = open + 1;
904
+ if (argsStart === -1 || script[argsStart] !== "{") continue;
905
+ const argsEnd = findObjectEnd(script, argsStart);
906
+ if (argsEnd === -1) return;
907
+ i = argsStart + 1;
908
+ if (script.slice(nameStart, nameEnd) !== EXEC_TOOL) continue;
909
+ const command = readExecArguments(script.slice(argsStart, argsEnd + 1));
910
+ if (command !== void 0) scan.commands.push(command);
911
+ }
912
+ }
913
+ function isIdentifierChar(ch) {
914
+ return ch !== void 0 && /[A-Za-z0-9_$]/.test(ch);
915
+ }
916
+ function skipNonCode(script, at) {
917
+ const ch = script[at];
918
+ if (ch === '"' || ch === "'" || ch === "`") return skipStringLiteral(script, at);
919
+ if (ch !== "/") return at;
920
+ const next = script[at + 1];
921
+ if (next === "/") {
922
+ const eol = script.indexOf("\n", at + 2);
923
+ return eol === -1 ? script.length : eol + 1;
924
+ }
925
+ if (next === "*") {
926
+ const end = script.indexOf("*/", at + 2);
927
+ return end === -1 ? -1 : end + 2;
928
+ }
929
+ return at;
930
+ }
931
+ function skipStringLiteral(script, at) {
932
+ const quote = script[at];
933
+ for (let i = at + 1; i < script.length; i++) {
934
+ const ch = script[i];
935
+ if (ch === "\\") {
936
+ i++;
937
+ continue;
938
+ }
939
+ if (ch === quote) return i + 1;
940
+ if (quote === "`" && ch === "$" && script[i + 1] === "{") {
941
+ const end = findObjectEnd(script, i + 1);
942
+ if (end === -1) return -1;
943
+ i = end;
944
+ }
945
+ }
946
+ return -1;
947
+ }
948
+ function skipWhitespaceAndComments(script, at) {
949
+ let i = at;
950
+ while (i < script.length) {
951
+ if (/\s/.test(script[i] ?? "")) {
952
+ i++;
953
+ continue;
954
+ }
955
+ if (script[i] !== "/") return i;
956
+ const skipped = skipNonCode(script, i);
957
+ if (skipped === -1) return -1;
958
+ if (skipped === i) return i;
959
+ i = skipped;
960
+ }
961
+ return i;
962
+ }
963
+ function readIdentifierEnd(script, start) {
964
+ let i = start;
965
+ while (i < script.length && /[A-Za-z0-9_$]/.test(script[i] ?? "")) i++;
966
+ return i;
967
+ }
968
+ function findObjectEnd(script, start) {
969
+ const closers = { "{": "}", "(": ")", "[": "]" };
970
+ const stack = [];
971
+ let i = start;
972
+ while (i < script.length) {
973
+ const skipped = skipNonCode(script, i);
974
+ if (skipped === -1) return -1;
975
+ if (skipped !== i) {
976
+ i = skipped;
977
+ continue;
978
+ }
979
+ const ch = script[i] ?? "";
980
+ const closer = closers[ch];
981
+ if (closer !== void 0) {
982
+ stack.push(closer);
983
+ } else if (ch === "}" || ch === ")" || ch === "]") {
984
+ if (stack.pop() !== ch) return -1;
985
+ if (stack.length === 0) return i;
986
+ }
987
+ i++;
988
+ }
989
+ return -1;
990
+ }
991
+ function readExecArguments(literal) {
992
+ const values = /* @__PURE__ */ new Map();
993
+ let i = 1;
994
+ while (i < literal.length) {
995
+ const at = skipWhitespaceAndComments(literal, i);
996
+ if (at === -1) return void 0;
997
+ const ch = literal[at];
998
+ if (ch === "}" || ch === void 0) break;
999
+ if (ch === ",") {
1000
+ i = at + 1;
1001
+ continue;
1002
+ }
1003
+ if (literal.startsWith("...", at)) return void 0;
1004
+ let key;
1005
+ let afterKey;
1006
+ if (ch === '"' || ch === "'") {
1007
+ const end = skipStringLiteral(literal, at);
1008
+ if (end === -1) return void 0;
1009
+ key = literal.slice(at + 1, end - 1);
1010
+ afterKey = end;
1011
+ } else {
1012
+ afterKey = readIdentifierEnd(literal, at);
1013
+ if (afterKey === at) return void 0;
1014
+ key = literal.slice(at, afterKey);
1015
+ }
1016
+ const colon = skipWhitespaceAndComments(literal, afterKey);
1017
+ if (colon === -1) return void 0;
1018
+ if (literal[colon] !== ":") {
1019
+ if (key === "cmd" || key === "workdir") return void 0;
1020
+ i = colon;
1021
+ const next2 = skipToPropertyEnd(literal, colon);
1022
+ if (next2 === -1) return void 0;
1023
+ i = next2;
1024
+ continue;
1025
+ }
1026
+ const valueAt = skipWhitespaceAndComments(literal, colon + 1);
1027
+ if (valueAt === -1) return void 0;
1028
+ const valueChar = literal[valueAt];
1029
+ let value;
1030
+ let afterValue = valueAt;
1031
+ if (valueChar === '"' || valueChar === "'" || valueChar === "`") {
1032
+ const end = skipStringLiteral(literal, valueAt);
1033
+ if (end === -1) return void 0;
1034
+ const decoded = unescapeScriptString(literal.slice(valueAt, end));
1035
+ afterValue = end;
1036
+ const after = skipWhitespaceAndComments(literal, end);
1037
+ if (after === -1) return void 0;
1038
+ const terminator = literal[after];
1039
+ if (terminator === "," || terminator === "}") {
1040
+ value = decoded.length > 0 ? decoded : void 0;
1041
+ afterValue = after;
1042
+ } else if (key === "cmd" || key === "workdir") {
1043
+ return void 0;
1044
+ }
1045
+ } else if (key === "cmd" || key === "workdir") {
1046
+ return void 0;
1047
+ }
1048
+ if (key === "cmd" || key === "workdir") {
1049
+ if (values.has(key)) return void 0;
1050
+ values.set(key, value);
1051
+ }
1052
+ const next = skipToPropertyEnd(literal, afterValue);
1053
+ if (next === -1) return void 0;
1054
+ i = next;
1055
+ }
1056
+ const cmd = values.get("cmd");
1057
+ if (cmd === void 0) return void 0;
1058
+ return { cmd, workdir: values.get("workdir") };
1059
+ }
1060
+ function skipToPropertyEnd(literal, at) {
1061
+ let i = at;
1062
+ while (i < literal.length) {
1063
+ const skipped = skipNonCode(literal, i);
1064
+ if (skipped === -1) return -1;
1065
+ if (skipped !== i) {
1066
+ i = skipped;
1067
+ continue;
1068
+ }
1069
+ const ch = literal[i];
1070
+ if (ch === "," || ch === "}") return i;
1071
+ if (ch === "{" || ch === "(" || ch === "[") {
1072
+ const end = findObjectEnd(literal, i);
1073
+ if (end === -1) return -1;
1074
+ i = end + 1;
1075
+ continue;
1076
+ }
1077
+ i++;
1078
+ }
1079
+ return -1;
1080
+ }
1081
+ var SCRIPT_STRING_ESCAPES = {
1082
+ n: "\n",
1083
+ r: "\r",
1084
+ t: " ",
1085
+ b: "\b",
1086
+ f: "\f",
1087
+ v: "\v",
1088
+ "0": "\0"
1089
+ };
1090
+ function unescapeScriptString(quoted) {
1091
+ return quoted.slice(1, -1).replace(
1092
+ /\\(u\{[0-9a-fA-F]{1,6}\}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|\r\n|[\s\S])/g,
1093
+ (match, sequence) => {
1094
+ if (sequence.length <= 2) {
1095
+ if (sequence === "\n" || sequence === "\r" || sequence === "\r\n") return "";
1096
+ if (sequence === "u" || sequence === "x") return match;
1097
+ return SCRIPT_STRING_ESCAPES[sequence] ?? sequence;
1098
+ }
1099
+ const hex = sequence.startsWith("u{") ? sequence.slice(2, -1) : sequence.slice(1);
1100
+ return codePointOrLiteral(hex, match);
1101
+ }
1102
+ );
1103
+ }
1104
+ function codePointOrLiteral(hex, literal) {
1105
+ const code = Number.parseInt(hex, 16);
1106
+ if (!Number.isInteger(code) || code < 0 || code > 1114111) return literal;
1107
+ try {
1108
+ return String.fromCodePoint(code);
1109
+ } catch {
1110
+ return literal;
1111
+ }
1112
+ }
843
1113
  function turnIntervalFromComplete(endTs, payload, startMsByTurnId) {
844
1114
  const endMs = Date.parse(endTs);
845
1115
  if (!Number.isFinite(endMs)) return void 0;
@@ -871,7 +1141,7 @@ function parseExitCode(output) {
871
1141
  }
872
1142
  function parseWallTimeMs(output) {
873
1143
  if (output === void 0) return 0;
874
- const match = output.match(/Wall time:\s*([\d.]+)\s*seconds/);
1144
+ const match = output.match(/Wall time:?\s*([\d.]+)\s*seconds/);
875
1145
  if (match?.[1] === void 0) return 0;
876
1146
  const seconds = Number.parseFloat(match[1]);
877
1147
  return Number.isFinite(seconds) ? Math.round(seconds * 1e3) : 0;
@@ -882,13 +1152,20 @@ function indexOutputs(records) {
882
1152
  if (readString4(record.type) !== "response_item") continue;
883
1153
  const payload = isObject4(record.payload) ? record.payload : void 0;
884
1154
  if (payload === void 0) continue;
885
- if (readString4(payload.type) !== "function_call_output") continue;
1155
+ const payloadType = readString4(payload.type);
1156
+ const output = payloadType === "function_call_output" ? readString4(payload.output) : payloadType === "custom_tool_call_output" ? readOutputParts(payload.output) : void 0;
886
1157
  const callId = readString4(payload.call_id);
887
- const output = readString4(payload.output);
888
1158
  if (callId !== void 0 && output !== void 0) byId.set(callId, output);
889
1159
  }
890
1160
  return byId;
891
1161
  }
1162
+ function readOutputParts(value) {
1163
+ const asString = readString4(value);
1164
+ if (asString !== void 0) return asString;
1165
+ if (!Array.isArray(value)) return void 0;
1166
+ const texts = value.map((part) => isObject4(part) ? readString4(part.text) : void 0).filter((text) => text !== void 0);
1167
+ return texts.length > 0 ? texts.join("\n") : void 0;
1168
+ }
892
1169
 
893
1170
  // src/approval/approval-store.ts
894
1171
  import { readdir } from "fs/promises";