@ccpocket/bridge 1.33.3 → 1.34.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.
@@ -27,6 +27,15 @@ export interface CodexSkillMetadata {
27
27
  defaultPrompt?: string;
28
28
  brandColor?: string;
29
29
  }
30
+ /** App / connector metadata returned by the Codex `app/list` RPC. */
31
+ export interface CodexAppMetadata {
32
+ id: string;
33
+ name: string;
34
+ description: string;
35
+ installUrl?: string;
36
+ isAccessible: boolean;
37
+ isEnabled: boolean;
38
+ }
30
39
  export interface CodexThreadSummary {
31
40
  id: string;
32
41
  preview: string;
@@ -64,10 +73,17 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
64
73
  private lastTokenUsage;
65
74
  /** Full skill metadata from the last `skills/list` response. */
66
75
  private _skills;
76
+ /** Full app metadata from the last `app/list` response. */
77
+ private _apps;
67
78
  /** Project path stored for re-fetching skills on `skills/changed`. */
68
79
  private _projectPath;
80
+ /** Prevent redundant completion fetch storms from repeated change notifications. */
81
+ private _completionFetchInFlight;
82
+ private _completionFetchQueued;
83
+ private _lastCompletionEntitiesSignature;
69
84
  /** Expose skill metadata so session/websocket can access it. */
70
85
  get skills(): CodexSkillMetadata[];
86
+ get apps(): CodexAppMetadata[];
71
87
  private rpcSeq;
72
88
  private pendingRpc;
73
89
  private stdoutBuffer;
@@ -135,6 +151,20 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
135
151
  name: string;
136
152
  path: string;
137
153
  }): void;
154
+ sendInputStructured(text: string, options?: {
155
+ images?: Array<{
156
+ base64: string;
157
+ mimeType: string;
158
+ }>;
159
+ skills?: Array<{
160
+ name: string;
161
+ path: string;
162
+ }>;
163
+ mentions?: Array<{
164
+ name: string;
165
+ path: string;
166
+ }>;
167
+ }): void;
138
168
  approve(toolUseId?: string, _updatedInput?: Record<string, unknown>): void;
139
169
  approveAlways(toolUseId?: string): void;
140
170
  reject(toolUseId?: string, _message?: string): void;
@@ -158,12 +188,8 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
158
188
  private handlePlanRejected;
159
189
  private bootstrap;
160
190
  private initializeRpcConnection;
161
- /**
162
- * Fetch skills from Codex app-server via `skills/list` RPC and emit them
163
- * as a `supported_commands` system message so the Flutter client can display
164
- * skill entries alongside built-in slash commands.
165
- */
166
- private fetchSkills;
191
+ private fetchCompletionEntities;
192
+ private _fetchCompletionEntitiesInternal;
167
193
  private runInputLoop;
168
194
  private handleStdoutChunk;
169
195
  private handleRpcEnvelope;
@@ -45,12 +45,21 @@ export class CodexProcess extends EventEmitter {
45
45
  lastTokenUsage = null;
46
46
  /** Full skill metadata from the last `skills/list` response. */
47
47
  _skills = [];
48
+ /** Full app metadata from the last `app/list` response. */
49
+ _apps = [];
48
50
  /** Project path stored for re-fetching skills on `skills/changed`. */
49
51
  _projectPath = null;
52
+ /** Prevent redundant completion fetch storms from repeated change notifications. */
53
+ _completionFetchInFlight = null;
54
+ _completionFetchQueued = false;
55
+ _lastCompletionEntitiesSignature = null;
50
56
  /** Expose skill metadata so session/websocket can access it. */
51
57
  get skills() {
52
58
  return this._skills;
53
59
  }
60
+ get apps() {
61
+ return this._apps;
62
+ }
54
63
  rpcSeq = 1;
55
64
  pendingRpc = new Map();
56
65
  stdoutBuffer = "";
@@ -273,13 +282,21 @@ export class CodexProcess extends EventEmitter {
273
282
  resolve({ text, images });
274
283
  }
275
284
  sendInputWithSkill(text, skill) {
285
+ this.sendInputStructured(text, { skills: [skill] });
286
+ }
287
+ sendInputStructured(text, options) {
276
288
  if (!this.inputResolve) {
277
- console.error("[codex-process] No pending input resolver for sendInputWithSkill");
289
+ console.error("[codex-process] No pending input resolver for sendInputStructured");
278
290
  return;
279
291
  }
280
292
  const resolve = this.inputResolve;
281
293
  this.inputResolve = null;
282
- resolve({ text, skill });
294
+ resolve({
295
+ text,
296
+ images: options?.images,
297
+ skills: options?.skills,
298
+ mentions: options?.mentions,
299
+ });
283
300
  }
284
301
  approve(toolUseId, _updatedInput) {
285
302
  // Check if this is a plan completion approval
@@ -369,7 +386,7 @@ export class CodexProcess extends EventEmitter {
369
386
  return undefined;
370
387
  return {
371
388
  toolUseId: pendingAsk.toolUseId,
372
- toolName: "AskUserQuestion",
389
+ toolName: pendingAsk.toolName,
373
390
  input: { ...pendingAsk.input },
374
391
  };
375
392
  }
@@ -526,9 +543,13 @@ export class CodexProcess extends EventEmitter {
526
543
  : {}),
527
544
  });
