ection**

When `capture` is set to auto-detect (empty string), the initialization logic currently verifies and marks every available backend in the `sources` bitset, even after finding a working one. This causes unnecessary probing and can lead to confusion when multiple backends are technically present but only the first suitable one should be selected.

**Expected behavior:**
When auto-detecting capture backends (i.e., `config::video.capture` is empty), the enumeration should stop as soon as a working backend is found and verified. This short-circuits the initialization process and avoids redundant capability checks.

**Constraint:**
X11 must always be enumerated regardless of whether an earlier backend succeeded, because it serves as a necessary fallback for NvFBC when software encoding is required on X11 sessions.

**Behavior with explicit configuration:**
If `config::video.capture` is explicitly set to a specific backend (e.g., "nvfbc", "wlr", "kms", "x11"), the current behavior should be preserved—only that specific backend is verified, regardless of the `sources` state.

The `sources` bitset tracks which backends are available; once any bit is set during auto-detection, subsequent backends (except X11) should be skipped.

Stop enumeration after finding a working capture backend


 Windows: Preserve spatial audio settings by limiting virtual sink to 24-bit

The virtual audio sink initialization currently attempts 32-bit float and integer formats, which causes Windows to clobber existing Dolby/DTS spatial audio configurations on user endpoints.

Update `create_virtual_sink_waveformats()` in `src/platform/windows/audio.cpp` to only offer 24-bit (s24in32, s24) and 16-bit formats for stereo configurations, removing f32 and s32 from the candidate list.

fix(audio/windows): don't set virtual speakers higher than 24-bit mode (#3294)


## Bug: Local provider dashboard shows broken "Open Existing" action; trust boundaries always show default label

### Symptoms

**Local provider dashboard actions**
On the dashboard for the local (in-browser) provider, the first action card ("Open Existing") links to `/local/threatmodels`. This route does not work for the local provider – models are loaded via import, not from a repository listing. The existing import functionality is present as a later action but is not the primary entry point. The broken action should be removed and import should become the first dashboard action, linking to `/local/threatmodel/import` with the `file-import` icon.

**Trust boundary label**
When a trust boundary edge is created or an older diagram is migrated, the edge always displays the text "Trust Boundary". This happens even when the source model had no label set for that boundary. The expected behavior is that trust boundaries should not show any default text – an empty label should stay empty, and only explicitly provided text should be displayed.

### Requirements

1. **Local provider dashboard**
   - Remove the non-functional "Open Existing" dashboard action.
   - Ensure the import action (`key: 'import'`, route `/local/threatmodel/import`, icon `file-import`) is present and appears as the first dashboard button.
   - No other provider dashboards should be affected.

2. **Trust boundary label**
   - Remove the hardcoded default label `"Trust Boundary"` from trust boundary shape definitions.
   - A newly created trust boundary edge should have its label text empty.
   - Migration of version-1 diagrams should preserve an empty label for trust boundaries rather than injecting a default.
   - Existing diagrams that already have a non-empty label on a trust boundary must not be altered.

### Relevant areas

- Dashboard action configuration for each provider type (currently defined in `td.vue/src/service/provider/`).
- Trust boundary curve shape definition (`td.vue/src/service/x6/shapes/trust-boundary-curve.js`).
- Diagram migration logic in `td.vue/src/service/migration/`, which may affect how labels are mapped for imported boundaries.


**Feature request: Consolidate threat template selection and rename "by property" to "by context"**

The threat editing dialog opens different edit-pane templates (STRIDE, CIA, LINDDUN) depending on the `modelType` of the threat. Currently the logic that maps a model type to the correct template is duplicated in two locations: inside the threat suggestion/sweep flow and inside the element-threats directive. This duplication should be removed, and the mapping should live in one place (the `dialogs` service) so future threat-model types can be added without risking divergent copies.

At the same time, the button label `Threats by property` in the threat summary pane does not accurately describe the feature. It should be relabeled to **“Threats by context”** and all associated code identifiers updated consistently.

### Acceptance criteria

- **Template selection consolidation**
  The template-picker logic (`modelType` → `diagrams/StrideEditPane.html`, `diagrams/CiaEditPane.html`, `diagrams/LinddunEditPane.html`, with a fallback to STRIDE) is moved into a reusable method on the `dialogs` service.
  Both the threat-suggestion flow and the element-threats directive must call this centralized method instead of maintaining their own private copies.
  The resulting dialog behaviour (the edit pane shown, the prompt headings, and the edit/confirm/cancel interactions) must remain unchanged.

- **Label and function rename**
  The button with id `buttonNewThreatByProperty` and its click handler `onThreatsByProperty` must be renamed to reflect “context”:
  - Button text: `Threats by context`
  - Button id: `buttonNewThreatByContext`
  - Handler name: `onThreatsByContext`

  The `threatengine` service method `generateByProperty` must also be renamed to `generateByContext` so the naming is consistent throughout the codebase.

- **No behaviour change**
  Threat generation, threat suggestions, and dialog confirmations must continue to work identically. The only observable differences should be the corrected button label and the internal cleanup of template selection.


**Title:** Abstract refresh token storage into a dedicated repository module

**Context:**
The server currently stores refresh tokens in an in-memory array inside `td.server/src/controllers/auth.js`. That array is lost on restart, and there is no clean separation between token storage and authentication logic. We want to prepare for future persistent storage (e.g. a database) without touching the authentication API.

**Desired behaviour:**

1. **New token repository module**
   Create a repository module (e.g. `td.server/src/repositories/token.js`) that encapsulates refresh token storage. It must provide at least three operations:
   - **add** a refresh token to the store
   - **remove** a refresh token from the store (invalidating it)
   - **verify** a refresh token: return `true` only if the token is present in the store **and** is a valid JWT signed with the refresh token signing key (`JWT_REFRESH_SIGNING_KEY`). If verification fails (expired, bad signature, etc.), the token should be removed from the store automatically so it cannot be used again.

2. **JWT helper extension**
   The existing `jwt.helper.js` only exposes a `verifyToken` function that uses the access token signing key. Add a corresponding `verifyRefresh` function that calls `jsonwebtoken.verify` with `env.get().config.JWT_REFRESH_SIGNING_KEY`. This keeps refresh‑token validation consistent and testable in isolation.

3. **Controller integration**
   In `auth.js`’s `completeLogin` handler, replace the direct `refreshTokens.push(refreshToken)` with a call to the new repository’s `add` method. Remove the local `refreshTokens` array and its comment. The controller should import the repository instead of managing the array itself.

4. **Behavioural invariants**
   - The in‑memory array is still acceptable as the initial store, but its location should be inside the repository (not in the controller).
   - The repository’s public interface (`add`, `remove`, `verify`) must remain stable so that the store can be replaced later without changing consumers (e.g. logout).
   - Existing authentication flow must not break: after a successful OAuth login, the returned `refreshToken` must still be valid and verifiable through the repository.

**References:**
- `threatmodelrepository.js` can serve as a style guide for repository modules.
- Environment variable `JWT_REFRESH_SIGNING_KEY` is already used during token creation; now it must also be used for verification.

**Note:** The existing test for `completeLogin` should be updated to verify that the repository’s `add` is called with the correct token, but no other security behaviour should change.

Abstracting token store to a repository implementation.  This could be moved to an external data source later without refactoring the API for it


**Request**: Add handling for TM-BOM trust zones during import.

Currently the TM-BOM import ignores trust zones in the model. Trust zones should be rendered on the imported diagram as trust-boundary-box cells, so users can see the boundaries defined in the model.

Behavioral requirements:
- For each trust zone defined in the model (`trust_zones`), create a boundary box cell (shape `trust-boundary-box`).
- The boundary box size should scale with the number of entities assigned to that zone: count all `actors`, `components`, and `data_stores` whose `trust_zone` matches the zone’s symbolic name. Use a simple sizing heuristic (e.g., cells per side grows with number of nodes).
- Position the boundary boxes on the diagram without overlapping; lay them out in rows with adequate padding.
- Use the default boundary box properties from the entity defaults, but set `name` and `description` from the trust zone’s title/description. Ensure the default `boundary` and `boundaryBox` objects both include `hasOpenThreats: false` (to stay consistent with other entity defaults).
- When the model has no explicit `diagrams` array, generate a single default diagram of type `generic` that contains all the generated boundary boxes. If a `diagrams` array is present, each diagram should include the boundary box cells.

The import logic for diagrams should be updated to call the new trust zone rendering, and the module structure may need reorganizing to support this cleanly (e.g., extracting trust zone box generation into its own helper).


```
The shell controller currently declares `$location` as a dependency (in the DI annotation and the function signature) but never uses it. Remove this unused injection.

Additionally, the existing test file `shell_spec.jsx` is not executed by the test runner because of its extension, and it relies on Angular integration mocks that are fragile and don't properly verify the controller's initialization logic. Replace it with a standard `shell_spec.js` that unit-tests the controller function directly, using plain mock objects for `$rootScope`, `$scope`, `common`, and `config`.

The controller should, when instantiated:

- Log activation via the logger obtained from `common.logger`.
- Invoke `common.activateController` (with whatever identifier the controller passes).
- Register a `$viewContentLoaded` listener on `$scope` that, when triggered, sets `$rootScope.appLoaded = true`.
- Register a `$routeChangeStart` listener on `$rootScope` (the handler must be a function; the test does not need to validate any behaviour inside that handler beyond its existence).

Make the tests cover this contract so that `shell unit tests passing` becomes the stable baseline.
```


As part of adding TM‑BOM (Threat‑Modeling Bill‑of‑Materials) support to Threat Dragon, implement import and export converters for a model’s **metadata (meta)** and **scope** sections.
The converters must live in the migration service and follow the existing import/export patterns already used for other threat‑model formats.

---

### Acceptance criteria

#### Import (TM‑BOM → Threat Dragon)

When importing a valid TM‑BOM model:

1. The threat‑model `summary` is populated from the TM‑BOM `scope`:
   - `summary.title` ← `scope.title`
   - `summary.description` ← `scope.description`
   - `summary.owner` set to an empty string
   - `summary.id` set to 0

2. TM‑BOM `scope` fields that Threat Dragon **does not (yet) use** are stored in a compatibility object inside the summary, so they survive a round‑trip:
   - `business_criticality`
   - `data_sensitivity`
   - `exposure`
   - `tier`

3. The **top‑level TM‑BOM metadata** is also stored in the same compatibility object, even though Threat Dragon does not currently use it:
   - `version` (the original TM‑BOM version)
   - `description` (top‑level, distinct from scope description)
   - `frozen`
   - `release_docs_link`
   - `reviewed_at`
   - `repo_link`

