@ccpocket/bridge 1.69.0 → 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;
@@ -1805,7 +1874,11 @@ export class CodexProcess extends EventEmitter {
1805
1874
  }
1806
1875
  handleTurnCompleted(turn) {
1807
1876
  const status = String(turn?.status ?? "completed");
1877
+ if (status === "completed") {
1878
+ this.prepareTurnCompletionAgentSummary(turn);
1879
+ }
1808
1880
  this.finalizePendingAgentText();
1881
+ this.confirmedAgentTextByItemId.clear();
1809
1882
  const usage = this.lastTokenUsage;
1810
1883
  this.lastTokenUsage = null;
1811
1884
  if (status === "failed") {
@@ -1871,6 +1944,41 @@ export class CodexProcess extends EventEmitter {
1871
1944
  }
1872
1945
  this.cleanupSteerTempPaths();
1873
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
+ }
1874
1982
  finalizePendingAgentText() {
1875
1983
  const pendingItems = [...this.pendingAgentTextByItemId.entries()];
1876
1984
  this.pendingAgentTextByItemId.clear();
@@ -1902,11 +2010,6 @@ export class CodexProcess extends EventEmitter {
1902
2010
  const itemType = normalizeItemType(item.type);
1903
2011
  switch (itemType) {
1904
2012
  case "commandexecution": {
1905
- const commandText = typeof item.command === "string"
1906
- ? item.command
1907
- : Array.isArray(item.command)
1908
- ? item.command.map((part) => String(part)).join(" ")
1909
- : "";
1910
2013
  this.emitMessage({
1911
2014
  type: "assistant",
1912
2015
  message: {
@@ -1917,7 +2020,7 @@ export class CodexProcess extends EventEmitter {
1917
2020
  type: "tool_use",
1918
2021
  id: itemId,
1919
2022
  name: "Bash",
1920
- input: { command: commandText },
2023
+ input: commandExecutionToolUseInput(item),
1921
2024
  },
1922
2025
  ],
1923
2026
  model: this.getMessageModel(),
@@ -2035,13 +2138,20 @@ export class CodexProcess extends EventEmitter {
2035
2138
  const completedText = extractAgentText(item);
2036
2139
  const hasCompletedText = completedText?.trim().length > 0;
2037
2140
  const directPendingText = this.pendingAgentTextByItemId.get(itemId);
2141
+ const unknownPendingText = directPendingText === undefined
2142
+ ? this.pendingAgentTextByItemId.get(UNKNOWN_AGENT_ITEM_ID)
2143
+ : undefined;
2038
2144
  const usedUnknownPendingText = !hasCompletedText && directPendingText === undefined;
2145
+ const matchedUnknownPendingText = hasCompletedText &&
2146
+ Boolean(unknownPendingText &&
2147
+ (completedText?.startsWith(unknownPendingText) ||
2148
+ unknownPendingText.startsWith(completedText ?? "")));
2039
2149
  const pendingText = usedUnknownPendingText
2040
- ? (this.pendingAgentTextByItemId.get(UNKNOWN_AGENT_ITEM_ID) ?? "")
2150
+ ? (unknownPendingText ?? "")
2041
2151
  : (directPendingText ?? "");
2042
2152
  const text = hasCompletedText ? completedText : pendingText;
2043
2153
  this.pendingAgentTextByItemId.delete(itemId);
2044
- if (usedUnknownPendingText) {
2154
+ if (usedUnknownPendingText || matchedUnknownPendingText) {
2045
2155
  this.pendingAgentTextByItemId.delete(UNKNOWN_AGENT_ITEM_ID);
2046
2156
  }
2047
2157
  if (!text.trim())
@@ -2061,6 +2171,7 @@ export class CodexProcess extends EventEmitter {
2061
2171
  }
2062
2172
  }
2063
2173
  this.syntheticAgentTextByItemId.delete(itemId);
2174
+ this.confirmedAgentTextByItemId.set(itemId, text);
2064
2175
  this.lastResultText = text;
2065
2176
  this.emitMessage({
2066
2177
  type: "assistant",
@@ -2307,7 +2418,7 @@ export class CodexProcess extends EventEmitter {
2307
2418
  }
2308
2419
  request(method, params) {
2309
2420
  const id = this.rpcSeq++;
2310
- const envelope = { id, method, params };
2421
+ const envelope = params === undefined ? { id, method } : { id, method, params };
2311
2422
  return new Promise((resolve, reject) => {
2312
2423
  this.pendingRpc.set(id, { resolve, reject, method });
2313
2424
  try {
@@ -2750,6 +2861,20 @@ function toToolUseInput(value) {
2750
2861
  }
2751
2862
  return { value };
2752
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
+ }
2753
2878
  function toImageGenerationToolInput(item) {
2754
2879
  const input = {};
2755
2880
  const status = typeof item.status === "string" ? item.status : undefined;