@ccpocket/bridge 1.68.2 → 1.69.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.
@@ -16,6 +16,11 @@ export interface CodexStartOptions {
16
16
  networkAccessEnabled?: boolean;
17
17
  webSearchMode?: "disabled" | "cached" | "live";
18
18
  collaborationMode?: "plan" | "default";
19
+ /**
20
+ * Managed Browser Use policy cached by Bridge metadata loading.
21
+ * `null` means app-server must read the policy before starting the thread.
22
+ */
23
+ autoReviewDisabledByPolicy?: boolean | null;
19
24
  }
20
25
  export interface CodexProcessEvents {
21
26
  message: [ServerMessage];
@@ -73,10 +78,25 @@ export interface CodexThreadSummary {
73
78
  name: string | null;
74
79
  }
75
80
  export type CodexThreadSourceKind = "cli" | "vscode" | "exec" | "appServer" | "subAgent" | "subAgentReview" | "subAgentCompact" | "subAgentThreadSpawn" | "subAgentOther" | "unknown";
81
+ export declare class CodexRpcError extends Error {
82
+ readonly method: string;
83
+ readonly code: number | undefined;
84
+ readonly data: unknown;
85
+ constructor(method: string, error: {
86
+ code?: number;
87
+ message?: string;
88
+ data?: unknown;
89
+ });
90
+ }
91
+ export declare function isCodexThreadWriterConflict(error: unknown): boolean;
92
+ export declare function codexErrorMessage(error: unknown): string;
76
93
  export interface CodexProfileConfig {
77
94
  profiles: string[];
78
95
  defaultProfile?: string;
79
96
  }
97
+ export interface CodexConfigRequirements {
98
+ autoReviewDisabled: boolean;
99
+ }
80
100
  export interface CodexModelMetadata {
81
101
  model: string;
82
102
  supportedReasoningEfforts: string[];
@@ -120,6 +140,7 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
120
140
  private _approvalPolicy;
121
141
  private _approvalsReviewer;
122
142
  private _codexPermissionsMode;
143
+ private _autoReviewDisabledByPolicy;
123
144
  private _collaborationMode;
124
145
  private _runtimeModel;
125
146
  private _runtimeModelReasoningEffort;
@@ -129,6 +150,8 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
129
150
  private lastResultText;
130
151
  /** Agent text received as deltas but not yet confirmed by item/completed. */
131
152
  private readonly pendingAgentTextByItemId;
153
+ /** Agent text confirmed by item/completed during the active turn. */
154
+ private readonly confirmedAgentTextByItemId;
132
155
  /** Suppresses late item/completed events after synthetic fallbacks. */
133
156
  private readonly syntheticAgentTextByItemId;
134
157
  private pendingPlanCompletion;
@@ -300,6 +323,7 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
300
323
  private resolveWritableRootsConfig;
301
324
  private initializeRpcConnection;
302
325
  readProfileConfig(cwd?: string): Promise<CodexProfileConfig>;
326
+ readConfigRequirements(): Promise<CodexConfigRequirements>;
303
327
  private fetchCompletionEntities;
304
328
  private scheduleCompletionFetchFromNotification;
305
329
  private _fetchCompletionEntitiesInternal;
@@ -312,6 +336,7 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
312
336
  private handleNotification;
313
337
  private isForeignThreadNotification;
314
338
  private handleTurnCompleted;
339
+ private prepareTurnCompletionAgentSummary;
315
340
  private finalizePendingAgentText;
316
341
  private cleanupSteerTempPaths;
317
342
  private processItemStarted;
@@ -11,6 +11,29 @@ const DEFAULT_CODEX_MODEL = "gpt-5.5";
11
11
  const COMPLETION_FETCH_COOLDOWN_MS = 1000;
12
12
  const UNKNOWN_AGENT_ITEM_ID = "__unknown_agent_message__";
13
13
  const CODEX_CLI_NOT_FOUND_MESSAGE = "Codex CLI is not installed or not available on PATH on the Bridge machine. Install it with `curl -fsSL https://chatgpt.com/codex/install.sh | sh`, then restart Bridge.";
14
+ export class CodexRpcError extends Error {
15
+ method;
16
+ code;
17
+ data;
18
+ constructor(method, error) {
19
+ super(error.message ?? `RPC error ${error.code ?? ""}`.trim());
20
+ this.name = "CodexRpcError";
21
+ this.method = method;
22
+ this.code = error.code;
23
+ this.data = error.data;
24
+ }
25
+ }
26
+ export function isCodexThreadWriterConflict(error) {
27
+ return (error instanceof CodexRpcError &&
28
+ error.code === -32600 &&
29
+ /\b(active|live local)\s+writer\b/i.test(error.message));
30
+ }
31
+ export function codexErrorMessage(error) {
32
+ if (isCodexThreadWriterConflict(error)) {
33
+ return "This Codex thread is already open in another client. Close it there and try again.";
34
+ }
35
+ return error instanceof Error ? error.message : String(error);
36
+ }
14
37
  function isCodexCliNotFoundError(err) {
15
38
  const code = err.code;
16
39
  return (code === "ENOENT" ||
@@ -71,6 +94,7 @@ export class CodexProcess extends EventEmitter {
71
94
  _approvalPolicy = undefined;
72
95
  _approvalsReviewer = undefined;
73
96
  _codexPermissionsMode;
97
+ _autoReviewDisabledByPolicy = false;
74
98
  _collaborationMode = "default";
75
99
  _runtimeModel;
76
100
  _runtimeModelReasoningEffort;
@@ -80,6 +104,8 @@ export class CodexProcess extends EventEmitter {
80
104
  lastResultText = null;
81
105
  /** Agent text received as deltas but not yet confirmed by item/completed. */
82
106
  pendingAgentTextByItemId = new Map();
107
+ /** Agent text confirmed by item/completed during the active turn. */
108
+ confirmedAgentTextByItemId = new Map();
83
109
  /** Suppresses late item/completed events after synthetic fallbacks. */
84
110
  syntheticAgentTextByItemId = new Map();
85
111
  pendingPlanCompletion = null;
@@ -169,7 +195,9 @@ export class CodexProcess extends EventEmitter {
169
195
  * Takes effect on the next `turn/start` RPC call.
170
196
  */
171
197
  setApprovalsReviewer(reviewer) {
172
- this._approvalsReviewer = normalizeApprovalsReviewerForAppServer(reviewer);
198
+ this._approvalsReviewer = this._autoReviewDisabledByPolicy
199
+ ? "user"
200
+ : normalizeApprovalsReviewerForAppServer(reviewer);
173
201
  console.log(`[codex-process] Approvals reviewer changed to: ${this.approvalsReviewer}`);
174
202
  }
175
203
  /**
@@ -425,6 +453,8 @@ export class CodexProcess extends EventEmitter {
425
453
  ? undefined
426
454
  : normalizeApprovalsReviewerForAppServer(options.approvalsReviewer);
427
455
  this._codexPermissionsMode = options?.codexPermissionsMode;
456
+ this._autoReviewDisabledByPolicy =
457
+ options?.autoReviewDisabledByPolicy === true;
428
458
  this._collaborationMode = options?.collaborationMode ?? "default";
429
459
  this.lastPlanItemText = null;
430
460
  this.lastResultText = null;
@@ -434,6 +464,7 @@ export class CodexProcess extends EventEmitter {
434
464
  this._lastCompletionEntitiesSignature = null;
435
465
  this._launchStartedAt = Date.now();
436
466
  this.pendingAgentTextByItemId.clear();
467
+ this.confirmedAgentTextByItemId.clear();
437
468
  this.syntheticAgentTextByItemId.clear();
438
469
  this.pendingPlanCompletion = null;
439
470
  this._pendingPlanInput = null;
@@ -888,13 +919,31 @@ export class CodexProcess extends EventEmitter {
888
919
  async bootstrap(projectPath, options) {
889
920
  try {
890
921
  await this.initializeRpcConnection();
922
+ const autoReviewDisabled = options?.autoReviewDisabledByPolicy === null
923
+ ? (await this.readConfigRequirements()).autoReviewDisabled
924
+ : options?.autoReviewDisabledByPolicy === true;
925
+ this._autoReviewDisabledByPolicy = autoReviewDisabled;
926
+ const effectiveApprovalsReviewer = autoReviewDisabled
927
+ ? "user"
928
+ : options?.approvalsReviewer;
929
+ const effectiveCodexPermissionsMode = autoReviewDisabled && options?.codexPermissionsMode === "autoReview"
930
+ ? "default"
931
+ : options?.codexPermissionsMode;
932
+ if (autoReviewDisabled) {
933
+ console.warn("[codex-process] Auto-review disabled by managed Browser Use policy");
934
+ }
935
+ this._approvalsReviewer =
936
+ effectiveApprovalsReviewer === undefined
937
+ ? undefined
938
+ : normalizeApprovalsReviewerForAppServer(effectiveApprovalsReviewer);
939
+ this._codexPermissionsMode = effectiveCodexPermissionsMode;
891
940
  const requestedApprovalPolicy = options?.approvalPolicy
892
941
  ? normalizeApprovalPolicy(options.approvalPolicy)
893
942
  : undefined;
894
- const requestedApprovalsReviewer = options?.approvalsReviewer === undefined
943
+ const requestedApprovalsReviewer = effectiveApprovalsReviewer === undefined
895
944
  ? undefined
896
- : normalizeApprovalsReviewerForAppServer(options.approvalsReviewer);
897
- const requestedClientApprovalsReviewer = normalizeApprovalsReviewerForClient(options?.approvalsReviewer);
945
+ : normalizeApprovalsReviewerForAppServer(effectiveApprovalsReviewer);
946
+ const requestedClientApprovalsReviewer = normalizeApprovalsReviewerForClient(effectiveApprovalsReviewer);
898
947
  const requestedSandboxMode = options?.sandboxMode
899
948
  ? normalizeSandboxMode(options.sandboxMode)
900
949
  : undefined;
@@ -970,14 +1019,17 @@ export class CodexProcess extends EventEmitter {
970
1019
  this.startModel = thread.model;
971
1020
  }
972
1021
  const resolvedSettings = extractResolvedSettingsFromThreadResponse(response);
1022
+ const resolvedApprovalsReviewer = autoReviewDisabled
1023
+ ? "user"
1024
+ : resolvedSettings.approvalsReviewer;
973
1025
  if (resolvedSettings.model) {
974
1026
  this.startModel = resolvedSettings.model;
975
1027
  }
976
1028
  if (resolvedSettings.approvalPolicy) {
977
1029
  this._approvalPolicy = resolvedSettings.approvalPolicy;
978
1030
  }
979
- if (resolvedSettings.approvalsReviewer) {
980
- this._approvalsReviewer = normalizeApprovalsReviewerForAppServer(resolvedSettings.approvalsReviewer);
1031
+ if (resolvedApprovalsReviewer) {
1032
+ this._approvalsReviewer = normalizeApprovalsReviewerForAppServer(resolvedApprovalsReviewer);
981
1033
  }
982
1034
  this._threadId = threadId;
983
1035
  this._agentNickname = stringOrNull(thread?.agentNickname);
@@ -996,18 +1048,18 @@ export class CodexProcess extends EventEmitter {
996
1048
  approvalPolicy: resolvedSettings.approvalPolicy ?? requestedApprovalPolicy,
997
1049
  }
998
1050
  : {}),
999
- ...(resolvedSettings.approvalsReviewer ?? options?.approvalsReviewer
1051
+ ...(resolvedApprovalsReviewer ?? effectiveApprovalsReviewer
1000
1052
  ? {
1001
- approvalsReviewer: resolvedSettings.approvalsReviewer
1002
- ? normalizeApprovalsReviewerForClient(resolvedSettings.approvalsReviewer)
1053
+ approvalsReviewer: resolvedApprovalsReviewer
1054
+ ? normalizeApprovalsReviewerForClient(resolvedApprovalsReviewer)
1003
1055
  : requestedClientApprovalsReviewer,
1004
1056
  }
1005
1057
  : {}),
1006
1058
  ...(resolvedSettings.sandboxMode ?? options?.sandboxMode
1007
1059
  ? { sandboxMode: resolvedSettings.sandboxMode ?? requestedSandboxMode }
1008
1060
  : {}),
1009
- ...(options?.codexPermissionsMode
1010
- ? { codexPermissionsMode: options.codexPermissionsMode }
1061
+ ...(effectiveCodexPermissionsMode
1062
+ ? { codexPermissionsMode: effectiveCodexPermissionsMode }
1011
1063
  : {}),
1012
1064
  ...(resolvedSettings.modelReasoningEffort
1013
1065
  ? { modelReasoningEffort: resolvedSettings.modelReasoningEffort }
@@ -1038,7 +1090,7 @@ export class CodexProcess extends EventEmitter {
1038
1090
  }
1039
1091
  catch (err) {
1040
1092
  if (!this.stopped) {
1041
- const message = err instanceof Error ? err.message : String(err);
1093
+ const message = codexErrorMessage(err);
1042
1094
  console.error("[codex-process] bootstrap error:", err);
1043
1095
  this.emitMessage({ type: "error", message: `Codex error: ${message}` });
1044
1096
  this.emitMessage({
@@ -1090,6 +1142,23 @@ export class CodexProcess extends EventEmitter {
1090
1142
  : undefined,
1091
1143
  };
1092
1144
  }
1145
+ async readConfigRequirements() {
1146
+ try {
1147
+ const response = (await this.request("configRequirements/read"));
1148
+ const requirements = asRecord(response.requirements);
1149
+ const browserUse = asRecord(requirements?.browserUse ?? requirements?.browser_use);
1150
+ return {
1151
+ autoReviewDisabled: (browserUse?.disableAutoReview ??
1152
+ browserUse?.disable_auto_review) === true,
1153
+ };
1154
+ }
1155
+ catch (err) {
1156
+ if (err instanceof CodexRpcError && err.code === -32601) {
1157
+ return { autoReviewDisabled: false };
1158
+ }
1159
+ throw err;
1160
+ }
1161
+ }
1093
1162
  async fetchCompletionEntities(projectPath) {
1094
1163
  if (this._completionFetchInFlight) {
1095
1164
  return this._completionFetchInFlight;
@@ -1403,8 +1472,7 @@ export class CodexProcess extends EventEmitter {
1403
1472
  return;
1404
1473
  this.pendingRpc.delete(envelope.id);
1405
1474
  if ("error" in envelope && envelope.error) {
1406
- const message = envelope.error.message ?? `RPC error ${envelope.error.code ?? ""}`;
1407
- pending.reject(new Error(message));
1475
+ pending.reject(new CodexRpcError(pending.method, envelope.error));
1408
1476
  return;
1409
1477
  }
1410
1478
  pending.resolve(envelope.result);
@@ -1606,6 +1674,7 @@ export class CodexProcess extends EventEmitter {
1606
1674
  }
1607
1675
  this.lastResultText = null;
1608
1676
  this.pendingAgentTextByItemId.clear();
1677
+ this.confirmedAgentTextByItemId.clear();
1609
1678
  this.syntheticAgentTextByItemId.clear();
1610
1679
  this.setStatus("running");
1611
1680
  break;
@@ -1737,10 +1806,24 @@ export class CodexProcess extends EventEmitter {
1737
1806
  const message = stringValue(params.message);
1738
1807
  if (!message)
1739
1808
  break;
1740
- if (isInformationalGuardianApproval(message)) {
1809
+ const approval = parseGuardianApproval(message);
1810
+ if (approval?.risk === "low" &&
1811
+ isLowRiskAllowDecision(approval.reason)) {
1741
1812
  console.debug("[codex-process] suppressed informational guardian approval notification");
1742
1813
  break;
1743
1814
  }
1815
+ if (approval &&
1816
+ (approval.risk === "medium" || approval.risk === "high")) {
1817
+ this.emitMessage({
1818
+ type: "guardian_approval",
1819
+ risk: approval.risk,
1820
+ reason: approval.reason,
1821
+ ...(approval.authorization
1822
+ ? { authorization: approval.authorization }
1823
+ : {}),
1824
+ });
1825
+ break;
1826
+ }
1744
1827
  this.emitMessage({
1745
1828
  type: "error",
1746
1829
  errorCode: "codex_warning",
@@ -1791,7 +1874,11 @@ export class CodexProcess extends EventEmitter {
1791
1874
  }
1792
1875
  handleTurnCompleted(turn) {
1793
1876
  const status = String(turn?.status ?? "completed");
1877
+ if (status === "completed") {
1878
+ this.prepareTurnCompletionAgentSummary(turn);
1879
+ }
1794
1880
  this.finalizePendingAgentText();
1881
+ this.confirmedAgentTextByItemId.clear();
1795
1882
  const usage = this.lastTokenUsage;
1796
1883
  this.lastTokenUsage = null;
1797
1884
  if (status === "failed") {
@@ -1857,6 +1944,41 @@ export class CodexProcess extends EventEmitter {
1857
1944
  }
1858
1945
  this.cleanupSteerTempPaths();
1859
1946
  }
1947
+ prepareTurnCompletionAgentSummary(turn) {
1948
+ if (turn?.itemsView !== "summary" || !Array.isArray(turn.items))
1949
+ return;
1950
+ const summaryItem = [...turn.items]
1951
+ .reverse()
1952
+ .find((item) => typeof item === "object" &&
1953
+ item !== null &&
1954
+ normalizeItemType(item.type) ===
1955
+ "agentmessage" &&
1956
+ Boolean(extractAgentText(item)?.trim()));
1957
+ if (!summaryItem)
1958
+ return;
1959
+ const itemId = typeof summaryItem.id === "string"
1960
+ ? summaryItem.id
1961
+ : UNKNOWN_AGENT_ITEM_ID;
1962
+ const summaryText = extractAgentText(summaryItem);
1963
+ if (!summaryText?.trim())
1964
+ return;
1965
+ const confirmedText = this.confirmedAgentTextByItemId.get(itemId);
1966
+ if (confirmedText !== undefined) {
1967
+ this.pendingAgentTextByItemId.delete(itemId);
1968
+ this.lastResultText = confirmedText;
1969
+ return;
1970
+ }
1971
+ if (itemId !== UNKNOWN_AGENT_ITEM_ID &&
1972
+ !this.pendingAgentTextByItemId.has(itemId)) {
1973
+ const unknownText = this.pendingAgentTextByItemId.get(UNKNOWN_AGENT_ITEM_ID);
1974
+ if (unknownText &&
1975
+ (summaryText.startsWith(unknownText) ||
1976
+ unknownText.startsWith(summaryText))) {
1977
+ this.pendingAgentTextByItemId.delete(UNKNOWN_AGENT_ITEM_ID);
1978
+ }
1979
+ }
1980
+ this.pendingAgentTextByItemId.set(itemId, summaryText);
1981
+ }
1860
1982
  finalizePendingAgentText() {
1861
1983
  const pendingItems = [...this.pendingAgentTextByItemId.entries()];
1862
1984
  this.pendingAgentTextByItemId.clear();
@@ -1888,11 +2010,6 @@ export class CodexProcess extends EventEmitter {
1888
2010
  const itemType = normalizeItemType(item.type);
1889
2011
  switch (itemType) {
1890
2012
  case "commandexecution": {
1891
- const commandText = typeof item.command === "string"
1892
- ? item.command
1893
- : Array.isArray(item.command)
1894
- ? item.command.map((part) => String(part)).join(" ")
1895
- : "";
1896
2013
  this.emitMessage({
1897
2014
  type: "assistant",
1898
2015
  message: {
@@ -1903,7 +2020,7 @@ export class CodexProcess extends EventEmitter {
1903
2020
  type: "tool_use",
1904
2021
  id: itemId,
1905
2022
  name: "Bash",
1906
- input: { command: commandText },
2023
+ input: commandExecutionToolUseInput(item),
1907
2024
  },
1908
2025
  ],
1909
2026
  model: this.getMessageModel(),
@@ -2021,13 +2138,20 @@ export class CodexProcess extends EventEmitter {
2021
2138
  const completedText = extractAgentText(item);
2022
2139
  const hasCompletedText = completedText?.trim().length > 0;
2023
2140
  const directPendingText = this.pendingAgentTextByItemId.get(itemId);
2141
+ const unknownPendingText = directPendingText === undefined
2142
+ ? this.pendingAgentTextByItemId.get(UNKNOWN_AGENT_ITEM_ID)
2143
+ : undefined;
2024
2144
  const usedUnknownPendingText = !hasCompletedText && directPendingText === undefined;
2145
+ const matchedUnknownPendingText = hasCompletedText &&
2146
+ Boolean(unknownPendingText &&
2147
+ (completedText?.startsWith(unknownPendingText) ||
2148
+ unknownPendingText.startsWith(completedText ?? "")));
2025
2149
  const pendingText = usedUnknownPendingText
2026
- ? (this.pendingAgentTextByItemId.get(UNKNOWN_AGENT_ITEM_ID) ?? "")
2150
+ ? (unknownPendingText ?? "")
2027
2151
  : (directPendingText ?? "");
2028
2152
  const text = hasCompletedText ? completedText : pendingText;
2029
2153
  this.pendingAgentTextByItemId.delete(itemId);
2030
- if (usedUnknownPendingText) {
2154
+ if (usedUnknownPendingText || matchedUnknownPendingText) {
2031
2155
  this.pendingAgentTextByItemId.delete(UNKNOWN_AGENT_ITEM_ID);
2032
2156
  }
2033
2157
  if (!text.trim())
@@ -2047,6 +2171,7 @@ export class CodexProcess extends EventEmitter {
2047
2171
  }
2048
2172
  }
2049
2173
  this.syntheticAgentTextByItemId.delete(itemId);
2174
+ this.confirmedAgentTextByItemId.set(itemId, text);
2050
2175
  this.lastResultText = text;
2051
2176
  this.emitMessage({
2052
2177
  type: "assistant",
@@ -2293,7 +2418,7 @@ export class CodexProcess extends EventEmitter {
2293
2418
  }
2294
2419
  request(method, params) {
2295
2420
  const id = this.rpcSeq++;
2296
- const envelope = { id, method, params };
2421
+ const envelope = params === undefined ? { id, method } : { id, method, params };
2297
2422
  return new Promise((resolve, reject) => {
2298
2423
  this.pendingRpc.set(id, { resolve, reject, method });
2299
2424
  try {
@@ -2736,6 +2861,20 @@ function toToolUseInput(value) {
2736
2861
  }
2737
2862
  return { value };
2738
2863
  }
2864
+ function commandExecutionToolUseInput(item) {
2865
+ const command = typeof item.command === "string"
2866
+ ? item.command
2867
+ : Array.isArray(item.command)
2868
+ ? item.command.map((part) => String(part)).join(" ")
2869
+ : "";
2870
+ const pluginId = stringOrNull(item.pluginId ?? item.plugin_id);
2871
+ const scriptPath = stringOrNull(item.scriptPath ?? item.script_path);
2872
+ return {
2873
+ command,
2874
+ ...(pluginId ? { pluginId } : {}),
2875
+ ...(scriptPath ? { scriptPath } : {}),
2876
+ };
2877
+ }
2739
2878
  function toImageGenerationToolInput(item) {
2740
2879
  const input = {};
2741
2880
  const status = typeof item.status === "string" ? item.status : undefined;
@@ -3040,9 +3179,34 @@ function parseResultObject(rawResult) {
3040
3179
  return { byId: {}, byQuestion: {} };
3041
3180
  }
3042
3181
  }
3043
- function isInformationalGuardianApproval(message) {
3044
- const normalized = message.trim().replace(/\s+/g, " ").toLowerCase();
3045
- return /^automatic approval review approved\b/.test(normalized);
3182
+ function parseGuardianApproval(message) {
3183
+ const match = message
3184
+ .trim()
3185
+ .match(/^automatic approval review approved\s*\(([^)]*)\)\s*:\s*([\s\S]+)$/i);
3186
+ if (!match)
3187
+ return null;
3188
+ const metadata = new Map();
3189
+ for (const field of match[1].split(",")) {
3190
+ const separator = field.indexOf(":");
3191
+ if (separator === -1)
3192
+ continue;
3193
+ metadata.set(field.slice(0, separator).trim().toLowerCase(), field.slice(separator + 1).trim());
3194
+ }
3195
+ const risk = metadata.get("risk")?.toLowerCase();
3196
+ const reason = match[2].trim();
3197
+ if (!reason || (risk !== "low" && risk !== "medium" && risk !== "high")) {
3198
+ return null;
3199
+ }
3200
+ const authorization = metadata.get("authorization");
3201
+ return {
3202
+ risk,
3203
+ reason,
3204
+ ...(authorization ? { authorization } : {}),
3205
+ };
3206
+ }
3207
+ function isLowRiskAllowDecision(reason) {
3208
+ const normalized = reason.trim().replace(/\s+/g, " ");
3209
+ return /^auto-review returned a low[- ]risk allow decision\.?$/i.test(normalized);
3046
3210
  }
3047
3211
  function normalizeAnswerValues(value) {
3048
3212
  if (typeof value === "string") {