528
545
  this.setStatus("idle");
529
- // Fetch skills in background (non-blocking)
546
+ // Fetch skills/apps in background (non-blocking)
530
547
  this._projectPath = projectPath;
531
- void this.fetchSkills(projectPath);
548
+ setTimeout(() => {
549
+ if (!this.stopped) {
550
+ void this.fetchCompletionEntities(projectPath);
551
+ }
552
+ }, 25);
532
553
  await this.runInputLoop(options);
533
554
  }
534
555
  catch (err) {
@@ -560,58 +581,101 @@ export class CodexProcess extends EventEmitter {
560
581
  });
561
582
  this.notify("initialized", {});
562
583
  }
563
- /**
564
- * Fetch skills from Codex app-server via `skills/list` RPC and emit them
565
- * as a `supported_commands` system message so the Flutter client can display
566
- * skill entries alongside built-in slash commands.
567
- */
568
- async fetchSkills(projectPath) {
584
+ async fetchCompletionEntities(projectPath) {
585
+ if (this._completionFetchInFlight) {
586
+ this._completionFetchQueued = true;
587
+ return this._completionFetchInFlight;
588
+ }
589
+ this._completionFetchInFlight = this._fetchCompletionEntitiesInternal(projectPath);
590
+ try {
591
+ await this._completionFetchInFlight;
592
+ }
593
+ finally {
594
+ this._completionFetchInFlight = null;
595
+ if (this._completionFetchQueued && !this.stopped && this._projectPath) {
596
+ this._completionFetchQueued = false;
597
+ void this.fetchCompletionEntities(this._projectPath);
598
+ }
599
+ }
600
+ }
601
+ async _fetchCompletionEntitiesInternal(projectPath) {
569
602
  const TIMEOUT_MS = 10_000;
570
603
  try {
571
- const result = (await Promise.race([
604
+ const skillsResult = (await Promise.race([
572
605
  this.request("skills/list", { cwds: [projectPath] }),
573
606
  new Promise((resolve) => setTimeout(() => resolve(null), TIMEOUT_MS)),
574
607
  ]));
575
- if (this.stopped || !result?.data)
576
- return;
608
+ const appsResult = (await Promise.race([
609
+ this.request("app/list", {
610
+ cursor: null,
611
+ limit: 100,
612
+ threadId: this._threadId ?? undefined,
613
+ forceRefetch: false,
614
+ }),
615
+ new Promise((resolve) => setTimeout(() => resolve(null), TIMEOUT_MS)),
616
+ ]));
577
617
  const skills = [];
578
- const slashCommands = [];
579
618
  const skillMetadata = [];
580
- for (const entry of result.data) {
581
- for (const skill of entry.skills) {
582
- if (skill.enabled) {
583
- skills.push(skill.name);
584
- slashCommands.push(skill.name);
585
- skillMetadata.push({
586
- name: skill.name,
587
- path: skill.path,
588
- description: skill.description,
589
- shortDescription: skill.shortDescription ??
590
- skill.interface?.shortDescription ??
591
- undefined,
592
- enabled: skill.enabled,
593
- scope: skill.scope,
594
- displayName: skill.interface?.displayName ?? undefined,
595
- defaultPrompt: skill.interface?.defaultPrompt ?? undefined,
596
- brandColor: skill.interface?.brandColor ?? undefined,
597
- });
619
+ if (skillsResult?.data) {
620
+ for (const entry of skillsResult.data) {
621
+ for (const skill of entry.skills) {
622
+ if (skill.enabled) {
623
+ skills.push(skill.name);
624
+ skillMetadata.push({
625
+ name: skill.name,
626
+ path: skill.path,
627
+ description: skill.description,
628
+ shortDescription: skill.shortDescription ??
629
+ skill.interface?.shortDescription ??
630
+ undefined,
631
+ enabled: skill.enabled,
632
+ scope: skill.scope,
633
+ displayName: skill.interface?.displayName ?? undefined,
634
+ defaultPrompt: skill.interface?.defaultPrompt ?? undefined,
635
+ brandColor: skill.interface?.brandColor ?? undefined,
636
+ });
637
+ }
598
638
  }
599
639
  }
600
640
  }
601
641
  this._skills = skillMetadata;
602
- if (slashCommands.length > 0) {
603
- console.log(`[codex-process] skills/list returned ${slashCommands.length} skills`);
642
+ const appMetadata = (appsResult?.data ?? [])
643
+ .filter((app) => (app.isAccessible ?? true) && (app.isEnabled ?? true))
644
+ .map((app) => ({
645
+ id: app.id,
646
+ name: app.name,
647
+ description: app.description,
648
+ installUrl: app.installUrl ?? undefined,
649
+ isAccessible: app.isAccessible ?? true,
650
+ isEnabled: app.isEnabled ?? true,
651
+ }));
652
+ this._apps = appMetadata;
653
+ if (this.stopped)
654
+ return;
655
+ const signature = JSON.stringify({
656
+ skills,
657
+ skillMetadata,
658
+ apps: appMetadata.map((app) => app.id),
659
+ appMetadata,
660
+ });
661
+ if (signature === this._lastCompletionEntitiesSignature) {
662
+ return;
663
+ }
664
+ this._lastCompletionEntitiesSignature = signature;
665
+ if (skills.length > 0 || appMetadata.length > 0) {
666
+ console.log(`[codex-process] completion entities loaded: ${skills.length} skills, ${appMetadata.length} apps`);
604
667
  this.emitMessage({
605
668
  type: "system",
606
669
  subtype: "supported_commands",
607
- slashCommands,
608
670
  skills,
609
671
  skillMetadata,
672
+ apps: appMetadata.map((app) => app.id),
673
+ appMetadata,
610
674
  });
611
675
  }
612
676
  }
613
677
  catch (err) {
614
- console.log(`[codex-process] skills/list failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
678
+ console.log(`[codex-process] completion entity fetch failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
615
679
  }
616
680
  }
617
681
  async runInputLoop(options) {
@@ -997,9 +1061,15 @@ export class CodexProcess extends EventEmitter {
997
1061
  break;
998
1062
  }
999
1063
  case "skills/changed": {
1000
- // Re-fetch skills when Codex notifies us of changes
1064
+ // Re-fetch skills/apps when Codex notifies us of changes
1001
1065
  if (this._projectPath) {
1002
- void this.fetchSkills(this._projectPath);
1066
+ void this.fetchCompletionEntities(this._projectPath);
1067
+ }
1068
+ break;
1069
+ }
1070
+ case "app/list/updated": {
1071
+ if (this._projectPath) {
1072
+ void this.fetchCompletionEntities(this._projectPath);
1003
1073
  }
1004
1074
  break;
1005
1075
  }
@@ -1367,12 +1437,19 @@ export class CodexProcess extends EventEmitter {
1367
1437
  async toRpcInput(pendingInput) {
1368
1438
  const input = [];
1369
1439
  const tempPaths = [];
1370
- // Prepend SkillUserInput if a skill reference is attached
1371
- if (pendingInput.skill) {
1440
+ // Prepend structured input items before the free-form text body.
1441
+ for (const skill of pendingInput.skills ?? []) {
1372
1442
  input.push({
1373
1443
  type: "skill",
1374
- name: pendingInput.skill.name,
1375
- path: pendingInput.skill.path,
1444
+ name: skill.name,
1445
+ path: skill.path,
1446
+ });
1447
+ }
1448
+ for (const mention of pendingInput.mentions ?? []) {
1449
+ input.push({
1450
+ type: "mention",
1451
+ name: mention.name,
1452
+ path: mention.path,
1376
1453
  });
1377
1454
  }
1378
1455
  input.push({ type: "text", text: pendingInput.text });