@ccpocket/bridge 1.45.3 → 1.46.0

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.
@@ -40,6 +40,23 @@ export interface CodexAppMetadata {
40
40
  isAccessible: boolean;
41
41
  isEnabled: boolean;
42
42
  }
43
+ /** Plugin metadata returned by the Codex `plugin/list` RPC. */
44
+ export interface CodexPluginMetadata {
45
+ id: string;
46
+ name: string;
47
+ path: string;
48
+ marketplaceName: string;
49
+ marketplacePath?: string;
50
+ installed: boolean;
51
+ enabled: boolean;
52
+ displayName?: string;
53
+ shortDescription?: string;
54
+ longDescription?: string;
55
+ defaultPrompt?: string;
56
+ brandColor?: string;
57
+ composerIcon?: string;
58
+ composerIconUrl?: string;
59
+ }
43
60
  export interface CodexThreadSummary {
44
61
  id: string;
45
62
  preview: string;
@@ -370,7 +370,7 @@ export class CodexProcess extends EventEmitter {
370
370
  const pending = this.resolvePendingApproval(toolUseId);
371
371
  if (!pending) {
372
372
  // Fallback: McpElicitation lives in pendingUserInputs
373
- if (this.approveUserInput(toolUseId, "Accept"))
373
+ if (this.approveUserInput(toolUseId, "Allow for this session"))
374
374
  return;
375
375
  console.log("[codex-process] approveAlways() called but no pending permission requests");
376
376
  return;
@@ -398,7 +398,7 @@ export class CodexProcess extends EventEmitter {
398
398
  return;
399
399
  }
400
400
  this.pendingApprovals.delete(pending.toolUseId);
401
- this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "decline"));
401
+ this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, resolveApprovalRejectDecision(pending)));
402
402
  this.emitToolResult(pending.toolUseId, "Rejected");
