@kodelyth/codex 2026.5.42 → 2026.6.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.
Files changed (138) hide show
  1. package/package.json +16 -1
  2. package/doctor-contract-api.test.ts +0 -44
  3. package/doctor-contract-api.ts +0 -68
  4. package/harness.ts +0 -72
  5. package/index.test.ts +0 -230
  6. package/index.ts +0 -66
  7. package/media-understanding-provider.test.ts +0 -486
  8. package/media-understanding-provider.ts +0 -521
  9. package/prompt-overlay-runtime-contract.test.ts +0 -48
  10. package/prompt-overlay.ts +0 -21
  11. package/provider-catalog.ts +0 -83
  12. package/provider-discovery.ts +0 -45
  13. package/provider.test.ts +0 -384
  14. package/provider.ts +0 -243
  15. package/src/app-server/app-inventory-cache.test.ts +0 -176
  16. package/src/app-server/app-inventory-cache.ts +0 -324
  17. package/src/app-server/approval-bridge.test.ts +0 -1471
  18. package/src/app-server/approval-bridge.ts +0 -1211
  19. package/src/app-server/auth-bridge.test.ts +0 -1449
  20. package/src/app-server/auth-bridge.ts +0 -614
  21. package/src/app-server/auth-profile-runtime-contract.test.ts +0 -239
  22. package/src/app-server/capabilities.ts +0 -27
  23. package/src/app-server/client-factory.ts +0 -24
  24. package/src/app-server/client.test.ts +0 -563
  25. package/src/app-server/client.ts +0 -715
  26. package/src/app-server/compact.test.ts +0 -710
  27. package/src/app-server/compact.ts +0 -500
  28. package/src/app-server/computer-use.test.ts +0 -788
  29. package/src/app-server/computer-use.ts +0 -683
  30. package/src/app-server/config.test.ts +0 -879
  31. package/src/app-server/config.ts +0 -1038
  32. package/src/app-server/context-engine-projection.test.ts +0 -252
  33. package/src/app-server/context-engine-projection.ts +0 -403
  34. package/src/app-server/delivery-no-reply-runtime-contract.test.ts +0 -80
  35. package/src/app-server/dynamic-tool-diagnostics.ts +0 -73
  36. package/src/app-server/dynamic-tool-profile.ts +0 -69
  37. package/src/app-server/dynamic-tools.test.ts +0 -1302
  38. package/src/app-server/dynamic-tools.ts +0 -623
  39. package/src/app-server/elicitation-bridge.test.ts +0 -1056
  40. package/src/app-server/elicitation-bridge.ts +0 -783
  41. package/src/app-server/event-projector.test.ts +0 -2668
  42. package/src/app-server/event-projector.ts +0 -2057
  43. package/src/app-server/image-payload-sanitizer.test.ts +0 -49
  44. package/src/app-server/image-payload-sanitizer.ts +0 -167
  45. package/src/app-server/klaw-owned-tool-runtime-contract.test.ts +0 -456
  46. package/src/app-server/local-runtime-attribution.ts +0 -39
  47. package/src/app-server/managed-binary.test.ts +0 -139
  48. package/src/app-server/managed-binary.ts +0 -193
  49. package/src/app-server/models.test.ts +0 -246
  50. package/src/app-server/models.ts +0 -172
  51. package/src/app-server/native-hook-relay.test.ts +0 -271
  52. package/src/app-server/native-hook-relay.ts +0 -150
  53. package/src/app-server/native-subagent-task-mirror.test.ts +0 -573
  54. package/src/app-server/native-subagent-task-mirror.ts +0 -497
  55. package/src/app-server/outcome-fallback-runtime-contract.test.ts +0 -404
  56. package/src/app-server/plugin-activation.test.ts +0 -336
  57. package/src/app-server/plugin-activation.ts +0 -283
  58. package/src/app-server/plugin-app-cache-key.ts +0 -74
  59. package/src/app-server/plugin-approval-roundtrip.ts +0 -122
  60. package/src/app-server/plugin-inventory.test.ts +0 -355
  61. package/src/app-server/plugin-inventory.ts +0 -357
  62. package/src/app-server/plugin-thread-config.test.ts +0 -865
  63. package/src/app-server/plugin-thread-config.ts +0 -455
  64. package/src/app-server/protocol-generated/json/DynamicToolCallParams.json +0 -33
  65. package/src/app-server/protocol-generated/json/v2/ErrorNotification.json +0 -199
  66. package/src/app-server/protocol-generated/json/v2/GetAccountResponse.json +0 -102
  67. package/src/app-server/protocol-generated/json/v2/ModelListResponse.json +0 -227
  68. package/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json +0 -2630
  69. package/src/app-server/protocol-generated/json/v2/ThreadStartResponse.json +0 -2630
  70. package/src/app-server/protocol-generated/json/v2/TurnCompletedNotification.json +0 -1659
  71. package/src/app-server/protocol-generated/json/v2/TurnStartResponse.json +0 -1655
  72. package/src/app-server/protocol-validators.test.ts +0 -75
  73. package/src/app-server/protocol-validators.ts +0 -203
  74. package/src/app-server/protocol.ts +0 -520
  75. package/src/app-server/rate-limit-cache.ts +0 -48
  76. package/src/app-server/rate-limits.test.ts +0 -202
  77. package/src/app-server/rate-limits.ts +0 -583
  78. package/src/app-server/request.ts +0 -73
  79. package/src/app-server/run-attempt.context-engine.test.ts +0 -1004
  80. package/src/app-server/run-attempt.test.ts +0 -9477
  81. package/src/app-server/run-attempt.ts +0 -4683
  82. package/src/app-server/run-attempt.vision-tools.test.ts +0 -35
  83. package/src/app-server/schema-normalization-runtime-contract.test.ts +0 -206
  84. package/src/app-server/session-binding.test.ts +0 -303
  85. package/src/app-server/session-binding.ts +0 -398
  86. package/src/app-server/session-history.ts +0 -44
  87. package/src/app-server/shared-client.test.ts +0 -589
  88. package/src/app-server/shared-client.ts +0 -289
  89. package/src/app-server/side-question.test.ts +0 -1175
  90. package/src/app-server/side-question.ts +0 -1007
  91. package/src/app-server/test-support.ts +0 -48
  92. package/src/app-server/thread-lifecycle.test.ts +0 -447
  93. package/src/app-server/thread-lifecycle.ts +0 -939
  94. package/src/app-server/thread-lifecycle.user-mcp-servers.test.ts +0 -442
  95. package/src/app-server/timeout.ts +0 -9
  96. package/src/app-server/tool-progress-normalization.ts +0 -77
  97. package/src/app-server/trajectory.test.ts +0 -205
  98. package/src/app-server/trajectory.ts +0 -365
  99. package/src/app-server/transcript-mirror.test.ts +0 -524
  100. package/src/app-server/transcript-mirror.ts +0 -208
  101. package/src/app-server/transcript-repair-runtime-contract.test.ts +0 -44
  102. package/src/app-server/transport-stdio.test.ts +0 -171
  103. package/src/app-server/transport-stdio.ts +0 -107
  104. package/src/app-server/transport-websocket.test.ts +0 -69
  105. package/src/app-server/transport-websocket.ts +0 -90
  106. package/src/app-server/transport.ts +0 -117
  107. package/src/app-server/user-input-bridge.test.ts +0 -249
  108. package/src/app-server/user-input-bridge.ts +0 -316
  109. package/src/app-server/version.ts +0 -4
  110. package/src/app-server/vision-tools.ts +0 -12
  111. package/src/command-account.ts +0 -544
  112. package/src/command-formatters.ts +0 -425
  113. package/src/command-handlers.ts +0 -2004
  114. package/src/command-rpc.test.ts +0 -16
  115. package/src/command-rpc.ts +0 -142
  116. package/src/commands.test.ts +0 -3312
  117. package/src/commands.ts +0 -65
  118. package/src/conversation-binding-data.ts +0 -124
  119. package/src/conversation-binding.test.ts +0 -599
  120. package/src/conversation-binding.ts +0 -561
  121. package/src/conversation-control.test.ts +0 -126
  122. package/src/conversation-control.ts +0 -303
  123. package/src/conversation-turn-collector.test.ts +0 -191
  124. package/src/conversation-turn-collector.ts +0 -186
  125. package/src/conversation-turn-input.test.ts +0 -141
  126. package/src/conversation-turn-input.ts +0 -106
  127. package/src/manifest.test.ts +0 -20
  128. package/src/migration/apply.ts +0 -501
  129. package/src/migration/helpers.ts +0 -55
  130. package/src/migration/plan.ts +0 -461
  131. package/src/migration/provider.test.ts +0 -1741
  132. package/src/migration/provider.ts +0 -41
  133. package/src/migration/source.ts +0 -643
  134. package/src/migration/targets.ts +0 -25
  135. package/src/node-cli-sessions.test.ts +0 -180
  136. package/src/node-cli-sessions.ts +0 -711
  137. package/test-api.ts +0 -82
  138. package/tsconfig.json +0 -16
