codex-copilot-dx 0.9.23 → 0.9.24

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.
package/README.md CHANGED
@@ -5,6 +5,7 @@ Use Codex App with GitHub Copilot through a local adapter.
5
5
  CCDX targets Codex App. It discovers Copilot models, adapts the client's model
6
6
  catalog, and preserves Responses streaming, tool calls and conversation history.
7
7
  Default Auto-review: **GPT-6.1 Sol / low**. Image generation is **off by default**.
8
+ Native predictions are also **off by default**.
8
9
 
9
10
  ## Install and run
10
11
 
@@ -38,14 +39,13 @@ The dashboard does not poll in the background; refresh when needed.
38
39
  | `ccdx models` | Fresh upstream model list |
39
40
  | `ccdx usage` | Recorded tokens and upstream prompt-cache hit rate |
40
41
  | `ccdx auto-review-model` | Select model, then reasoning effort; `0` follows client |
41
- | `ccdx suggestion on` / `ccdx suggestion off` | Enable or disable the optional next-message panel |
42
- | `ccdx suggestion model` / `ccdx suggestion status` | Choose its model by number, or check settings |
43
42
  | `ccdx animation` | Preview and select terminal animation |
44
43
  | `ccdx cache --limit 128` | Set the history-cache budget in MiB |
45
44
  | `ccdx cache --clean` | Clear the rebuildable image-transform cache |
46
45
  | `ccdx limits --decoded 256` | Set the decoded request-body budget; restart to apply |
47
46
  | `ccdx enable-image` | Opt in to image generation/editing with your own API |
48
47
  | `ccdx image-status` / `ccdx disable-image` | Check or disable optional images |
48
+ | `ccdx prediction on` / `off` / `status` | Opt in to native next-message predictions (macOS) |
49
49
  | `ccdx update` | Update the global package |
50
50
 
51
51
  ## Compatibility and safety
@@ -64,20 +64,17 @@ The dashboard does not poll in the background; refresh when needed.
64
64
  break continuations. Generated images and Codex transcripts are not deleted.
65
65
  - Optional image setup/maintenance never blocks the primary service. Disabled
66
66
  users get no CCDX image skill or MCP installation.
67
- - Next-message suggestions are **off by default**. Opt in with `ccdx suggestion on`,
68
- then open **Next message / 下一句** in a local conversation panel or ask Codex for
69
- a next-message suggestion. **Add to draft / 加入草稿** appends ordinary text; it
70
- never sends, replaces your draft, or patches the client. Default model:
71
- `gpt-6-luna`, with `low` reasoning when advertised. Requires a running local
72
- adapter and a recent Codex with paginated thread reads and MCP draft support.
73
- - Suggestions read only a bounded page from the host-identified current thread.
74
- Images, reasoning items and tool outputs are excluded. On supported runtimes,
75
- `suggestion on` configures an async Stop hook; review it in Codex before it runs.
76
- CCDX never grants trust. The panel reads saved suggestions; adding drafts is manual.
77
- The dashboard shows settings and generation status. Model selection alone stays off.
78
- Existing user-managed MCP/profile configuration is preserved. Native prediction
79
- settings are untouched; account/remote defaults in all future clients cannot be
80
- reliably detected. Use `ccdx suggestion off` when preferring an official feature.
67
+ - The manual prediction MCP, background hook and dashboard panel are retired.
68
+ Startup removes only unchanged CCDX registrations and disables saved prediction
69
+ settings. The optional image MCP remains unchanged. Modified old registrations
70
+ can use inert compatibility entrypoints; they no longer generate suggestions.
71
+ - Native predictions are a separate, opt-in compatibility feature. Quit Codex,
72
+ run `ccdx prediction on`, then restart CCDX. This adds App-profile certificate
73
+ trust and an App-only route for `chatgpt.com` and `ab.chatgpt.com`; HTTPS on those
74
+ hosts is decrypted locally, but only prediction configuration is changed.
75
+ Other domains and the CCDX model endpoint stay on their existing routes.
76
+ No program files or OS trust are changed. Existing proxies are not replaced.
77
+ See the reference for restart, rollback and client-version boundaries.
81
78
  - CCDX adds experimental local history compression only when installed App
82
79
  runtimes support it and report it off. Existing choices are preserved; an
83
80
  already-enabled default, unavailable runtime or failed check is skipped.
package/bin/cli.mjs CHANGED
@@ -77,8 +77,21 @@ if (CLI.command === "image") {
77
77
  }