403
403
  if (this.pendingApprovals.size === 0) {
404
404
  this.setStatus("running");
@@ -491,7 +491,7 @@ export class CodexProcess extends EventEmitter {
491
491
  if (!pending)
492
492
  return false;
493
493
  this.pendingUserInputs.delete(pending.toolUseId);
494
- this.respondToServerRequest(pending.requestId, buildUserInputResponse(pending, result));
494
+ this.respondToServerRequest(pending.requestId, buildUserInputResponse(pending, resolveUserInputRejectResult(pending, result)));
495
495
  this.emitToolResult(pending.toolUseId, "Rejected");
496
496
  if (this.pendingApprovals.size === 0 && this.pendingUserInputs.size === 0) {
497
497
  this.setStatus("running");
@@ -759,6 +759,13 @@ export class CodexProcess extends EventEmitter {
759
759
  async _fetchCompletionEntitiesInternal(projectPath) {
760
760
  const TIMEOUT_MS = 10_000;
761
761
  try {
762
+ const requestOrNull = (method, params) => Promise.race([
763
+ this.request(method, params).catch((err) => {
764
+ console.log(`[codex-process] ${method} failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
765
+ return null;
766
+ }),
767
+ new Promise((resolve) => setTimeout(() => resolve(null), TIMEOUT_MS)),
768
+ ]);
762
769
  const skillsResult = (await Promise.race([
763
770
  this.request("skills/list", { cwds: [projectPath] }),
764
771
  new Promise((resolve) => setTimeout(() => resolve(null), TIMEOUT_MS)),
@@ -772,6 +779,15 @@ export class CodexProcess extends EventEmitter {
772
779
  }),
773
780
  new Promise((resolve) => setTimeout(() => resolve(null), TIMEOUT_MS)),
774
781
  ]));
782
+ const pluginsResult = await requestOrNull("plugin/list", { cwds: [projectPath] });
783
+ const optionalString = (value) => typeof value === "string" ? value : undefined;
784
+ const optionalFirstString = (value) => {
785
+ if (typeof value === "string")
786
+ return value;
787
+ if (!Array.isArray(value))
788
+ return undefined;
789
+ return value.find((entry) => typeof entry === "string");
790
+ };
775
791
  const skills = [];
776
792
  const skillMetadata = [];
777
793
  if (skillsResult?.data) {
@@ -808,6 +824,30 @@ export class CodexProcess extends EventEmitter {
808
824
  isEnabled: app.isEnabled ?? true,
809
825
  }));
810
826
  this._apps = appMetadata;
827
+ const pluginMetadata = [];
828
+ for (const marketplace of pluginsResult?.marketplaces ?? []) {
829
+ for (const plugin of marketplace.plugins ?? []) {
830
+ if (!plugin.installed || !plugin.enabled)
831
+ continue;
832
+ pluginMetadata.push({
833
+ id: plugin.id,
834
+ name: plugin.name,
835
+ path: `plugin://${plugin.id}`,
836
+ marketplaceName: marketplace.name,
837
+ marketplacePath: marketplace.path ?? undefined,
838
+ installed: plugin.installed,
839
+ enabled: plugin.enabled,
840
+ displayName: optionalString(plugin.interface?.displayName),
841
+ shortDescription: optionalString(plugin.interface?.shortDescription),
842
+ longDescription: optionalString(plugin.interface?.longDescription),
843
+ defaultPrompt: optionalFirstString(plugin.interface?.defaultPrompt),
844
+ brandColor: optionalString(plugin.interface?.brandColor),
845
+ composerIcon: optionalString(plugin.interface?.composerIcon),
846
+ composerIconUrl: optionalString(plugin.interface?.composerIconUrl),
847
+ });
848
+ }
849
+ }
850
+ const plugins = pluginMetadata.map((plugin) => plugin.name);
811
851
  if (this.stopped)
812
852
  return;
813
853
  const signature = JSON.stringify({
@@ -815,13 +855,17 @@ export class CodexProcess extends EventEmitter {
815
855
  skillMetadata,
816
856
  apps: appMetadata.map((app) => app.id),
817
857
  appMetadata,
858
+ plugins,
859
+ pluginMetadata,
818
860
  });
819
861
  if (signature === this._lastCompletionEntitiesSignature) {
820
862
  return;
821
863
  }
822
864
  this._lastCompletionEntitiesSignature = signature;
823
- if (skills.length > 0 || appMetadata.length > 0) {
824
- console.log(`[codex-process] completion entities loaded: ${skills.length} skills, ${appMetadata.length} apps`);
865
+ if (skills.length > 0 ||
866
+ appMetadata.length > 0 ||
867
+ pluginMetadata.length > 0) {
868
+ console.log(`[codex-process] completion entities loaded: ${skills.length} skills, ${appMetadata.length} apps, ${pluginMetadata.length} plugins`);
825
869
  this.emitMessage({
826
870
  type: "system",
827
871
  subtype: "supported_commands",
@@ -829,6 +873,8 @@ export class CodexProcess extends EventEmitter {
829
873
  skillMetadata,
830
874
  apps: appMetadata.map((app) => app.id),
831
875
  appMetadata,
876
+ plugins,
877
+ pluginMetadata,
832
878
  });
833
879
  }
834
880
  }
@@ -1783,6 +1829,16 @@ function buildApprovalResponse(pending, decision) {
1783
1829
  decision,
1784
1830
  };
1785
1831
  }
1832
+ function resolveApprovalRejectDecision(pending) {
1833
+ const availableDecisions = pending.input.availableDecisions;
1834
+ if (!Array.isArray(availableDecisions))
1835
+ return "decline";
1836
+ const decisions = new Set(availableDecisions.filter((entry) => typeof entry === "string"));
1837
+ if (decisions.has("cancel") && !decisions.has("decline")) {
1838
+ return "cancel";
1839
+ }
1840
+ return "decline";
1841
+ }
1786
1842
  function buildUserInputResponse(pending, rawResult) {
1787
1843
  if (pending.kind === "questions") {
1788
1844
  return {
@@ -1791,6 +1847,18 @@ function buildUserInputResponse(pending, rawResult) {
1791
1847
  }
1792
1848
  return buildElicitationResponse(pending, rawResult);
1793
1849
  }
1850
+ function resolveUserInputRejectResult(pending, fallback) {
1851
+ if (pending.kind !== "elicitation_approval")
1852
+ return fallback;
1853
+ const availableDecisions = pending.input.availableDecisions;
1854
+ if (!Array.isArray(availableDecisions))
1855
+ return fallback;
1856
+ const decisions = new Set(availableDecisions.filter((entry) => typeof entry === "string"));
1857
+ if (decisions.has("cancel") && !decisions.has("decline")) {
1858
+ return "Cancel";
1859
+ }
1860
+ return fallback;
1861
+ }
1794
1862
  function extractWritableRootsFromConfigRead(response) {
1795
1863
  if (!response || typeof response !== "object")
1796
1864
  return [];
@@ -2410,8 +2478,9 @@ function createElicitationInput(params) {
2410
2478
  }
2411
2479
  const schema = asRecord(params.requestedSchema);
2412
2480
  const elicitationMeta = asRecord(params._meta);
2413
- if (isToolApprovalElicitation(schema, elicitationMeta)) {
2481
+ if (isApprovalActionElicitation(schema, elicitationMeta)) {
2414
2482
  const questionId = "approval";
2483
+ const isToolApproval = isToolApprovalElicitation(elicitationMeta);
2415
2484
  return {
2416
2485
  kind: "elicitation_approval",
2417
2486
  questions: [{ id: questionId, question: message }],
@@ -2420,12 +2489,13 @@ function createElicitationInput(params) {
2420
2489
  serverName,
2421
2490
  message,
2422
2491
  _meta: elicitationMeta ?? null,
2492
+ availableDecisions: buildApprovalActionElicitationAvailableDecisions(elicitationMeta, isToolApproval),
2423
2493
  questions: [
2424
2494
  {
2425
2495
  id: questionId,
2426
2496
  header: "Approve app tool call?",
2427
2497
  question: message,
2428
- options: buildToolApprovalElicitationOptions(elicitationMeta),
2498
+ options: buildApprovalActionElicitationOptions(elicitationMeta, isToolApproval),
2429
2499
  multiSelect: false,
2430
2500
  isOther: false,
2431
2501
  isSecret: false,
@@ -2504,10 +2574,8 @@ function createElicitationInput(params) {
2504
2574
  },
2505
2575
  };
2506
2576
  }
2507
- function isToolApprovalElicitation(schema, meta) {
2508
- if (meta?.codex_approval_kind !== "mcp_tool_call")
2509
- return false;
2510
- return isEmptyObjectSchema(schema);
2577
+ function isApprovalActionElicitation(schema, meta) {
2578
+ return isEmptyObjectSchema(schema) && !isToolSuggestionElicitation(meta);
2511
2579
  }
2512
2580
  function isEmptyObjectSchema(schema) {
2513
2581
  if (!schema)
@@ -2517,29 +2585,63 @@ function isEmptyObjectSchema(schema) {
2517
2585
  const properties = asRecord(schema.properties);
2518
2586
  return properties != null && Object.keys(properties).length === 0;
2519
2587
  }
2520
- function buildToolApprovalElicitationOptions(meta) {
2588
+ function isToolApprovalElicitation(meta) {
2589
+ return meta?.codex_approval_kind === "mcp_tool_call";
2590
+ }
2591
+ function isToolSuggestionElicitation(meta) {
2592
+ return meta?.codex_approval_kind === "tool_suggestion";
2593
+ }
2594
+ function buildApprovalActionElicitationOptions(meta, isToolApproval) {
2521
2595
  const persistModes = extractPersistModes(meta);
2522
2596
  const options = [
2523
- { label: "Allow", description: "Run the tool and continue." },
2597
+ {
2598
+ label: "Allow",
2599
+ description: isToolApproval
2600
+ ? "Run the tool and continue."
2601
+ : "Allow this request and continue.",
2602
+ },
2524
2603
  ];
2525
2604
  if (persistModes.has("session")) {
2526
2605
  options.push({
2527
2606
  label: "Allow for this session",
2528
- description: "Run the tool and remember this choice for this session.",
2607
+ description: isToolApproval
2608
+ ? "Run the tool and remember this choice for this session."
2609
+ : "Allow this request and remember this choice for this session.",
2529
2610
  });
2530
2611
  }
2531
2612
  if (persistModes.has("always")) {
2532
2613
  options.push({
2533
2614
  label: "Always allow",
2534
- description: "Run the tool and remember this choice for future tool calls.",
2615
+ description: isToolApproval
2616
+ ? "Run the tool and remember this choice for future tool calls."
2617
+ : "Allow this request and remember this choice for future requests.",
2535
2618
  });
2536
2619
  }
2537
- options.push({
2538
- label: "Cancel",
2539
- description: "Cancel this tool call.",
2540
- });
2620
+ if (!isToolApproval) {
2621
+ options.push({
2622
+ label: "Deny",
2623
+ description: "Decline this request and continue.",
2624
+ });
2625
+ }
2626
+ options.push(isToolApproval
2627
+ ? {
2628
+ label: "Cancel",
2629
+ description: "Cancel this tool call.",
2630
+ }
2631
+ : {
2632
+ label: "Cancel",
2633
+ description: "Cancel this request.",
2634
+ });
2541
2635
  return options;
2542
2636
  }
2637
+ function buildApprovalActionElicitationAvailableDecisions(meta, isToolApproval) {
2638
+ const persistModes = extractPersistModes(meta);
2639
+ return [
2640
+ "accept",
2641
+ ...(persistModes.has("session") ? ["acceptForSession"] : []),
2642
+ isToolApproval ? "cancel" : "decline",
2643
+ ];
2644
+ }
2543
2645
  function extractPersistModes(meta) {
2544
2646
  const persist = meta?.persist;
2545
2647
  const modes = new Set();