Develop a Wework plugin
Treat the Wework package as the outer delivery unit. It may declare a nested
Codex plugin through wework.codexPlugin; the Codex plugin must never own or
gate the Wework plugin.
Use the bundled development kit
This Skill includes the complete public Wework extension catalog, one exhaustive smoke-test plugin, three focused API references, and three product-oriented showcase plugins:
- Read references/extension-points.md before choosing a UI surface. It documents descriptor fields, component props, and the browser-module boundary.
- Inspect assets/ui-extension-demo before writing a low-level UI contribution.
- Inspect assets/reference-plugins for runnable,
independently installable examples of Composer augmentation, persistent
workflow UI, website navigation, and typed desktop-host integration. For a
left-navigation button that opens a website in the Wework built-in browser,
start from
sidebar-browser-panel-demo. Copy only the closest example into the user's writable project, then delete every contribution the plugin does not need. - Start with assets/showcase-plugins when the user wants a product-quality example. Workspace Copilot, Test Explorer, and Dev Environments represent popular AI-assistant, testing, and development-environment categories with deliberately different interaction models.
- Never edit files inside an installed plugin cache. Resolve these resources relative to this Skill, and copy examples to the active project first.
The catalog covers all public host slots:
- Navigation and apps:
wework.action,wework.app,wework.route,wework.sidebar.navigation,wework.settings.page, andwework.settings.section. - Project and workspace:
wework.plugins.action,wework.project.create.section,wework.project.work.section,wework.workspace.menu.section,wework.workspace.tab,wework.workspace.sidebar.tab,wework.workspace.toolbar.action,wework.workspace.bottom-panel.tab, andwework.runtime-profile.workspace-policy. - Composer:
wework.composer.action. - Home and context:
wework.home,wework.task.status,wework.conversation.summary, andwework.board.card.status. - Shell:
wework.shell.before,wework.shell.after, andwework.shell.overlay.
It also documents the public ctx.wework.host, backend, commands,
composer, contributions, chat, testing, environments, context,
menus, keybindings, localization, configuration, storage, and
secrets services. Prefer a command-backed menu contribution when the host
already owns the button surface; use a raw UI slot when the plugin needs custom
rendering.
Understand the package boundaries
A Wework Core DSH plugin normally contains:
plugin-root/
├── package.json
├── cordis.patch.yml
├── index.js
├── client.js
└── codex-plugin/ # optional nested official Codex plugin
├── .codex-plugin/plugin.json
└── skills/<skill-name>/SKILL.md
Wework creates .wework/plugin-development.json beside the project files to
classify the local project and enable the 插件调试 tab. Treat that file as
Wework project metadata, not as part of the distributable Codex plugin.
The boundaries are strict:
package.json,cordis.patch.yml,index.js, andclient.jsbelong to the outer Wework package.- New projects contain only the outer Wework package by default. Add
wework.codexPluginand a nested Codex plugin only when the requested product capability actually includes Codex Skills, MCP servers, agents, or other official Codex plugin resources. - A nested Codex plugin uses the official
.codex-plugin/plugin.jsonformat and official Codex folders only. Do not add Wework-only keys or files to its manifest. index.jsruns in the Core DSH Node host.client.jsruns in the browser client. Share data through declared services or messages, not accidental module state or startup order.- Third-party plugins consume public Wework DSH slots and services. Never import private Wework application modules.
Inspect before editing
- Read
.wework/plugin-development.json,package.json, and the declareddsh.bundle.patch. - When
wework.codexPluginis present, read its.codex-plugin/plugin.jsonand every affected Skill or MCP entry below that directory. - Read every declared host, browser, or sidecar entry affected by the change.
- Reuse public Wework DSH slots and services. Do not import private Wework application modules from a user plugin.
- Remove duplicate loaders, watchers, compatibility paths, and generated examples that no longer serve the plugin.
Choose the smallest extension surface
Start from the user-visible outcome, then select the narrowest matching slot:
- Add a complete application with
wework.app. - Register the full-content route rendered by an existing workspace tab with
wework.route. It does not create a tab; when the tab'scontentRoutematches, the route component replaces that tab's entire content surface. - Add left navigation with
wework.sidebar.navigation. - Create a selectable top-level workspace tab with
wework.workspace.tab. - Add project-scoped inspection or controls with
wework.workspace.sidebar.tab. - Add compact workspace-level actions with
wework.workspace.toolbar.action. - Add a full bottom-panel tool with
wework.workspace.bottom-panel.tab. - Add an input-adjacent action with
wework.composer.action. - Add plugin-management actions with
wework.plugins.action. - Add a standalone settings page with
wework.settings.page. - Add controls to an existing settings page with
wework.settings.section; declare the target page in the contribution descriptor. - Replace the empty-task hero above the Composer with
wework.home; use the provided heading and callonSelectSuggestionto fill the active draft. - Use contextual and shell slots only when the UI genuinely belongs to that lifecycle.
Do not build a parallel navigation, panel, or settings system when a public slot already owns that surface.
When a left-navigation item should open a website beside the current workspace,
register a descriptor-only wework.workspace.sidebar.tab with
mode: 'iframe' and url, then point the navigation descriptor's
workspaceSidebarTab to that tab id. Do not register wework.app or create a
top-level workspace tab for this interaction.
Open a route in its own workspace tab
When a sidebar item must preserve the current tab and open the plugin route in
another top-level tab, put the public workspace-tab parameters in the
navigation descriptor's path:
function workspaceTabPath(path, id, title) {
const separator = path.includes('?') ? '&' : '?'
const params = new URLSearchParams({
workspaceTab: id,
workspaceTabTitle: title,
})
return `${path}${separator}${params}`
}
ctx.slots.inject('wework.sidebar.navigation', () =>
ctx.wework.contributions.register(ctx, 'wework.sidebar.navigation', {
id: 'example.navigation',
label: 'Example',
path: workspaceTabPath('/example', 'auxiliary-example', 'Example'),
})
)
Register the matching /example content with wework.route. A stable
workspaceTab value creates the tab on the first navigation and selects that
same tab later. Omitting workspaceTab intentionally replaces the active
tab's route. Use a generated ID only when every action must create another
tab. Do not import WorkspaceTabsContext, navigateTo, or other private
Wework modules into a plugin.
Build the capability
- Register reusable behavior once through
ctx.wework.commands; invoke that same command from menus, shortcuts, or plugin UI. - Register privileged Node behavior through the injected
ctx.weworkPluginRuntime, then call it from browser UI through a namespacedctx.wework.backendclient. Do not invent plugin-specific loopback servers. - Use
ctx.wework.hostfor typed desktop capabilities; do not call private Electron bridge URLs. - Localize visible copy with
ctx.wework.localization.translate(...). Standalone plugins must not import Wework's private React localization hooks. - Publish state used by visibility and enablement rules through
ctx.wework.context. - Use
ctx.wework.menusfor standard Composer and workspace toolbar actions. - A command selected from
composer.slashreceivesinvocation.composer; use itsgetValue,setValue,insertText, andfocusmethods to edit the active draft without importing Composer internals. - Use
ctx.wework.composer.referencesfor resources that users should discover and insert through the Composer@menu. - Use
ctx.wework.chat.providers,ctx.wework.testing.providers, orctx.wework.environments.providerswhen the capability must be reusable across multiple host or plugin surfaces. - Use namespaced
ctx.wework.storagefor JSON state andctx.wework.secretsonly for sensitive strings. - Declare settings through
ctx.wework.configuration; do not invent a second configuration store. - Register host-readable metadata through
ctx.wework.contributions. - Register visual components directly through native
ctx.slots.register. Do not attach descriptors to component statics or flatten DSHkind,scope, child slots, stores, and injected business faces into a generic UI API. - Inject the same slot with
ctx.slots.injectso both registrations follow the Core DSH lifecycle. - Keep contribution ids stable and provide
data-testidvalues for interactive controls. - Use the descriptor fields and component props documented for the selected slot. Do not infer undocumented host internals.
- Keep Node and browser dependencies in their declared entries. Browser code must not call Node APIs directly.
- When creating or changing a Skill, keep it inside the declared nested Codex plugin, use official Codex structure, and keep its description discriminating.
- Keep the plugin cohesive: remove unused Demo contributions, generated placeholders, duplicate watchers, and obsolete compatibility paths.
A minimal browser registration follows this shape:
ctx.slots.inject('wework.workspace.sidebar.tab', function* () {
const descriptor = {
id: 'example.inspector',
label: 'Inspector',
when: { projectKinds: ['wework-core-dsh-plugin'] },
}
yield ctx.wework.contributions.register(ctx, 'wework.workspace.sidebar.tab', descriptor)
yield ctx.slots.register(
{
name: 'wework.workspace.sidebar.tab',
id: descriptor.id,
label: descriptor.label,
},
InspectorPanel
)
})
Use the exhaustive Demo for API coverage and the reference plugins for product-oriented patterns instead of expanding this snippet into guessed APIs.
Debug
Use the current project's right-side 插件调试 tab:
- Validate the project and fix the structured errors it reports.
- Start the isolated Wework development instance.
- Exercise the exact UI surface contributed by the plugin.
- Edit browser source and confirm the visible behavior changes through HMR.
- Read host, browser, or sidecar logs when behavior does not match the source.
- Restart Core DSH only after dependency, manifest, bundle-patch, host-process, or other process-state changes that cannot be hot reloaded.
Operate Wework through the general wework desktop CLI. This is the required
control surface for inspecting and interacting with both the main Wework
instance and the isolated plugin-development instance:
wework desktop instances
wework desktop status --project .
wework desktop inspect --project . --interactive true
wework desktop click --project . --selector '[data-testid="example-action"]'
wework desktop fill --project . --selector '[data-testid="example-input"]' --value 'value'
wework desktop press --project . --selector '[data-testid="example-input"]' --key Enter
wework desktop wait --project . --selector '[data-testid="example-result"]' --text 'ready'
wework desktop screenshot --project . --output test-results/plugin-debug.png
--project . resolves the running Wework instance registered for the current
project, so commands do not contain cache directories, ports, tokens, process
ids, or other machine-specific values. Use wework desktop instances and an
explicit --instance only when more than one matching instance exists.
Use inspect before selecting a target, prefer stable data-testid selectors,
and verify every mutating command with wait or another inspect. The CLI
intentionally has no arbitrary JavaScript evaluation command. Do not bypass
the CLI with a plugin-owned control implementation.
Read the actual structured failure and logs before modifying code. Confirm the changed behavior in the development instance; a file-write event alone is not evidence that HMR succeeded.
The development instance has separate login state, data, executor home, Core DSH profile, cache, and logs. Never copy authentication data from the main instance.
Verify
Before handing off:
- Validate the outer Wework package and its declared bundle patch.
- Validate the nested official Codex plugin when present.
- Run focused tests for changed host, browser, manifest, and Skill behavior.
- Exercise both Node and browser halves when both exist.
- Verify one real source edit through HMR.
- Confirm install, enable, disable, and uninstall lifecycles when UI contributions changed.
- Confirm every interactive control has a stable
data-testid. - Stop the isolated development instance and verify its temporary runtime resources are cleaned up.