4. The Threat Dragon model version is set to the **converter’s hard‑coded version** corresponding to the Threat Dragon release for which the migration was written. At this time the value is `2.4.1`.

5. The import must handle the case where `scope` is missing (produce a summary with no extra fields, no errors).

#### Export (Threat Dragon → TM‑BOM)

When exporting a Threat Dragon model that was previously imported from TM‑BOM:

1. The TM‑BOM `scope` is reconstructed:
   - `scope.title` ← `summary.title`
   - `scope.description` ← `summary.description`

2. The extra `scope` fields are reinstated from the compatibility object **if it exists**:
   - `scope.business_criticality`, `data_sensitivity`, `exposure`, `tier`

3. The top‑level TM‑BOM metadata is reinstated from the compatibility object **if it exists**:
   - `frozen`, `release_docs_link`, `reviewed_at`, `repo_link`, `description`

4. The exported TM‑BOM model version is taken from the compatibility object’s `version` field **if present**; otherwise it defaults to `'1.0'`.

---

### Design constraints

- **Round‑trip fidelity** is critical: a TM‑BOM file imported and immediately exported must produce an equivalent TM‑BOM structure, with all original fields present and unchanged (except that the top‑level `version` may be the original one, not the converter version).
- The converters must be **hosted in the existing migration area** under `td.vue/src/service/migration/tmBom/` and be structured as separate modules for the top‑level model and for the scope, just as other format converters are organized.
- Use only plain JavaScript objects; no prototypes or classes.
- The export must gracefully handle the absence of the compatibility object (in models that were never imported from TM‑BOM), supplying sensible defaults where required.

---

### Out of scope

This request covers **only meta and scope** – the diagram/entities/threats mapping will be handled separately.

TM-BOM import and export: meta and scope


**Request: Restrict server rate limiting to production environments only**

**Background**
The Threat Dragon server applies an Express rate‑limiting middleware. Currently it is active in all environments *except* when `NODE_ENV=development`. This causes problems for automated end‑to‑end tests and staging deployments that run in environments other than `development` (e.g. `test`, `staging`, or when `NODE_ENV` is unset) – they encounter unexpected rate‑limit rejections.

**Acceptance criteria**

1. Rate limiting is **active** only when the `NODE_ENV` environment variable is explicitly set to the string `"production"`.
2. Rate limiting is **inactive** for every other value of `NODE_ENV` (including when it is not set, empty, `test`, `staging`, or any other custom value).
3. When rate limiting is activated, the server writes an informational log message that clearly indicates rate limiting is being applied in a production environment.
4. When rate limiting is deactivated (any non‑production environment), the existing warning message (“Rate limiting disabled for development environments”) is updated to reflect that it is disabled for non‑production environments in general (the exact wording may be adjusted to be accurate, e.g. “Rate limiting disabled – not a production environment”).
5. The change must not alter any other startup behaviour (static file serving, repository configuration, proxying, etc.).

**Affected surface**
The Express application bootstrap inside the `td.server` component. No changes are required to the rate‑limiter configuration itself; only the guard that decides whether to call `app.use(limiter)` needs to be tightened to a strict equality check against `"production"`.

**Rationale**
This makes the rate‑limiting policy explicit and safe: only the live production deployment is protected by rate limiting, while any other environment (CI, QA, local development) can operate without artificial request caps.

rate limiting explicitly for production environments


**Ticket title:** Re-enable and fix unit tests for Branch, Dashboard, and Home views

**Description:**
Several view unit tests are currently skipped (`xdescribe`) and need to be re-enabled.

- **Branch view test** – the router mock is broken.
- **Dashboard view test** – the state shape is incorrect after the provider store module refactor (the test references `datasource.provider` but the actual module now has `provider.selected`). The test should also check for the welcoming text rather than only asserting element existence.
- **Home view test** – the component performs an OAuth login by calling the backend endpoint with raw `fetch`. Inline `fetch` is hard to mock cleanly and deviates from the project’s established API service pattern. Refactor the call so that it uses a dedicated service that wraps our `api` client (imported from `@/service/api.js`). The service should be unit-testable on its own and then mocked in the Home view test.

**Acceptance criteria:**
- Branch, Dashboard, and Home view tests are no longer skipped (`describe` instead of `xdescribe`).
- All three test suites pass with the current production code.
- The Home view’s login method no longer calls `fetch` directly. It dispatches the selected provider event, calls the new service function, and uses the returned data to redirect via `window.location.href`.
- The dashboard test uses the correct state shape (`provider.selected`).
- The branch router mock is fixed so the test can mount the component without error.


```
Title: Improve robustness and API consistency of Notebook component

The `Notebook` component (`src/wirecloud/commons/static/js/StyledElements/Notebook.js`) needs a round of API hygiene improvements:

- Make the following methods chainable (return `this`, matching other StyledElements components): `shiftLeftTabs`, `shiftRightTabs`, `repaint`, `clear`, `requestFullscreen`, `exitFullscreen`.
- `shiftLeftTabs` currently does not guard against an empty notebook or a state where the first tab is already fully visible. Modify its internal transition logic so that these cases do not cause errors or invalid shifts.
- `repaint` should handle an empty tab list gracefully without throwing and still return `this`.
- `exitFullscreen` should not invoke `document.exitFullscreen` when the notebook is not actually in fullscreen mode; it should just return `this` in that situation.
- Remove the `isDisabled` method if it is dead code and its role is already covered by `setDisabled` / the disabled layer.
- Add or improve JSDoc comments for `shiftLeftTabs`, `shiftRightTabs`, `requestFullscreen`, `exitFullscreen`, and any other public methods that currently lack documentation, describing their purpose and return type.

Focus on the external behavior and keep the changes localised to the Notebook class; do not alter the component's public API beyond the described improvements.
```

Improve style notebook code, doc and tests


**Title:** `find_iwidgets()` and `instantiate()` return stale element references causing flaky Selenium failures

**Description:**
When running the desktop Selenium test suite, helper methods that locate widget instances on the workspace frequently cause `StaleElementReferenceException` or operate on elements that are no longer in the DOM. This affects two public surfaces:

1. `RemoteTestCase.find_iwidgets(tab=None)` – returns a list of `IWidgetTester` objects, but some of those objects may refer to stale `WebElement` references or be `None`, especially after the workspace updates asynchronously.
2. `WidgetWalletResourceTester.instantiate()` – adds a widget from the wallet to the workspace and returns an `IWidgetTester` for the newly added widget, but the returned tester often becomes stale immediately.

These problems make tests unreliable and cause hard-to-diagnose failures, for example in `LocalCatalogueSeleniumTests.test_resource_uninstall_all_version`, where indexing into the result of `find_iwidgets()` fails because the referenced element has been detached.

The root cause appears to be that the current implementation fetches widget DOM elements via `execute_script` (getting `iwidget.element` from the JavaScript runtime) and then reuses those references, which become invalid when the DOM is re-rendered. The desktop test helpers should instead locate the `.iwidget` DOM elements fresh each time using CSS selectors scoped to the active workspace tab.

**Expected behavior:**
- `find_iwidgets()` should always return a list of valid `IWidgetTester` instances that represent the current workspace state, with no stale or `None` entries.
- `instantiate()` should reliably return a tester for the newly added widget after it becomes visible, using a stable attribute (like the widget’s id) to locate its DOM element rather than caching a reference obtained before the widget is fully rendered.
- The mobile test subclass (`MobileWirecloudRemoteTestCase`) should preserve its current behaviour (which may legitimately rely on JavaScript evaluation and retry on stale references), so any change must be scoped to the desktop path.
- The workspace tab wrapper elements need a `data-id` attribute so that CSS selectors can scope widget queries to a specific tab. This attribute is currently missing from the `Tab` constructor.

**Impact:**
Flaky tests, wasted debugging time, and false-positive CI failures. Fixing this will make the desktop test helpers robust against DOM changes and avoid the need for fragile retry loops or `sleep` calls.

Fix some problems with current find_iwidgets implementation


Allow users to delete their last personal workspace.
- Remove the restriction that hides the "Remove" option when the user has only one workspace. The remove action should be available for every workspace the user owns, regardless of workspace count.
- After deletion, instead of trying to switch to another personal workspace (which may not exist), redirect the user to the built-in `wirecloud/home` dashboard. That dashboard is owned by the `wirecloud` org, not by the logged-in user, so the UI must not show workspace-editing actions (rename, settings, remove) while this dashboard is displayed.
- Make sure `changeActiveWorkspace` can accept a workspace identifier as an `{owner, name}` plain object (without an `id`) in addition to the full workspace record, so the redirect to `wirecloud/home` works properly.

Allow users to remove the last workspace they have


```markdown
**Title:** Improve Wirecloud.Task: subtask aggregation, value storage, toTask for promise chains, and toString

**Component:** `wirecloud/platform/static/js/wirecloud/Task.js` (the `Wirecloud.Task` class)

**Request:**
The `Wirecloud.Task` class (promise-like object with progress and abort) needs several enhancements to make it more useful for representing composite operations and operation chains. The following behaviors should be added or clarified:

1. **Value storage:**
   When a task is fulfilled (`fulfill(value)`) or rejected (`reject(reason)`), the passed value should be stored on the task instance and exposed as a property (e.g., `task.value`). Currently the callbacks can receive the value, but the task itself does not retain it.

2. **Subtask aggregation:**
   The constructor should accept an array of existing `Task` objects in addition to a function. When an array is passed, the new task represents an aggregation of those subtasks.
   - The aggregated task’s progress must be the average of the subtasks’ progresses, updated whenever any subtask’s progress changes.
   - The aggregated task’s status should be derived as follows:
     - If *any* subtask is still `"pending"`, the aggregation is `"pending"`.
     - If any subtask is `"aborted"`, the aggregation is `"aborted"`.
     - Otherwise, if any subtask is `"rejected"`, the aggregation is `"rejected"`.
     - Only when all subtasks are `"resolved"` should the aggregation be `"resolved"`.
   - The aggregated task’s value should be an array of the subtasks’ values (in order).
   - The subtasks should be accessible via a read-only property (e.g., `task.subtasks`).
   - Passing an empty array must throw a `TypeError`.
   - Aborting a single subtask should cause the aggregation to transition to `"aborted"`.

3. **Title immutability during progress updates:**
   The `update` callback provided to the executor currently allows changing the title. Remove that capability so that `title` is set only at construction time and cannot be modified later.

4. **`toString()` implementation:**
   The task should implement `toString()` so that it returns a string containing its title and progress (e.g., `"task title: 42%"`).

5. **`toTask()` for promise chains:**
   When `toTask(title)` is called on a task-returning chain built with `.then(...)`, the resulting `Task` should:
   - Include all steps of the chain as subtasks (including any intermediate `Task` instances returned from callbacks).
   - Compute progress based on the number of completed steps over the total number of steps.
   - Expose the sequence via its `subtasks` property.
   - Resolve/reject with the same final value/reason as the original chain.
   - Respect the normal aggregation rules (e.g., aborting any subtask aborts the whole task).

   Calling `toTask` on a simple (non-chained) task should similarly wrap it, making the original task its single subtask and propagating progress.

**Acceptance criteria (conceptual):**
- A task created with an array of immediate‑fulfill subtasks is immediately resolved with value `[sub1value, sub2value]` and progress `100`.
- A task created with one immediately‑failed subtask is immediately rejected with value containing both subtask values, and progress reflects the completed fraction (50% for two subtasks).
- Aborting a subtask that is part of an aggregation changes the aggregated task’s status to `"aborted"`.
- `task.toString()` returns a non‑default string that includes the title.
- Chaining several `.then()` calls (some returning Promises, some returning Tasks) and calling `.toTask("name")` produces a task with the correct number of subtasks, tracks progress through completion, and ultimately resolves with the final value.

**Non-goals (for this ticket):**
- Changing the existing `abort()`, `catch()`, or event‑dispatch semantics outside what is needed for aggregation.
- Modifying the `renameTask` method.
- Adding serialization/deserialization support.

Let me know if you need additional clarification.```

