@ccpocket/bridge 1.66.1 → 1.67.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.
@@ -253,6 +253,12 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
253
253
  approveAlways(toolUseId?: string): void;
254
254
  reject(toolUseId?: string, _message?: string): void;
255
255
  answer(toolUseId: string, result: string): void;
256
+ /**
257
+ * Install a plugin or begin connector authentication proposed by Codex.
258
+ * The elicitation remains pending while external app authentication is
259
+ * required, and is accepted only after installation is complete.
260
+ */
261
+ installToolSuggestion(toolUseId: string): Promise<void>;
256
262
  getPendingPermission(toolUseId?: string): {
257
263
  toolUseId: string;
258
264
  toolName: string;
@@ -269,6 +275,8 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
269
275
  * the permission (approve/reject) path.
270
276
  */
271
277
  private approveUserInput;
278
+ private updateToolSuggestion;
279
+ private resolveToolSuggestion;
272
280
  /**
273
281
  * Reject a pending user-input request (McpElicitation fallback).
274
282
  */
@@ -303,6 +311,7 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
303
311
  private request;
304
312
  private notify;
305
313
  private respondToServerRequest;
314
+ private respondToServerRequestError;
306
315
  private writeEnvelope;
307
316
  private rejectAllPending;
308
317
  private setStatus;
@@ -611,6 +611,94 @@ export class CodexProcess extends EventEmitter {
611
611
  this.setStatus("running");
612
612
  }
613
613
  }