@@ -1,1741 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import type { MigrationProviderContext } from "klaw/plugin-sdk/plugin-entry";
5
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
- import { defaultCodexAppInventoryCache } from "../app-server/app-inventory-cache.js";
7
- import { CODEX_PLUGINS_MARKETPLACE_NAME } from "../app-server/config.js";
8
- import { buildCodexPluginAppCacheKey } from "../app-server/plugin-app-cache-key.js";
9
- import type { CodexGetAccountResponse, v2 } from "../app-server/protocol.js";
10
- import { buildCodexMigrationProvider } from "./provider.js";
11
-
12
- const appServerRequest = vi.hoisted(() => vi.fn());
13
-
14
- vi.mock("../app-server/request.js", () => ({
15
- requestCodexAppServerJson: appServerRequest,
16
- }));
17
-
18
- const tempRoots = new Set<string>();
19
-
20
- const logger = {
21
- info() {},
22
- warn() {},
23
- error() {},
24
- debug() {},
25
- };
26
-
27
- async function makeTempRoot(): Promise<string> {
28
- const root = await fs.mkdtemp(path.join(os.tmpdir(), "klaw-migrate-codex-"));
29
- tempRoots.add(root);
30
- return root;
31
- }
32
-
33
- async function writeFile(filePath: string, content = ""): Promise<void> {
34
- await fs.mkdir(path.dirname(filePath), { recursive: true });
35
- await fs.writeFile(filePath, content, "utf8");
36
- }
37
-
38
- function makeContext(params: {
39
- source: string;
40
- stateDir: string;
41
- workspaceDir: string;
42
- overwrite?: boolean;
43
- verifyPluginApps?: boolean;
44
- providerOptions?: MigrationProviderContext["providerOptions"];
45
- reportDir?: string;
46
- config?: MigrationProviderContext["config"];
47
- runtime?: MigrationProviderContext["runtime"];
48
- }): MigrationProviderContext {
49
- return {
50
- config:
51
- params.config ??
52
- ({
53
- agents: {
54
- defaults: {
55
- workspace: params.workspaceDir,
56
- },
57
- },
58
- } as MigrationProviderContext["config"]),
59
- runtime: params.runtime,
60
- source: params.source,
61
- stateDir: params.stateDir,
62
- overwrite: params.overwrite,
63
- providerOptions:
64
- params.providerOptions ?? (params.verifyPluginApps ? { verifyPluginApps: true } : undefined),
65
- reportDir: params.reportDir,
66
- logger,
67
- };
68
- }
69
-
70
- function findItem(items: readonly { id?: string }[], id: string) {
71
- const item = items.find((entry) => entry.id === id);
72
- if (!item) {
73
- throw new Error(`Expected migration item ${id}`);
74
- }
75
- return item as Record<string, unknown>;
76
- }
77
-
78
- function findItemByReason(items: readonly { reason?: string }[], reason: string) {
79
- const item = items.find((entry) => entry.reason === reason);
80
- if (!item) {
81
- throw new Error(`Expected migration item reason ${reason}`);
82
- }
83
- return item as Record<string, unknown>;
84
- }
85
-
86
- function expectRecordFields(record: unknown, expected: Record<string, unknown>) {
87
- if (!record || typeof record !== "object") {
88
- throw new Error("Expected record");
89
- }
90
- const actual = record as Record<string, unknown>;
91
- for (const [key, value] of Object.entries(expected)) {
92
- expect(actual[key]).toEqual(value);
93
- }
94
- return actual;
95
- }
96
-
97
- function mockCallArg(mock: ReturnType<typeof vi.fn>, callIndex = 0, argIndex = 0) {
98
- const call = mock.mock.calls[callIndex];
99
- if (!call) {
100
- throw new Error(`Expected mock call ${callIndex}`);
101
- }
102
- return call[argIndex];
103
- }
104
-
105
- async function createCodexFixture(): Promise<{
106
- root: string;
107
- homeDir: string;
108
- codexHome: string;
109
- stateDir: string;
110
- workspaceDir: string;
111
- }> {
112
- const root = await makeTempRoot();
113
- const homeDir = path.join(root, "home");
114
- const codexHome = path.join(root, ".codex");
115
- const stateDir = path.join(root, "state");
116
- const workspaceDir = path.join(root, "workspace");
117
- vi.stubEnv("HOME", homeDir);
118
- await writeFile(path.join(codexHome, "skills", "tweet-helper", "SKILL.md"), "# Tweet helper\n");
119
- await writeFile(path.join(codexHome, "skills", ".system", "system-skill", "SKILL.md"));
120
- await writeFile(path.join(homeDir, ".agents", "skills", "personal-style", "SKILL.md"));
121
- await writeFile(
122
- path.join(
123
- codexHome,
124
- "plugins",
125
- "cache",
126
- "openai-primary-runtime",
127
- "documents",
128
- "1.0.0",
129
- ".codex-plugin",
130
- "plugin.json",
131
- ),
132
- JSON.stringify({ name: "documents" }),
133
- );
134
- await writeFile(path.join(codexHome, "config.toml"), 'model = "gpt-5.5"\n');
135
- await writeFile(path.join(codexHome, "hooks", "hooks.json"), "{}\n");
136
- return { root, homeDir, codexHome, stateDir, workspaceDir };
137
- }
138
-
139
- function sourceAppCacheKey(fixture: { codexHome: string }): string {
140
- return buildCodexPluginAppCacheKey({
141
- appServer: {
142
- start: {
143
- transport: "stdio",
144
- command: "codex",
145
- commandSource: "managed",
146
- args: ["app-server", "--listen", "stdio://"],
147
- headers: {},
148
- env: {
149
- CODEX_HOME: fixture.codexHome,
150
- HOME: path.dirname(fixture.codexHome),
151
- },
152
- },
153
- },
154
- });
155
- }
156
-
157
- afterEach(async () => {
158
- vi.useRealTimers();
159
- vi.unstubAllEnvs();
160
- appServerRequest.mockReset();
161
- defaultCodexAppInventoryCache.clear();
162
- for (const root of tempRoots) {
163
- await fs.rm(root, { recursive: true, force: true });
164
- }
165
- tempRoots.clear();
166
- });
167
-
168
- describe("buildCodexMigrationProvider", () => {
169
- beforeEach(() => {
170
- appServerRequest.mockRejectedValue(new Error("codex app-server unavailable"));
171
- });
172
-
173
- it("plans Codex skills while keeping plugins and native config explicit", async () => {
174
- const fixture = await createCodexFixture();
175
- const provider = buildCodexMigrationProvider();
176
-
177
- const plan = await provider.plan(
178
- makeContext({
179
- source: fixture.codexHome,
180
- stateDir: fixture.stateDir,
181
- workspaceDir: fixture.workspaceDir,
182
- verifyPluginApps: true,
183
- }),
184
- );
185
-
186
- expect(plan.providerId).toBe("codex");
187
- expect(plan.source).toBe(fixture.codexHome);
188
- expectRecordFields(findItem(plan.items, "skill:tweet-helper"), {
189
- kind: "skill",
190
- action: "copy",
191
- status: "planned",
192
- target: path.join(fixture.workspaceDir, "skills", "tweet-helper"),
193
- });
194
- expectRecordFields(findItem(plan.items, "skill:personal-style"), {
195
- kind: "skill",
196
- action: "copy",
197
- status: "planned",
198
- target: path.join(fixture.workspaceDir, "skills", "personal-style"),
199
- });
200
- expectRecordFields(findItem(plan.items, "plugin:documents:1"), {
201
- kind: "manual",
202
- action: "manual",
203
- status: "skipped",
204
- });
205
- expectRecordFields(findItem(plan.items, "archive:config.toml"), {
206
- kind: "archive",
207
- action: "archive",
208
- status: "planned",
209
- });
210
- expectRecordFields(findItem(plan.items, "archive:hooks/hooks.json"), {
211
- kind: "archive",
212
- action: "archive",
213
- status: "planned",
214
- });
215
- expect(plan.items.some((item) => item.id === "skill:system-skill")).toBe(false);
216
- });
217
-
218
- it("plans source-installed curated plugins without installing during dry-run", async () => {
219
- const fixture = await createCodexFixture();
220
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
221
- if (method === "plugin/list") {
222
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
223
- }
224
- if (method === "plugin/read") {
225
- return pluginRead("google-calendar");
226
- }
227
- throw new Error(`unexpected request ${method}`);
228
- });
229
- const provider = buildCodexMigrationProvider();
230
-
231
- const plan = await provider.plan(
232
- makeContext({
233
- source: fixture.codexHome,
234
- stateDir: fixture.stateDir,
235
- workspaceDir: fixture.workspaceDir,
236
- verifyPluginApps: true,
237
- }),
238
- );
239
-
240
- expect(appServerRequest).toHaveBeenCalledTimes(2);
241
- expectRecordFields(mockCallArg(appServerRequest), {
242
- method: "plugin/list",
243
- requestParams: { cwds: [] },
244
- });
245
- expectRecordFields((mockCallArg(appServerRequest) as { startOptions?: unknown }).startOptions, {
246
- command: "codex",
247
- commandSource: "managed",
248
- env: {
249
- CODEX_HOME: fixture.codexHome,
250
- HOME: path.dirname(fixture.codexHome),
251
- },
252
- });
253
- expect(
254
- appServerRequest.mock.calls.some(
255
- ([arg]) => (arg as { method?: string }).method === "plugin/install",
256
- ),
257
- ).toBe(false);
258
- const pluginItem = findItem(plan.items, "plugin:google-calendar");
259
- expectRecordFields(pluginItem, {
260
- kind: "plugin",
261
- action: "install",
262
- status: "planned",
263
- });
264
- expectRecordFields(pluginItem.details, {
265
- configKey: "google-calendar",
266
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
267
- pluginName: "google-calendar",
268
- });
269
- expectRecordFields(findItem(plan.items, "config:codex-plugins"), {
270
- kind: "config",
271
- action: "merge",
272
- status: "planned",
273
- });
274
- });
275
-
276
- it("skips source-installed plugins whose owned apps are inaccessible", async () => {
277
- const fixture = await createCodexFixture();
278
- appServerRequest.mockImplementation(
279
- async ({ method, requestParams }: { method: string; requestParams?: unknown }) => {
280
- if (method === "plugin/list") {
281
- return pluginList([pluginSummary("readwise", { installed: true, enabled: true })]);
282
- }
283
- if (method === "plugin/read") {
284
- return pluginRead("readwise", [
285
- pluginApp("asdk_app_readwise", { name: "Readwise", needsAuth: false }),
286
- ]);
287
- }
288
- if (method === "account/read") {
289
- return chatGptAccount();
290
- }
291
- if (method === "app/list") {
292
- expectRecordFields(requestParams, { forceRefetch: true });
293
- return appsList([
294
- appInfo("asdk_app_readwise", {
295
- name: "Readwise",
296
- isAccessible: false,
297
- isEnabled: true,
298
- }),
299
- ]);
300
- }
301
- throw new Error(`unexpected request ${method}`);
302
- },
303
- );
304
- const provider = buildCodexMigrationProvider();
305
-
306
- const plan = await provider.plan(
307
- makeContext({
308
- source: fixture.codexHome,
309
- stateDir: fixture.stateDir,
310
- workspaceDir: fixture.workspaceDir,
311
- verifyPluginApps: true,
312
- }),
313
- );
314
-
315
- expect(plan.items.some((item) => item.id === "plugin:readwise")).toBe(false);
316
- expect(plan.items.some((item) => item.id === "config:codex-plugins")).toBe(false);
317
- const manualItem = findItemByReason(plan.items, "app_inaccessible");
318
- expectRecordFields(manualItem, {
319
- kind: "manual",
320
- action: "manual",
321
- status: "skipped",
322
- reason: "app_inaccessible",
323
- });
324
- const details = expectRecordFields(manualItem.details, {
325
- pluginName: "readwise",
326
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
327
- });
328
- expect(details).not.toHaveProperty("code");
329
- expect(details.apps).toEqual([
330
- {
331
- id: "asdk_app_readwise",
332
- name: "Readwise",
333
- isAccessible: false,
334
- isEnabled: true,
335
- needsAuth: false,
336
- },
337
- ]);
338
- expect(appServerRequest.mock.calls.filter(([arg]) => arg.method === "app/list")).toHaveLength(
339
- 1,
340
- );
341
- });
342
-
343
- it("plans app-backed plugins without source app/list by default", async () => {
344
- const fixture = await createCodexFixture();
345
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
346
- if (method === "plugin/list") {
347
- return pluginList([pluginSummary("gmail", { installed: true, enabled: true })]);
348
- }
349
- if (method === "plugin/read") {
350
- return pluginRead("gmail", [pluginApp("app-gmail", { name: "Gmail", needsAuth: true })]);
351
- }
352
- if (method === "account/read") {
353
- return chatGptAccount();
354
- }
355
- throw new Error(`unexpected request ${method}`);
356
- });
357
- const provider = buildCodexMigrationProvider();
358
-
359
- const plan = await provider.plan(
360
- makeContext({
361
- source: fixture.codexHome,
362
- stateDir: fixture.stateDir,
363
- workspaceDir: fixture.workspaceDir,
364
- }),
365
- );
366
-
367
- expectRecordFields(findItem(plan.items, "plugin:gmail"), {
368
- kind: "plugin",
369
- action: "install",
370
- status: "planned",
371
- });
372
- expectRecordFields(findItem(plan.items, "config:codex-plugins"), {
373
- kind: "config",
374
- action: "merge",
375
- status: "planned",
376
- });
377
- expect(plan.warnings).toEqual([]);
378
- expect(appServerRequest.mock.calls.filter(([arg]) => arg.method === "app/list")).toHaveLength(
379
- 0,
380
- );
381
- });
382
-
383
- it("warns and skips app-backed plugins when source Codex account is not ChatGPT subscription auth", async () => {
384
- const fixture = await createCodexFixture();
385
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
386
- if (method === "plugin/list") {
387
- return pluginList([pluginSummary("gmail", { installed: true, enabled: true })]);
388
- }
389
- if (method === "plugin/read") {
390
- return pluginRead("gmail", [pluginApp("app-gmail", { name: "Gmail", needsAuth: true })]);
391
- }
392
- if (method === "account/read") {
393
- return {
394
- account: { type: "apiKey" },
395
- requiresOpenaiAuth: true,
396
- } satisfies CodexGetAccountResponse;
397
- }
398
- throw new Error(`unexpected request ${method}`);
399
- });
400
- const provider = buildCodexMigrationProvider();
401
-
402
- const plan = await provider.plan(
403
- makeContext({
404
- source: fixture.codexHome,
405
- stateDir: fixture.stateDir,
406
- workspaceDir: fixture.workspaceDir,
407
- }),
408
- );
409
-
410
- expect(plan.items.some((item) => item.id === "plugin:gmail")).toBe(false);
411
- expect(plan.items.some((item) => item.id === "config:codex-plugins")).toBe(false);
412
- const manualItem = findItemByReason(plan.items, "codex_subscription_required");
413
- expectRecordFields(manualItem, {
414
- kind: "manual",
415
- action: "manual",
416
- status: "skipped",
417
- reason: "codex_subscription_required",
418
- });
419
- const details = expectRecordFields(manualItem.details, {
420
- pluginName: "gmail",
421
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
422
- });
423
- expect(details).not.toHaveProperty("code");
424
- expect(details.apps).toEqual([
425
- {
426
- id: "app-gmail",
427
- name: "Gmail",
428
- needsAuth: true,
429
- },
430
- ]);
431
- expect(plan.warnings).toEqual([
432
- "Codex app-backed plugin migration requires the Codex app-server source account to be logged in with a ChatGPT subscription account. Log in to the Codex app with subscription auth; Klaw auth or API-key auth does not satisfy Codex app connector access.",
433
- ]);
434
- expect(appServerRequest.mock.calls.filter(([arg]) => arg.method === "app/list")).toHaveLength(
435
- 0,
436
- );
437
- });
438
-
439
- it("warns and skips app-backed plugins when source Codex account is missing", async () => {
440
- const fixture = await createCodexFixture();
441
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
442
- if (method === "plugin/list") {
443
- return pluginList([pluginSummary("gmail", { installed: true, enabled: true })]);
444
- }
445
- if (method === "plugin/read") {
446
- return pluginRead("gmail", [pluginApp("app-gmail", { name: "Gmail", needsAuth: true })]);
447
- }
448
- if (method === "account/read") {
449
- return {
450
- account: null,
451
- requiresOpenaiAuth: true,
452
- } satisfies CodexGetAccountResponse;
453
- }
454
- throw new Error(`unexpected request ${method}`);
455
- });
456
- const provider = buildCodexMigrationProvider();
457
-
458
- const plan = await provider.plan(
459
- makeContext({
460
- source: fixture.codexHome,
461
- stateDir: fixture.stateDir,
462
- workspaceDir: fixture.workspaceDir,
463
- }),
464
- );
465
-
466
- expect(plan.items.some((item) => item.id === "plugin:gmail")).toBe(false);
467
- expect(plan.items.some((item) => item.id === "config:codex-plugins")).toBe(false);
468
- expectRecordFields(findItemByReason(plan.items, "codex_subscription_required"), {
469
- reason: "codex_subscription_required",
470
- status: "skipped",
471
- });
472
- expect(appServerRequest.mock.calls.filter(([arg]) => arg.method === "app/list")).toHaveLength(
473
- 0,
474
- );
475
- });
476
-
477
- it("falls through to app inventory when source account read fails and app verification is requested", async () => {
478
- const fixture = await createCodexFixture();
479
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
480
- if (method === "plugin/list") {
481
- return pluginList([pluginSummary("gmail", { installed: true, enabled: true })]);
482
- }
483
- if (method === "plugin/read") {
484
- return pluginRead("gmail", [pluginApp("app-gmail", { name: "Gmail", needsAuth: true })]);
485
- }
486
- if (method === "account/read") {
487
- throw new Error("account unavailable");
488
- }
489
- if (method === "app/list") {
490
- return appsList([appInfo("app-gmail")]);
491
- }
492
- throw new Error(`unexpected request ${method}`);
493
- });
494
- const provider = buildCodexMigrationProvider();
495
-
496
- const plan = await provider.plan(
497
- makeContext({
498
- source: fixture.codexHome,
499
- stateDir: fixture.stateDir,
500
- workspaceDir: fixture.workspaceDir,
501
- verifyPluginApps: true,
502
- }),
503
- );
504
-
505
- expectRecordFields(findItem(plan.items, "plugin:gmail"), {
506
- kind: "plugin",
507
- action: "install",
508
- status: "planned",
509
- });
510
- expect(appServerRequest.mock.calls.filter(([arg]) => arg.method === "app/list")).toHaveLength(
511
- 1,
512
- );
513
- });
514
-
515
- it("skips app-backed plugins by default when source account read fails", async () => {
516
- const fixture = await createCodexFixture();
517
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
518
- if (method === "plugin/list") {
519
- return pluginList([pluginSummary("gmail", { installed: true, enabled: true })]);
520
- }
521
- if (method === "plugin/read") {
522
- return pluginRead("gmail", [pluginApp("app-gmail", { name: "Gmail", needsAuth: true })]);
523
- }
524
- if (method === "account/read") {
525
- throw new Error("account unavailable");
526
- }
527
- throw new Error(`unexpected request ${method}`);
528
- });
529
- const provider = buildCodexMigrationProvider();
530
-
531
- const plan = await provider.plan(
532
- makeContext({
533
- source: fixture.codexHome,
534
- stateDir: fixture.stateDir,
535
- workspaceDir: fixture.workspaceDir,
536
- }),
537
- );
538
-
539
- expect(plan.items.some((item) => item.id === "plugin:gmail")).toBe(false);
540
- expect(plan.items.some((item) => item.id === "config:codex-plugins")).toBe(false);
541
- const manualItem = findItemByReason(plan.items, "codex_account_unavailable");
542
- expectRecordFields(manualItem, {
543
- kind: "manual",
544
- action: "manual",
545
- reason: "codex_account_unavailable",
546
- status: "skipped",
547
- });
548
- expectRecordFields(manualItem.details, { error: "account unavailable" });
549
- expect(appServerRequest.mock.calls.filter(([arg]) => arg.method === "app/list")).toHaveLength(
550
- 0,
551
- );
552
- });
553
-
554
- it("reads source plugin readiness with native source auth instead of target agent auth", async () => {
555
- const fixture = await createCodexFixture();
556
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
557
- if (method === "plugin/list") {
558
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
559
- }
560
- if (method === "plugin/read") {
561
- return pluginRead("google-calendar", [
562
- pluginApp("app-google-calendar", { name: "Google Calendar", needsAuth: false }),
563
- ]);
564
- }
565
- if (method === "account/read") {
566
- return chatGptAccount();
567
- }
568
- if (method === "app/list") {
569
- return appsList([appInfo("app-google-calendar")]);
570
- }
571
- throw new Error(`unexpected request ${method}`);
572
- });
573
- const provider = buildCodexMigrationProvider();
574
-
575
- await provider.plan(
576
- makeContext({
577
- source: fixture.codexHome,
578
- stateDir: fixture.stateDir,
579
- workspaceDir: fixture.workspaceDir,
580
- verifyPluginApps: true,
581
- config: {
582
- agents: {
583
- defaults: {
584
- workspace: fixture.workspaceDir,
585
- },
586
- },
587
- auth: {
588
- order: {
589
- "openai-codex": ["openai-codex:target"],
590
- },
591
- },
592
- } as MigrationProviderContext["config"],
593
- }),
594
- );
595
-
596
- expect(appServerRequest).toHaveBeenCalledTimes(4);
597
- for (const [arg] of appServerRequest.mock.calls) {
598
- expect(arg.authProfileId).toBeNull();
599
- expect(arg.isolated).toBe(true);
600
- expect(arg.startOptions?.env).toEqual({
601
- CODEX_HOME: fixture.codexHome,
602
- HOME: path.dirname(fixture.codexHome),
603
- });
604
- expect(arg).not.toHaveProperty("agentDir");
605
- expect(arg).not.toHaveProperty("config");
606
- }
607
- });
608
-
609
- it("reports inaccessible before missing when multiple owned apps are blocked", async () => {
610
- const fixture = await createCodexFixture();
611
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
612
- if (method === "plugin/list") {
613
- return pluginList([pluginSummary("readwise", { installed: true, enabled: true })]);
614
- }
615
- if (method === "plugin/read") {
616
- return pluginRead("readwise", [
617
- pluginApp("asdk_app_readwise", { name: "Readwise", needsAuth: false }),
618
- pluginApp("asdk_app_reader", { name: "Reader", needsAuth: false }),
619
- ]);
620
- }
621
- if (method === "account/read") {
622
- return chatGptAccount();
623
- }
624
- if (method === "app/list") {
625
- return appsList([
626
- appInfo("asdk_app_readwise", {
627
- name: "Readwise",
628
- isAccessible: false,
629
- isEnabled: true,
630
- }),
631
- ]);
632
- }
633
- throw new Error(`unexpected request ${method}`);
634
- });
635
- const provider = buildCodexMigrationProvider();
636
-
637
- const plan = await provider.plan(
638
- makeContext({
639
- source: fixture.codexHome,
640
- stateDir: fixture.stateDir,
641
- workspaceDir: fixture.workspaceDir,
642
- verifyPluginApps: true,
643
- }),
644
- );
645
-
646
- const manualItem = findItemByReason(plan.items, "app_inaccessible");
647
- expectRecordFields(manualItem, {
648
- reason: "app_inaccessible",
649
- status: "skipped",
650
- });
651
- const details = expectRecordFields(manualItem.details, {
652
- pluginName: "readwise",
653
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
654
- });
655
- expect(details).not.toHaveProperty("code");
656
- expect(details.apps).toEqual([
657
- {
658
- id: "asdk_app_reader",
659
- name: "Reader",
660
- needsAuth: false,
661
- },
662
- {
663
- id: "asdk_app_readwise",
664
- name: "Readwise",
665
- isAccessible: false,
666
- isEnabled: true,
667
- needsAuth: false,
668
- },
669
- ]);
670
- });
671
-
672
- it("force-refreshes source app inventory once for app-backed plugins sharing a cache key", async () => {
673
- const fixture = await createCodexFixture();
674
- await defaultCodexAppInventoryCache.refreshNow({
675
- key: sourceAppCacheKey(fixture),
676
- request: async () => appsList([appInfo("app-google-calendar", { isAccessible: false })]),
677
- });
678
- appServerRequest.mockImplementation(
679
- async ({ method, requestParams }: { method: string; requestParams?: unknown }) => {
680
- if (method === "plugin/list") {
681
- return pluginList([
682
- pluginSummary("google-calendar", { installed: true, enabled: true }),
683
- pluginSummary("gmail", { installed: true, enabled: true }),
684
- ]);
685
- }
686
- if (method === "plugin/read") {
687
- const pluginName = (requestParams as v2.PluginReadParams).pluginName;
688
- return pluginRead(pluginName, [pluginApp(`app-${pluginName}`)]);
689
- }
690
- if (method === "account/read") {
691
- return chatGptAccount();
692
- }
693
- if (method === "app/list") {
694
- expectRecordFields(requestParams, { forceRefetch: true });
695
- return appsList([appInfo("app-google-calendar"), appInfo("app-gmail")]);
696
- }
697
- throw new Error(`unexpected request ${method}`);
698
- },
699
- );
700
- const provider = buildCodexMigrationProvider();
701
-
702
- const plan = await provider.plan(
703
- makeContext({
704
- source: fixture.codexHome,
705
- stateDir: fixture.stateDir,
706
- workspaceDir: fixture.workspaceDir,
707
- verifyPluginApps: true,
708
- }),
709
- );
710
-
711
- expectRecordFields(findItem(plan.items, "plugin:google-calendar"), { status: "planned" });
712
- expectRecordFields(findItem(plan.items, "plugin:gmail"), { status: "planned" });
713
- expect(appServerRequest.mock.calls.filter(([arg]) => arg.method === "app/list")).toHaveLength(
714
- 1,
715
- );
716
- });
717
-
718
- it("fails closed for disabled plugins and plugin/read failures", async () => {
719
- const fixture = await createCodexFixture();
720
- appServerRequest.mockImplementation(
721
- async ({ method, requestParams }: { method: string; requestParams?: unknown }) => {
722
- if (method === "plugin/list") {
723
- return pluginList([
724
- pluginSummary("readwise", { installed: true, enabled: false }),
725
- pluginSummary("gmail", { installed: true, enabled: true }),
726
- ]);
727
- }
728
- if (method === "plugin/read") {
729
- expectRecordFields(requestParams, { pluginName: "gmail" });
730
- throw new Error("detail unavailable");
731
- }
732
- throw new Error(`unexpected request ${method}`);
733
- },
734
- );
735
- const provider = buildCodexMigrationProvider();
736
-
737
- const plan = await provider.plan(
738
- makeContext({
739
- source: fixture.codexHome,
740
- stateDir: fixture.stateDir,
741
- workspaceDir: fixture.workspaceDir,
742
- verifyPluginApps: true,
743
- }),
744
- );
745
-
746
- expectRecordFields(findItemByReason(plan.items, "plugin_disabled"), {
747
- reason: "plugin_disabled",
748
- status: "skipped",
749
- });
750
- expectRecordFields(findItemByReason(plan.items, "plugin_read_unavailable"), {
751
- reason: "plugin_read_unavailable",
752
- status: "skipped",
753
- });
754
- expect(plan.items.some((item) => item.id === "config:codex-plugins")).toBe(false);
755
- expect(appServerRequest.mock.calls.filter(([arg]) => arg.method === "app/list")).toHaveLength(
756
- 0,
757
- );
758
- });
759
-
760
- it("fails closed when app inventory refresh fails for app-backed plugins", async () => {
761
- const fixture = await createCodexFixture();
762
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
763
- if (method === "plugin/list") {
764
- return pluginList([pluginSummary("readwise", { installed: true, enabled: true })]);
765
- }
766
- if (method === "plugin/read") {
767
- return pluginRead("readwise", [pluginApp("asdk_app_readwise", { name: "Readwise" })]);
768
- }
769
- if (method === "account/read") {
770
- return chatGptAccount();
771
- }
772
- if (method === "app/list") {
773
- throw new Error("app inventory unavailable");
774
- }
775
- throw new Error(`unexpected request ${method}`);
776
- });
777
- const provider = buildCodexMigrationProvider();
778
-
779
- const plan = await provider.plan(
780
- makeContext({
781
- source: fixture.codexHome,
782
- stateDir: fixture.stateDir,
783
- workspaceDir: fixture.workspaceDir,
784
- verifyPluginApps: true,
785
- }),
786
- );
787
-
788
- expectRecordFields(findItemByReason(plan.items, "app_inventory_unavailable"), {
789
- reason: "app_inventory_unavailable",
790
- status: "skipped",
791
- });
792
- expect(plan.items.some((item) => item.id === "plugin:readwise")).toBe(false);
793
- });
794
-
795
- it("treats auth-required source apps as ready when app inventory says they are accessible", async () => {
796
- const fixture = await createCodexFixture();
797
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
798
- if (method === "plugin/list") {
799
- return pluginList([pluginSummary("reader", { installed: true, enabled: true })]);
800
- }
801
- if (method === "plugin/read") {
802
- return pluginRead("reader", [
803
- pluginApp("ready-app", { name: "Ready App", needsAuth: false }),
804
- pluginApp("auth-app", { name: "Auth App", needsAuth: true }),
805
- ]);
806
- }
807
- if (method === "account/read") {
808
- return chatGptAccount();
809
- }
810
- if (method === "app/list") {
811
- return appsList([appInfo("ready-app"), appInfo("auth-app")]);
812
- }
813
- throw new Error(`unexpected request ${method}`);
814
- });
815
- const provider = buildCodexMigrationProvider();
816
-
817
- const plan = await provider.plan(
818
- makeContext({
819
- source: fixture.codexHome,
820
- stateDir: fixture.stateDir,
821
- workspaceDir: fixture.workspaceDir,
822
- verifyPluginApps: true,
823
- }),
824
- );
825
-
826
- const pluginItem = findItem(plan.items, "plugin:reader");
827
- expectRecordFields(pluginItem, {
828
- kind: "plugin",
829
- action: "install",
830
- status: "planned",
831
- });
832
- expectRecordFields(pluginItem.details, {
833
- configKey: "reader",
834
- pluginName: "reader",
835
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
836
- });
837
- });
838
-
839
- it("copies planned skills and archives native config during apply", async () => {
840
- const fixture = await createCodexFixture();
841
- const reportDir = path.join(fixture.root, "report");
842
- const provider = buildCodexMigrationProvider();
843
-
844
- const result = await provider.apply(
845
- makeContext({
846
- source: fixture.codexHome,
847
- stateDir: fixture.stateDir,
848
- workspaceDir: fixture.workspaceDir,
849
- reportDir,
850
- }),
851
- );
852
-
853
- await expect(
854
- fs.access(path.join(fixture.workspaceDir, "skills", "tweet-helper", "SKILL.md")),
855
- ).resolves.toBeUndefined();
856
- await expect(
857
- fs.access(path.join(fixture.workspaceDir, "skills", "personal-style", "SKILL.md")),
858
- ).resolves.toBeUndefined();
859
- await expect(
860
- fs.access(path.join(reportDir, "archive", "config.toml")),
861
- ).resolves.toBeUndefined();
862
- expectRecordFields(findItem(result.items, "plugin:documents:1"), { status: "skipped" });
863
- expectRecordFields(findItem(result.items, "skill:tweet-helper"), { status: "migrated" });
864
- expectRecordFields(findItem(result.items, "archive:config.toml"), { status: "migrated" });
865
- await expect(fs.access(path.join(reportDir, "report.json"))).resolves.toBeUndefined();
866
- });
867
-
868
- it("installs selected curated plugins during apply and writes codexPlugins config", async () => {
869
- const fixture = await createCodexFixture();
870
- const reportDir = path.join(fixture.root, "report");
871
- const configState: MigrationProviderContext["config"] = {
872
- plugins: {
873
- entries: {
874
- codex: {
875
- enabled: true,
876
- config: {
877
- appServer: { sandbox: "workspace-write" },
878
- },
879
- },
880
- },
881
- },
882
- agents: { defaults: { workspace: fixture.workspaceDir } },
883
- } as MigrationProviderContext["config"];
884
- let targetPluginListCalls = 0;
885
- let targetPluginListCallsAtInstall = 0;
886
- appServerRequest.mockImplementation(
887
- async ({ method, agentDir }: { method: string; agentDir?: string }) => {
888
- const isTarget = typeof agentDir === "string";
889
- if (method === "plugin/list" && !isTarget) {
890
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
891
- }
892
- if (method === "plugin/list" && isTarget) {
893
- targetPluginListCalls += 1;
894
- if (targetPluginListCalls === 1) {
895
- return { marketplaces: [], marketplaceLoadErrors: [], featuredPluginIds: [] };
896
- }
897
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
898
- }
899
- if (method === "plugin/read") {
900
- return pluginRead("google-calendar");
901
- }
902
- if (method === "plugin/install") {
903
- targetPluginListCallsAtInstall = targetPluginListCalls;
904
- return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse;
905
- }
906
- if (method === "skills/list") {
907
- return { data: [] } satisfies v2.SkillsListResponse;
908
- }
909
- if (method === "hooks/list") {
910
- return { data: [] } satisfies v2.HooksListResponse;
911
- }
912
- if (method === "config/mcpServer/reload") {
913
- return {};
914
- }
915
- if (method === "app/list") {
916
- return appsList([]);
917
- }
918
- throw new Error(`unexpected request ${method}`);
919
- },
920
- );
921
- const provider = buildCodexMigrationProvider({
922
- runtime: createConfigRuntime(configState),
923
- });
924
-
925
- const result = await provider.apply(
926
- makeContext({
927
- source: fixture.codexHome,
928
- stateDir: fixture.stateDir,
929
- workspaceDir: fixture.workspaceDir,
930
- reportDir,
931
- config: configState,
932
- }),
933
- );
934
-
935
- const installCall = appServerRequest.mock.calls.find(
936
- ([arg]) => (arg as { method?: string }).method === "plugin/install",
937
- )?.[0] as Record<string, unknown>;
938
- expect(targetPluginListCallsAtInstall).toBe(2);
939
- expectRecordFields(installCall, {
940
- method: "plugin/install",
941
- requestParams: {
942
- marketplacePath: "/marketplaces/openai-curated",
943
- pluginName: "google-calendar",
944
- },
945
- });
946
- const pluginItem = findItem(result.items, "plugin:google-calendar");
947
- expectRecordFields(pluginItem, {
948
- status: "migrated",
949
- reason: "already active",
950
- });
951
- expectRecordFields(pluginItem.details, {
952
- code: "already_active",
953
- installAttempted: true,
954
- });
955
- expectRecordFields(findItem(result.items, "config:codex-plugins"), {
956
- status: "migrated",
957
- });
958
- expect(configState.plugins?.entries?.codex?.enabled).toBe(true);
959
- expect(configState.plugins?.entries?.codex?.config?.appServer).toEqual({
960
- sandbox: "workspace-write",
961
- });
962
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).toEqual({
963
- enabled: true,
964
- allow_destructive_actions: true,
965
- plugins: {
966
- "google-calendar": {
967
- enabled: true,
968
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
969
- pluginName: "google-calendar",
970
- },
971
- },
972
- });
973
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).not.toHaveProperty("*");
974
- });
975
-
976
- it("leaves selected Codex plugins as warnings when target curated plugins never load", async () => {
977
- vi.stubEnv("KLAW_CODEX_MIGRATION_PLUGIN_LIST_TIMEOUT_MS", "1");
978
- const fixture = await createCodexFixture();
979
- const configState: MigrationProviderContext["config"] = {
980
- agents: { defaults: { workspace: fixture.workspaceDir } },
981
- } as MigrationProviderContext["config"];
982
- appServerRequest.mockImplementation(
983
- async ({ method, agentDir }: { method: string; agentDir?: string }) => {
984
- const isTarget = typeof agentDir === "string";
985
- if (method === "plugin/list" && !isTarget) {
986
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
987
- }
988
- if (method === "plugin/read" && !isTarget) {
989
- return pluginRead("google-calendar");
990
- }
991
- if (method === "plugin/list" && isTarget) {
992
- return {
993
- marketplaces: [],
994
- marketplaceLoadErrors: [],
995
- featuredPluginIds: [],
996
- } satisfies v2.PluginListResponse;
997
- }
998
- if (method === "skills/list") {
999
- return { data: [] } satisfies v2.SkillsListResponse;
1000
- }
1001
- if (method === "hooks/list") {
1002
- return { data: [] } satisfies v2.HooksListResponse;
1003
- }
1004
- if (method === "config/mcpServer/reload") {
1005
- return {};
1006
- }
1007
- if (method === "app/list") {
1008
- return appsList([]);
1009
- }
1010
- throw new Error(`unexpected request ${method}`);
1011
- },
1012
- );
1013
- const provider = buildCodexMigrationProvider({
1014
- runtime: createConfigRuntime(configState),
1015
- });
1016
-
1017
- const result = await provider.apply(
1018
- makeContext({
1019
- source: fixture.codexHome,
1020
- stateDir: fixture.stateDir,
1021
- workspaceDir: fixture.workspaceDir,
1022
- config: configState,
1023
- }),
1024
- );
1025
-
1026
- expect(
1027
- appServerRequest.mock.calls.some(
1028
- ([arg]) => (arg as { method?: string }).method === "plugin/install",
1029
- ),
1030
- ).toBe(false);
1031
- expectRecordFields(findItem(result.items, "plugin:google-calendar"), {
1032
- kind: "plugin",
1033
- action: "install",
1034
- status: "warning",
1035
- reason: "marketplace_missing",
1036
- });
1037
- expect(result.warnings).toContain(
1038
- "Some Codex plugins could not be migrated. Run `klaw migrate codex` after onboarding.",
1039
- );
1040
- expect(result.nextSteps).toContain(
1041
- "Some Codex plugins could not be migrated. Run `klaw migrate codex` after onboarding.",
1042
- );
1043
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).toBeUndefined();
1044
- });
1045
-
1046
- it("leaves selected Codex plugins as warnings when target inventory times out", async () => {
1047
- const fixture = await createCodexFixture();
1048
- const configState: MigrationProviderContext["config"] = {
1049
- agents: { defaults: { workspace: fixture.workspaceDir } },
1050
- } as MigrationProviderContext["config"];
1051
- appServerRequest.mockImplementation(
1052
- async ({ method, agentDir }: { method: string; agentDir?: string }) => {
1053
- const isTarget = typeof agentDir === "string";
1054
- if (method === "plugin/list" && !isTarget) {
1055
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
1056
- }
1057
- if (method === "plugin/read" && !isTarget) {
1058
- return pluginRead("google-calendar");
1059
- }
1060
- if (method === "plugin/list" && isTarget) {
1061
- throw new Error("codex app-server plugin/list timed out");
1062
- }
1063
- if (method === "skills/list") {
1064
- return { data: [] } satisfies v2.SkillsListResponse;
1065
- }
1066
- if (method === "hooks/list") {
1067
- return { data: [] } satisfies v2.HooksListResponse;
1068
- }
1069
- if (method === "config/mcpServer/reload") {
1070
- return {};
1071
- }
1072
- if (method === "app/list") {
1073
- return appsList([]);
1074
- }
1075
- throw new Error(`unexpected request ${method}`);
1076
- },
1077
- );
1078
- const provider = buildCodexMigrationProvider({
1079
- runtime: createConfigRuntime(configState),
1080
- });
1081
-
1082
- const result = await provider.apply(
1083
- makeContext({
1084
- source: fixture.codexHome,
1085
- stateDir: fixture.stateDir,
1086
- workspaceDir: fixture.workspaceDir,
1087
- config: configState,
1088
- }),
1089
- );
1090
-
1091
- expectRecordFields(findItem(result.items, "plugin:google-calendar"), {
1092
- kind: "plugin",
1093
- action: "install",
1094
- status: "warning",
1095
- reason: "plugin_inventory_unavailable",
1096
- message: 'Codex plugin "google-calendar" could not be migrated automatically',
1097
- });
1098
- expect(result.warnings).toContain(
1099
- "Some Codex plugins could not be migrated. Run `klaw migrate codex` after onboarding.",
1100
- );
1101
- expect(result.nextSteps).toContain(
1102
- "Some Codex plugins could not be migrated. Run `klaw migrate codex` after onboarding.",
1103
- );
1104
- expect(result.summary.errors).toBe(0);
1105
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).toBeUndefined();
1106
- });
1107
-
1108
- it("plans already configured target Codex plugins as plugin-level conflicts", async () => {
1109
- const fixture = await createCodexFixture();
1110
- const configState: MigrationProviderContext["config"] = {
1111
- plugins: {
1112
- entries: {
1113
- codex: {
1114
- enabled: true,
1115
- config: {
1116
- codexPlugins: {
1117
- enabled: true,
1118
- allow_destructive_actions: false,
1119
- plugins: {
1120
- "google-calendar": {
1121
- enabled: true,
1122
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
1123
- pluginName: "google-calendar",
1124
- },
1125
- },
1126
- },
1127
- },
1128
- },
1129
- },
1130
- },
1131
- agents: { defaults: { workspace: fixture.workspaceDir } },
1132
- } as MigrationProviderContext["config"];
1133
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
1134
- if (method === "plugin/list") {
1135
- return pluginList([
1136
- pluginSummary("google-calendar", { installed: true, enabled: true }),
1137
- pluginSummary("gmail", { installed: true, enabled: true }),
1138
- ]);
1139
- }
1140
- if (method === "plugin/read") {
1141
- return pluginRead("google-calendar");
1142
- }
1143
- throw new Error(`unexpected request ${method}`);
1144
- });
1145
- const provider = buildCodexMigrationProvider();
1146
-
1147
- const result = await provider.plan(
1148
- makeContext({
1149
- source: fixture.codexHome,
1150
- stateDir: fixture.stateDir,
1151
- workspaceDir: fixture.workspaceDir,
1152
- config: configState,
1153
- }),
1154
- );
1155
-
1156
- expectRecordFields(findItem(result.items, "plugin:google-calendar"), {
1157
- status: "conflict",
1158
- reason: "plugin exists",
1159
- });
1160
- expectRecordFields(findItem(result.items, "plugin:gmail"), { status: "planned" });
1161
- expectRecordFields(findItem(result.items, "config:codex-plugins"), { status: "planned" });
1162
- });
1163
-
1164
- it("preserves explicit app-server settings during plugin migration", async () => {
1165
- const fixture = await createCodexFixture();
1166
- const configState: MigrationProviderContext["config"] = {
1167
- plugins: {
1168
- entries: {
1169
- codex: {
1170
- enabled: true,
1171
- config: {
1172
- appServer: { sandbox: "workspace-write" },
1173
- },
1174
- },
1175
- },
1176
- },
1177
- agents: { defaults: { workspace: fixture.workspaceDir } },
1178
- } as MigrationProviderContext["config"];
1179
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
1180
- if (method === "plugin/list") {
1181
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
1182
- }
1183
- if (method === "plugin/read") {
1184
- return pluginRead("google-calendar");
1185
- }
1186
- if (method === "plugin/install") {
1187
- return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse;
1188
- }
1189
- if (method === "skills/list") {
1190
- return { data: [] } satisfies v2.SkillsListResponse;
1191
- }
1192
- if (method === "hooks/list") {
1193
- return { data: [] } satisfies v2.HooksListResponse;
1194
- }
1195
- if (method === "config/mcpServer/reload") {
1196
- return {};
1197
- }
1198
- if (method === "app/list") {
1199
- return appsList([]);
1200
- }
1201
- throw new Error(`unexpected request ${method}`);
1202
- });
1203
- const provider = buildCodexMigrationProvider({
1204
- runtime: createConfigRuntime(configState),
1205
- });
1206
-
1207
- await provider.apply(
1208
- makeContext({
1209
- source: fixture.codexHome,
1210
- stateDir: fixture.stateDir,
1211
- workspaceDir: fixture.workspaceDir,
1212
- config: configState,
1213
- }),
1214
- );
1215
-
1216
- expect(configState.plugins?.entries?.codex?.config?.appServer).toEqual({
1217
- sandbox: "workspace-write",
1218
- });
1219
- });
1220
-
1221
- it("returns Codex plugin config patches without mutating config in return mode", async () => {
1222
- const fixture = await createCodexFixture();
1223
- const configState: MigrationProviderContext["config"] = {
1224
- plugins: {
1225
- entries: {
1226
- codex: {
1227
- enabled: true,
1228
- config: {
1229
- appServer: { sandbox: "workspace-write" },
1230
- },
1231
- },
1232
- },
1233
- },
1234
- agents: { defaults: { workspace: fixture.workspaceDir } },
1235
- } as MigrationProviderContext["config"];
1236
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
1237
- if (method === "plugin/list") {
1238
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
1239
- }
1240
- if (method === "plugin/read") {
1241
- return pluginRead("google-calendar");
1242
- }
1243
- if (method === "plugin/install") {
1244
- return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse;
1245
- }
1246
- if (method === "skills/list") {
1247
- return { data: [] } satisfies v2.SkillsListResponse;
1248
- }
1249
- if (method === "hooks/list") {
1250
- return { data: [] } satisfies v2.HooksListResponse;
1251
- }
1252
- if (method === "config/mcpServer/reload") {
1253
- return {};
1254
- }
1255
- if (method === "app/list") {
1256
- return appsList([]);
1257
- }
1258
- throw new Error(`unexpected request ${method}`);
1259
- });
1260
- const mutateConfigFile = vi.fn(async () => {
1261
- throw new Error("mutateConfigFile should not be called in return mode");
1262
- });
1263
- const provider = buildCodexMigrationProvider({
1264
- runtime: {
1265
- config: {
1266
- current: () => configState,
1267
- mutateConfigFile,
1268
- },
1269
- } as unknown as MigrationProviderContext["runtime"],
1270
- });
1271
-
1272
- const result = await provider.apply(
1273
- makeContext({
1274
- source: fixture.codexHome,
1275
- stateDir: fixture.stateDir,
1276
- workspaceDir: fixture.workspaceDir,
1277
- config: configState,
1278
- providerOptions: { configPatchMode: "return" },
1279
- }),
1280
- );
1281
-
1282
- expect(mutateConfigFile).not.toHaveBeenCalled();
1283
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).toBeUndefined();
1284
- const configItem = findItem(result.items, "config:codex-plugins");
1285
- expectRecordFields(configItem, { status: "migrated" });
1286
- const configDetails = configItem.details as Record<string, unknown>;
1287
- expectRecordFields(configDetails, {
1288
- path: ["plugins", "entries", "codex"],
1289
- });
1290
- expect(configDetails.value).toEqual({
1291
- enabled: true,
1292
- config: {
1293
- codexPlugins: {
1294
- enabled: true,
1295
- allow_destructive_actions: true,
1296
- plugins: {
1297
- "google-calendar": {
1298
- enabled: true,
1299
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
1300
- pluginName: "google-calendar",
1301
- },
1302
- },
1303
- },
1304
- },
1305
- });
1306
- });
1307
-
1308
- it("merges migrated plugin config with existing Codex plugins when entries do not conflict", async () => {
1309
- const fixture = await createCodexFixture();
1310
- const sourceKey = sourceAppCacheKey(fixture);
1311
- await defaultCodexAppInventoryCache.refreshNow({
1312
- key: sourceKey,
1313
- request: async () => appsList([appInfo("source-only-app")]),
1314
- });
1315
- const configState: MigrationProviderContext["config"] = {
1316
- plugins: {
1317
- entries: {
1318
- codex: {
1319
- enabled: true,
1320
- config: {
1321
- codexPlugins: {
1322
- enabled: true,
1323
- allow_destructive_actions: true,
1324
- plugins: {
1325
- slack: {
1326
- enabled: true,
1327
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
1328
- pluginName: "slack",
1329
- },
1330
- },
1331
- },
1332
- },
1333
- },
1334
- },
1335
- },
1336
- agents: { defaults: { workspace: fixture.workspaceDir } },
1337
- } as MigrationProviderContext["config"];
1338
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
1339
- if (method === "plugin/list") {
1340
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
1341
- }
1342
- if (method === "plugin/read") {
1343
- return pluginRead("google-calendar");
1344
- }
1345
- if (method === "plugin/install") {
1346
- return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse;
1347
- }
1348
- if (method === "skills/list") {
1349
- return { data: [] } satisfies v2.SkillsListResponse;
1350
- }
1351
- if (method === "hooks/list") {
1352
- return { data: [] } satisfies v2.HooksListResponse;
1353
- }
1354
- if (method === "config/mcpServer/reload") {
1355
- return {};
1356
- }
1357
- if (method === "app/list") {
1358
- return appsList([]);
1359
- }
1360
- throw new Error(`unexpected request ${method}`);
1361
- });
1362
- const provider = buildCodexMigrationProvider({
1363
- runtime: createConfigRuntime(configState),
1364
- });
1365
-
1366
- const result = await provider.apply(
1367
- makeContext({
1368
- source: fixture.codexHome,
1369
- stateDir: fixture.stateDir,
1370
- workspaceDir: fixture.workspaceDir,
1371
- config: configState,
1372
- }),
1373
- );
1374
-
1375
- expectRecordFields(findItem(result.items, "config:codex-plugins"), { status: "migrated" });
1376
- const sourceCacheRead = defaultCodexAppInventoryCache.read({
1377
- key: sourceKey,
1378
- request: async () => {
1379
- throw new Error("source app cache was cleared");
1380
- },
1381
- });
1382
- expect(sourceCacheRead.state).toBe("fresh");
1383
- expect(sourceCacheRead.snapshot?.apps.map((app) => app.id)).toEqual(["source-only-app"]);
1384
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).toEqual({
1385
- allow_destructive_actions: true,
1386
- plugins: {
1387
- "google-calendar": {
1388
- enabled: true,
1389
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
1390
- pluginName: "google-calendar",
1391
- },
1392
- slack: {
1393
- enabled: true,
1394
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
1395
- pluginName: "slack",
1396
- },
1397
- },
1398
- enabled: true,
1399
- });
1400
- });
1401
-
1402
- it("preserves existing destructive plugin policy when overwrite is explicit", async () => {
1403
- const fixture = await createCodexFixture();
1404
- const configState: MigrationProviderContext["config"] = {
1405
- plugins: {
1406
- entries: {
1407
- codex: {
1408
- enabled: true,
1409
- config: {
1410
- codexPlugins: {
1411
- enabled: true,
1412
- allow_destructive_actions: true,
1413
- plugins: {},
1414
- },
1415
- },
1416
- },
1417
- },
1418
- },
1419
- agents: { defaults: { workspace: fixture.workspaceDir } },
1420
- } as MigrationProviderContext["config"];
1421
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
1422
- if (method === "plugin/list") {
1423
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
1424
- }
1425
- if (method === "plugin/read") {
1426
- return pluginRead("google-calendar");
1427
- }
1428
- if (method === "plugin/install") {
1429
- return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse;
1430
- }
1431
- if (method === "skills/list") {
1432
- return { data: [] } satisfies v2.SkillsListResponse;
1433
- }
1434
- if (method === "hooks/list") {
1435
- return { data: [] } satisfies v2.HooksListResponse;
1436
- }
1437
- if (method === "config/mcpServer/reload") {
1438
- return {};
1439
- }
1440
- if (method === "app/list") {
1441
- return appsList([]);
1442
- }
1443
- throw new Error(`unexpected request ${method}`);
1444
- });
1445
- const provider = buildCodexMigrationProvider({
1446
- runtime: createConfigRuntime(configState),
1447
- });
1448
-
1449
- const result = await provider.apply(
1450
- makeContext({
1451
- source: fixture.codexHome,
1452
- stateDir: fixture.stateDir,
1453
- workspaceDir: fixture.workspaceDir,
1454
- config: configState,
1455
- overwrite: true,
1456
- }),
1457
- );
1458
-
1459
- expectRecordFields(findItem(result.items, "config:codex-plugins"), { status: "migrated" });
1460
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).toEqual({
1461
- enabled: true,
1462
- allow_destructive_actions: true,
1463
- plugins: {
1464
- "google-calendar": {
1465
- enabled: true,
1466
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
1467
- pluginName: "google-calendar",
1468
- },
1469
- },
1470
- });
1471
- });
1472
-
1473
- it("records auth-required plugin installs as disabled explicit config entries", async () => {
1474
- const fixture = await createCodexFixture();
1475
- const configState: MigrationProviderContext["config"] = {
1476
- agents: { defaults: { workspace: fixture.workspaceDir } },
1477
- } as MigrationProviderContext["config"];
1478
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
1479
- if (method === "plugin/list") {
1480
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
1481
- }
1482
- if (method === "plugin/read") {
1483
- return pluginRead("google-calendar");
1484
- }
1485
- if (method === "plugin/install") {
1486
- return {
1487
- authPolicy: "ON_USE",
1488
- appsNeedingAuth: [
1489
- {
1490
- id: "google-calendar",
1491
- name: "Google Calendar",
1492
- description: "Calendar",
1493
- installUrl: "https://example.invalid/auth",
1494
- needsAuth: true,
1495
- },
1496
- ],
1497
- } satisfies v2.PluginInstallResponse;
1498
- }
1499
- if (method === "skills/list") {
1500
- return { data: [] } satisfies v2.SkillsListResponse;
1501
- }
1502
- if (method === "hooks/list") {
1503
- return { data: [] } satisfies v2.HooksListResponse;
1504
- }
1505
- if (method === "config/mcpServer/reload") {
1506
- return {};
1507
- }
1508
- if (method === "app/list") {
1509
- return appsList([]);
1510
- }
1511
- throw new Error(`unexpected request ${method}`);
1512
- });
1513
- const provider = buildCodexMigrationProvider({
1514
- runtime: createConfigRuntime(configState),
1515
- });
1516
-
1517
- const result = await provider.apply(
1518
- makeContext({
1519
- source: fixture.codexHome,
1520
- stateDir: fixture.stateDir,
1521
- workspaceDir: fixture.workspaceDir,
1522
- config: configState,
1523
- }),
1524
- );
1525
-
1526
- const pluginItem = findItem(result.items, "plugin:google-calendar");
1527
- expectRecordFields(pluginItem, {
1528
- status: "skipped",
1529
- reason: "auth_required",
1530
- });
1531
- expectRecordFields(pluginItem.details, {
1532
- code: "auth_required",
1533
- appsNeedingAuth: [
1534
- {
1535
- id: "google-calendar",
1536
- name: "Google Calendar",
1537
- needsAuth: true,
1538
- },
1539
- ],
1540
- });
1541
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).toEqual({
1542
- enabled: true,
1543
- allow_destructive_actions: true,
1544
- plugins: {
1545
- "google-calendar": {
1546
- enabled: false,
1547
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
1548
- pluginName: "google-calendar",
1549
- },
1550
- },
1551
- });
1552
- });
1553
-
1554
- it("does not write config entries for failed plugin installs", async () => {
1555
- const fixture = await createCodexFixture();
1556
- const configState: MigrationProviderContext["config"] = {
1557
- agents: { defaults: { workspace: fixture.workspaceDir } },
1558
- } as MigrationProviderContext["config"];
1559
- appServerRequest.mockImplementation(async ({ method }: { method: string }) => {
1560
- if (method === "plugin/list") {
1561
- return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
1562
- }
1563
- if (method === "plugin/read") {
1564
- return pluginRead("google-calendar");
1565
- }
1566
- if (method === "plugin/install") {
1567
- throw new Error("install failed");
1568
- }
1569
- if (method === "skills/list") {
1570
- return { data: [] } satisfies v2.SkillsListResponse;
1571
- }
1572
- if (method === "hooks/list") {
1573
- return { data: [] } satisfies v2.HooksListResponse;
1574
- }
1575
- throw new Error(`unexpected request ${method}`);
1576
- });
1577
- const provider = buildCodexMigrationProvider({
1578
- runtime: createConfigRuntime(configState),
1579
- });
1580
-
1581
- const result = await provider.apply(
1582
- makeContext({
1583
- source: fixture.codexHome,
1584
- stateDir: fixture.stateDir,
1585
- workspaceDir: fixture.workspaceDir,
1586
- config: configState,
1587
- }),
1588
- );
1589
-
1590
- expectRecordFields(findItem(result.items, "plugin:google-calendar"), {
1591
- status: "error",
1592
- reason: "install failed",
1593
- });
1594
- expectRecordFields(findItem(result.items, "config:codex-plugins"), {
1595
- status: "skipped",
1596
- reason: "no selected Codex plugins",
1597
- });
1598
- expect(configState.plugins?.entries?.codex?.config?.codexPlugins).toBeUndefined();
1599
- });
1600
-
1601
- it("reports existing skill targets as conflicts unless overwrite is set", async () => {
1602
- const fixture = await createCodexFixture();
1603
- await writeFile(path.join(fixture.workspaceDir, "skills", "tweet-helper", "SKILL.md"));
1604
- const provider = buildCodexMigrationProvider();
1605
-
1606
- const plan = await provider.plan(
1607
- makeContext({
1608
- source: fixture.codexHome,
1609
- stateDir: fixture.stateDir,
1610
- workspaceDir: fixture.workspaceDir,
1611
- }),
1612
- );
1613
- const overwritePlan = await provider.plan(
1614
- makeContext({
1615
- source: fixture.codexHome,
1616
- stateDir: fixture.stateDir,
1617
- workspaceDir: fixture.workspaceDir,
1618
- overwrite: true,
1619
- }),
1620
- );
1621
-
1622
- expectRecordFields(findItem(plan.items, "skill:tweet-helper"), { status: "conflict" });
1623
- expectRecordFields(findItem(overwritePlan.items, "skill:tweet-helper"), {
1624
- status: "planned",
1625
- });
1626
- });
1627
- });
1628
-
1629
- function createConfigRuntime(
1630
- configState: MigrationProviderContext["config"],
1631
- ): MigrationProviderContext["runtime"] {
1632
- type Runtime = NonNullable<MigrationProviderContext["runtime"]>;
1633
- type MutateConfigFileParams = Parameters<Runtime["config"]["mutateConfigFile"]>[0];
1634
- type MutateConfigFileResult = Awaited<ReturnType<Runtime["config"]["mutateConfigFile"]>>;
1635
- return {
1636
- config: {
1637
- current: () => configState,
1638
- mutateConfigFile: async (params: MutateConfigFileParams): Promise<MutateConfigFileResult> => {
1639
- const result = await params.mutate(configState, {
1640
- snapshot: {} as never,
1641
- previousHash: null,
1642
- });
1643
- return {
1644
- path: "/tmp/klaw.json",
1645
- previousHash: null,
1646
- persistedHash: "test-persisted-hash",
1647
- snapshot: {} as never,
1648
- nextConfig: configState,
1649
- afterWrite: { mode: "auto" },
1650
- followUp: { mode: "auto", requiresRestart: false },
1651
- result,
1652
- };
1653
- },
1654
- },
1655
- } as unknown as MigrationProviderContext["runtime"];
1656
- }
1657
-
1658
- function pluginList(plugins: v2.PluginSummary[]): v2.PluginListResponse {
1659
- return {
1660
- marketplaces: [
1661
- {
1662
- name: CODEX_PLUGINS_MARKETPLACE_NAME,
1663
- path: "/marketplaces/openai-curated",
1664
- interface: null,
1665
- plugins,
1666
- },
1667
- ],
1668
- marketplaceLoadErrors: [],
1669
- featuredPluginIds: [],
1670
- };
1671
- }
1672
-
1673
- function pluginRead(pluginName: string, apps: v2.AppSummary[] = []): v2.PluginReadResponse {
1674
- return {
1675
- plugin: {
1676
- marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
1677
- marketplacePath: "/marketplaces/openai-curated",
1678
- summary: pluginSummary(pluginName, { installed: true, enabled: true }),
1679
- description: null,
1680
- skills: [],
1681
- apps,
1682
- mcpServers: [],
1683
- },
1684
- };
1685
- }
1686
-
1687
- function pluginApp(id: string, overrides: Partial<v2.AppSummary> = {}): v2.AppSummary {
1688
- return {
1689
- id,
1690
- name: id,
1691
- description: null,
1692
- installUrl: null,
1693
- needsAuth: false,
1694
- ...overrides,
1695
- };
1696
- }
1697
-
1698
- function appInfo(id: string, overrides: Partial<v2.AppInfo> = {}): v2.AppInfo {
1699
- return {
1700
- id,
1701
- name: id,
1702
- description: null,
1703
- logoUrl: null,
1704
- logoUrlDark: null,
1705
- distributionChannel: null,
1706
- branding: null,
1707
- appMetadata: null,
1708
- labels: null,
1709
- installUrl: null,
1710
- isAccessible: true,
1711
- isEnabled: true,
1712
- pluginDisplayNames: [],
1713
- ...overrides,
1714
- };
1715
- }
1716
-
1717
- function appsList(apps: v2.AppInfo[]): v2.AppsListResponse {
1718
- return { data: apps, nextCursor: null };
1719
- }
1720
-
1721
- function chatGptAccount(): CodexGetAccountResponse {
1722
- return {
1723
- account: { type: "chatgpt", email: "codex@example.test", planType: "plus" },
1724
- requiresOpenaiAuth: false,
1725
- };
1726
- }
1727
-
1728
- function pluginSummary(id: string, overrides: Partial<v2.PluginSummary> = {}): v2.PluginSummary {
1729
- return {
1730
- id,
1731
- name: id,
1732
- source: { type: "remote" },
1733
- installed: false,
1734
- enabled: false,
1735
- installPolicy: "AVAILABLE",
1736
- authPolicy: "ON_USE",
1737
- availability: "AVAILABLE",
1738
- interface: null,
1739
- ...overrides,
1740
- };
1741
- }