Now, evaluate how well this prompt fits the criteria: It does not mention tests or commits; it describes features and invariants. It uses code-specific terms (Wirecloud.Task, toTask, then, progress, title) as needed for disambiguation. It is concise but covers edge cases like empty array, abort propagation, progress averaging, and chain step counting. It's from a maintainer's perspective. This should be a solid prompt for training.


**Ticket: Fix variable handling after model refactoring**

**Background**
We recently replaced the old abstract‑variable system with a new `Variable` model (a foreign key to `VariableDef`). Several bugs in the data layer and frontend contract were introduced at that point.

**Observable symptoms**
- Igadget variables are returned as a JSON array, but the frontend’s `VarManager.parseIGadgetVariables` now expects them as an object keyed by variable **name** (the structure that `GadgetTemplate.getVariables` returns).
- User‑preference values for igadgets are sometimes wrong or missing because the values cache is keyed by `vardef.id` instead of `variable.id` – this causes collisions when multiple igadgets share the same gadget template.
- Creating a new igadget for a gadget that has only SLOT/EVENT variables fails to create the necessary `Variable` records and connectable objects.
- After saving an igadget, the server still serves stale variable values because the values cache is not invalidated.
- The variable serialization still includes `igadgetId` and `vardefId` fields that the frontend no longer touches, and it also sends `value`/`shared` for non‑user‑perference aspects (SLOT, EVENT, GCTX, ECTX).

**What a fix must achieve**

1. **Data format for igadget variables**
   `get_igadget_data` must return `variables` as a dictionary keyed by the variable **name** (from `VariableDef.name`), not a list. Each value should contain `id` and `name`. The fields `igadgetId` and `vardefId` must be removed.

2. **Limit exposed value data**
   `get_variable_data` must include `value` and `shared` **only** for aspects `PREF` and `PROP`. For SLOT/EVENT/GCTX/ECTX, just return `id` and `name` (no `value`).

3. **Cache correctness**
   The per‑user cache (`_populate_variables_values_cache`) must be indexed by `variable.id` (not `variable.vardef.id`), and `get_variable_data` must use the same key for look‑ups.

4. **Variable creation**
   `addIGadgetVariable` must unconditionally create a `Variable` record (for **all** aspects). Only for PREF/PROP should it then create a `VariableValue`. For SLOT/EVENT it must call `createConnectable` with the new `Variable` instance (not the `VariableDef`).
   The `createConnectable` helper itself must use `Out.objects.create`/`In.objects.create` (standard Django ORM), not any custom `.create()` method.

5. **Cache invalidation**
   After an igadget is saved (`SaveIGadget`), invalidate the user’s cached variable values so the next request picks up fresh data.

6. **Variable‑value cloning**
   `clone_original_variable_value` must accept a `Variable` instance instead of a workspace. Look‑up should be by `variable` and `user`, and creation should use `VariableValue.objects.create(variable=..., user=..., value=...)`.

7. **Side‑cleanup**
   Remove the unused import `ContextOption` from `gadget.models` in the data module.
   In the frontend, methods of `GadgetTemplate` that iterate over `variableList` must handle it as an object (formed by variable names), consistent with the `getVariables` return shape. `VarManager.parseIGadgetVariables` must walk the expected object structure and correctly default values for fields that may be missing in the server response (`readOnly`, `hidden`, etc.).

**Non‑goal**
- No schema migration needed – this is just about how existing model relations are used.
- The workspace‑variable collection endpoint does not need structural changes; just clean up any leftover unused variables.

Fix some bugs introduced in the model refactorization


Fix operator reinstallation after uninstall/reinstall cycle

When an operator is uninstalled while it is wired in a workspace and then reinstalled, it does not recover correctly:
- Connections that existed before uninstall do not propagate events after reinstall.
- Preferences revert to defaults instead of keeping their previously saved values.
- Placeholder (“ghost”) target endpoints left behind by the missing operator sometimes throw errors because they attempt to propagate incoming values.
- The wiring connection-filtering logic misidentifies which connections belong to a given entity, causing some connections to be overlooked or incorrectly processed.

The expected behavior after reinstalling an operator is that all its previous wiring connections become fully operational again, and its preference values are restored to whatever they were before uninstallation. Ghost endpoints must never attempt to forward events.


The catalogue API responses (from listing/search endpoints and from the resource upload endpoint) currently represent each resource as a flat array of version objects, where each version repeats the resource’s `vendor`, `name`, and `type`. Since these three properties are constant across all versions of the same resource, they should be lifted to a parent object level and not duplicated per version.

Acceptance criteria:
- When fetching the catalogue resource list, search results, or the response after adding a new resource, each resource is returned as an **object** with keys `vendor`, `name`, `type`, and `versions`. `versions` is an array of per-version data that does **not** include `vendor`, `name`, or `type`.
- The shape of the version objects inside `versions` remains the same except for the removal of those three duplicated fields.
- The client-side code that consumes these responses (specifically the `ResourceState` constructor and its accessors like `getVendor`, `getName`, `isMashup`, `isGadget`, the version iteration logic, and the `CatalogueResourceSubmitter`) must be updated to read `vendor`/`name`/`type` from the root resource object rather than from the current version object, and to traverse `versions` instead of treating the response as a plain list.
- All existing resource-detail and version-switching behaviours must continue to work correctly with the new structure.

refs #75. Provide the vendor, name and type attributes in the root resource description instead of repeating it for each version of the resource


Feature: Allow superusers to add public marketplaces

Background:
Currently, when a user adds a marketplace, it is visible only to that user. There is no way for a superuser to create a marketplace that is available to all users of the instance.

Acceptance criteria:

1. **Superuser UI** – When a superuser opens the "Add new marketplace" dialog, a "Public" checkbox (boolean field) must appear. If checked, the marketplace should be created as public. If unchecked, it is private to the superuser. A non‑superuser must never see this checkbox.

2. **Public visibility** – A public marketplace must appear in the marketplace menu (popup) for any authenticated user, alongside their own private marketplaces. A private marketplace added by user A must not appear for user B.

3. **Server‑side permission** – The API that creates marketplaces must reject requests that attempt to set the user to `null` (i.e., public) when the authenticated user is not a superuser, returning an appropriate 403 response (e.g., "You don't have permissions for adding public marketplaces").

4. **Ownership model** – A public marketplace must be stored with no associated user (`user` = None in the database). A private marketplace continues to be stored with the creating user as owner.

5. **Marketplace listing for a user** – The logic that retrieves the list of marketplaces available to a user must include public marketplaces (user is `None`) in addition to the user’s own private marketplaces. Consequently, the representation of each marketplace must include a `user` field in its options: `null` for public marketplaces, or the owner’s username for private ones. This field is needed so the client can distinguish public from private.

6. **Client‑side creation payload** – When the superuser submits the add‑marketplace form, the request payload must set the `user` value appropriately: `null` if the "Public" checkbox was checked, otherwise the current user’s username. This `user` value is sent inside the `options` object; the backend must later remove that temporary `user` key from the options before persisting to the database, so that the stored options do not contain a redundant `user` entry.

Constrains / non‑goals:
- Do not change the behaviour of the existing "Add new marketplace" flow for non‑superusers (private marketplaces remain private).
- The existing `MarketCollection.create` endpoint currently assumes the user is always the requester; this must be extended safely to handle the `null` ownership case.
- The `get_market_managers` utility function must be updated to set `options['user']` as described, ensuring callers receive correct ownership information.
- The test helper `add_marketplace` should support an optional `public` parameter (default `False`) that, when `True`, clicks the public checkbox during the UI flow, to facilitate verification.

Add support for adding public marketplaces


Title: Update wiring editor test helpers and component sidebar classes for current UI

The wiring editor’s Selenium test framework (in `src/wirecloud/commons/utils/remote.py`) is out of date after recent UI changes. Several interactions that tests rely on are broken because CSS classes and interaction patterns changed. We need the test helpers to support the current wiring editor behaviour so that we can write and maintain reliable end-to-end tests.

Required capabilities / acceptance criteria:

1. **Open component settings menu**
   A wiring component’s preferences/settings menu is now triggered by a button with class `.option-preferences`. The test helper method that opens the menu must use this button instead of the removed `.editPos_button`. Clicking it should wait for the `.se-popup-menu` to appear and return a popup menu tester object.

2. **Open sidebar component groups**
   The sidebar group buttons for “Operators” and “Widgets” now carry CSS classes `opt-operator-group` and `opt-widget-group` respectively.
   - The JS component manager (`ComponentManager.js`) must add those classes to the corresponding `StyledButton` instances.
   - The wiring view tester’s method for opening a component group must select the button using the `opt-` prefix (i.e. `.opt-<component_type>-group`) instead of the old `. <component_type>-group`.