78
78
  if (CLI.command === "suggestion") {
79
79
  try {
80
- const { runSuggestionCommand } = await import("../src/cli-suggestion.mjs");
81
- await runSuggestionCommand({ action: CLI.action, model: CLI.model, commandName: CLI_NAME });
80
+ const { retirePredictionIntegration } = await import("../src/prediction-retirement.mjs");
81
+ const result = retirePredictionIntegration({ dryRun: CLI.action === "status" });
82
+ console.log("The CCDX prediction panel has been retired and cannot be enabled.");
83
+ if (CLI.action === "status" && result.changed) console.log(`Legacy settings remain; run ${CLI_NAME} suggestion off to clean them up.`);
84
+ if (result.preserved) console.warn("Modified prediction settings were preserved.");
85
+ process.exit(0);
86
+ } catch (error) {
87
+ console.error(status("err", error.message));
88
+ process.exit(1);
89
+ }
90
+ }
91
+ if (CLI.command === "prediction") {
92
+ try {
93
+ const { runPredictionCommand } = await import("../src/native-prediction.mjs");
94
+ await runPredictionCommand({ action: CLI.action });
82
95
  process.exit(0);
83
96
  } catch (error) {
84
97
  console.error(status("err", error.message));
@@ -251,6 +264,7 @@ const LOGGING = configureLogging();
251
264
  let activeServer = null;
252
265
  let modelRefreshTimer = null;
253
266
  let shuttingDown = false;
267
+ let nativePrediction = null;
254
268
 
255
269
  if (LOGGING.filePath) {
256
270
  console.log(status("info", `Debug log: ${LOGGING.filePath}`));
@@ -272,7 +286,18 @@ async function reuseRunningAdapterIfAvailable() {
272
286
  console.log(status("ok", `Using existing adapter at ${running.baseUrl}`));
273
287
  const { compressionSetup } = await configureCodexClient();
274
288
 
275
- await openCodex();
289
+ let predictionPacUrl = null;
290
+ let predictionApp = null;
291
+ try {
292
+ const { readPredictionSettings } = await import("../src/native-prediction-profile.mjs");
293
+ const preference = readPredictionSettings();
294
+ if (preference.enabled) {
295
+ predictionPacUrl = running.data?.native_prediction_pac || null;
296
+ predictionApp = preference.app?.app;
297
+ if (!predictionPacUrl) console.warn(status("warn", "Restart ccdx to activate the newly enabled prediction bridge"));
298
+ }
299
+ } catch (error) { console.warn(status("warn", error.message)); }
300
+ await openCodex({ predictionPacUrl, predictionApp });
276
301
  console.log(`
277
302
  ${status("ok", "Ready, using the existing ccdx adapter")}
278
303
 
@@ -285,6 +310,14 @@ async function reuseRunningAdapterIfAvailable() {
285
310
  }
286
311
 
287
312
  async function configureCodexClient() {
313
+ try {
314
+ const { retirePredictionIntegration } = await import("../src/prediction-retirement.mjs");
315
+ const result = retirePredictionIntegration();
316
+ if (result.changed) console.log(status("info", "Removed the retired CCDX prediction integration"));
317
+ if (result.preserved) console.warn(status("warn", "Modified prediction settings were preserved"));
318
+ } catch (error) {
319
+ console.warn(status("warn", `${error.message}. Core startup will continue.`));
320
+ }
288
321
  const imageProviderEnabled = Boolean(readImageProviderConfig());
289
322
  const { imageMcpSkipped } = ensureCodexConfig(ADAPTER_PORT, { host: ADAPTER_HOST, imageProviderEnabled });
290
323
  if (imageProviderEnabled && !imageMcpSkipped) {
@@ -369,13 +402,20 @@ try {
369
402
  streamHandshakeTimeoutMs: RUNTIME.streamHandshakeTimeoutMs,
370
403
  streamIdleTimeoutMs: RUNTIME.streamIdleTimeoutMs,
371
404
  terminalAnimationTheme,
405
+ nativePredictionPac: () => nativePrediction?.pacUrl || null,
372
406
  });
373
407
 
374
408
  // Point Codex App at the adapter.
375
409
  const { compressionSetup } = await configureCodexClient();
376
410
 
377
- // Launch Codex when available.
378
- await openCodex();
411
+ // Optional native prediction setup must never prevent the core adapter from running.
412
+ try {
413
+ const { startNativePrediction } = await import("../src/native-prediction.mjs");
414
+ nativePrediction = await startNativePrediction({ warning: (message) => console.log(status("info", message)) });
415
+ } catch (error) { console.warn(status("warn", `${error.message}. Core startup will continue.`)); }
416
+
417
+ // Launch Codex when available. A running App is never forcibly restarted.
418
+ await openCodex({ predictionPacUrl: nativePrediction?.pacUrl, predictionApp: nativePrediction?.app });
379
419
 
380
420
  // Periodically refresh the model list + endpoint cache so a long-running
381
421
  // adapter picks up newly released models without a restart.
@@ -404,6 +444,7 @@ try {
404
444
  let exitCode = 0;
405
445
  try {
406
446
  if (modelRefreshTimer) clearInterval(modelRefreshTimer);
447
+ await nativePrediction?.close();
407
448
  const result = await closeHttpServer(activeServer, { timeoutMs: RUNTIME.shutdownTimeoutMs });
408
449
  if (result.forced) console.warn(status("warn", "Forced remaining adapter connections closed"));
409
450
  await flushUsageWrites();
@@ -420,6 +461,7 @@ try {
420
461
  await compressionSetup;
421
462
  } catch (e) {
422
463
  console.error(status("err", e.message));
464
+ await nativePrediction?.close().catch(() => {});
423
465
  await closeHttpServer(activeServer, { timeoutMs: RUNTIME.shutdownTimeoutMs }).catch(() => {});
424
466
  await flushUsageWrites();
425
467
  await LOGGING.cleanup();
package/docs/REFERENCE.md CHANGED
@@ -6,78 +6,86 @@ CCDX is a local compatibility adapter for Codex App. It configures Codex to use
6
6
 
7
7
  ## How it works
8
8
 
9
- ### Optional next-message suggestions
10
-
11
- Suggestions are disabled by default. No suggestion MCP or skill is installed at
12
- startup, and the primary adapter does not start a predictor.
9
+ ### Retired prediction integration
10
+
11
+ The manual prediction MCP, automatic Stop hook and dashboard panel are removed.
12
+ CCDX does not install a prediction skill or replacement predictor. Startup disables
13
+ the old `suggestion.json` preference and removes only unchanged CCDX declarations
14
+ from the active `CODEX_HOME/config.toml` (or `~/.codex/config.toml`).
15
+ `CCDX_SUGGESTION_CONFIG` still identifies the legacy settings path for cleanup.
16
+ For an immediate cleanup without starting the adapter, run `ccdx suggestion off`.
17
+ The old `suggestion on` and `suggestion model` commands cannot re-enable it.
18
+
19
+ Modified, inline and profile-specific declarations are preserved. Cleanup failures
20
+ do not block the core adapter. Saved model preferences and old candidate files are
21
+ not deleted; they are inactive. Native Codex preference settings, hook trust records,
22
+ other hooks and the optional image MCP are untouched. Two inert compatibility
23
+ entrypoints prevent user-modified registrations from invoking a removed module:
24
+ the old Hook exits with an empty result, and the old MCP exposes no tools or panel.
25
+
26
+ ### Optional native predictions
27
+
28
+ Native next-message predictions are **off by default** and use no MCP, Hook, skill,
29
+ panel, renderer injection, or client-program modification. This macOS-only
30
+ compatibility feature uses the App's existing composer UI and the conversation's
31
+ selected model through CCDX. Suggestions are not sent automatically. Native
32
+ eligibility rules still apply; availability is not promised for every thread type.
13
33
 
14
34
  ```bash
15
- ccdx suggestion on
16
- ccdx suggestion off
17
- ccdx suggestion model
18
- ccdx suggestion model gpt-6.1-sol
19
- ccdx suggestion status
35
+ # Fully quit Codex App first.
36
+ ccdx prediction on
37
+ # Restart the running CCDX process, then launch Codex through ccdx.
38
+ ccdx
39
+ ccdx prediction status
40
+
41
+ # To remove the optional setup, fully quit Codex first:
42
+ ccdx prediction off
20
43
  ```
21
44
 
22
- Start CCDX, enable suggestions, then open **Next message / 下一句** from a local
23
- conversation panel, or ask Codex for a next-message suggestion. **Add to draft /
24
- 加入草稿** appends ordinary text and never sends. The user still presses Send.
25
- If the MCP has not appeared in an open App, restart the App once. There is no
26
- client patch, grey ghost text, automatic draft insertion, or auto-approval.
27
- Model selection alone keeps the feature disabled. The initial model is
28
- `gpt-6-luna`; `low` reasoning is used when advertised. No service tier is forced.
29
-
30
- Settings are stored in `suggestion.json` beside CCDX's `config.json`.
31
- `CCDX_SUGGESTION_CONFIG` overrides this file path; the managed MCP sets it to
32
- the exact saved file, so normal users do not need to set it. The managed
33
- MCP is `mcp_servers.ccdx_suggestion` in the active `CODEX_HOME/config.toml` (or
34
- `~/.codex/config.toml`). Existing user-managed, inline or profile-specific entries
35
- are preserved rather than overwritten; identical enabled setup is a no-op.
36
- Native prediction settings are never changed. Official account/remote defaults
37
- currently have no reliable generic local signal, so CCDX does not promise to
38
- detect every future client default. Use `suggestion off` when preferring native
39
- predictions. An unavailable capability reports a local error, not a fallback
40
- that edits the client or sends a chat message.
41
-
42
- The MCP uses the host-supplied thread ID and the installed Codex's paginated
43
- `thread/items/list` API. It reads at most 24 recent items, retaining up to eight
44
- user/assistant text excerpts and 16,000 characters. Images, reasoning items and
45
- tool outputs are excluded; there is no transcript scan, fork or resumed turn.
46
- Recent text is sent through the local CCDX adapter to the selected Copilot model
47
- when requested manually, or after a reply through the separately reviewed optional
48
- hook described below. Provider data policies still apply.
49
- Context lookup is capped at 5 seconds/2 MiB, the operation at 30 seconds, output
50
- at 64 KiB/300 characters, concurrent work at two, and in-memory candidates at 16.
51
- Candidates expire for adoption after 10 minutes and are bound to their thread
52
- and selected model. Failure/HTTP 200 incomplete responses never become drafts.
53
- No additional retry is added by the suggestion feature. Existing adapter policies
54
- remain unchanged. App support requires MCP draft insertion; the executable can
55
- be selected explicitly with `CCDX_CODEX_BINARY` when automatic discovery fails.
56
-
57
- On supported macOS/Linux Codex runtimes, `suggestion on` also registers an
58
- asynchronous `Stop` hook. It generates after a finished reply only after the user
59
- reviews and trusts that exact `suggestion-auto.mjs` command through Codex's normal
60
- hook review screen (`/hooks` in Codex CLI). CCDX never writes hook trust or bypasses
61
- review. Existing handlers and native prediction settings are preserved. Windows,
62
- unsupported hook runtimes and conflicting hook declarations keep the manual panel.
63
- Existing 0.9.22 users can run `ccdx suggestion on` once to configure the hook.
64
-
65
- Automatic work has one local slot, a 30-second deadline, no added retries and one
66
- attempt per observed turn. It checks the latest turn before and after inference;
67
- newer turns or changed settings discard the result. The hook does not start a new
68
- turn, send a message, edit a draft or wait on the foreground reply. Enabled users
69
- incur additional selected-model usage after replies. `suggestion off` removes only
70
- the unchanged CCDX hook and disables both automatic and manual requests.
71
-
72
- Only the candidate and generation metadata are saved locally in `suggestion-state`
73
- beside settings, with at most 32 entries. Candidates expire for adoption after ten
74
- minutes; stale files are pruned on the next automatic operation. No context excerpts
75
- are saved there. The loopback dashboard reads settings and safe recent status only;
76
- it does not invoke a model or confirm hook trust. Refresh it after changing settings.
77
- Opening Next message reads a current cached candidate without a second model call.
78
- The user must still choose **Add to draft**, then Send. Closed panels cannot access
79
- the composer, and the host exposes no atomic empty-draft insertion; automatic
80
- draft filling is therefore intentionally unavailable.
45
+ Enabling explicitly adds a private certificate trust record to the selected App
46
+ profile and a process-scoped PAC route for `chatgpt.com` and `ab.chatgpt.com` only.
47
+ HTTPS traffic for **all paths on those two hosts** is decrypted and forwarded
48
+ locally; PAC cannot select HTTPS paths before TLS. Only the bootstrap prediction
49
+ flag, prediction configuration, and corresponding Statsig refresh values are
50
+ overlaid. Other paths preserve upstream status, cookies, streaming and WebSocket
51
+ traffic. Upstream TLS verification stays enabled; no OS trust, global proxy, login,
52
+ account identity or account-permission value is rewritten. Credentials are not
53
+ logged or persisted by the bridge. The local private key has owner-only permissions;
54
+ its signing key is discarded after certificate creation.
55
+
56
+ Existing App proxy/PAC/environment routes cause setup to skip with an explanation.
57
+ This initial version does not compose enterprise proxies. The default App profile
58
+ is `~/Library/Application Support/Codex`; an explicit
59
+ `CODEX_ELECTRON_USER_DATA_PATH` is respected. Quit the App before trust changes;
60
+ CCDX never kills it. The launcher uses the exact App that passed the compatibility
61
+ check. Updating the App invalidates that check: quit it and run `prediction on`
62
+ again. Unknown client/store/configuration formats are not forcibly rewritten.
63
+ These are checked compatibility boundaries, not a promise for every future App.
64
+
65
+ The bridge changes only its own prediction fields and preserves already-enabled
66
+ official prediction configuration. A missing prediction-config route can receive
67
+ CCDX's own short JSON-suggestion prompt using low reasoning effort. Authentication,
68
+ permission, rate-limit and server errors are returned unchanged. Full and live
69
+ refresh schemas are supported; delta requests ask for complete values to avoid
70
+ merging upstream checksums with locally changed fields. Configuration bodies are
71
+ bounded at 16 MiB; larger or unknown bodies pass through without an overlay.
72
+ No extra inference or bridge exists for users who leave the feature disabled.
73
+ When enabled, prediction requests consume Copilot usage; the setting does not
74
+ provide a separate prediction-model picker.
75
+
76
+ `prediction off` immediately disables new local overlays. Quit/reopen Codex to
77
+ clear its cached predictions and temporary route. If the App is still running,
78
+ trust removal is deferred; quit it and run `prediction off` again. Only the exact
79
+ owned certificate record is removed, never the whole store. Failed activation
80
+ rolls back newly added trust; certificate replacement keeps old trust until the
81
+ new preference is committed. If the bridge stops, new connections can fall back
82
+ to direct routing; already-interrupted requests are not replayed. Run `ccdx`
83
+ after quitting Codex to attach a newly started bridge. `CCDX_AUTO_LAUNCH=0` keeps
84
+ automatic launch disabled and does not attach the route to a manually opened App.
85
+ Optional prediction failures never prevent the core adapter or image service from
86
+ starting. `prediction status` reports the saved preference, not proof of live UI
87
+ availability. Prediction settings are stored separately in
88
+ `~/.config/codex-copilot-dx/native-prediction/` (or under `XDG_CONFIG_HOME`).
81
89
 
82
90
  ### Core adapter
83
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.9.23",
3
+ "version": "0.9.24",
4
4
  "description": "Use Codex App with GitHub Copilot",
5
5
  "author": "Dale Xiao",
6
6
  "type": "module",
package/src/adapter.mjs CHANGED
@@ -64,7 +64,7 @@ export {
64
64
 
65
65
  const ADAPTER_RUNTIME_CONFIG = loadRuntimeConfig();
66
66
  const DASHBOARD_PATHS = new Set(["/", "/ui.css", "/theme.js", "/language.js", "/ui.js"]);
67
- const DASHBOARD_API_PATHS = new Set(["/_ccdx/ui/animation", "/_ccdx/ui/models/live", "/_ccdx/ui/usage", "/_ccdx/ui/auth", "/_ccdx/ui/suggestion"]);
67
+ const DASHBOARD_API_PATHS = new Set(["/_ccdx/ui/animation", "/_ccdx/ui/models/live", "/_ccdx/ui/usage", "/_ccdx/ui/auth"]);
68
68
 
69
69
  export function requestPath(reqUrl) {
70
70
  return new URL(reqUrl || "/", "http://localhost").pathname;
@@ -242,7 +242,9 @@ export function createAdapterHandler(options = {}) {
242
242
 
243
243
  if (req.method === "GET" && pathname === ADAPTER_HEALTH_PATH) {
244
244
  res.writeHead(200, { "Content-Type": "application/json" });
245
- res.end(JSON.stringify(adapterHealthPayload()));
245
+ const predictionPac = isLoopbackAddress(req.socket?.remoteAddress) && isLoopbackHostHeader(req.headers?.host)
246
+ ? options.nativePredictionPac?.() : null;
247
+ res.end(JSON.stringify({ ...adapterHealthPayload(), ...(predictionPac ? { native_prediction_pac: predictionPac } : {}) }));
246
248
  return;
247
249
  }
248
250
 
@@ -28,6 +28,7 @@ const HELP_TOPICS = new Set([
28
28
  "image-status",
29
29
  "auto-review-model",
30
30
  "suggestion",
31
+ "prediction",
31
32
  "update",
32
33
  "version",
33
34
  ]);
@@ -252,11 +253,18 @@ export function parseCliArgs(args = []) {
252
253
  if (rest.length) unexpectedArgs(rest);
253
254
  return baseCommand("animation");
254
255
  }
256
+ if (command === "prediction") {
257
+ const [action = "status", ...extra] = rest;
258
+ if (!["on", "off", "status"].includes(action)) throw new Error("Prediction action must be on, off, or status");
259
+ if (extra.length) unexpectedArgs(extra);
260
+ return baseCommand("prediction", { action });
261
+ }
255
262
  if (command === "suggestion") {
256
263
  const [action = "status", model, ...extra] = rest;
257
264
  if (!["on", "off", "model", "status"].includes(action)) throw new Error("Suggestion action must be on, off, model, or status");
258
265
  if (extra.length || (model !== undefined && action !== "model")) unexpectedArgs(rest.slice(1));
259
- return baseCommand("suggestion", { action, ...(model !== undefined ? { model } : {}) });
266
+ if (["on", "model"].includes(action)) throw new Error("The CCDX prediction panel has been retired; use ccdx prediction for the separate optional native feature");
267
+ return baseCommand("suggestion", { action });
260
268
  }
261
269
  if (command === "enable-image" || command === "disable-image" || command === "image-status") {
262
270
  if (rest.length) unexpectedArgs(rest);
@@ -363,7 +371,8 @@ function topicHelp(name, topic) {
363
371
  "disable-image": `Usage:\n ${name} disable-image\n\nDisables the local CCDX image tool and removes its stored API credential and unmodified CCDX image guidance.`,
364
372
  "image-status": `Usage:\n ${name} image-status\n\nShows provider configuration and locally advertised generation/editing tools without generating an image or exposing its API key.`,
365
373
  "auto-review-model": `Usage:\n ${name} auto-review-model\n\nSelect a Responses model, then an advertised reasoning effort. Enter keeps the default/current choice; q cancels both steps without saving. Effort 0 follows the client (low when omitted). Settings apply only to Auto-review on the next request; no client patch or restart is required for adapters running 0.9.13 or newer.`,
366
- "suggestion": `Usage:\n ${name} suggestion on\n ${name} suggestion off\n ${name} suggestion model [MODEL_ID]\n ${name} suggestion status\n\nOptional next-message panel; disabled by default. Open it in a local Codex conversation to generate a suggestion, then choose Add to draft. Ordinary text only: no client patch, background prediction, automatic insertion or sending. Model selection alone does not enable the feature. Requires a running local adapter and recent Codex. Existing user-owned MCP configuration is preserved; native prediction settings are never modified. Future account/remote defaults cannot be universally detected.`,
374
+ "suggestion": `The CCDX prediction panel has been retired. Run ${name} suggestion off to safely remove unchanged CCDX registrations. This cleanup does not change native predictions; see ${name} prediction --help for that separate opt-in feature.`,
375
+ prediction: `Usage:\n ${name} prediction [status|on|off]\n\nOptional native next-message predictions; off by default. macOS only. Quit Codex before on/off, then restart ccdx. On adds private App-profile certificate trust and an App-only route for chatgpt.com and ab.chatgpt.com. HTTPS on those hosts is decrypted locally; only prediction configuration is changed. No client program files, OS trust, account permissions, MCP or Hooks are changed. Existing proxies are not replaced. Official prediction configuration is preserved; the conversation's model is used. App updates require another compatibility check.`,
367
376
  update: `Usage:\n ${name} update [npm|github]\n\nUpdates the global package from the configured npm registry or the latest stable GitHub release. With no source, an interactive terminal prompts for one.`,
368
377
  version: `Usage:\n ${name} --version`,
369
378
  };
@@ -389,7 +398,7 @@ export function cliHelp(commandName = "ccdx", topic = "") {
389
398
  ${name} disable-image
390
399
  ${name} image-status
391
400
  ${name} auto-review-model
392
- ${name} suggestion on|off|model|status
401
+ ${name} prediction [status|on|off]
393
402
  ${name} update [npm|github]
394
403
  ${name} --version
395
404
  ${name} --help
@@ -408,7 +417,7 @@ Commands:
408
417
  disable-image Disable the image provider and remove its credential
409
418
  image-status Show the optional image provider status
410
419
  auto-review-model Select the Codex Auto-review model and reasoning effort
411
- suggestion Optional next-message suggestions (off by default)
420
+ prediction Toggle optional native next-message predictions
412
421
  update Update the global package from npm or GitHub
413
422
 
414
423
  Run ${name} <command> --help for command details.`;
@@ -50,12 +50,6 @@
50
50
  </article>
51
51
  </section>
52
52
 
53
- <section class="panel dashboard-section" id="suggestion-panel">
54
- <div class="panel-heading"><h2 data-i18n>NEXT MESSAGE</h2><span data-i18n>OPTIONAL</span></div>
55
- <dl class="stats"><div><dt data-i18n>Status</dt><dd id="suggestion-enabled">—</dd></div><div><dt data-i18n>Model</dt><dd id="suggestion-model">—</dd></div><div><dt data-i18n>Auto generation</dt><dd id="suggestion-auto">—</dd></div><div><dt data-i18n>Draft insertion</dt><dd data-i18n>Choose Add to draft</dd></div></dl>
56
- <p id="suggestion-recent" class="hint" role="status">—</p><p id="suggestion-detail" class="hint">—</p><p class="hint"><span data-i18n>Commands</span>: <code>ccdx suggestion on|off|model|status</code></p>
57
- </section>
58
-
59
53
  <details class="panel dashboard-section" id="request-context">
60
54
  <summary data-i18n>RECENT REQUEST CONTEXT</summary>
61
55
  <p id="context-note" class="table-note">—</p>
@@ -1,26 +1,5 @@
1
1
  (() => {
2
2
  const translations = {
3
- "NEXT MESSAGE":["NEXT MESSAGE","下一句"],
4
- "Status":["Status","状态"],
5
- "Auto generation":["Auto generation","自动生成"],
6
- "Configured; review hook":["Configured; review hook","已配置,需审核"],
7
- "{time} / {reason}":["{time} / {reason}","{time} / {reason}"],
8
- "Context unavailable":["Context unavailable","无法读取对话。"],
9
- "Model request failed":["Model request failed","模型请求失败。"],
10
- "Settings changed":["Settings changed","设置已变化。"],
11
- "Conversation changed":["Conversation changed","对话已变化。"],
12
- "Suggestion timed out":["Suggestion timed out","建议生成超时。"],
13
- "No recorded error":["No recorded error","暂无错误记录。"],
14
- "Draft insertion":["Draft insertion","加入草稿"],
15
- "Choose Add to draft":["Click to add","点击加入"],
16
- "Commands":["Commands","命令"],
17
- "No automatic suggestion yet":["No automatic suggestion yet.","暂无自动建议。"],
18
- "Generating suggestion":["Generating…","正在生成。"],
19
- "Suggestion ready; open Next message":["Ready. Open Next message.","已生成,请打开下一句。"],
20
- "No useful suggestion":["No useful suggestion.","暂无合适建议。"],
21
- "Suggestion failed; retry manually":["Failed. Retry in Next message.","生成失败,请手动重试。"],
22
- "Suggestion cancelled":["Cancelled.","已取消。"],
23
- "Run ccdx suggestion status":["Run ccdx suggestion status.","请用命令检查设置。"],
24
3
  "First output": ["First output", "首次输出"],
25
4
  "Token time": ["Token time", "输出耗时"],
26
5
  "Gateway timing. Token time is estimated.": ["Observed at the gateway. Token time is an estimate.", "网关观测时间。\n每Token耗时为估算值。"],
@@ -16,7 +16,6 @@ const finite = (value) => value !== null && value !== undefined && Number.isFini
16
16
  const number = (value) => finite(value) ? Math.max(0, Math.round(Number(value))).toLocaleString() : "—";
17
17
  const mib = (value) => finite(value) ? `${(Number(value) / 1048576).toFixed(1)} MiB` : "—";
18
18
  let authLoading = false;
19
- let suggestionLoading=false;
20
19
  let contextRequests = [];
21
20
  let contextCompactions = {};
22
21
 
@@ -41,23 +40,6 @@ async function loadAuth() {
41
40
  } finally { authLoading = false; }
42
41
  }
43
42
 
44
- async function loadSuggestion(){
45
- if(suggestionLoading)return;
46
- suggestionLoading=true;
47
- try{
48
- const response=await fetch("/_ccdx/ui/suggestion",{cache:"no-store",headers:{"X-CCDX-Dashboard":"1"},signal:AbortSignal.timeout(5000)});
49
- const data=await response.json();
50
- if(!response.ok||data.source!=="local_suggestion_settings")throw new Error();
51
- text("suggestion-enabled",data.enabled?"ENABLED":"DISABLED");
52
- element("suggestion-model").textContent=data.model;
53
- text("suggestion-auto",data.automatic_generation_configured?"Configured; review hook":"DISABLED");
54
- const state={idle:"No automatic suggestion yet",generating:"Generating suggestion",ready:"Suggestion ready; open Next message",empty:"No useful suggestion",failed:"Suggestion failed; retry manually",cancelled:"Suggestion cancelled",unknown:"Unknown"}[data.recent?.state]||"Unknown";
55
- text("suggestion-recent",state);
56
- text("suggestion-detail","{time} / {reason}",{time:data.recent?.at?new Date(data.recent.at).toLocaleTimeString():"—",reason:phrase({context_unavailable:"Context unavailable",model_failed:"Model request failed",settings_changed:"Settings changed",turn_changed:"Conversation changed",timeout:"Suggestion timed out"}[data.recent?.reason]||"No recorded error")});
57
- }catch{text("suggestion-enabled","UNAVAILABLE");element("suggestion-model").textContent="—";text("suggestion-auto","UNAVAILABLE");text("suggestion-recent","Run ccdx suggestion status");element("suggestion-detail").textContent="—";}
58
- finally{suggestionLoading=false;}
59
- }
60
-
61
43
  function duration(value) {
62
44
  if (!finite(value)) return "—";
63
45
  const seconds = Math.max(0, Math.round(Number(value) / 1000));
@@ -241,11 +223,10 @@ async function refresh() {
241
223
  }
242
224
  }
243
225
 
244
- element("refresh").addEventListener("click", () => { refresh(); loadAuth(); loadSuggestion(); });
226
+ element("refresh").addEventListener("click", () => { refresh(); loadAuth(); });
245
227
  element("request-context").addEventListener("toggle", () => { if (element("request-context").open) renderContext(); });
246
228
  refresh();
247
229
  loadAuth();
248
- loadSuggestion();
249
230
 
250
231
  const animationRows = new Map();
251
232
  let animationTimelines = [];
@@ -15,14 +15,11 @@ import { cacheReadTokens, summarizeUsageLogs, usageCacheHitRate } from "./usage-
15
15
  import { createUsageAnalytics } from "./usage-analytics.mjs";
16
16
  import { analyzeUsageLogs } from "./usage-analysis.mjs";
17
17
  import { readUserSettings, terminalAnimationPreference, writeTerminalAnimationTheme } from "./user-settings.mjs";
18
- import { readSuggestionConfig, suggestionConfigPath } from "./suggestion-config.mjs";
19
- import { automaticSuggestionStatus } from "./suggestion-auto.mjs";
20
18
 
21
19
  const ANIMATION_PATH = "/_ccdx/ui/animation";
22
20
  const MODELS_PATH = "/_ccdx/ui/models/live";
23
21
  const USAGE_PATH = "/_ccdx/ui/usage";
24
22
  const AUTH_PATH = "/_ccdx/ui/auth";
25
- const SUGGESTION_PATH = "/_ccdx/ui/suggestion";
26
23
  const DISABLED_ANIMATION = /^(0|false|no|off)$/i;
27
24
 
28
25
  function sendJson(res, statusCode, value) {
@@ -149,15 +146,6 @@ export async function handleDashboardApi(req, res, pathname, {
149
146
  }
150
147
  return;
151
148
  }
152
- if(pathname===SUGGESTION_PATH && req.method==="GET"){
153
- try{
154
- const filePath=suggestionConfigPath({env,home}),config=readSuggestionConfig({filePath,strict:true});
155
- sendJson(res,200,{source:"local_suggestion_settings",enabled:config.enabled,model:config.model,
156
- automatic_generation_configured:config.enabled&&config.automatic===true,hook_trust:"unknown",automatic_insertion:false,
157
- recent:config.enabled?automaticSuggestionStatus(filePath):{state:"idle",at:null,reason:null}});
158
- }catch{sendJson(res,409,{error:"Suggestion settings are invalid; run ccdx suggestion status"})}
159
- return;
160
- }
161
149
 
162
150
  if (pathname === ANIMATION_PATH && req.method === "GET") {
163
151
  try {
package/src/launcher.mjs CHANGED
@@ -36,7 +36,7 @@ export function launchAttempts() {
36
36
  return [["-b", CODEX_BUNDLE_ID], ...APP_PATH_CANDIDATES.map((path) => [path])];
37
37
  }
38
38
 
39
- export async function openCodex({ env = process.env, spawnImpl = spawn, platform = process.platform } = {}) {
39
+ export async function openCodex({ env = process.env, spawnImpl = spawn, platform = process.platform, predictionPacUrl = null, predictionApp = null } = {}) {
40
40
  if (platform !== "darwin") {
41
41
  console.log(status("warn", "Auto-launch is only supported on macOS"));
42
42
  console.log(status("info", "Open Codex App manually; it will connect to the adapter."));
@@ -48,7 +48,12 @@ export async function openCodex({ env = process.env, spawnImpl = spawn, platform
48
48
  return false;
49
49
  }
50
50
 
51
- for (const args of launchAttempts()) {
51
+ const extraArgs = typeof predictionPacUrl === "string" && /^http:\/\/127\.0\.0\.1:\d{1,5}\/proxy\.pac$/.test(predictionPacUrl)
52
+ && APP_PATH_CANDIDATES.includes(predictionApp)
53
+ ? ["--args", `--proxy-pac-url=${predictionPacUrl}`] : [];
54
+ const attempts = extraArgs.length ? [[predictionApp]] : launchAttempts();
55
+ for (const attempt of attempts) {
56
+ const args = [...attempt, ...extraArgs];
52
57
  if (await tryOpen(args, { spawnImpl })) {
53
58
  console.log(status("ok", "Codex app launched"));
54
59
  return true;