Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/docker-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ on:
description: "Git tag (for example v0.1.2)"
required: true
type: string
publish-latest:
description: "Advance stable image aliases after a checked release"
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
tag:
Expand Down Expand Up @@ -59,7 +64,7 @@ jobs:
- id: tags
env:
REQUESTED_TAG: ${{ inputs.tag || github.event.inputs.tag }}
CALLED_FROM_RELEASE: ${{ github.event_name == 'workflow_call' }}
CALLED_FROM_RELEASE: ${{ inputs.publish-latest == true }}
run: |
set -euo pipefail
tag="${REQUESTED_TAG#v}"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ jobs:
uses: ./.github/workflows/docker-images.yml
with:
tag: ${{ github.ref_name }}
publish-latest: true

verify-runtime-image:
name: "E2E: released runtime image"
Expand Down
25 changes: 19 additions & 6 deletions apps/web/content/docs/guides/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@ Both are filtered server-side to the agent's **declared, non-secret** keys — a
ignored, and a setting can never introduce a credential. Discover what a given agent declares with
`GET /agent` (`mw.agent()` / `client.Agent(ctx)`).

## Native command access

Direct workspaces use the computer account running MindWire, including root when the service was
started as root. Commands have that account's normal access to files, installed tools and the
network. The launcher preserves inherited PATH entries after login-shell startup; explicitly
managed CLI versions still take precedence. Docker workspaces keep their container boundary.

Codex defaults to `danger-full-access` unless a sandbox was explicitly selected. A native
`sandbox_mode` in the user config or its selected profile, a sticky `sandbox` setting, or a per-turn
override remains effective. Approval policy is independent. These settings apply to new and resumed
sessions through both the CLI and app-server transports.

To restrict commands, select `workspace-write` or `read-only`. Codex's `workspace-write` network
restrictions still apply unless enabled in its native configuration. A blocked GitHub command can
report a DNS error even when the host itself is online.

## Sticky config

Read and merge persisted settings. `setConfig` merges recognized keys; unknown keys are dropped.
Expand Down Expand Up @@ -130,12 +146,9 @@ per-turn prompt uses. A per-turn `options.systemPrompt` still wins for that one
await mw.setConfig({ systemPrompt: "You are a terse senior engineer." });
```

<Callout type="warn">
**Codex approval mode.** Codex only carries a system prompt on its autonomous `exec` transport. With
any `permissionMode` other than `never`, a turn carrying a system prompt (sticky or per-turn) is
rejected with a `400` rather than silently dropped — the interactive-approval (app-server) transport
can't take the overlay. Claude has no such constraint.
</Callout>
Codex sends system instructions through the private app-server connection for interactive turns.
Headless `exec` turns use a temporary native config overlay. Both honor the same prompt and approval
settings without placing private instructions on the command line.

The sticky system prompt is one of the three persistent layers beneath a turn; the full picture —
memory files (`CLAUDE.md` / `AGENTS.md`) and saved prompt templates — is in
Expand Down
1 change: 1 addition & 0 deletions apps/web/content/docs/guides/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"destinations",
"personal-computers",
"desktops",
"project-library",
"project-sync"
]
}
50 changes: 50 additions & 0 deletions apps/web/content/docs/guides/project-library.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: Project folders & ordering
description: Organize projects in the workspace registry, with atomic edits and offline synchronization.
---

The workspace registry stores project folders, membership, and manual order. Folders organize
project references; moving or deleting a folder does not move files, run Git, or delete projects
and conversations. Clients can keep their own flat or folder-based view preferences.

Read `health.projectLibraryVersion` before using the library API. Version `1` supports
`GET /workspace/project-library` and `PATCH /workspace/project-library`, plus the matching
TypeScript and Go SDK methods.

## Read and edit

```ts
const library = await mw.workspace.library();
const folderId = crypto.randomUUID();
const saved = await mw.workspace.editLibrary({
expectedRevision: library.revision,
folders: [{ id: folderId, name: "Client work" }],
placements: [{ projectId, folderId }],
order: [projectId],
});
```

The project ID must belong to this workspace. The Go equivalent is
`client.Workspace.Library()` and `client.Workspace.EditLibrary(edit)`.
The returned library contains `version`, `revision`, `folders`, `membership`, and `order`.

An edit can upsert folders, delete folder IDs, place projects into a folder or set their folder to
`null`, and reorder selected projects or folders. Reordering a subset preserves the other entries'
positions. The daemon validates and saves the complete edit in one transaction; invalid edits
change nothing.

## Offline clients and conflicts

Cache the library and save outgoing edits durably with local UI changes. Send edits through one
writer per workspace. The API requires the last observed `expectedRevision`; a `409` means the
library changed and must be read again before rebasing. An identical retry is a no-op.

Full workspace snapshots include `projectLibrary`. Incremental snapshots include it only when
organization changes, so an idle client needs no additional polling. Keep disconnected workspace
data until the daemon confirms a deletion. When a folder is deleted remotely, remove dependent
stale edits instead of recreating that folder; its deletion is recorded in the registry.

Use `importIfEmpty: true` only to migrate existing local organization into an untouched registry.
Existing daemon organization wins. Older services can keep the client's organization local until
upgraded. [Project switching](/docs/guides/project-sync) can retain logical membership when a project
has copies on multiple workspaces.
5 changes: 5 additions & 0 deletions apps/web/content/docs/guides/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ configured, call [`agent()`](/docs/guides/agents#discover-an-agent) — its `ins
already satisfied is skipped, so calling it on a healthy agent is a no-op that just reports "satisfied."
`update()` is the same flow aimed at upgrading an already-installed agent.

