Codex Plugin Runtime
Wework's plugin feature is compatible with Codex plugins, skills, and apps. The plugin pages handle discovery, installation, creation, and management; the chat runtime passes user-selected skills and apps to the Codex app-server as structured mentions instead of treating display text as plain prompt content.
Page Entry
In the desktop sidebar, the plugin entry is fixed as the third primary action: New Chat, Search, Plugins, then Cloud Work. Inside the plugin page:
- The header shows marketplaces, installed plugins, and search.
- The top-right refresh button reloads the current marketplace.
- The create entry opens a Codex plugin creator-style page.
- The management entry opens the installed plugin, skill, and app enablement and uninstall view.
Plugin marketplaces are not split into local and cloud modes. Wework shows the OpenAI official marketplace returned by Codex app-server by default and lets users add multiple named marketplaces. Custom marketplaces can come from GitHub repositories, remote addresses, or local marketplace.json files/directories. When no marketplace is available, Wework shows a welcome page that guides the user to add a custom marketplace or open management.
Codex plugin runtime configuration is available under Settings → Integrations → Plugins and currently exposes the remote Apps / Connectors switch. Settings no longer includes a standalone worktree management page or an action panel that migrates Claude and Codex skill directories into shared symlinks. Worktree lifecycle is managed by conversation flows, while skill and plugin content is managed through the plugin pages and Codex app-server.
Boundary between Codex plugins and Wework plugins
The desktop management page separates Codex plugins from Wework plugins. Codex plugins continue to use the Codex app-server, Wegent cloud marketplace, and Executor synchronization paths. Wework plugins are bundle dependencies of the wework-core DSH profile and are managed directly by the Electron main process. Wework plugin management is available only in the managed desktop runtime and does not expose installation or removal HTTP endpoints through the DSH web server.
The renderer invokes allowlisted Electron capabilities to list, install, update, enable, disable, and uninstall plugins. The main process serializes these operations and snapshots the profile package.json, lockfile, workspace configuration, and Wework plugin state before each mutation. It then validates the result with dsh --profile wework-core --dump-config. If package management or validation fails, it restores the snapshot and reinstalls dependencies from the lockfile.
User plugins must declare dsh.bundle.patch. Bundled DSH packages are read-only in the management UI. User plugin order and disabled state are stored in a Wework state file inside the profile and used to rebuild dsh.profile.bundles. Configuration changes do not restart the desktop runtime implicitly; the UI prompts the user to call runtime.restartCoreDsh after completing a group of changes.
Marketplace and Install
Wework reads local marketplaces and the OpenAI official marketplace through the Codex app-server exposed by the local executor. Local discovery and post-mutation reconciliation use marketplaceKinds: ['local']. Only opening the OpenAI official tab or explicitly refreshing that tab reads the default remote catalog through a separate entry point; Codex currently exposes neither pagination nor a remote-only kind for that catalog. Official metadata has a 60-second cache scoped by device, runtime instance, Codex auth fingerprint and Wegent account, with concurrent reads shared. Expiry alone never triggers a request, and installation mutations or local refreshes do not invalidate this cache. Account changes and logout reject old responses. Before IPC delivery, the Executor projects list summaries; selected details use plugin/read. Diagnostics report field byte counts without their contents. Projection reduces internal traffic, while reducing first-fetch network traffic still requires upstream support. Custom remote GitHub marketplaces are cloned into a local cache directory, and later reads use the cached marketplace data and plugin folders. Codex app-server manages local marketplace operations except local cache installation and uninstall in the Wework personal marketplace.
When Wework is connected to Wegent cloud, the plugin page also displays the Backend-provided Wegent cloud marketplace. Its details and installation state come from Backend. After a normal installation, Backend synchronizes the user's global InstalledPlugin desired state to online local and cloud devices. After the device Executor obtains the package from Wework Backend / object storage, it writes runtime caches, marketplace metadata, and enablement configuration directly into the isolated Claude and Codex homes. Cloud-plugin install, update, and removal do not call Codex app-server plugin/install or plugin/uninstall and do not refresh GitHub or OpenAI marketplaces. Pure skill plugins activate through a local configuration reload RPC after publication, as described below. Enterprise-internal, Wework-public, and published personal plugins therefore have no GitHub, OpenAI, or Codex network dependency once their Wework package is reachable. The OpenAI official marketplace remains managed by Codex and does not appear in the custom marketplace edit, reorder, or delete list.
Cloud synchronization commits the package replacement, both runtime caches, registries, and configuration files as one local transaction. Extraction, parsing, or write failures restore the pre-sync state so an update cannot leave a new package paired with old runtime metadata, and removal cannot stop halfway. Connector localAuth remains a separate Wework step after package synchronization and is not bypassed by local materialization.
Pure Skill Plugin Hot Updates
After the local transaction commits an installation or removal of a managed pure skill plugin, the Executor reuses config/batchWrite with empty edits and reloadUserConfig: true for already running Codex app-server processes in the matching Home. Success leaves the process invalidation revision unchanged, preserving the PID and existing conversations. The new catalog applies on an existing conversation's next turn; removed skills disappear from that turn's available catalog. Cold processes read the committed state at their first startup.
Eligibility checks both package states, native enablement, skill selectors, and registries, accepting only verified pure skill membership additions or removals. Package content or version updates, whole-plugin toggles, MCP, Hooks, Apps, unknown manifest capabilities, unrelated configuration changes, and uninspectable state retain the existing process invalidation policy. A failed or cancelled reload RPC also invalidates the old process rather than acknowledging an incomplete reload. This avoids mutation-triggered restarts and repeated initialization; downloading the installation archive is still required.
The desktop CI plugin-toggle-persistence scenario verifies three real turns in the same conversation before installation, after installation, and after removal. It asserts unchanged task and Codex thread IDs, no additional process starts, and absence, presence, then absence of the target skill in the latest model-facing catalog. Catalog reads use forceReload: false so the assertions cannot hide stale caches.
Desktop Bundled Plugin Upgrades and Recovery
After startup becomes ready, macOS and Windows asynchronously check wework-space, smart-app-builder, and wework-plugin-developer; entering the workbench does not wait for upgrades. Checks compare both the version and complete file contents, so script fixes within the same version also reach the installed cache. Current plugins with existing configuration remain usable during unrelated work. Only cache or configuration mutations wait for active tasks and Codex requests to finish.
Upgrades copy and validate a staging directory and retain the previous cache until configuration commits. Failure restores the previous cache, and existing disabled state is preserved. Plugin details display update status and offer retry after failure. Manual uninstall stops background synchronization in the current window and waits for admitted synchronization requests to finish; an explicit reinstall resumes synchronization.
Plugin upgrade listeners and other Executor event subscribers in the same window share one event connection, leaving HTTP request slots available for popout loading and main-window restoration. The last unsubscribe closes the connection; reconnects preserve the event cursor and filter duplicate events.
Local uninstall in the Wework personal marketplace uses plugin identity and does not require the original marketplace manifest path to exist. It removes only the matching configuration and cache, preserving chats, authentication, and other plugins. Smart App publishing and export still require passing formal verification. Linked local projects expose a Verify Smart App menu action and require re-verification after file changes.
Inventory Consistency and Retries
The marketplace, management view, and chat picker share one plugin inventory. Memory retains details; durable snapshots retain only fields needed for presentation and recovery, including default prompts, and omit long descriptions and screenshots. Uninstall matches marketplace and plugin identity, never just a shared display name across marketplaces.
When Codex accepts installation before plugin/installed confirms membership, the receipt keeps its original acceptedAt and bridges the discovery race for at most five minutes. Expired or legacy receipts without a timestamp no longer override authoritative inventory. Failed cloud removals reuse the device installation record's attempt count and sync timestamp for exponential backoff from 60 seconds to a maximum interval of one hour. Initial removal is immediate, and later heartbeats can recover cleanup. Cleanup database reads and writes run on worker threads without blocking asynchronous heartbeats.
Install-time local authorization
Plugins can declare device-side authorization under connectors[].localAuth. local_qr is used for QR login, while browser_oauth is used when a local CLI opens an OAuth flow in the browser. Both modes must provide health and start commands relative to the plugin root; QR mode must also provide a non-blocking poll command. With authPolicy: on_install, Wework checks the login after the plugin package is synchronized to the device and opens the authorization UI when needed. Cancellation or failure aborts that installation. First-use and mid-run checks remain recovery paths for expired credentials.
Selecting a plugin and sending a message do not preflight authorization or read the full plugin/installed inventory, plugin/list, or plugin details. The model uses plugins as needed. If an actual invocation reports missing or expired authentication, it preserves connector_auth_required, the exact pluginKey, and connectorSlug, stops that plugin's operations, and ends the turn so Wework can show the login card. Network failures, missing business permissions, and native remote MCP authorization requests must not become local login cards.
Runtime recovery sends the plugin and connector identity directly to the Executor, which resolves the local manifest and bypasses cached healthy results. Missing identities must not be guessed or resolved by enumerating all installations. Each local health command retains its 45-second timeout, while interactive login has its separate timeout; health checks no longer block message submission. Successful authorization sends a continuation instruction for the blocked step instead of resubmitting the original user request or retrying the entire failed turn.
Runtime authorization recovery checks only the final assistant or system message after completion or failure; task switching must not rescan the complete transcript. Detection text has a fixed size bound and includes only message errors, message content, and textual or known structured error fields from tool blocks, never renderPayload or other unbounded presentation data. Cancelling, sending another message, or starting a new conversation consumes the old request. Temporarily leaving the pane cancels active recovery but permits authorization again when returning. Late responses and duplicate success callbacks cannot continue obsolete work, and automatic continuation must not read or clear the current draft, attachments, or code comments.
Browser OAuth runs as an asynchronous authorization session with preparing, waiting_browser, verifying, and ok/error states. Closing the UI calls the Executor cancel RPC and terminates the login process. CLI bridges must emit one status JSON object and must never include tokens, cookies, or other credentials.
Local authorization tools have two sources:
bundledresolves a sidecar shipped with the desktop client. The DingTalk plugin uses Wework's bundled DWS instead of downloading the older copy declared by the plugin repository.managedselects an immutable artifact by operating system and CPU architecture, verifies its declared SHA-256, and installs it atomically under the Executor home. The current implementation only accepts official GitLab CLI release URLs, and all three internal GitLab plugins share one managedglabinstallation.
Plugin installation is user-scoped, while CLI credentials are device-scoped. Install-time authorization therefore makes the current device ready; every other device checks and authorizes independently. logoutOnUninstall defaults to enabled for QR connectors and disabled for browser OAuth so uninstalling one plugin does not silently remove credentials shared by another plugin or profile.
wegent-sites and weibo-miniapp-h5-develop-agent are maintained in separate plugin repositories. Before a Backend image build, pnpm prepare:builtin-plugins copies each configured external plugin into the ignored backend-rs/init_data/plugins/<plugin-name> staging directory. The standard build_image.sh and build_image_mac.sh scripts run this step automatically. Official image workflows download each configured archive, verify its pinned SHA-256, and run the same staging operation. Download, verification, or staging failures stop the image build. Backend then idempotently publishes every staged plugin as a workspace-scoped, featured Wegent cloud marketplace item owned by the system user_id=0.
Built-in application plugin identity is defined by the Backend built-in plugin registry. The current registry contains only wegent-sites and weibo-miniapp-h5-develop-agent; both use visibility=workspace, so their canonical marketplace name is wegent. public remains valid in the data model but is reserved for system/official public catalogs; a regular user's enterprise submission cannot select it. Only when the built-in installation path finds one of these two system-owned user_id=0 marketplace rows still stored as visibility=public does Backend treat it as a legacy row and normalize it to workspace before installing. This prevents the same built-in plugin from appearing as plugin://...@wework in old data and plugin://...@wegent in the current application create flow.
The Applications page reads its lists through GET /api/sites. Sites and Mini Programs share this endpoint and pass app_type=web and app_type=miniapp, respectively. Omitting the parameter defaults to Sites for backward compatibility. Platform uses the trusted user identity to return projects that the user owns or collaborates on. The response identifies the owner and current user's owner or collaborator role with owner_username and access_role, while app_type discriminates the fields for each application type. The page also calls GET /api/sites/app-types to discover the types enabled by the current Backend, their display order, and capabilities such as create, publish, edit, delete, open_experience, configure_environment, and manage_access. Wework only shows types that are both enabled by the server and represented by a local Definition, and hides operations that are not supported by the advertised capabilities. URLs in the Sites list always open in the system browser, regardless of the Wework embedded-browser preference.
When Wework is connected to Wegent cloud, it calls POST /api/users/me/wegent-runtime-token to issue the token that local application Skills use for Backend runtime APIs, then writes it to the local Codex shell environment as WEGENT_RUNTIME_AUTH_TOKEN. Wework refreshes this token before the returned expires_in. AUTH_TOKEN remains the existing per-task bearer token, and WEGENT_AUTH_TOKEN remains reserved for executor device connections; these credentials must not be used interchangeably.
Backend remains the authenticated management proxy for Sites; Wework never calls Platform directly. Collaborator management uses GET/POST /api/sites/{siteid}/collaborators and DELETE /api/sites/{siteid}/collaborators/{subject}. Only owners see this management entry in the UI, and add requests require an Idempotency-Key. Internal Site access policies use GET/PUT /api/sites/{siteid}/access; both owners and collaborators can read and update them. The supported audiences are anonymous access, signed-in users, project members only, and project members plus specified users, and writes require an Idempotency-Key. Environment configuration uses GET/PATCH /api/sites/{siteid}/environment-variables, with single-variable PUT/DELETE /api/sites/{siteid}/environment-variables/{key} endpoints also available. Writes carry an idempotency key and an optional expected_revision_id; clients reload after a conflict. Backend forwards the authenticated user's X-Wegent-Username to Platform and marks environment responses Cache-Control: no-store. Incoming and outgoing environment HTTP bodies must not be captured in request logs or telemetry. Secret responses expose configured metadata only and never return the value.
To add an application type, add its response model and ApplicationTypeHandler to the Backend, then register the handler in APPLICATION_TYPE_HANDLERS. Add the matching Wework Definition in applicationTypeDefinitions.tsx, where only its icon, copy, columns, and row renderer are declared. Creation plugin identity comes from GET /api/sites/app-types through create.plugin_name and create.marketplace_name; Wework caches the last successful app-types descriptor and reuses it when cloud discovery is briefly unavailable. Cache reads must first verify that every descriptor in items is an object and that optional create.plugin_name and create.marketplace_name fields are strings when present; invalid cached data is treated as no cache so discovery can fall back to the server or default Definitions. When the type uses a new built-in plugin, also add it to the Backend built-in plugin registry and builtin-plugin-staging.mjs. The shared workspace and creation flow should not gain new type-specific branches. The server can independently change ordering, availability, capabilities, and creation plugins, while older clients safely ignore unknown types.
Create Site, Create Mini Program, and Continue developing share preparePluginTrial. Desktop entries first read current, complete inventory through the local plugin API. They reuse a plugin without requesting Backend inventory or installation only when the returned deviceId matches the target device and the plugin matches the configured name and marketplace, is installed, and is enabled. The local read includes Executor executor.plugins.store.list, which preserves defaultPrompt so local reuse retains the default prompts. A locally confirmed missing or disabled plugin enters the installation flow; stale cloud records cannot override that result. When no local reader is available or its inventory belongs to another device, Wework calls GET /api/plugins/installed?device_id=<target> and requires installation acknowledgement in the target device’s status.devices plus an enabled plugin. Inventory failures show “Could not check plugin installation on the target device. Try again.” and do not trigger installation. Continue developing also retains the wegent-sites-project://<project-id> reference. When installation is required, creating a Site calls POST /api/plugins/builtin/wegent-sites/ensure-installed; creating a Mini Program calls POST /api/plugins/builtin/weibo-miniapp-h5-develop-agent/ensure-installed. Both requests include the target device_id. The endpoint only installs built-in plugins published by the system owner. Built-in application plugins use visibility=workspace, so Backend returns wegent in both create.marketplace_name and the installed row's source.marketplace. Different visibility values map to different marketplace names: personal uses wework-personal, workspace uses wegent, and public uses wework; the frontend must reuse the shared marketplace identity helper instead of hard-coding one marketplace name. Repeated calls reuse and re-enable the matching installation record; Backend may first run a full replace sync and then run a single-plugin merge only when the target device still lacks that plugin. The frontend checks only the target device acknowledgement and requires the requested plugin installation ID or name to return synced; if an older response omits sync.results, Wework treats it as having no target-specific device result and continues with the top-level sync.plugins fallback validation. Failures from other devices or unrelated historical capabilities do not block application chat creation. A missing or offline target, or failure to synchronize the requested plugin to the target device, still prevents chat creation. After confirmation, the frontend opens a new task with the stable plugin://wegent-sites@wegent or plugin://weibo-miniapp-h5-develop-agent@wegent reference. The Mini Program entry also uses the plugin-provided default creation prompt. While plugin installation and synchronization are running, the Applications page shows the “Installing the application plugin. The chat will open when it finishes...” status notice. Clicking the mention loads the matching cloud plugin detail.
Local custom and OpenAI official marketplace uninstall continues through Codex app-server, except the Wework personal marketplace local uninstall described below. Wegent cloud-plugin uninstall instead removes the account installation intent, device desired state, and matching local enablement configuration. With shared package storage, the Executor retains immutable packages and Claude / Codex caches, and persists each cache path's plugin ownership in the capability manifest so reinstall can reuse previously managed directories. Existing directories without matching ownership records remain protected from replacement. Personal local plugins and OpenAI marketplace configuration are preserved. Connector login state still follows the plugin authorization policy.
Separate Codex Home
Wework uses a separate Codex home so it does not write directly into the user's command-line Codex config directory. By default this is the codex child directory under the executor home, and it can be overridden with WEGENT_CODEX_HOME.
To reuse the user's existing login, Wework links the user's ~/.codex/auth.json into the Wework Codex home. If the target is a stale symlink, it is removed and recreated; on non-Unix systems the auth file is copied. Plugins, marketplace caches, and Wework runtime config remain under Wework's own Codex home.
When no reusable Codex login exists on the device, the user can select Sign in under Settings → Model settings → Codex Settings → Authentication. Wework asks the local Executor to start Codex app-server's ChatGPT browser-login protocol, opens the authorization page in the system browser, and detects completion from the changed auth.json digest. After sign-in, authentication remains stored in the separate Wework Codex home. The login callback uses Codex app-server's local success page and does not redirect into the Codex application. Cancelling, timing out, leaving the page, or failing to open the browser cancels the matching login session; the frontend never reads or displays tokens, authentication file paths, or file digests.
After sign-in, the Authentication section lists the Codex accounts saved on the current device. Selecting Add account first saves a private snapshot of the current auth.json, then starts another browser login; after it completes, both the previous and new accounts remain in the list. Selecting Switch atomically replaces only the active auth.json and restarts the Codex app-server process. It does not create, resume, or switch a Wework conversation, and it does not modify the Codex thread ID saved by a task. To prevent an in-flight request from continuing under another account, switching is rejected while a turn or app-server request is active and can be retried after the work finishes.
Account metadata is read directly from non-secret JWT claims in auth.json instead of relying on account/read, which may omit the account when a custom model provider is active. The account index and authentication snapshots are stored under wework-account-profiles in the Wework Codex home; files use mode 0600 on Unix. If the Wework Codex home's auth.json is a symlink to the user's native Codex home, switching updates the symlink target and preserves the symlink itself.
On first startup, if the Wework Codex home has not been initialized and a native ~/.codex directory exists, the app shows a migration choice during startup. The user can choose to:
- Create a new Wework Codex home and only reuse the auth link.
- Migrate config from the native Codex home into the Wework Codex home.
- Enable or disable Codex remote app fetching. This switch only controls remote app initialization and does not refer to any specific bundled capability.
Initial migration and subsequent manual Codex configuration imports remove the source configuration's top-level model field so the destination runtime's model catalog determines the default model. The native Codex source configuration file is unchanged.
After initialization, the state is written into the Wework Codex home, and both the plugin pages and chat runtime read plugin state from the same Codex app-server. The settings page also exposes the remote apps switch so users can later update their Wework Codex config.
Runtime Config Normalization
Before starting the Codex app-server, the executor parses and normalizes config.toml in the Wework Codex home. It creates a missing config, writes pragmatic when personality is absent, and migrates the legacy instructions field to developer_instructions. In current Codex versions, instructions completely replaces the model's base instructions; leaving user copy there removes built-in personality, commentary, and progress-update rules. Wework therefore no longer uses that field for custom instructions.
Custom instructions from Settings → Context are read and written through Codex app-server config/read and config/batchWrite. Writes merge the user's copy with Wework embedded-browser routing instructions and use reloadUserConfig to update loaded threads. Startup normalization is idempotent, uses TOML parsing and atomic file replacement, and preserves existing file permissions; newly created config files use mode 0600 on Unix.
The interaction style uses the same config.toml as its single source of truth. Selecting Friendly or Pragmatic updates personality through config/batchWrite; Wework no longer stores personality in localStorage or repeats it as an override on every thread or turn request.
Managed Plugin Directory Recovery
The Executor runs its dedicated directory migration check only at startup, after manifest initialization and before starting Codex or backend connections, using the current Home and stable plugin identity. Unchanged paths with complete runtime directories skip package reads and content verification. Existing v2 manifests and manifests imported from v1 use the same flow. Historical absolute paths are migration hints, never authority to claim or remove native user directories. Shared packages must pass the existing checksum, package-record, and content checks before any actual cache or marketplace registration rebuild.
Recovery preserves enablement, component settings, cloud installation IDs, and authorization associations, including current, previous, and retained cache ownership. Missing packages and unrelated occupied destinations affect only their installation: the manifest record remains visible as “Repair required,” and normal synchronization can retry once a verified package is available. Retained ownership or caches cannot resurrect an explicitly uninstalled plugin.
Normal installation, updates, and retries process only the requested plugins, using the current Home and the verified package to update installation records. apply_sync does not recover all plugins; Skills and MCP synchronization do not trigger migration checks for unrelated plugins.
Recovery uses the manifest lock, atomic publication, and a journal per plugin. Restart recovery distinguishes committed publication from incomplete work. Unproven ownership or later user changes preserve snapshots and report a conflict instead of overwriting unknown files. Recovery does not run before each message submission.
The Wework managed Executor environment fixes WEGENT_PLUGIN_RUNTIME_TARGET=codex, registering only Codex plugin runtimes. Generic Executors without this variable retain dual-runtime plugin support. This switch does not clean up Skills, native Homes, or credentials. Codex-only mode never creates Claude-side state for plugins, but uninstalling or reconciling stale plugins still removes Claude registrations left by earlier dual-runtime installations (installed_plugins.json, settings.json enablement, and recorded runtime caches), so a removed plugin cannot stay enabled in later Claude conversations.
The Executor implements recovery, Electron selects the runtime target, and the plugin UI displays recovery errors; ship them together as one desktop version. Updating only the frontend does not add recovery to an older Executor. The manifest remains v2 and preserves unrelated fields, Skills, and MCP records. Pending recovery journals must be resumed by the matching updated Executor.
The plugin-runtime-path-recovery desktop checkpoint belongs to existing Cloud shard 14. It installs and invokes a real plugin before stopping the app and relocating isolated Homes, then checks offline recovery, isolated failures, settings, updates, uninstall without resurrection, and reinstall invocation. Actual execution must be reported separately; static contract checks do not establish desktop verification.
Runtime Permission Modes
The Wework composer provides three permission modes for local Codex tasks and persists the selection in modelSelection.options.permissionMode. New tasks and historical tasks without this field default to Full access. When a user explicitly switches from another mode to Full access, the UI displays a risk confirmation:
| Permission mode | Codex permission profile | Approval policy | Behavior |
|---|---|---|---|
| Read only | :read-only | on-request | Reads the workspace; file writes, commands outside the permission boundary, and additional permission requests require approval |
| Workspace | :workspace | on-request | Reads and writes inside the workspace; access outside it or permission expansion requires approval |
| Full access | :danger-full-access | granular | Runs file, terminal, and network operations without approval; MCP plugin business forms can still request user input |
The frontend sends runtime_permission_profile with every local runtime request. The Executor applies the corresponding permissions and approvalPolicy to thread/start, thread/resume, thread/fork, and turn/start. Resuming or continuing from a task runtime handle must reconstruct the same profile from the persisted permission mode and must not fall back to a more permissive profile.
Full access uses granular approval policy. It disables sandbox_approval, rules, skill_approval, and request_permissions, while preserving mcp_elicitations: true. MCP elicitation is plugin-initiated business interaction rather than an execution-safety approval for commands, files, or permission escalation, so it must not be disabled by setting approvalPolicy: "never".
Ordinary Wework Claude Code conversations run as non-interactive child processes and cannot display or complete Claude CLI approval prompts. On this execution path, the Claude Code default setting therefore maps to bypassPermissions; explicit acceptEdits, plan, auto, and bypassPermissions selections are still passed through unchanged. Interactive local terminals do not use this mapping.
Codex app-server requests from item/commandExecution/requestApproval, item/fileChange/requestApproval, and item/permissions/requestApproval are mapped to Wework request_user_input cards. The card preserves the order of availableDecisions, displays only decisions actually advertised by the protocol, and returns stable protocol values. The Executor supports one-time accept, session-scoped acceptForSession, command-rule acceptWithExecpolicyAmendment, network-host applyNetworkPolicyAmendment, decline, and cancel responses. Structured rules reuse the amendment carried by the Codex request; missing or mismatched payloads are safely declined instead of deriving broader authority from display text. Permission requests can grant turn or session scope, or enable strictAutoReview for the current turn so each subsequent command is reviewed; a denial grants no additional permissions.
Model List
Wework requests the model catalog from the Codex app-server's model/list method through the local executor, then uses the returned provider and model array order unchanged in the model picker. The frontend does not reorder official or default models or custom providers, and does not add models that Codex did not return. The request uses includeHidden: false, so models Codex marks as hidden are not displayed.
Existing tasks preserve the model selection saved when the task was created or last sent. If that model is temporarily absent from the current catalog, Wework requires the user to select an available model and blocks sending; it does not silently replace the task model with the default model for new tasks.
Supervisor Models
Task supervision and chat use the same model selector component and the same model catalog, while remembering their most recent selections independently. Supervision must not maintain a second model-filtering or ordering path; official Codex models, custom providers, and local models available in chat must also be selectable for supervision.
Cloud public, user, and group models continue to execute through the Backend /api/model-runtime/responses endpoint. When a runtime model is saved for supervision, Wework builds its modelConfig with the same rules used by chat and sends it to the Executor over local IPC. The Executor keeps that configuration in process memory only and starts an independent, ephemeral Codex app-server turn for each inspection instead of reusing the task thread. This prevents inspections from polluting the primary transcript and avoids writing local-model credentials into persisted task data or logs.
Image Attachment Preprocessing
Wework includes the current model category in local runtime requests. Official Codex models receive the original image. Codex providers, local model interfaces, and cloud models are treated as non-official models; before sending an image, the executor creates a temporary model-input file and proportionally reduces the image's short edge to at most 720px. The long edge is not capped, so panoramic and long screenshots preserve their full aspect ratio instead of being forced into a fixed 1280×720 box. Images whose short edge is already at most 720px remain unchanged. The original attachment, transcript, and preview URL are never rewritten. Temporary model-input files exist only for the current turn and are removed after it finishes.
Chat Runtime
The Executor reuses one app-server for local Codex conversations. After a normal task turn completes, the Executor retains that thread's owner subscription and sends thread/unsubscribe only after 30 minutes of inactivity, allowing background terminals and MCP sessions started by Codex to remain available across short gaps between turns. A follow-up reactivates the same thread and invalidates its previous idle timer. To bound resident resources, each app-server retains at most four idle task threads and releases the oldest idle thread when the limit is exceeded. Archiving a task still unsubscribes immediately instead of waiting for the idle window.
When a conversation is forked from a historical turn, the Executor passes the source task's model, provider, permissions, and workspace configuration to Codex thread/fork, then copies the same executionRequest and modelSelection into the new task. A source thread routed through Wework's local model router must also reuse its existing proxy registration. Persisted task data has already redacted model credentials, so it must not re-register or overwrite the authenticated upstream. The returned fork thread is bound to that registration, ensuring that follow-up messages keep using the source model and provider instead of falling back to the current default.
For a new chat, the composer shows the plugin entry with previews for up to three available plugins. After the conversation starts, the entry collapses to a single icon to reduce toolbar usage, while clicking the icon still opens the complete plugin picker. Narrow toolbars use the icon form as well.
When a user selects a skill, app, or plugin in the composer, the editor inserts an indivisible inline mention. The cursor can only stop before or after the mention; copy and submit serialize it as Codex app-server-compatible Markdown:
- Skills use
[$name](/absolute/path/to/SKILL.md). - Apps use
[$name](app://connector_id). - Plugins use
[$name](plugin://plugin_name@marketplace_name).
Clicking a skill mention opens its local SKILL.md in the right workspace. Before sending turn/input, the executor parses those markdown mentions and builds Responses API-style input text elements. The legacy skill:///absolute/path/to/SKILL.md form remains parseable for persisted messages. This lets Codex receive the actual skill/app/plugin reference instead of only the display text.
Plugins that the user has not selected are not injected into ordinary conversations automatically. Installing a plugin only makes its skills and apps discoverable to the Codex app-server; activation still depends on Codex app-server plugin state and the user's selection in the conversation.
When the user clicks "Try in chat" from plugin detail or marketplace rows, Wework writes one plugin mention according to the Codex protocol instead of writing both plugin and skill mentions. The trial content is placed into a new chat draft, and related templates are shown above the composer. After the user sends the message, the message bubble still renders plugin:// mentions as badges so protocol strings are not shown as plain text.
Backend Marketplace and Upload
Backend accepts both Codex and Claude Code plugin packages for upload, cloud marketplace publication, installation, and device synchronization. It normalizes each stored package with manifests for both runtimes. Backend installation records are the source of truth for the Wegent cloud marketplace; local custom marketplaces and the OpenAI official marketplace still use the local Codex app-server.
After catalog rebuilds or migrations, Backend reconciles installed Kinds by checking pluginId, releaseId, source.catalogItemId, and source.marketplace. Even when the Plugin and Release IDs are unchanged, a source.marketplace value that no longer matches the plugin's current visibility-derived marketplace must update the installed record and reset failed device installation state. This keeps installed rows moving from wework to wegent when visibility changes from public to workspace instead of incorrectly treating the row as a no-op.
Project-space Issue comments
Plugins such as PR Review read Issue activity discussions with wework_space.list_board_item_comments and publish visible comments with add_board_item_comment. Both tools reuse project/Issue permissions and the activity message store. The returned comment ID is the activity message ID. Existing Socket.IO pushes refresh the UI, and comments remain available after reload.
To mention a member, resolve their ID and name with get_assignment_candidates, then pass mentions to add_board_item_comment, for example {"body":"@qindi please review","mentions":[{"type":"user","id":"52","label":"qindi"}]}, together with explicit space_id and item_id. Plain @name text does not notify. MCP comments in shared Backend projects use the page's recipient authorization, transaction and notification flow: duplicate mentions create one notification, self-mentions create none, and an unauthorized recipient rolls back both the comment and notifications. Do not also call send_notification for the same mention. Pure local projects have no shared member inbox and reject structured mentions explicitly.
The native Executor uses GET/POST /api/v1/cloud-projects/{project_id}/loop-items/{item_id}/activity-comments. Reads return the latest 200 messages by default, up to 500, ordered by ascending sequence number; use after_sequence to page forward. Read-only members can read but cannot post, and messages from other Issues are excluded.
The legacy /loop-items/{item_id}/comments endpoint still handles provider comments and historical assignment events. Its underlying data must not be deleted wholesale, and historical records are not automatically migrated into Issue activity. Plugins should use project-space tools instead of treating an ID returned by the old endpoint as evidence of a visible activity comment.
Desktop CI Core shard 15 includes collaboration-issue-mcp-comments: MCP writes appear in the UI, survive reload, native HTTP writes appear live, both entries notify an inherited member exactly once, plain text does not notify, MCP reads back mentions and the PR link, Issues remain isolated, and the page has no collaboration loading error. Cleanup closes Electron before stopping cloud test services so an open page cannot display a service-shutdown error.