3. **Endpoint lookup and dragging**
   To create a wiring connection a test must identify endpoints by their type (“source” for output endpoints, “target” for input endpoints) and by their label text. The test helpers should provide this capability, returning an endpoint object that exposes:
   - an **anchor** element (the draggable circle, `.endpoint-anchor`)
   - the endpoint’s **name** (the label text from `.endpoint-label`)

   A test should be able to do something like:
   `source = component.get_endpoint_by_name('source', 'Output')`
   `target = component.get_endpoint_by_name('target', 'Input')`
   and then `drag_and_drop(source.anchor, target.anchor)` to create a connection.

4. **Migration of existing brittle tests**
   Any wiring-editor tests that currently rely on raw XPath expressions or hard‑coded CSS classes to find components, open menus, or drag endpoints should be rewritten to use the stable helpers described above. The test suite must continue to cover the same scenarios (e.g. basic wiring creation, operator reinstall behaviour, and component availability after deletion).

The overall goal is to have a behaviour‑oriented test API that abstracts away the exact CSS classes used for common wiring‑editor interactions, making the tests less fragile and easier to update when the UI evolves.


**Title:** Workspace tabs missing expected CSS class and `find_iwidget` cannot locate unpainted widgets

**Background:**
After some internal refactoring, two regressions have been observed:

1. **Tab styling / selector break:** Workspace tabs created via `Tab.js` in the operator manager no longer receive the CSS class `wc-workspace-tab` on their wrapper element. As a result, tab styling is lost and any code or selectors that depend on that class stop working. The class should be applied unconditionally to every workspace tab’s DOM wrapper.

2. **`find_iwidget` helper unreliable for lazy-painted widgets:** The `RemoteTestCase.find_iwidget` method only looks through widgets that are currently painted (as returned by `find_iwidgets`). This makes it impossible to locate a widget by its ID when it belongs to a tab that has not yet been displayed (e.g., a widget in a background tab that receives a wiring event and needs to be loaded on demand).
   **Expected behaviour:** When an ID is given, `find_iwidget` should search the workspace DOM directly for an element matching `.iwidget[data-id="..."]` and, if found, wrap it in an `IWidgetTester` – regardless of whether the widget is currently painted. If no such element exists, it should return `None`. The existing title-based fallback can keep its current behaviour.

**Acceptance criteria:**
- Every workspace tab wrapper element reliably has the `wc-workspace-tab` CSS class.
- `RemoteTestCase.find_iwidget` can successfully locate and return an `IWidgetTester` for a widget by its `id` even when that widget is not in the currently painted list (e.g., when it is on a different, non‑visible workspace tab).
- The existing behaviour for title-based lookups and for already painted widgets remains unchanged.


**Request: Form Class Usability and API Improvements**

The `StyledElements.Form` has several gaps in its public API and behavior that prevent its use in newer UI components. This request outlines concrete improvements needed for both form construction and runtime behavior.

---

### 1. Read‑only forms
When a form is constructed with `options.readOnly = true`, it must not show accept or cancel buttons. The `acceptButton` and `cancelButton` properties of the form instance should be `null`. All input fields must be disabled.

### 2. Custom buttons via options
It must be possible to pass pre‑built button instances for the standard actions through the options object. Accept a button for each of the following option keys and assign it to the corresponding form property:
- `acceptButton`
- `cancelButton`
- `resetButton`
- `setdefaultsButton`

When a custom button is provided, the form must use it instead of creating a default one.

### 3. Grouped fields (tabs)
Allow a field descriptor to have `type: 'group'` with a nested `fields` array. Such groups must be rendered as tabs using `StyledElements.Notebook`. The tab label comes from the group’s `shortTitle` property. The tab content should be built from the nested fields using the same table layout as the main form.

### 4. Repaint improvements
- `repaint()` must return `this` so callers can chain method calls.
- When `repaint` is called with a truthy temporal flag (i.e., `repaint(true)`), it must repaint every field interface, not just the overall form container.

### 5. Type checking for `update()`
The `update(data)` method must reject invalid argument types with a `TypeError`. The only allowed values for `data` are:
- An object (plain object or array)
- `null`
- `undefined`

Passing a number, boolean, string, or function must throw an error that indicates the type is invalid.

### 6. Public message display
Expose a new public method `displayMessage(message)` on the form. It must display a single message in the form’s existing alert area (currently only used internally for validation errors). This method replaces uses of the old private `pSetMsgs` throughout the form’s own methods (`getData`, `setData`, `update`, `is_valid`, `defaults`).

### 7. Error handling in `FormWindowMenu`
In `FormWindowMenu`, the failure callback currently only re‑enables buttons but does not show the error. Modify the callback so that the first argument (the error message) is passed to `this.form.displayMessage()` before re‑enabling the accept and cancel buttons.

### 8. Removal of deprecated APIs
The following obsolete methods must be removed:
- `pSetMsgs` – replaced by consistent use of the new message display mechanism
- `normalSubmit` – no longer used by any part of the platform

The private helper that renders messages into the alert element should be used directly (via `call`) in all the form methods that previously called `this.pSetMsgs`.

StyledElements: general improvements into the Form class


The REST API endpoint for updating an existing IWidget (name, refused version, etc.) is broken on both the server and client sides.

**Observed behavior:**
1. The web dragboard’s “rename” action sends a `PUT` request to the entry endpoint:
   `api/workspace/<workspace_id>/tab/<tab_id>/iwidget/<iwidget_id>/`
   The request body includes both `name` and `id`. The update is not applied.
2. Marking a widget version as refused also sends a `PUT` to the same entry URL with `refused_version` in the body; the change does not persist.
3. The iPhone dragboard’s `save` operation evaluates the entry URL pattern without providing an `iwidget_id`, causing a malformed request; it never reaches a valid handler.

**Expected behavior:**
- The IWidget entry endpoint should support updating an IWidget’s fields. The IWidget ID should be taken from the URL, not from the request body. Only the fields to be changed should be sent.
- The correct HTTP method must be used. Given the existing `Resource` dispatch conventions, `POST` (mapped to a `create` handler) is the appropriate method for updates on this entry resource, not `PUT`.
- The web client’s `setName` and `setRefusedVersion` calls should use the entry URL with the IWidget ID, send only the changed fields, and use the correct HTTP method.
- The iPhone client’s `save` method should use the IWidget collection URL (`iwidgets/`) rather than the entry URL, since it does not specify an individual IWidget ID.

**Impact:** IWidget rename, upgrade refusal, and iPhone persistence are non-functional for all users.

Fix "Update IWidget Status" rest API operations


**Title:** OMC blocks tmux-dependent features on Windows even when a tmux-compatible binary like psmux is installed