For a managed CLI, a failed version probe reports `compatibility: "unavailable"`; it does not
claim the selected version changed. A real mismatch reports both versions. When
`repairAvailable` is true, an explicit `update()` repairs the managed installation using the
recommended supported version. Externally installed CLIs are not silently downgraded or replaced.

<ApiTabs>

<Tab value="TypeScript">
Expand Down
23 changes: 20 additions & 3 deletions daemon/DESKTOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,24 @@ opening their own display connections.

The Go RFB adapter handles standard RFB 3.8 None security results, explicit BGR
true-color pixels, bounded block decoding, resize notifications and release of held
keys/buttons. Before pointer input it requests a one-pixel update to reconcile
display geometry. Captures request the full image and return actual PNG image
keys/buttons. Button transitions and clicks request a one-pixel update to reconcile
display geometry; continuing human motion reuses geometry observed in the last
250ms. Agent actions still validate their capture. Captures request the full image and return actual PNG image
content to the harness. Repeated announcements of an unchanged size preserve the
frame and pointer revision; an actual resize invalidates the old coordinates.

The controller also negotiates RichCursor. Changed cursor PNGs and hotspots are
optional `cursor` fields in surface snapshots/events; the stable image ID avoids
decoding an unchanged cursor again. This is needed because TigerVNC can render the
cursor into a separate video viewer instead of sending it local cursor images.
One incremental 1px request waits for changes with no idle polling. Explicit
geometry checks/captures share that connection and take priority. Pixel coverage
tracks partial/tiled replies; cursor-only responses cannot acknowledge a capture.
If the server consumes a request with a cursor or partial reply, remaining pixels
are requested again. Input is never sent by the observer. Cursor dimensions,
hotspots and per-update allocations are bounded; empty cursor updates retain the
last usable shape. Closing the last control/view session closes the observer too.

The initial SetPixelFormat must also use network byte order for channel maxima.
The pinned go-vnc dependency encodes those fields incorrectly during Connect;
`connectDesktopRFB` corrects that handshake message before sending it. Sending a
Expand Down Expand Up @@ -155,7 +168,11 @@ cannot take over; after revocation it needs a fresh, approved session.

Input is serialized across all clients. Each action has a stable request ID and a
durable receipt written before dispatch. Identical retries return the receipt;
a different request with the same ID conflicts. Transport failures and a crash
a different request with the same ID conflicts. A cancelled request is checked
after acquiring the operation lock and immediately
before provider dispatch. It cannot click later when the queue drains. A provider
failure releases held input under the same lock with a separate bounded deadline.
Transport failures and a crash
after dispatch produce `outcome_unknown`: observe the desktop before issuing any
replacement action. `dispatched` acknowledges VNC/provider delivery, not the remote
application's final state. Capture to verify the application.
Expand Down
19 changes: 15 additions & 4 deletions daemon/GIT_ACCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,17 @@ reservation until recovery and returns an observation error.
Pull only fast-forwards the current branch from origin; push sets upstream to
origin and never forces. A forwarded connection rejects another push destination.
Concurrent managed Git mutations and agent/project operations in overlapping directories
conflict. Clients must wait for `activeOperations == 0` before replacing the
daemon, in addition to existing run/setup/project-operation checks. Native
terminal commands remain subject to Git's own locking.
conflict, except that staging and unstaging can run while an agent, ordinary command,
or terminal is active. These index-only actions still reserve the repository against
other managed Git mutations and project synchronization/removal, and retain Git's
native index lock. A competing managed Git operation returns HTTP 409 with a Git-busy
message instead of the unrelated registry revision-conflict message. Other Git
actions retain their active-work guard.