614
+ /**
615
+ * Install a plugin or begin connector authentication proposed by Codex.
616
+ * The elicitation remains pending while external app authentication is
617
+ * required, and is accepted only after installation is complete.
618
+ */
619
+ async installToolSuggestion(toolUseId) {
620
+ const pending = this.resolvePendingUserInput(toolUseId);
621
+ if (!pending || pending.kind !== "tool_suggestion") {
622
+ throw new Error("No pending tool suggestion found");
623
+ }
624
+ const currentState = pending.input.installState;
625
+ if (currentState === "installing")
626
+ return;
627
+ if (currentState === "needs_auth")
628
+ return;
629
+ const meta = asRecord(pending.input._meta) ?? {};
630
+ const toolType = stringValue(meta.tool_type) ?? "";
631
+ const suggestType = stringValue(meta.suggest_type) ?? "";
632
+ if (suggestType !== "install") {
633
+ this.updateToolSuggestion(pending, {
634
+ installState: "failed",
635
+ installError: `Unsupported suggestion action: ${suggestType || "unknown"}`,
636
+ });
637
+ return;
638
+ }
639
+ if (toolType === "connector") {
640
+ const installUrl = stringValue(meta.install_url);
641
+ if (!installUrl) {
642
+ this.updateToolSuggestion(pending, {
643
+ installState: "failed",
644
+ installError: "This connector did not provide an installation URL.",
645
+ });
646
+ return;
647
+ }
648
+ this.updateToolSuggestion(pending, { installState: "needs_auth" });
649
+ return;
650
+ }
651
+ if (toolType !== "plugin") {
652
+ this.updateToolSuggestion(pending, {
653
+ installState: "failed",
654
+ installError: `Unsupported tool type: ${toolType || "unknown"}`,
655
+ });
656
+ return;
657
+ }
658
+ const toolId = stringValue(meta.tool_id) ?? "";
659
+ const remotePluginId = stringValue(meta.remote_plugin_id);
660
+ const separator = toolId.lastIndexOf("@");
661
+ const fallbackPluginName = separator > 0 ? toolId.slice(0, separator) : toolId;
662
+ const remoteMarketplaceName = separator > 0 ? toolId.slice(separator + 1) : "openai-curated-remote";
663
+ const pluginName = remotePluginId ?? fallbackPluginName;
664
+ if (!pluginName) {
665
+ this.updateToolSuggestion(pending, {
666
+ installState: "failed",
667
+ installError: "This plugin did not provide an installation identifier.",
668
+ });
669
+ return;
670
+ }
671
+ this.updateToolSuggestion(pending, {
672
+ installState: "installing",
673
+ installError: null,
674
+ });
675
+ try {
676
+ const result = (await this.request("plugin/install", {
677
+ remoteMarketplaceName,
678
+ pluginName,
679
+ }));
680
+ // The user may reject the suggestion while installation is in flight.
681
+ if (this.pendingUserInputs.get(toolUseId) !== pending)
682
+ return;
683
+ const appsNeedingAuth = normalizeToolSuggestionApps(result.appsNeedingAuth);
684
+ if (appsNeedingAuth.length > 0) {
685
+ this.updateToolSuggestion(pending, {
686
+ installState: "needs_auth",
687
+ appsNeedingAuth,
688
+ });
689
+ return;
690
+ }
691
+ this.resolveToolSuggestion(pending, "Installed");
692
+ }
693
+ catch (err) {
694
+ if (this.pendingUserInputs.get(toolUseId) !== pending)
695
+ return;
696
+ this.updateToolSuggestion(pending, {
697
+ installState: "failed",
698
+ installError: err instanceof Error ? err.message : String(err),
699
+ });
700
+ }
701
+ }
614
702
  getPendingPermission(toolUseId) {
615
703
  // Check plan completion first
616
704
  if (this.pendingPlanCompletion) {
@@ -669,6 +757,15 @@ export class CodexProcess extends EventEmitter {
669
757
  const pending = this.resolvePendingUserInput(toolUseId);
670
758
  if (!pending)
671
759
  return false;
760
+ if (pending.kind === "tool_suggestion") {
761
+ if (pending.input.installState === "needs_auth") {
762
+ this.resolveToolSuggestion(pending, "Installed");
763
+ }
764
+ else {
765
+ void this.installToolSuggestion(pending.toolUseId);
766
+ }
767
+ return true;
768
+ }
672
769
  this.pendingUserInputs.delete(pending.toolUseId);
673
770
  this.respondToServerRequest(pending.requestId, buildUserInputResponse(pending, result));
674
771
  this.emitToolResult(pending.toolUseId, "Approved");
@@ -677,6 +774,31 @@ export class CodexProcess extends EventEmitter {
677
774
  }
678
775
  return true;
679
776
  }
777
+ updateToolSuggestion(pending, changes) {
778
+ pending.input = { ...pending.input, ...changes };
779
+ this.emitMessage({
780
+ type: "permission_request",
781
+ toolUseId: pending.toolUseId,
782
+ toolName: "ToolSuggestion",
783
+ input: { ...pending.input },
784
+ });
785
+ }
786
+ resolveToolSuggestion(pending, toolResult) {
787
+ this.pendingUserInputs.delete(pending.toolUseId);
788
+ this.respondToServerRequest(pending.requestId, {
789
+ action: "accept",
790
+ content: null,
791
+ _meta: null,
792
+ });
793
+ this.emitMessage({
794
+ type: "permission_resolved",
795
+ toolUseId: pending.toolUseId,
796
+ });
797
+ this.emitToolResult(pending.toolUseId, toolResult);
798
+ if (this.pendingApprovals.size === 0 && this.pendingUserInputs.size === 0) {
799
+ this.setStatus("running");
800
+ }
801
+ }
680
802
  /**
681
803
  * Reject a pending user-input request (McpElicitation fallback).
682
804
  */
@@ -1402,10 +1524,13 @@ export class CodexProcess extends EventEmitter {
1402
1524
  case "mcpServer/elicitation/request": {
1403
1525
  const toolUseId = this.extractToolUseId(params, id);
1404
1526
  const elicitation = createElicitationInput(params);
1527
+ const toolName = elicitation.kind === "tool_suggestion"
1528
+ ? "ToolSuggestion"
1529
+ : "McpElicitation";
1405
1530
  this.pendingUserInputs.set(toolUseId, {
1406
1531
  requestId: id,
1407
1532
  toolUseId,
1408
- toolName: "McpElicitation",
1533
+ toolName,
1409
1534
  questions: elicitation.questions,
1410
1535
  input: elicitation.input,
1411
1536
  kind: elicitation.kind,
@@ -1413,15 +1538,23 @@ export class CodexProcess extends EventEmitter {
1413
1538
  this.emitMessage({
1414
1539
  type: "permission_request",
1415
1540
  toolUseId,
1416
- toolName: "McpElicitation",
1541
+ toolName,
1417
1542
  input: elicitation.input,
1418
1543
  });
1419
1544
  this.setStatus("waiting_approval");
1420
1545
  break;
1421
1546
  }
1422
- default:
1423
- this.respondToServerRequest(id, {});
1547
+ case "currentTime/read": {
1548
+ this.respondToServerRequest(id, {
1549
+ currentTimeAt: Math.floor(Date.now() / 1000),
1550
+ });
1424
1551
  break;
1552
+ }
1553
+ default: {
1554
+ console.warn(`[codex-process] unsupported server request: ${method}`);
1555
+ this.respondToServerRequestError(id, -32601, `Unsupported server request: ${method}`);
1556
+ break;
1557
+ }
1425
1558
  }
1426
1559
  }
1427
1560
  handleNotification(method, params) {
@@ -1555,6 +1688,55 @@ export class CodexProcess extends EventEmitter {
1555
1688
  this.handleServerRequestResolved(params);
1556
1689
  break;
1557
1690
  }
1691
+ case "warning": {
1692
+ const message = stringValue(params.message);
1693
+ if (message) {
1694
+ this.emitMessage({
1695
+ type: "error",
1696
+ errorCode: "codex_warning",
1697
+ message,
1698
+ });
1699
+ }
1700
+ break;
1701
+ }
1702
+ case "guardianWarning": {
1703
+ const message = stringValue(params.message);
1704
+ if (!message)
1705
+ break;
1706
+ if (isInformationalGuardianApproval(message)) {
1707
+ console.debug("[codex-process] suppressed informational guardian approval notification");
1708
+ break;
1709
+ }
1710
+ this.emitMessage({
1711
+ type: "error",
1712
+ errorCode: "codex_warning",
1713
+ message,
1714
+ });
1715
+ break;
1716
+ }
1717
+ case "configWarning":
1718
+ case "deprecationNotice": {
1719
+ const summary = stringValue(params.summary);
1720
+ const details = stringValue(params.details);
1721
+ if (summary || details) {
1722
+ this.emitMessage({
1723
+ type: "error",
1724
+ errorCode: "codex_warning",
1725
+ message: [summary, details].filter(Boolean).join("\n"),
1726
+ });
1727
+ }
1728
+ break;
1729
+ }
1730
+ case "error": {
1731
+ const error = asRecord(params.error);
1732
+ const message = stringValue(error?.message) ?? "Codex runtime error";
1733
+ this.emitMessage({
1734
+ type: "error",
1735
+ errorCode: params.willRetry ? "codex_warning" : "codex_runtime_error",
1736
+ message: params.willRetry ? `${message}\nCodex will retry.` : message,
1737
+ });
1738
+ break;
1739
+ }
1558
1740
  default:
1559
1741
  break;
1560
1742
  }
@@ -1954,6 +2136,22 @@ export class CodexProcess extends EventEmitter {
1954
2136
  this.lastPlanItemText = planText;
1955
2137
  break;
1956
2138
  }
2139
+ case "exitedreviewmode": {
2140
+ const text = typeof item.review === "string" ? item.review : "";
2141
+ if (!text)
2142
+ break;
2143
+ this.lastResultText = text;
2144
+ this.emitMessage({
2145
+ type: "assistant",
2146
+ message: {
2147
+ id: itemId,
2148
+ role: "assistant",
2149
+ content: [{ type: "text", text }],
2150
+ model: this.getMessageModel(),
2151
+ },
2152
+ });
2153
+ break;
2154
+ }
1957
2155
  case "error": {
1958
2156
  const message = typeof item.message === "string" ? item.message : "Codex item error";
1959
2157
  this.emitMessage({ type: "error", message });
@@ -2039,6 +2237,16 @@ export class CodexProcess extends EventEmitter {
2039
2237
  }
2040
2238
  }
2041
2239
  }
2240
+ respondToServerRequestError(id, code, message) {
2241
+ try {
2242
+ this.writeEnvelope({ id, error: { code, message } });
2243
+ }
2244
+ catch (err) {
2245
+ if (!this.stopped) {
2246
+ console.warn(`[codex-process] failed to reject server request: ${err instanceof Error ? err.message : String(err)}`);
2247
+ }
2248
+ }
2249
+ }
2042
2250
  writeEnvelope(envelope) {
2043
2251
  if (!this.transport || !this.transport.isRunning) {
2044
2252
  throw new Error("codex app-server is not running");
@@ -2751,12 +2959,15 @@ function parseResultObject(rawResult) {
2751
2959
  return { byId: {}, byQuestion: {} };
2752
2960
  }
2753
2961
  }
2962
+ function isInformationalGuardianApproval(message) {
2963
+ const normalized = message.trim().replace(/\s+/g, " ").toLowerCase();
2964
+ return (normalized.startsWith("automatic approval review approved") &&
2965
+ /: auto-review returned a low[- ]risk allow decision\.?$/.test(normalized));
2966
+ }
2754
2967
  function normalizeAnswerValues(value) {
2755
2968
  if (typeof value === "string") {
2756
- return value
2757
- .split(",")
2758
- .map((part) => part.trim())
2759
- .filter((part) => part.length > 0);
2969
+ const normalized = value.trim();
2970
+ return normalized ? [normalized] : [];
2760
2971
  }
2761
2972
  if (Array.isArray(value)) {
2762
2973
  return value
@@ -2777,6 +2988,13 @@ function normalizeAnswerValues(value) {
2777
2988
  return normalized ? [normalized] : [];
2778
2989
  }
2779
2990
  function buildElicitationResponse(pending, rawResult) {
2991
+ if (pending.kind === "tool_suggestion") {
2992
+ return {
2993
+ action: parseElicitationAction(rawResult),
2994
+ content: null,
2995
+ _meta: null,
2996
+ };
2997
+ }
2780
2998
  if (pending.kind === "elicitation_url") {
2781
2999
  const action = parseElicitationAction(rawResult);
2782
3000
  return {
@@ -2790,23 +3008,20 @@ function buildElicitationResponse(pending, rawResult) {
2790
3008
  }
2791
3009
  const parsed = parseResultObject(rawResult);
2792
3010
  const content = {};
3011
+ const schema = asRecord(pending.input.requestedSchema);
3012
+ const properties = asRecord(schema?.properties) ?? {};
2793
3013
  for (const question of pending.questions) {
2794
3014
  const candidate = parsed.byId[question.id] ?? parsed.byQuestion[question.question];
2795
- const answers = normalizeAnswerValues(candidate);
2796
- if (answers.length === 1) {
2797
- content[question.id] = answers[0];
2798
- }
2799
- else if (answers.length > 1) {
2800
- content[question.id] = answers;
3015
+ const value = coerceElicitationValue(candidate, asRecord(properties[question.id]));
3016
+ if (value !== undefined) {
3017
+ content[question.id] = value;
2801
3018
  }
2802
3019
  }
2803
3020
  if (Object.keys(content).length === 0 && pending.questions.length === 1) {
2804
- const answers = normalizeAnswerValues(rawResult);
2805
- if (answers.length === 1) {
2806
- content[pending.questions[0].id] = answers[0];
2807
- }
2808
- else if (answers.length > 1) {
2809
- content[pending.questions[0].id] = answers;
3021
+ const questionId = pending.questions[0].id;
3022
+ const value = coerceElicitationValue(rawResult, asRecord(properties[questionId]));
3023
+ if (value !== undefined) {
3024
+ content[questionId] = value;
2810
3025
  }
2811
3026
  }
2812
3027
  return {
@@ -2815,6 +3030,48 @@ function buildElicitationResponse(pending, rawResult) {
2815
3030
  _meta: null,
2816
3031
  };
2817
3032
  }
3033
+ function coerceElicitationValue(value, field) {
3034
+ if (value == null)
3035
+ return undefined;
3036
+ const type = stringValue(field?.type) ?? "string";
3037
+ if (type === "array") {
3038
+ if (Array.isArray(value)) {
3039
+ const entries = value.map((entry) => String(entry));
3040
+ return entries.length > 0 ? entries : undefined;
3041
+ }
3042
+ if (typeof value === "string") {
3043
+ return value
3044
+ .split(",")
3045
+ .map((entry) => entry.trim())
3046
+ .filter(Boolean);
3047
+ }
3048
+ return [String(value)];
3049
+ }
3050
+ const scalar = Array.isArray(value) ? value[0] : value;
3051
+ if (scalar == null)
3052
+ return undefined;
3053
+ if (typeof scalar === "string" && scalar.trim().length === 0) {
3054
+ return undefined;
3055
+ }
3056
+ if (type === "boolean") {
3057
+ if (typeof scalar === "boolean")
3058
+ return scalar;
3059
+ if (String(scalar).toLowerCase() === "true")
3060
+ return true;
3061
+ if (String(scalar).toLowerCase() === "false")
3062
+ return false;
3063
+ return undefined;
3064
+ }
3065
+ if (type === "number" || type === "integer") {
3066
+ const number = typeof scalar === "number" ? scalar : Number(scalar);
3067
+ if (!Number.isFinite(number))
3068
+ return undefined;
3069
+ if (type === "integer" && !Number.isInteger(number))
3070
+ return undefined;
3071
+ return number;
3072
+ }
3073
+ return String(scalar);
3074
+ }
2818
3075
  function buildApprovalElicitationResponse(pending, rawResult) {
2819
3076
  const selection = resolveApprovalElicitationSelection(pending, rawResult);
2820
3077
  const normalized = selection.trim().toLowerCase();
@@ -2908,7 +3165,32 @@ function createElicitationInput(params) {
2908
3165
  }
2909
3166
  const schema = asRecord(params.requestedSchema);
2910
3167
  const elicitationMeta = asRecord(params._meta);
2911
- if (isApprovalActionElicitation(schema, elicitationMeta)) {
3168
+ if (isToolSuggestionElicitation(serverName, elicitationMeta)) {
3169
+ const toolName = stringValue(elicitationMeta?.tool_name) ?? "Tool";
3170
+ return {
3171
+ kind: "tool_suggestion",
3172
+ questions: [],
3173
+ input: {
3174
+ mode: "form",
3175
+ serverName,
3176
+ message,
3177
+ _meta: elicitationMeta ?? null,
3178
+ toolType: stringValue(elicitationMeta?.tool_type),
3179
+ suggestType: stringValue(elicitationMeta?.suggest_type),
3180
+ suggestReason: stringValue(elicitationMeta?.suggest_reason) ?? message,
3181
+ toolId: stringValue(elicitationMeta?.tool_id),
3182
+ toolName,
3183
+ installUrl: stringValue(elicitationMeta?.install_url),
3184
+ remotePluginId: stringValue(elicitationMeta?.remote_plugin_id),
3185
+ appConnectorIds: Array.isArray(elicitationMeta?.app_connector_ids)
3186
+ ? elicitationMeta.app_connector_ids.filter((entry) => typeof entry === "string")
3187
+ : [],
3188
+ installState: "idle",
3189
+ appsNeedingAuth: [],
3190
+ },
3191
+ };
3192
+ }
3193
+ if (isApprovalActionElicitation(schema, serverName, elicitationMeta)) {
2912
3194
  const questionId = "approval";
2913
3195
  const isToolApproval = isToolApprovalElicitation(elicitationMeta);
2914
3196
  return {
@@ -2944,26 +3226,15 @@ function createElicitationInput(params) {
2944
3226
  const field = value;
2945
3227
  const title = typeof field.title === "string" ? field.title : key;
2946
3228
  const description = typeof field.description === "string" ? field.description : message;
2947
- const enumValues = Array.isArray(field.enum)
2948
- ? field.enum.map((entry) => String(entry))
2949
- : [];
2950
3229
  const type = typeof field.type === "string" ? field.type : "";
2951
- const options = enumValues.length > 0
2952
- ? enumValues.map((entry, index) => ({
2953
- label: entry,
2954
- description: index === 0 ? description : "",
2955
- }))
2956
- : type === "boolean"
2957
- ? [
2958
- { label: "true", description: description },
2959
- { label: "false", description: "" },
2960
- ]
2961
- : [];
3230
+ const options = buildElicitationFieldOptions(field, description);
2962
3231
  return {
2963
3232
  id: key,
2964
3233
  question: requiredFields.has(key) ? `${title} (required)` : title,
2965
3234
  header: serverName,
2966
3235
  options,
3236
+ required: requiredFields.has(key),
3237
+ multiSelect: type === "array",
2967
3238
  isOther: options.length === 0,
2968
3239
  isSecret: false,
2969
3240
  };
@@ -2976,6 +3247,8 @@ function createElicitationInput(params) {
2976
3247
  question: message,
2977
3248
  header: serverName,
2978
3249
  options: [],
3250
+ required: true,
3251
+ multiSelect: false,
2979
3252
  isOther: true,
2980
3253
  isSecret: false,
2981
3254
  },
@@ -2985,6 +3258,7 @@ function createElicitationInput(params) {
2985
3258
  questions: normalizedQuestions.map((question) => ({
2986
3259
  id: question.id,
2987
3260
  question: question.question,
3261
+ required: question.required,
2988
3262
  })),
2989
3263
  input: {
2990
3264
  mode: "form",
@@ -2997,15 +3271,58 @@ function createElicitationInput(params) {
2997
3271
  header: question.header,
2998
3272
  question: question.question,
2999
3273
  options: question.options,
3000
- multiSelect: false,
3274
+ required: question.required,
3275
+ multiSelect: question.multiSelect,
3001
3276
  isOther: question.isOther,
3002
3277
  isSecret: question.isSecret,
3003
3278
  })),
3004
3279
  },
3005
3280
  };
3006
3281
  }
3007
- function isApprovalActionElicitation(schema, meta) {
3008
- return isEmptyObjectSchema(schema) && !isToolSuggestionElicitation(meta);
3282
+ function buildElicitationFieldOptions(field, description) {
3283
+ const type = stringValue(field.type);
3284
+ const source = type === "array" ? asRecord(field.items) ?? {} : field;
3285
+ const rawOptions = Array.isArray(source.oneOf)
3286
+ ? source.oneOf
3287
+ : Array.isArray(source.anyOf)
3288
+ ? source.anyOf
3289
+ : null;
3290
+ if (rawOptions) {
3291
+ return rawOptions.flatMap((entry, index) => {
3292
+ const option = asRecord(entry);
3293
+ const value = stringValue(option?.const);
3294
+ if (!value)
3295
+ return [];
3296
+ return [
3297
+ {
3298
+ label: stringValue(option?.title) ?? value,
3299
+ value,
3300
+ description: index === 0 ? description : "",
3301
+ },
3302
+ ];
3303
+ });
3304
+ }
3305
+ if (Array.isArray(source.enum)) {
3306
+ return source.enum.map((entry, index) => {
3307
+ const value = String(entry);
3308
+ return {
3309
+ label: value,
3310
+ value,
3311
+ description: index === 0 ? description : "",
3312
+ };
3313
+ });
3314
+ }
3315
+ if (type === "boolean") {
3316
+ return [
3317
+ { label: "true", value: "true", description },
3318
+ { label: "false", value: "false", description: "" },
3319
+ ];
3320
+ }
3321
+ return [];
3322
+ }
3323
+ function isApprovalActionElicitation(schema, serverName, meta) {
3324
+ return (isEmptyObjectSchema(schema) &&
3325
+ !isToolSuggestionElicitation(serverName, meta));
3009
3326
  }
3010
3327
  function isEmptyObjectSchema(schema) {
3011
3328
  if (!schema)
@@ -3018,8 +3335,9 @@ function isEmptyObjectSchema(schema) {
3018
3335
  function isToolApprovalElicitation(meta) {
3019
3336
  return meta?.codex_approval_kind === "mcp_tool_call";
3020
3337
  }
3021
- function isToolSuggestionElicitation(meta) {
3022
- return meta?.codex_approval_kind === "tool_suggestion";
3338
+ function isToolSuggestionElicitation(serverName, meta) {
3339
+ return (serverName === "codex_apps" &&
3340
+ meta?.codex_approval_kind === "tool_suggestion");
3023
3341
  }
3024
3342
  function buildApprovalActionElicitationOptions(meta, isToolApproval) {
3025
3343
  const persistModes = extractPersistModes(meta);
@@ -3108,6 +3426,32 @@ function asRecord(value) {
3108
3426
  ? value
3109
3427
  : undefined;
3110
3428
  }
3429
+ function stringValue(value) {
3430
+ return typeof value === "string" && value.length > 0 ? value : undefined;
3431
+ }
3432
+ function normalizeToolSuggestionApps(value) {
3433
+ if (!Array.isArray(value))
3434
+ return [];
3435
+ return value.flatMap((entry) => {
3436
+ const app = asRecord(entry);
3437
+ const id = stringValue(app?.id);
3438
+ const name = stringValue(app?.name);
3439
+ if (!id || !name)
3440
+ return [];
3441
+ const description = stringValue(app?.description);
3442
+ const installUrl = stringValue(app?.installUrl);
3443
+ const category = stringValue(app?.category);
3444
+ return [
3445
+ {
3446
+ id,
3447
+ name,
3448
+ ...(description ? { description } : {}),
3449
+ ...(installUrl ? { installUrl } : {}),
3450
+ ...(category ? { category } : {}),
3451
+ },
3452
+ ];
3453
+ });
3454
+ }
3111
3455
  function buildPlanUpdateToolUseInput(params) {
3112
3456
  const stepsRaw = params.plan;
3113
3457
  if (!Array.isArray(stepsRaw) || stepsRaw.length === 0)