**Observed behavior**
- On native Windows (win32) with [psmux](https://github.com/marlocarlo/psmux) installed and in `PATH` (which provides a `tmux` binary that implements the necessary sub-commands), OMC still prints a startup warning saying that native Windows is unsupported and tmux is unavailable.
- Features that require tmux (team orchestration, rate‑limit pane scanning, etc.) refuse to work because the internal tmux-availability check short‑circuits on `win32` without trying to detect the binary.
- The binary‑path lookup in CLI detection uses `which`, which does not exist on Windows; `where` is the correct command.
- Some tmux sub‑commands emitted by OMC use single‑quote argument quoting (e.g. `-t '%0'`), which can break under psmux and Windows shells; double‑quoting is more universally portable.
- The console warnings and error messages about missing tmux only mention WSL2 as a Windows solution; they do not mention psmux as a native option.

**Expected behavior**
1. All decision points that currently hard‑block on `process.platform === 'win32'` should instead probe for an actual working `tmux` binary (e.g. by running `tmux -V` and checking the exit code). If a compatible implementation is present, OMC should proceed as normal.
2. The startup warning should only appear when the platform is Windows **and** no `tmux` binary can be found, and the message should guide users to install psmux (`winget install psmux`) as an alternative to WSL2.
3. Path resolution should use `where` on Windows instead of `which`.
4. Tmux command strings (e.g. inside `list-panes`, `capture-pane`) should use double‑quote quoting for pane identifiers and formats to be safe across shells.
5. The error shown when tmux is completely missing should also mention the psmux installation option alongside the existing Unix‑centric package instructions.

**Impact**
- Windows users cannot use team features, auto‑resume of rate‑limited panes, or any other tmux‑dependent capability, even when they have a fully functional tmux-compatible toolchain.
- This makes the tool unnecessarily hostile to native Windows development environments.

**Additional context**
The change should be backwards‑compatible: on macOS/Linux the new detection logic should yield the same results as before (since `tmux -V` succeeds when tmux is installed). The quoting change from `'` to `"` around tmux arguments should not break tmux on any platform because tmux itself accepts both styles, but double‑quotes are generally safer in command strings built via `execSync`.

fix(platform): replace win32 hard-blocks with tmux capability checks (#1423)


**Title:** HUD background task cleanup uses working directory instead of project root for state file

**Component:** HUD initialization / background task cleanup

**Priority:** Medium

**Description**
When the HUD starts, it performs a one‑time cleanup of stale and orphaned background tasks from the local state file. Currently this cleanup always reads from and writes to the state file in `process.cwd()`, even when the HUD is launched with a project root different from the current working directory (the project root is communicated through `stdin.cwd` and resolved via `resolveToWorktreeRoot`).

This leads to at least two observable issues:
1. Stale tasks from the current project may not be cleaned up if the working directory is not the project root.
2. The HUD may incorrectly clean up tasks belonging to an unrelated area when the working directory happens to contain a different state file.

**Expected behavior**
The background task cleanup during HUD initialization must operate on the state file that corresponds to the resolved project root. The cleanup should always target `<projectRoot>/.omc/` (or whatever location is derived from that root) and never the raw `process.cwd()`.

**Additional context**
- The cleanup is currently triggered by the `initializeHUDState` call in `main`.
- `readHudState`/`writeHudState` accept an optional directory override; it is not being used here.
- The call to `initializeHUDState` happens before `cwd` is resolved from stdin, so the directory is not available yet.

**Acceptance criteria**
- The resolved project root directory is passed through so that stale/orphaned task cleanup always uses the state file belonging to the current project.
- The existing behavior (cleanup happens on startup, no regression in default operation) must be preserved.

fix(hud): propagate directory parameter to initializeHUDState


**Title:** Refresh-writeback for Keychain-sourced OAuth tokens

**Problem**
When `getUsage()` refreshes an expired token, `writeBackCredentials` only persists the new token pair if `~/.claude/.credentials.json` exists. On macOS, credentials are often stored exclusively in the Keychain (service `com.anthropic.claude`). In those cases the file is missing, the function bails out early, and the refreshed tokens are silently discarded. This leaves the Keychain with stale credentials, causing repeated (and eventually failing) refreshes.

**Desired behaviour**
After a successful token refresh, the new `accessToken`, `refreshToken`, and `expiresAt` must be written back to the **same Keychain entry** from which the credentials were originally read, so that subsequent usage polls see valid tokens. The existing file‑based write‑back path must remain unchanged and must not be triggered for Keychain‑sourced credentials.

**Constraints / Details**
- The credential object already carries a `source` discriminator (`'keychain'` vs `'file'`). Branch on that.
- When `source === 'keychain'`, retrieve the existing Keychain entry, merge only the token‑related fields into it, and update the entry with `security add-generic-password -U`. Preserve all other data that was already in the entry.
- The original Keychain read may be account‑scoped (`-a <account>`) or service‑only. The write‑back must target the same slot, so the account name (if any) must be recorded during the read and re‑used when writing.
- Keychain write‑back should be macOS‑only (`process.platform === 'darwin'`) and best‑effort: log a debug message on failure but never throw or break the caller.
- Do **not** create or touch `~/.claude/.credentials.json` when the source is Keychain.

**Acceptance criteria (behavioural)**
1. Keychain‑sourced credentials: after a refresh the new tokens are persisted to the same Keychain entry; no `.credentials.json` file is created or modified.
2. File‑sourced credentials: the existing write‑back logic works exactly as before, and no Keychain write occurs.
3. The written Keychain entry contains the merged token values alongside any pre‑existing non‑token fields.
4. Works for both account‑scoped (e.g. `-a email`) and service‑only Keychain entries.
5. No regression in usage polling, credential reading, or the subscription‑info pathway.

fix: write refreshed OAuth tokens back to Keychain (#3238) (#3239)


**Bug: Windows auto-update reconciliation fails when the OMC launcher ends with `.cmd`**

**Description**
On Windows, `oh-my-claudecode` installs a global launcher script (typically `omc.cmd`) through npm. After an automatic update, the `performUpdate` flow attempts to re-execute the freshly installed binary to run a reconciliation step. Because Node.js `child_process.execFileSync` cannot execute `.cmd` / `.bat` files directly without shell mediation, this call throws a spawn error (e.g., `ENOENT` for the `.cmd` path) and the update reports failure even though the binary was successfully upgraded.

**Steps to reproduce**
1. Install `oh-my-claudecode` globally on a Windows machine.
2. Trigger an automatic update (or simulate one that lands on a version where the resolved binary path ends with `.cmd`).
3. Observe that the update completes the package installation but the subsequent reconciliation step fails with a spawn error referencing the `.cmd` path, and the returned result indicates runtime reconciliation failure.

**Expected behavior**
The reconciliation re-exec should succeed on Windows regardless of whether the resolved OMC binary is a `.cmd` wrapper. The update should be reported as successful after reconciliation finishes.

**Actual behavior**
Reconciliation fails with an `ENOENT` spawn error for the `.cmd` path, and the update result contains an error message like “Updated to <version>, but runtime reconciliation failed”.

**Acceptance Criteria**
- On Windows, when the resolved OMC binary is a `.cmd` file, the reconciliation re-exec call must use shell execution so that `cmd.exe` can interpret the script.
- This mediation must be applied **only** to the reconciliation re-exec path; other `execFileSync` calls throughout the codebase (e.g., invoking `git`, `where.exe`, or direct executables) must not be altered.
- Non-Windows platforms must be unaffected.
- The update flow should complete successfully, with reconciliation executed, on Windows after the fix.

**Referenced Issue**
#1804

Prevent Windows update reconciliation from failing on omc.cmd (#1805)


**Title:** Codex advisor artifacts contain Rust debug noise when `RUST_LOG`/`RUST_BACKTRACE` are set

**Reporter:** user

**Summary**
When using `omc ask --provider codex`, the generated artifact can be polluted with lines from Rust’s debug output (e.g., `RUST_LEAK`, trace-level log messages) and the process stderr is similarly noisy. This occurs if the host environment has `RUST_LOG`, `RUST_BACKTRACE`, or `RUST_LIB_BACKTRACE` set.

**Steps to reproduce** (realistic scenario):
1. Have the OpenAI Codex CLI installed and accessible.
2. Set `RUST_LOG=trace` and `RUST_BACKTRACE=1` in the environment.
3. Run something like: `omc ask --provider codex "some prompt"`.
4. Inspect the resulting artifact file.
Observed: the artifact includes Rust diagnostic lines; stderr shows backtrace/trace information.

**Expected behavior**
When OMC invokes the Codex binary (for the `--version` probe and for the actual advisory execution), it should not expose ambient Rust-specific debug environment variables to the Codex process. Other environment variables should be inherited normally. This suppression should happen **only** for the `codex` provider – the `claude` and `gemini` providers must continue to receive the full environment. The artifact and stderr from the codex advisor must remain clean of Rust debug noise regardless of whether `RUST_LOG`, `RUST_BACKTRACE`, or `RUST_LIB_BACKTRACE` are set in the caller’s environment.

**Impact**
- Polluted artifacts cannot be used downstream.
- Debug log leakage may reveal unintended information.
- Every invocation with these env vars set requires manual cleanup workarounds.

fix(ask): strip Rust debug env from codex advisor (#1419)


**Title:** `captureFileSnapshot` crashes with `require is not defined` when permission enforcement is active

### Context
The project uses `"type": "module"` in `package.json`, so all source files are treated as ESM. In that environment, CommonJS `require` is not available and will throw `ReferenceError: require is not defined`.

### Observed behavior
When the orchestrator starts with `permissionEnforcement` set to any value other than `'off'` (the path that triggers `captureFileSnapshot`), the process immediately aborts with:

```
ReferenceError: require is not defined
    at captureFileSnapshot (.../mcp-team-bridge.ts:...)
```

This prevents any team-mode execution when permission enforcement is enabled.

### Expected behavior
- `captureFileSnapshot` should work correctly in an ESM module without relying on `require`.
- The existing top-level imports in the same file already use ESM syntax for other `child_process` members (e.g., `spawn`). The snapshot function should follow the same pattern, importing `execSync` from `'child_process'` at the top of the module.
- As a secondary improvement, `captureFileSnapshot` should be properly exported so that it can be imported and unit-tested directly, rather than being an unexported internal helper.

### Acceptance criteria
1. With `permissionEnforcement !== 'off'`, invoking the orchestrator no longer throws a `ReferenceError: require is not defined`.
2. `captureFileSnapshot` can be imported from its module by external test code (e.g., via `import { captureFileSnapshot } from '...mcp-team-bridge.js'`).
3. The existing snapshot behaviour is preserved: returns a `Set<string>` of relative file paths for a git-working-directory, returns an empty set for a non-git directory, and does not throw for any call.

### Notes
- Do not introduce a dynamic `import()` inside the function unless unavoidable; a static import is preferred because the module is already ESM and `execSync` is always needed.
- The rest of the module’s imports (`spawn`, `ChildProcess`, etc.) already follow the ESM pattern; this change should be consistent with them.

fix(team): replace require() with ESM import in captureFileSnapshot


## Bug Report: Transcript scanner doesn't decode Windows project paths

### Summary

`oh-my-claudecode`'s analytics transcript scanner fails to resolve project paths on Windows systems. This causes sessions to be misattributed or missing from analytics dashboards when running Claude Code on Windows.

### Background

Claude Code encodes project directory names into a munged form when naming transcript directories. The encoding scheme differs between platforms:

- **Unix**: `/home/user/my-project` → `-home-user-my-project`
- **Windows**: `C:\Users\user\my-project` → `C--Users-user-my-project`

The decoder currently only handles the Unix pattern (paths starting with `-`). It has no logic for Windows-encoded paths, where the drive letter is followed by `--` and then the path with all separators replaced by individual `-`.

### Observed behavior

On Windows:

- Encoded directory names like `C--Users-bellman-my-project` are either rejected (returned with no leading slash or drive colon) or decoded to nonsense paths.
- Paths containing consecutive hyphens from legitimate directory names (e.g., `my--project`) are split incorrectly, producing wrong `existsSync` checks and false mismatches even on Unix.

### Expected behavior

The decoder should:

1. **Detect Windows-encoded paths** — any encoded name matching `^[A-Za-z]-` (drive letter followed by a dash) should be recognized as Windows and decoded to `X:/...`.
2. **Decode Windows paths correctly** — `C--Users-user-project` should produce `C:/Users/user/project`, with filesystem-aware disambiguation for hyphenated directory names, just as the existing Unix decoder does.
3. **Handle consecutive hyphens** — `my--project` (where the original directory literally contains a hyphen) should survive splitting and reconstruction without collapsing into `my-project` or misinterpreting the double dash.
4. **Maintain backward compatibility** — existing Unix path decoding must continue to work unchanged for paths starting with `-`.

### Impact

Windows users get broken project attribution in analytics, effectively losing that data. This is a cross-platform correctness gap that blocks analytics adoption on Windows.

### Relevant constraints

- The decoder is already filesystem-aware: it tries multiple interpretations and checks `existsSync`. The Windows variant should use the same strategy (simple decode first, then combinatorial reconstruction if needed).
- The double-dash between drive letter and path content is an encoded artifact and must be distinguished from actual consecutive hyphens in directory names deeper in the path.
- Drive letters are single ASCII letters (case-insensitive). Don't assume only `C:`.
- No configuration or opt-in should be required; the function should automatically detect the encoding format.

fix(analytics): support Windows-encoded paths in transcript scanner (#117)


**Title:** tmux panes created by OMC don’t source shell rc files (missing PATH / env)

### Symptoms
When OMC launches a Claude session in a new tmux window, or opens a Codex interop pane, or creates the HUD watch pane, the commands inside that pane often fail because the user’s shell startup files (`.zshrc`, `.bashrc`, `.profile`, etc.) are **not loaded**.

Concrete observed failures:
- `claude` (or `codex`, `gemini`) **not found** — the user’s custom `PATH` entries are missing.
- Environment variables configured in rc files are absent (e.g. `NVM_DIR`, `JAVA_HOME`, API keys, or proxy settings).
- The HUD pane shows errors because the watcher script can’t find its dependencies.

### Context
- OMC launches several kinds of tmux sessions dynamically:
  - **Main Claude session** when launched from outside an existing tmux environment.
  - **Codex interop pane** (split‑window) for cross‑model work.
  - **HUD watch pane** that monitors Claude’s activity.
- The problem affects the **`outside‑tmux`** launch path, the **interop** pane, and the **HUD** pane. (Team worker panes already source rc files explicitly, so they are unaffected.)

### Root cause
Tmux, when given an explicit command as the final argument to `new-session` or `split-window`, executes it with a **non‑login** shell (`$SHELL -c 'command'`). Non‑login shells do not source rc files by default, so the session starts without the user’s normal environment.

### Expected behavior
Every tmux pane or session created by OMC that runs a user‑level command should run that command inside a **login shell** so that the user’s rc files are sourced. After the fix:

1. **`omc launch`** (outside tmux) → the new session’s command runs with a login shell, picking up PATH and env from rc files.
2. **Codex interop split** → the `codex` command in the new pane runs inside a login shell.
3. **HUD watch pane** → the HUD command runs inside a login shell.

### Implementation constraints & edge cases
- Respect `$SHELL`; fall back to `/bin/bash` if `$SHELL` is unset or empty.
- The wrapper should use `exec` to replace the outer shell process (avoiding an extra process layer).
- Properly quote the inner command so that commands containing single quotes, special characters, or multiple statements (`sleep 0.3; perl ...; claude --dangerously-skip-permissions`) are passed through intact.
- The existing `sleep`+`tcflush` workaround (for terminal DA1 query in the main Claude launch) must still be included in the final command, just wrapped in a login shell invocation.
- The fix must be applied consistently across all three affected call sites (main launch, interop pane, HUD pane).

fix(shell): load default shell rc in OMC tmux shell sessions (#1153) (#1154)


**Bug: HUD hides rate limit indicators when OAuth credentials lack subscription metadata**

### Observed behavior
When the OAuth credentials (e.g., from `~/.claude/credentials.json`) contain `subscriptionType: null` and `rateLimitTier: null`, the HUD status line does **not** display the usual `5h:` / `wk:` / `sn:` usage percentages, even though the Anthropic usage API returns valid rate-limit data (e.g., `five_hour`, `seven_day`, `seven_day_sonnet` utilization fields).

### Expected behavior
The HUD should always surface whatever rate-limit information the API provides, regardless of whether subscription metadata (`subscriptionType`, `rateLimitTier`) is present. Specifically:

- When the API response includes `five_hour.utilization`, the HUD should show the 5-hour percentage.
- When it includes a generic 7-day window (`seven_day.utilization`) or a model-specific 7-day window (e.g., `seven_day_sonnet`, `seven_day_opus`), the HUD should show those percentages and their reset times.
- Missing subscription metadata alone should **not** cause the HUD to discard valid rate-limit data.

### Impact
Users who authenticate via OAuth but do not have a consumer-facing subscription tier stored in the credentials (or whose credential file has nulls for those fields) lose visibility into their usage limits, making it hard to avoid hitting rate caps.

### Possible areas of concern
- The rate-limit parsing logic (`parseUsageResponse`) currently requires at least one of `five_hour`, `seven_day`, or usable enterprise credits to produce a result. It should also recognize that model-specific weekly windows (`seven_day_sonnet`, `seven_day_opus`) are valid usage data even when the generic `seven_day` is absent or null.
- The main HUD loop fetches subscription info from credentials and may pass null-ish values downstream in a way that later code treats as “no rate limits available.”
- A general guard should ensure that any code path that reads subscription metadata does not inadvertently suppress rate-limit rendering when that metadata is null.

### Steps to reproduce
1. Set up OAuth credentials where the `claudeAiOauth` block has `subscriptionType: null` and `rateLimitTier: null`.
2. Start `oh-my-claudecode` HUD mode.
3. Observe that the status line never shows the 5h/wk/sn percentages, even after the usage API returns a successful response containing utilization for those windows.

### Acceptance criteria
- The HUD displays `5h:`, `wk:`, and per-model weekly percentages (e.g., `sn:`) when the API provides the corresponding utilization values, **regardless of whether subscription metadata exists**.
- The fix must not break existing behavior for users who do have valid `subscriptionType` / `rateLimitTier`, nor for enterprise credit paths.

fix(hud): preserve rate limits without subscription metadata


**Short description**: Harden team worker binary path and RC-loading behavior

When a team starts (`startTeam`), CLI binary paths are validated but the resolved absolute path is not persisted. Later worker-spawn calls re-resolve the binary, which could pick up a different executable if `PATH` changes between validation and spawn. Pin the validated absolute binary path at team-start time and pass it through to the spawn logic so every worker pane uses exactly the same binary that was originally validated.

Additionally, the tmux pane startup command (`buildWorkerStartCommand`) always sources the user's shell RC files (e.g., `~/.zshrc`, `~/.bashrc`). This lets user shell customizations influence worker behaviour. Add an opt-out: when the environment variable `OMC_TEAM_NO_RC` is set to `"1"`, the generated shell command must skip RC sourcing entirely on both Unix and Windows Git-for-Bash shells.

The invariant to preserve is: after a team is created, every subsequent worker pane for that team launches the same validated CLI binary, and RC sourcing is controlled by a single, explicit environment flag.

fix(team-security): tighten CLI path trust and RC-loading behavior (#1230)


**Title:** Team scaling always launches `claude` workers regardless of requested agent type (codex / gemini)

---

### Description

When a team is configured with a non-Claude agent type (e.g., `codex` or `gemini`) and workers are scaled up, every new worker pane launches the `claude` binary instead of the correct provider binary. This happens even though the team config records the correct `agent_type` and the initial team session workers are launched with the right provider and flags.

### Steps to reproduce

1. Configure a team with `agent_type: 'gemini'` or `agent_type: 'codex'`.
2. Enable scaling (`OMC_TEAM_SCALING_ENABLED=1`).
3. Scale up the team – observe the new worker panes.
4. The workers start `claude` (ignoring the configured agent type) and miss the provider-specific permission/approval flags that are passed when workers are launched during initial team session creation.

### Expected behavior

Scale-up workers should honour the team’s configured agent type and launch the correct provider binary with the same flags (e.g. `--approval-mode yolo` for Gemini, `--dangerously-bypass-approvals-and-sandbox` for Codex) that are used when the initial team workers are created. In other words, the scaling path should reuse the same launch-contract logic that other worker-launch paths already follow, so the binary and flags are aligned with the requested agent type.

### Additional safety concern

If an unsupported agent type is somehow passed to the scaling operation, it would currently silently launch `claude` workers. It should instead fail clearly and early rather than masking the misconfiguration.

### Impact

Team scaling with external AI backends (Gemini, Codex) is unusable – all scaled workers boot as Claude, which defeats the purpose of multi-provider orchestration. Other worker-launch flows (e.g., initial team session spawn) respect the agent type, so this is an inconsistency specific to the scale-up code path.

Let scale-up workers follow the existing provider launch contract


**Title:** Custom notes from ProjectMemory are not surfaced in session context and are lost after rescan

**Area:** `src/hooks/project-memory/`

**Problem:**
The project memory system supports `customNotes` (manual and learned) and `userDirectives`, but three gaps prevent them from being useful at runtime:

1. **Context summary ignores custom notes** – When `formatContextSummary` builds a short-form memory string for injection into the session, it does not include `customNotes`. This means any manually added or learned notes never reach the agent through the summary channel.
2. **PreCompaction does not recognize custom notes as critical** – In the `PreCompact` handler (`processPreCompact`), the `hasCriticalInfo` condition decides whether to inject project memory as a system message before compaction. Currently it checks `userDirectives`, `hotPaths`, and `techStack.languages`, but not `customNotes`. Consequently, a session that only contains custom notes will not have them injected during compaction, risking their loss.
3. **Rescan wipes user-contributed data** – When a project memory rescan is triggered (automatically after 24h or explicitly via `rescanProjectEnvironment`), a fresh `detectProjectEnvironment()` result replaces the stored memory entirely. Existing `customNotes` and `userDirectives` are discarded because nothing merges the old user-contributed fields into the new detection output. The same happens inside `registerProjectMemoryContext` when it decides to rescan.

**Desired behaviour:**

- `formatContextSummary` should render a **Notes** section containing up to 5 custom notes (formatted as `[category] content`). The section should be omitted entirely when `customNotes` is empty.
- `processPreCompact` should treat `customNotes` as critical information: if the memory contains any custom notes, the handler must inject the memory summary as a system message, just as it does when `userDirectives` or `hotPaths` are present.
- `registerProjectMemoryContext` and `rescanProjectEnvironment` must preserve `customNotes` and `userDirectives` from the existing memory when performing a rescan, so that user-contributed data survives detection refreshes.

**Constraints / edge cases:**

- The notes rendering must not exceed a sensible limit (max 5 notes) to keep the summary compact.
- The merge logic should copy both `customNotes` and `userDirectives` from the old memory onto the newly detected memory object, leaving all other fields as detected.
- The fix should not alter the structure of `ProjectMemory` or the detection logic.

fix(project-memory): inject customNotes into session context (#1689)


**Title:** Standalone npm installs are missing slash skills after plugin-scoped refactoring

**Description:**
After the recent work that moved slash-command registration behind plugin-scoped skill discovery, a standard standalone `omc install` (global npm install) only materialises the `omc-reference` skill under `~/.claude/skills`. All other bundled slash skills (autopilot, ralph, team, ultrawork, etc.) are absent, which makes the standalone installation unusable for anybody relying on slash commands.

**What needs to happen:**
Restore full slash-skill installation for standalone installs, while keeping the plugin‑based installation path unchanged.

**Constraints and safety rules:**

1. **Claude-native command collisions**
   Some bundled skill names conflict with Claude’s own built‑in slash commands (for example `plan`). For standalone installs we must not shadow those native commands. When a skill’s intended name matches a known Claude‑native command, install it under a safe alias by prefixing it with `omc-` (e.g. `plan` → `omc-plan`). The skill’s frontmatter `name` field should be used as the base name when present, otherwise fall back to the directory name.

2. **Skininthegamebros-only skills**
   The bundled skills `remember`, `verify`, `debug`, and `skillify` are only intended for users who are part of the “skininthegamebros” group. For all other users those skills must not be synced, regardless of whether the install happens standalone or via plugin. This filter already exists in at least one code path (see #2070); the standalone sync must respect it as well.

3. **Sync source and destination**
   The source of truth for bundled skills is the `skills/` directory shipped with the package. Each subdirectory that contains a `SKILL.md` corresponds to one slash skill. The installer must copy those directories into `~/.claude/skills/<safe-name>/`.

4. **Result reporting**
   The returned `InstallResult.installedSkills` array should include every skill that was synced, formatted as `“<safe-name>/SKILL.md”`. The existing reporting contract must not change.

5. **Plugin vs. standalone**
   When the installer detects that it is running under a plugin context and the plugin already provides its own skill files, the current plugin‑scoped behaviour must remain untouched. The safe‑name logic should only activate when no enabled plugin‑provided skills are available, **or** when `--no-plugin` is explicitly requested. In other words, the standalone path is: `(no enabled plugin) OR (options.noPlugin === true)`.

6. **Deduplication**
   If, after applying the safe‑name rule, two bundled skills would end up with the same target directory name (case‑insensitive), only the first one encountered should be installed. This avoids overwrites in edge cases.

**Acceptance criteria (user‑visible):**
- After running `npm install -g oh-my-claude-sisyphus` and executing the installer without a plugin environment, listing `~/.claude/skills` shows directories for autopilot, ralph, ralplan, team, ultrawork, omc‑reference, and omc‑plan (but **not** a plain `plan` directory that would shadow the native command).
- The `remember`, `verify`, `debug`, and `skillify` skills are absent for a non‑skininthegamebros user.
- The installer still works as before when the oh‑my‑claudecode plugin is installed and enabled inside Claude Code.

Fix standalone slash-skill sync for npm installs (#2211)


**Bug report: Concurrent Claude Code instances leak state through shared `.omc/state` files**

## Steps to reproduce
1. Open two Claude Code sessions in the same repository.
2. In the first session, start a `ralph` task (or any mode that writes to `.omc/state/{mode}-state.json`).
3. In the second session, start a different `ralph` task.
4. Observe the HUD status in both sessions.

## What happens
Both sessions read and write the **same** state file at `.omc/state/{mode}-state.json`. The second session sees the first session's iteration counts, task descriptions, and completion status. They overwrite each other's work, causing:

- Tasks from one session incorrectly appear as active/completed in the other.
- Iteration counters jump unexpectedly.
- The HUD shows stale or misleading metadata (wrong `iteration`, wrong `task_description`).
- Long-running `ralph` tasks in one session can be prematurely considered "done" because another session cleared its state.

This is effectively **context bleeding** between completely independent instances.

## What I expected
Each Claude Code process should have its own **isolated** state namespace. Starting a new session (a new CLI process) should never interfere with another session's state, even if they share the same working directory. The HUD should still be able to display active state regardless of which session produced it.

## Context / clues
- The codebase already has **session-scoped state directories** under `.omc/state/sessions/{session_id}/` with helpers like `resolveSessionStatePath` and `ensureSessionStateDir`.
- The state tool handlers accept an optional `session_id` parameter, but when it is omitted, they fall back to the **shared legacy path** `.omc/state/{mode}-state.json`.
- There is no automatic `session_id` assignment — every process ends up with `session_id=undefined` and hits the shared path.
- The HUD state reader (`resolveStatePath` in the HUD layer) only checks the standard and legacy paths; it does **not** look inside the `sessions/` directory at all.

## Desired behavior
1. **Automatic per-process isolation**: When no explicit `session_id` is provided, the state tools should automatically generate a stable, unique session identifier for the lifetime of the process, and store state files under `.omc/state/sessions/{auto-id}/`.
2. **No breaking change for explicit `session_id` usage**: If a caller passes `session_id` explicitly, that should continue to work as before.
3. **HUD visibility**: The HUD state reader should scan the sessions directory so that active state from any session remains visible in the status bar.
4. **Backward compatible**: State files written to the old legacy paths by earlier versions should still be readable as a fallback (newest file wins if multiple locations have the same mode state).
5. **Cross-process isolation guarantee**: Two concurrent processes must never read each other's state by default.

fix: add PID/session ID namespace to state files to prevent context bleeding (#456) (#490)


The `runPreCommitChecks` hook currently only validates the commit message and runs a type check. It should also verify that the project’s tests and lint pass before allowing a commit.

Specifically:

- `runPreCommitChecks` must call the existing `runTests` helper and include its result in the checks list, so that a failed test suite causes `canCommit` to be `false`.
- Introduce a new `runLint` helper that behaves like `runTests`: look for a `lint` script in the project’s `package.json`, execute it via `npm run lint`, and return a success/failure status and message. If no `package.json` exists or no `lint` script is defined, the check should be treated as passing (no lint script to run). If the script exits non-zero, the check should fail with an appropriate message.
- Wire `runLint` into `runPreCommitChecks` the same way — add its result to the checks array so a lint failure blocks the commit.
- Export `runLint` from the public hooks barrel (`src/hooks/index.ts`), consistent with how `runTests` is already exported.

fix(plugin-patterns): runPreCommitChecks now runs tests and lint validation


**Request: `ensureStdinSymlink` should handle dangling symlinks and never block setup init**

## Problem
When `ensureStdinSymlink` cannot create a symlink (e.g., platform limitation) it falls back to copying the file. This fallback currently uses an existence check that treats a dangling symlink (pointing to a missing target) as “does not exist” and tries to copy. Because `copyFileSync` follows the destination symlink, the copy fails silently and the broken symlink remains—leaving the hook without the required file.

Separately, any unhandled exception thrown by `ensureStdinSymlink` can stop the entire `processSetupInit` flow. Since this healing is purely for upgrade robustness, it should never prevent initialization from completing.

## Acceptance criteria
1. **Dangling‑symlink fallback** — When symlink creation fails and a dangling symlink already exists at the destination, the fallback must remove the broken symlink and copy the source file in its place.
2. **Preserve working files** — When symlink creation fails and a valid regular file already exists at the destination, the fallback must leave it untouched (no overwrite).
3. **Non‑blocking init** — Any exception raised during the `ensureStdinSymlink` call inside setup initialization must be caught and must not cause the init to fail. The behavior should be best‑effort: log/ignore the failure and continue.
4. **No regression for existing flows** — The atomic replace strategy (create temp symlink, rename into place) must continue to work as before; only the fallback path should be adjusted.

## Context
The relevant code lives in `src/hooks/setup/index.ts` (the `ensureStdinSymlink` function and its call inside `processSetupInit`). The destination path is `<homeDir>/.claude/hooks/lib/stdin.mjs`.

fix(setup-hook): address Codex review feedback


The `purgeStalePluginCacheVersions()` cache-cleanup step (used by the doctor/cleanup commands) sometimes deletes the currently active plugin version. Fix the following issues:

- **Trailing slash:** `installPath` values in `installed_plugins.json` may end with `/`. Normalise paths so an active entry `/.../1.0.0/` does not cause version directory `1.0.0` to be treated as stale.
- **Subdirectory install:** An active `installPath` may point *inside* a version directory (e.g., `.../2.0.0/dist`). The entire version directory must be preserved, not just the exact path.
- **Race condition:** During a plugin update, a freshly written version directory may be picked up by a concurrent purge and deleted before the plugin is fully installed. Skip removal for any version directory whose modification time is less than one hour old.
- **Bad plugin structure:** If the `plugins` field in `installed_plugins.json` is not a plain object (e.g., an array), the purge throws a runtime error. Instead, bail out gracefully and report an error.

fix: harden cache purge path comparison and add race condition guard


**Bug: Team mode fails to launch tmux panes when `SHELL` is unsupported or empty**

**Observed behavior**
When the `SHELL` environment variable is set to a path like `/bin/tcsh` or is blank/whitespace-only, tmux pane creation for team workers fails. The error suggests the command passed to tmux contains shell syntax that the specified shell does not understand, leading to panes that never become ready or that crash immediately.

**Expected behavior**
The system should only pass shell paths to tmux if the shell is known to work with OMC’s POSIX startup commands (currently: `sh`, `bash`, `zsh`, `fish`). If `SHELL` is missing, empty, whitespace-only, or refers to an unsupported shell, the system must fall back to a safe, supported shell (`/bin/sh`) instead of weaving the original `SHELL` value into the pane start command.

On Windows:
- When no MSYSTEM-like environment is active, the code should continue to use `COMSPEC` (or `cmd.exe`) as before.
- When an MSYS environment *is* active (e.g., `MSYSTEM=MINGW64`), the `SHELL` variable should still be validated. If it contains an unsupported shell (like `tcsh`), the fallback must be `/bin/sh` — not `COMSPEC`/`cmd.exe`.

In all fallback cases, the resulting tmux start command should reliably source the user’s shell initialization file (`.shrc`) and must never include the unsupported shell path itself.

**Impact**
This is a common configuration on systems where users have `tcsh` or other less common shells set as their login shell, or where `SHELL` is accidentally empty. It breaks the team orchestration and degrades the “zero configuration” promise. The fix should make shell detection resilient to these cases while preserving existing affinity for `bash`/`zsh`/`fish` and native Windows cmd.

fix(team): preserve supported shell affinity


**Title:** Teleport command interpolates `--base` branch name into shell command, allowing command injection

**Description:**
When you run `teleport` with a `--base` value that contains shell metacharacters (e.g. `main; echo injected`, `dev|cat /etc/passwd`, `` `something` ``), the command spawns a shell that interprets those characters, leading to arbitrary command execution. This is a security risk because the user-supplied branch name is used directly inside a command string that is executed via a shell.

**Expected behavior:**
The teleport command should execute all git operations without spawning a shell, so that branch names are always treated as literal arguments. This must apply to every git subcommand that is run during worktree creation (fetch, branch, worktree add). Branch names containing any characters—spaces, semicolons, pipes, backticks, dollar signs—should be handled safely and never cause extra commands to be executed.

**Steps to reproduce (conceptual):**
1. Run `teleport` with `--base` set to a branch name like `main; touch /tmp/pwned`.
2. Observe that the shell command is constructed by string concatenation, allowing the injected command to run.
3. In a fixed version, the injected command should not execute; git should receive the branch name as a literal argument.

fix(teleport): replace execSync with execFileSync in createWorktree to prevent shell injection


**Title:** `getSessionStartTime` can misorder or crash on non-standard timestamp formats

**Area:** Session duration tracking (`src/hooks/session-end`)

**Problem**
`getSessionStartTime` compares `started_at` values from state JSON files as raw strings. While this works for strictly uniform ISO‑8601 timestamps, it can produce incorrect ordering if different state files contain timestamps with different degrees of precision or time‑zone offsets. More critically, a state file with a malformed or non‑parseable `started_at` value will either compare incorrectly or cause downstream failures.

**Desired behavior**
- Compare `started_at` timestamps as epoch‑milliseconds (derived via `Date.parse`) rather than as raw strings before picking the earliest.
- Skip any state file whose `started_at` cannot be parsed to a finite number (e.g., `"not-a-date"`, empty string, `null`). Such entries should be ignored entirely.
- The caller‑visible return value must still be the original `started_at` string from the selected state file, not the epoch value.
- This logic applies identically to both the direct session‑match path (when `sessionId` is provided and `state.session_id` matches) and the legacy fallback path (when `state.session_id` is absent).
- If all candidate state files contain unparseable timestamps, the function should return `undefined` (consistent with how it behaves when no valid states exist).
- No change to how `active`, `session_id`, or other fields are used for selection; only the comparison and validity of `started_at` should be adjusted.

**Edge cases / safety**
- Existing behavior where a single valid state exists (even if other files are present) must still return the correct earliest timestamp.
- The change must not alter the handling of stale states or other early‑exit conditions.
- Ensure that comparison uses a finite‑number guard (`Number.isFinite`), so that `NaN` values from `Date.parse` on invalid inputs are caught regardless of the input shape.

fix: parse timestamps to epoch for safe comparison in session duration


**Bug: Empty inline prompts return misleading generic error instead of a clear "empty prompt" message**

When using the inline prompt mode of the `ask-codex` or `ask-gemini` MCP tools (passing a `prompt` string instead of a `prompt_file`), providing an empty string or a whitespace-only string currently produces the generic fallback error telling me to supply either `prompt` or `prompt_file`.  This is confusing because I *did* supply the `prompt` parameter — it’s just empty.  I would expect a clear, specific error indicating that the inline prompt is empty, so I can fix my input without hunting for what went wrong.

Additionally, the tool schemas for `background` don’t warn callers that background execution is incompatible with inline prompts.  The implementation already rejects that combination at runtime, but the parameter description doesn’t surface the restriction, making it easy to miss when reading the schema or relying on LLM introspection.

**Acceptance criteria**

1. **Explicit “inline prompt is empty” error**
   For both `handleAskCodex` and `handleAskGemini` (when reached via the respective MCP servers/standalone servers):
   - Passing `prompt: ""` returns an error whose text indicates the inline prompt is empty.
   - Passing `prompt: "   "` (whitespace only) does the same.
   - The error must fire *before* the generic “prompt or prompt_file is required” fallback or any background-mode check that would conflict with an empty prompt.
   - The error message should be distinct from the “foreground only” message used when a non-empty inline prompt is combined with `background: true`.

2. **Schema description updates**
   - The `background` parameter description in the `askCodex` tool definition (both in the regular and standalone MCP servers) must include language to the effect that it is not available when using an inline prompt.
   - The same update applies to the `askGemini` tool definition (regular and standalone).

3. **No regressions for valid inline prompts**
   - Non-empty inline prompts continue to work, still produce an error when combined with `background: true` (“foreground only”), and never trigger the “Inline prompt is empty” or the generic prompt/prompt_file error.
   - Prompts that only provide `prompt_file` (no inline prompt) are unaffected.

fix: resolve unreachable empty-inline validation and schema gaps


**Title:** Auto-update reconciliation fails on Windows – binary resolution picks wrong executable

**Background**
After `npm install -g oh-my-claude-sisyphus@latest` completes, the updater re‑executes the `omc` CLI with an `update-reconcile` step to finalize the update and persist metadata. On Windows the binary lookup currently runs `which omc 2>/dev/null || where omc 2>NUL` and takes the first line. That line is often **not** `omc.cmd` (it could be a Powershell script or another non‑executable entry), so the re‑exec step fails silently. When it fails, metadata can still be written, leaving the user in a stuck state.

**Desired behavior**

1. **Windows‑specific binary resolution**
   - Instead of a generic `where omc`, look explicitly for `omc.cmd` (e.g., `where.exe omc.cmd`).
   - Parse `where` output correctly, handling `\r\n` line endings, and use the first non‑empty line as the resolved path.

2. **Re‑exec with reconcile on Windows**
   - Spawn the resolved binary with the argument `update-reconcile` and pass `OMC_UPDATE_RECONCILE=1`.
   - Add `windowsHide: true` to the spawn options so no console window flashes.
   - On non‑Windows the existing `which/where` approach should still work, but path extraction should follow the same “first non‑empty line” rule for consistency.

3. **Failure handling**
   - If the re‑exec step throws (e.g., `ENOENT` because the binary is missing), do **not** write the new version metadata.
   - Return a failure result with the reconciliation error, identical to how failures are surfaced on other platforms.

4. **No regressions for other platforms**
   - UNIX/macOS behavior must remain unchanged except for the more robust path extraction (first non‑empty line from the combined `which/where` output).

fix(update): resolve Windows reconcile binary via omc.cmd (#1560)


## Feature request: Deduplicate OpenClaw lifecycle events from multiple tmux panes

### Problem
When multiple tmux panes are attached to the same tmux session and workspace, each pane can independently trigger the same OpenClaw lifecycle event for a single user action. For example:

- `session-start` fires once per pane when a session begins.
- `keyword-detector` (prompt-submit) fires once per pane each time the user submits a prompt.
- `stop` / `session-end` fire once per pane when a session stops.

All duplicates are currently forwarded to downstream native gateways, causing noise, double-processing, and confusing operator feedback.

### Desired behavior
The OpenClaw event dispatch should collapse these duplicate bursts **before** any signal reaches a downstream gateway. The first occurrence of an event in a burst must be preserved; subsequent matching occurrences within a short time window should be silently dropped (not forwarded) and return a success result that indicates the event was skipped (e.g., an additional `skipped: "deduped"` field on the result).

#### Deduplication rules
- **Dedup key**: `(projectPath, tmuxSession)` — never use `sessionId` alone, because different panes have different Claude session IDs even though they share the same user action.
- For `keyword-detector`: also include a normalized form of the prompt (collapse whitespace, limit length) so that different prompts are not accidentally collapsed together.
- **Time windows** (approximate, and fine to be configurable constants):
  - `session-start`: ~10 seconds
  - `keyword-detector`: ~4 seconds
  - `stop` / `session-end`: ~12 seconds
- The deduplication state is per-project working-tree (scoped to the project directory) and may be stored ephemerally (a short TTL of a few hours is acceptable).
- If no tmux session can be determined (e.g., running outside tmux), apply **no** deduplication — forward everything as before.

### Constraints
- Do **not** change how signals are built, how tmux tail is captured, or how instructions are interpolated. Deduplication should be a separate, early check that happens before the signal is dispatched to any HTTP or command gateway.
- Do **not** add any new dependencies; use only Node.js built-ins.
- The first event of every burst must always be dispatched normally; only duplicates are suppressed.
- The deduplication must handle concurrent panes safely (a simple file-based lock is acceptable).

### Acceptance criteria
1. In a multi-pane tmux session with the same project, firing `session-start` from two panes within 10 seconds results in exactly one gateway dispatch; the second call returns `success: true, skipped: "deduped"`.
2. Firing `keyword-detector` with the same normalized prompt from two panes within 4 seconds results in exactly one dispatch; a different prompt submitted in the same window is dispatched independently.
3. Firing `stop` or `session-end` from two panes within 12 seconds results in exactly one dispatch.
4. The first event of any burst is always dispatched.
5. When `tmuxSession` is undefined/not-detected, all events are dispatched without any deduplication.
6. Existing tests for OpenClaw and bridge-openclaw continue to pass.

Collapse duplicate native lifecycle bursts for attached tmux sessions


When the unified MCP registry sync appends the managed MCP block to `~/.codex/config.toml`, it re-declares any `[mcp_servers.*]` tables that already exist outside the managed block. Codex then fails with duplicate-key TOML errors.

Change the Codex sync path so that registry entries whose server name already appears in the user-owned part of the config (i.e., any `[mcp_servers.<name>]` section outside the `# BEGIN OMC MANAGED MCP REGISTRY` / `# END OMC MANAGED MCP REGISTRY` markers) are **skipped** from the managed block. Missing entries must still be added. Repeated syncs must be idempotent—if the user already owns a table, the managed block must not reintroduce it.

Keep Codex MCP sync from duplicating user-owned tables


**Title:** Persistent execution mode (Ralph, Ultrawork, Autopilot) should pause on rate-limit stops instead of retrying indefinitely

**Problem:**
When the underlying Claude API returns a rate-limit response (HTTP 429, quota exceeded, overloaded, etc.), Claude Code stops and fires the Stop hook. The persistent‑mode handler currently treats this like any other stop and re‑injects a continuation prompt, causing Claude to immediately retry, hit the rate limit again, and stop again — triggering an infinite retry loop. Observation: Ralph loops forever with no progress, and the user must forcibly intervene to break the cycle.

**Expected behavior:**
Whenever a persistent execution mode (Ralph, Ultrawork, or Autopilot) is active and Claude Code stops due to a rate‑limit reason, the Stop hook should **not** block the stop. Instead, it should allow Claude Code to stop gracefully and return a status indicating the pause, so the user can manually resume once the rate limit has reset. Normal stops (e.g. `end_turn`, incomplete tasks) should still be blocked and trigger a continuation attempt as before.

**Acceptance criteria / constraints:**
1. **Detection:** Recognize rate‑limit stops regardless of the specific key used in the stop context (the reason may appear in `stop_reason`, `end_turn_reason`, or their camelCase variants). Match known rate‑limit indicators such as `rate_limit`, `rate_limited`, `ratelimit`, `too_many_requests`, `429`, `quota_exceeded`, `quota_limit`, `quota_exhausted`, `overloaded`, and similar — using substring matching, case‑insensitive.
2. **Ordering of checks:** The rate‑limit guard must execute **before** the existing Ralph/Ultrawork/Autopilot continuation logic so that a rate‑limited session never re‑enters the persistent loop.
3. **Return value when rate‑limited:** The hook result must have `shouldBlock: false` and a human‑readable `message` that clearly indicates the pause is due to a rate limit. The `mode` in the result should be `'none'` (no continuation mode active).
4. **Non‑regression:** When the stop reason is **not** rate‑limit‑related (e.g., normal `end_turn`, `context_limit`, `user_cancel`, etc.), the hook must continue to block the stop if persistent work remains unfinished, exactly as it does today. An active Ralph session stopped with `stop_reason: 'end_turn'` should still be forced to continue.
5. **Safety:** The rate‑limit detection must gracefully handle missing or null stop context without throwing.

**Note on existing code patterns:**
The stop‑context handling is already centralized in the persistent‑mode hook (the function that decides whether to block a stop). There are existing helpers for other special stop reasons (user abort, context limit) that can serve as a template. The new detection should follow the same conventions and be integrated into the same guard chain.

fix(ralph): prevent infinite retry loop when rate limited (#777)