Clients must wait for `activeOperations == 0` before replacing the daemon, in
addition to existing run/setup/project-operation checks. Native terminal commands
remain subject to Git's own locking. Observe `/workspace/git/operations/{id}/stream`
for immediate completion; a disconnected observer can read the same durable receipt.

The TypeScript HTTP SDK exposes `workspace.git.start/operation/operations/watch/cancel`,
account settings on `workspace.git`, `turn/resolve({gitAuth})`, and
Expand All @@ -138,4 +146,7 @@ revocation, cloning and restart recovery, metadata compatibility, authenticated
run/resume lifecycle, and local fetch/pull/push with divergence protection. Git
operation tests also cover lost acknowledgements, idempotency across restart,
interrupted recovery without replay, cancellation, persistence failure, literal
paths, unborn unstaging, and discard failures that preserve working files.
paths, unborn unstaging, and discard failures that preserve working files. HTTP
tests also stage and unstage real files during a live command, an idle terminal,
and an agent turn, while verifying that competing Git operations and project
synchronization still block the operation.
11 changes: 7 additions & 4 deletions daemon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ curl -s -H "Authorization: Bearer $DAEMON_TOKEN" http://127.0.0.1:8790/healthz
| `STATE_PATH` | `agent-state.json` | Local JSON state file. |
| `WORKSPACE_DB_PATH` | `workspace.db` beside `STATE_PATH` | Authoritative SQLite registry for agent profiles, projects and chat links. |
| `DAEMON_TOKEN` | *(required)* | Bearer token, also saved privately beside the state file for authorized workspace clients. |
| `MINDWIRE_ISOLATION` | `direct` | Trusted launch setting: `container` means the enclosing container provides isolation. Codex defaults to using that boundary; explicit sandbox settings and approvals are preserved. Reported by `/healthz` as `workspaceIsolation` with `workspaceIsolationVersion: 1`. |
| `MINDWIRE_ISOLATION` | `direct` | Trusted launch setting: `direct` uses the host account's normal access; `container` uses the enclosing container as its boundary. Codex adds no inner sandbox by default. Explicit sandbox settings and approvals are preserved. Reported by `/healthz` as `workspaceIsolation` with `workspaceIsolationVersion: 1`. |
| `DEV_CORS` | off | `1` allows a cross-origin browser client (e.g. the preview app's dev server). |

The runtime image and the SDK's Docker/SSH-container launchers set `MINDWIRE_ISOLATION=container`.
Direct host launchers leave it unset. This avoids requiring privileged nested Linux namespaces for
Codex inside Docker; it does not disable the container boundary or change approval policy. See
[Codex sandboxing](https://developers.openai.com/codex/sandboxing) for the native container guidance.
Direct host launchers leave it unset. Commands run as the account that started Mindwire, with its
normal filesystem, tools and network access (including root when started as root). Codex defaults
to `danger-full-access` in both placements; Docker still supplies the container boundary. This
also avoids requiring privileged nested Linux namespaces. Explicit Codex sandbox settings and
approval policies remain effective. See [Codex sandboxing](https://learn.chatgpt.com/docs/agent-approvals-security)
for the native controls and container guidance.
Mount only the workspace data you intend to expose to its agents. This contract requires service
0.1.17 or later; older services do not advertise `workspaceIsolationVersion`.

Expand Down
27 changes: 27 additions & 0 deletions daemon/SETTINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,33 @@ for the selected provider. Neither credentials nor native terminal UI preference
cross this boundary. A managed Foundry connection uses its own deployment name
and provider tuning rather than an unrelated native provider's values.

## Native command access

Direct workspaces run commands as the account that started Mindwire, using its
normal filesystem, PATH, home and network access. This includes root on a server
started as root; Mindwire neither changes users nor adds privileges. Docker
workspaces keep their existing container boundary.

Harness launchers restore inherited PATH entries after login-shell startup and
retain any additional login paths. An explicitly managed CLI version still takes
precedence. This keeps commands installed through Homebrew, npm or a user tool
directory available when a system login profile replaces PATH. Commands run by
the harness still honor that harness's native shell settings.

Codex defaults to `danger-full-access` when no sandbox has been selected. A
native `sandbox_mode` in the user config or its selected profile, a managed
`sandbox` setting, or a per-turn override takes precedence. The approval policy
is independent and remains unchanged. Both app-server and exec apply this to
new and resumed chats. Explicit `workspace-write` still uses Codex's network
restrictions unless network access is enabled in its native configuration.

To verify the installed Codex without a model account, run
`CODEX_LOCAL=1 go test ./internal/agent/codex -run '^TestLocalNativeCommandAccess$' -v`
from `daemon/`. A local Responses fixture requests real commands on new and
resumed sessions through both transports. It checks the invoking UID, inherited
tool PATH/environment, and a real HTTP connection. Codex state and credentials
are temporary. The check also runs as root in an ordinary Linux container.

## Models and reasoning

Codex reads native `model/list`, including pagination and hidden-model filtering.
Expand Down
31 changes: 31 additions & 0 deletions daemon/WORKSPACES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ release architectures remain static, with CGO disabled.
| Transcripts | Harness native history, with the daemon's recorded fallback |
| Active runs, stop/reconnect state and notifications | Daemon session/run store |
| Project setup, clone progress, folder deletion, cancellation and recovery | Daemon project service; durable operations in workspace.db |
| Library folders, folder membership and manual project order | Workspace registry; atomic revisioned library |
| List ordering by last use, collapsed UI state, navigation | Client cache |

Profiles are named harness selections. They do not duplicate harness configuration,
Expand All @@ -36,6 +37,8 @@ All routes require the daemon bearer credential. They are workspace-wide:
|---|---|
| `GET /workspace` | Full snapshot |
| `GET /workspace/changes?since=N&workspaceId=ID` | Changes newer than revision N |
| `GET /workspace/project-library` | Read folders and manual order without scanning conversations |
| `PATCH /workspace/project-library` | Conditionally edit folders, placements and order together |
| `POST /workspace/import` | Add missing legacy records without overwriting existing data |
| `PUT /workspace/{agents,projects,chats}/{id}` | Create or replace one record |
| `DELETE /workspace/{agents,projects,chats}/{id}?revision=N` | Remove membership at the expected record revision |
Expand Down Expand Up @@ -65,6 +68,34 @@ tombstones registry membership. Existing rename/fork endpoints update the regist
Registered turns use their saved profile's harness and project's directory; a
conflicting explicit harness is rejected.

## Project library

`projectLibraryVersion: 1` advertises durable folder organization. Full snapshots
include `projectLibrary`; deltas include it only when its revision changed.
The standalone read uses only that small record. There is no polling worker,
filesystem scan, transcript copy, or extra database process for this feature.

A library contains ordered `folders` (`id`, `name`), `membership` (project ID to
folder ID), and `order` (project IDs). PATCH accepts `expectedRevision`, folder
upserts, `deleteFolders`, `placements`, `order`, and `folderOrder`. A placement
with `folderId: null` returns a project to the flat list. Reorders replace only
the requested entries' slots, retaining other projects' relative positions.

Every edit commits in one SQLite transaction with the workspace revision.
Identical retries are no-ops. A stale differing edit returns 409; fetch the current
library and rebase only the intended fields. Deleted folder IDs return 410 and
cannot be recreated by a delayed rename. Folder deletion keeps all files,
projects and chats. Project deletion removes library references in its existing
transaction. `importIfEmpty: true` migrates a device's old local organization only
if the daemon library has never been edited; it cannot replace another phone's
saved organization.

TypeScript exposes `workspace.library()` and `workspace.editLibrary(edit)`;
Go exposes `Workspace.Library()` and `Workspace.EditLibrary(edit)`. Views and
recent/name sort preferences remain local. Clients can cache organization and
queue edits offline, but should distinguish pending edits from acknowledged
workspace data.

## Project icons

`projectIconsVersion: 1` enables an optional project-relative `iconPath` in the
Expand Down
Loading
Loading