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
5 changes: 5 additions & 0 deletions PLANS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ the durable truth after it changes. Git history is the archive.

## Active

- [[plans/web-conversation-surface.md]] — Generalizes WebPi into one Web
conversation surface: a neutral `WebSessionHost` with `pi-rpc`, `acp`,
`claude-stream-json`, and `codex-app-server` transports, first-class
permission requests, and capability-gated UI. Live per-runtime acceptance
remains open.
- [[plans/unified-page-topbar.md]] — Unifies navigator and content toolbars
across the UI, with fixed page actions and content-owned sidebar restoration.
Held on `codex/ui-usability-followup` for visual acceptance.
Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ GitHub navigation.
| [[docs/ui-interaction-and-motion.md]] | [UI interaction and motion](ui-interaction-and-motion.md) | Clickable affordances, shared motion tokens, entrances/disclosures, reduced-motion policy |
| [[docs/workspace-agent-guidance.md]] | [Workspace agent guidance](workspace-agent-guidance.md) | Always-loaded prompt contract, skill ownership, live CLI authority, guidance versioning |
| [[docs/workspace-lifecycle.md]] | [Workspace and Session lifecycle](workspace-lifecycle.md) | Offboarding, departed directories, handoff, restore/purge, Session retirement |
| [[docs/workspace-manager.md]] | [Workspace Manager](workspace-manager.md) | Launcher-owned control plane, WebPi quick start, active-desk inventory, and management boundaries |
| [[docs/workspace-manager.md]] | [Workspace Manager](workspace-manager.md) | Launcher-owned control plane, Web quick start, active-desk inventory, and management boundaries |
| [[docs/web-conversation-surface.md]] | [Web conversation surface](web-conversation-surface.md) | Structured-protocol browser conversation for any runtime: wires, transports, neutral snapshot, permission requests, routes |
| [[docs/workspace-template-upgrade.md]] | [Workspace Template Upgrade](workspace-template-upgrade.md) | Managed-asset baselines, three-way review, apply transactions, recovery, and the future Merge/Absorb boundary |
| [[docs/workspace-absorb.md]] | [Workspace Absorb](workspace-absorb.md) | Directional Workspace consolidation, collision review, archived source identity, and recovery |
| [[docs/workspace-issues-and-scheduling.md]] | [Workspace issues and scheduling](workspace-issues-and-scheduling.md) | Markdown issue contract, global board, schedule scanner, headless execution, Inbox delivery |
Expand Down
2 changes: 1 addition & 1 deletion docs/conversation-provenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Ask Alice and AutoQuant list those colleagues from persistent
Directory (`GET /api/workspaces/:id/resumes`) decorates those rows with
identity, presence, birth, and latest-execution facts; it never invents roster
membership. Settings → Harness controls whether a headless-born Session that
has never opened a TUI or WebPi appears on that shared roster (default off);
has never opened a TUI or Web conversation appears on that shared roster (default off);
the Issue page still owns those rows. The roster shows only
`presence=active` coworkers; Archive files them without destroying either
their `resumeId` or Session record. Soft-delete (`presence=deleted`) is still
Expand Down
4 changes: 2 additions & 2 deletions docs/managed-workspace-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,10 @@ authorize complete argv, prompts, credentials, or environment values in logs.

Pi project trust follows the runtime boundary:

- before TUI or WebPi startup, the Pi adapter records a genuinely undecided
- before TUI or Web startup, the Pi adapter records a genuinely undecided
OpenAlice-managed Workspace in the trust store used by that Pi process. This
prevents a fresh Quick Chat from stalling behind a terminal-only trust
selector that WebPi cannot render;
selector that the Web surface cannot render;
- an explicit saved allow or deny decision on the Workspace or its nearest
parent remains authoritative. OpenAlice never flips that decision;
- interactive argv does not receive the version-sensitive `--approve` flag.
Expand Down
2 changes: 1 addition & 1 deletion docs/model-semantics-and-runtime-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ Native Agent configuration files may contain user- or runtime-owned settings.
The compatibility exporter must update only OpenAlice-owned keys/nodes,
preserve unknown data, and restore the prior value on reset where a shared
scalar is overridden. It is reached through the advanced deprecated surface;
normal Workspace creation, Quick Chat, Issues, probes, WebPi, and resume do not
normal Workspace creation, Quick Chat, Issues, probes, Web Sessions, and resume do not
call it.

Pi uses one generic OpenAlice-managed project extension plus local provider and
Expand Down
10 changes: 8 additions & 2 deletions docs/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ Load-bearing paths:

- `src/workspaces/service.ts` — Workspace lifecycle and composition.
- `src/workspaces/session-pool.ts` — PTY process ownership.
- `src/workspaces/web-session-host.ts` and `src/workspaces/web-session/` —
the browser conversation surface: one long-lived structured-protocol
process per Session record, projected into a neutral snapshot. Transports
own the wire (`pi-rpc`, `acp`, `claude-stream-json`, `codex-app-server`);
adapters declare `capabilities.web` and compose the process command. The
persisted `SessionRecord.surface` value stays `webpi` for every runtime.
- `src/workspaces/harness-surface-manager.ts` — managed Harness web processes,
readiness, routes, logs, and cleanup.
- `src/workspaces/session-registry.ts` — durable session metadata.
Expand Down Expand Up @@ -244,7 +250,7 @@ provenance link:

Do not use a headless task id directly as a roster or process-attachment id,
and do not create another `SessionRecord` when the same `resumeId` changes
between headless, terminal, and WebPi execution. The run is execution
between headless, terminal, and Web execution. The run is execution
provenance; `resumeId` is the product identity; `SessionRecord` is its one
durable launcher-owned roster record.

Expand Down Expand Up @@ -323,7 +329,7 @@ Inbox is the durable agent-to-user delivery surface. Agents publish reports or
status by calling the injected `inbox_push` capability. Alice stamps the
product Session and exact execution identity out-of-band. The user can return
to the exact originating Session regardless of whether its first turn was
headless or interactive; opening TUI/WebPi attaches a process to the existing
headless or interactive; opening TUI/Web attaches a process to the existing
durable Session record.

## Persistent State
Expand Down
7 changes: 4 additions & 3 deletions docs/remote-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -602,8 +602,9 @@ That is a later protocol, not a shortcut in the SSH phase.
The existing Workspace PTY WebSocket crosses the SSH tunnel unchanged. The
remote PTY and Agent TUI remain authoritative; the local xterm-compatible
surface renders received terminal bytes. Shell, Claude Code, Codex, opencode,
and Pi retain the same terminal semantics. WebPi remains an optional structured
Pi surface, not a prerequisite or replacement for shell/TUI workflows.
and Pi retain the same terminal semantics. The Web conversation surface remains
an optional structured presentation of a runtime's own protocol, not a
prerequisite or replacement for shell/TUI workflows.

The browser's core health probe publishes a monotonic recovery generation only
when Alice transitions from unavailable back to available. PTY views use that
Expand Down Expand Up @@ -942,7 +943,7 @@ behavior.
- persistent terminal screen history by default;
- simultaneous writable control from multiple clients;
- replacing Electron with a browser wrapper;
- replacing Shell or native Agent TUIs with Pi/WebPi;
- replacing Shell or native Agent TUIs with the Web conversation surface;
- scanning arbitrary remote directories or silently cloning OpenAlice; managed
clone/update is restricted to the displayed destination and explicit plan;
- installing, pinning, downgrading, or repairing Agent Runtime executables on a
Expand Down
24 changes: 22 additions & 2 deletions docs/ui-interaction-and-motion.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,28 @@ before rendering and supplies only supported send/stop actions. Missing actions
do not produce fake controls. Reasoning, tool input/output, failed operations,
and unknown payloads remain inspectable; failures expand their activity details.
Presentation must not import runtime APIs, parse provider event discriminators,
or fetch Workspace data. Pi's conversion lives in `webpi-presentation.ts` and
its polling/commands in `useWebPiConversation`; `WebPiView` composes the adapter.
or fetch Workspace data. The backend already projects every runtime wire (Pi
RPC, ACP, Claude stream-json, Codex app-server) into one neutral message list;
`web-presentation.ts` converts that list, `useWebConversation` owns
polling/commands, and `WebSessionView` composes the adapter for any runtime
whose `capabilities.web` is declared. Runtime identity is a presentation fact
(placeholder, stop label, wire tooltip), never a branch on the protocol.

Runtime requests (tool permissions, file-change approvals, questions) render in
`ConversationRequestCard`, pinned above the composer in the `status` slot
rather than inline in the transcript, so the pending decision cannot scroll
away while it is the only way forward. Options come verbatim from the runtime
and answer with one option id; `allow`/`deny`/`neutral` tones map to the shared
button variants. While a request is pending the phase is `awaiting-input`: the
composer stays in stop mode, the card is the primary action, and further
requests are counted rather than stacked. Answer failures keep the card and
surface the error inline. `notice` items are neutral system remarks between
turns (stopped turn, mode change), not assistant prose.

Launch affordances (Resume CTA "Open in Web", the Workspace header surface
toggle, Manager Quick Start) gate on `agentSupportsWeb(agents, agent)`; a
runtime without a structured protocol keeps its terminal without a dead button,
and an unloaded runtime list hides the affordance rather than guessing.

Pending sends keep and lock their draft until acknowledgement, reject repeated
submission, and preserve the draft on failure. Enter respects IME composition;
Expand Down
117 changes: 117 additions & 0 deletions docs/web-conversation-surface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Web conversation surface

The Web surface presents an existing Workspace Session in the browser through
its runtime's own structured protocol instead of a PTY. It is a presentation of
the same durable Session, not another runtime: the runtime still owns the
transcript, credentials, tools, and approvals; Alice keeps one long-lived child
process per Session record and projects that process's protocol into one
neutral live snapshot.

Read this guide before changing `src/workspaces/web-session-host.ts`,
`src/workspaces/web-session/`, `composeWebCommand`, the `/api/workspaces/:id/sessions/:sid/web/*`
routes, or the browser modules `useWebConversation`, `web-presentation.ts`,
`WebSessionView`, and `ConversationRequestCard`. Rendering rules live in
[[docs/ui-interaction-and-motion.md]]; Manager-specific launch rules live in
[[docs/workspace-manager.md]].

## Ownership

| Layer | Owner | Must not |
|---|---|---|
| Adapter (`src/workspaces/adapters/<agent>.ts`) | Declare `capabilities.web = { wire, permissionPrompts, freshSession }` and compose the structured-mode argv in `composeWebCommand` | Parse protocol output or hold session state |
| Transport (`src/workspaces/web-session/<wire>-transport.ts`) | Speak exactly one wire over the child's stdio, drive `WebSessionState`, surface requests, learn the native session id | Spawn processes, touch the registry, or know which adapter launched it |
| Host (`src/workspaces/web-session-host.ts`) | Spawn/supervise the process, own the snapshot revision, dispatch prompt/abort/respond to the transport, tail stderr | Branch on agent ids or wire names |
| Service/routes | Bind the Session record and `resumeId`, enforce one live process per Session, expose the snapshot | Reach into transport internals |
| Browser | Poll the snapshot, group the neutral messages into turns, answer requests with an option id | Import runtime APIs or branch on the wire beyond copy |

`agy` and `shell` have no structured protocol and stay TUI-only. Do not add a
Web capability to a runtime whose structured mode cannot round-trip tool
approval or cannot reopen an exact recorded conversation.

## Wires

| Wire | Runtimes | Process | Permission prompts | Fresh session |
|---|---|---|---|---|
| `pi-rpc` | `pi`, `omp` | `--mode rpc` JSONL; Pi additionally `--approve`, omp `--auto-approve` | none in RPC mode; launch-time approval | yes (RPC allocates the id) |
| `acp` | `cursor`, `grok`, `opencode` | Agent Client Protocol JSON-RPC over stdio (`cursor-agent acp`, `grok agent stdio`, `opencode acp`) | `session/request_permission` with the agent's own options | `session/new`; resume via `session/load` when advertised |
| `claude-stream-json` | `claude` | `-p --input-format stream-json --output-format stream-json --include-partial-messages --permission-prompt-tool stdio` | `control_request` `can_use_tool`; answered with allow/deny | `--session-id <uuid>` chosen by the adapter |
| `codex-app-server` | `codex` | `codex app-server --listen stdio://` with MCP registration, `approvalPolicy: on-request`, `sandbox: workspace-write` | `item/commandExecution/requestApproval`, `item/fileChange/requestApproval` (answered with a `decision` enum), `item/permissions/requestApproval` (answered with the granted `permissions` profile + `scope`), `item/tool/requestUserInput` | `thread/start`; resume via `thread/resume` |

The Web surface never resumes "last": it reopens the exact recorded native id
or starts a fresh conversation the transport reports back, so the Session's
`resumeId` binds to one native transcript exactly as PTY discovery does.

Codex wire enums (`AskForApproval`, `SandboxMode`) are kebab-case and its
server-request response shapes differ per method; verify against
`codex app-server generate-json-schema --out <dir>` from the installed binary
before changing the transport rather than inferring from TypeScript-style
names.

Every transport must leave the snapshot in `idle` (or `failed` when the process
is gone) with `error` set when a prompt is rejected before the turn starts —
for example missing credentials. A snapshot stuck in `working` with no turn in
flight is a transport bug, not a runtime condition.

## Neutral model

`src/workspaces/web-session/model.ts` is the contract the browser mirrors in
`ui/src/components/workspace/api.ts`:

- `WebConversationMessage` borrows Pi's minimal roles — `user`, `assistant`
(text / thinking / toolCall / data parts), `toolResult`, plus `notice` for
system remarks and `unknown` for records a transport could not classify.
Keep unknown records; they are the audit trail for protocol drift.
- `WebPermissionRequest` carries the runtime's own option list. Transports
translate protocol enums into `{ id, label, tone }`; the browser answers
with the same `id`. Never synthesize options a runtime did not offer.
- `WebSessionPhase` adds `awaiting-input` to the WebPi phases: the turn is
still in flight and blocked on the user. Prompting during it is rejected;
aborting drops the pending requests.
- `streamingMessage` is the cumulative in-flight assistant message and is
replaced, never appended; transports move it into `messages` when the turn
ends.

The snapshot is ephemeral. Do not persist it, do not migrate it, and do not
read it back as a transcript.

## Persisted surface value

`SessionRecord.surface` keeps the shipped value `webpi` (migration 0040) for
every runtime that opens in the Web surface. Renaming it would require a
migration for no behavioral gain; free-floating identifiers (routes, host,
components, labels) use "Web". User-facing copy says "Web", never "WebPi".

## Routes

- `POST /web/open` — checks `capabilities.web`, refuses a Session with a
running headless turn, disposes a PTY on the same record, starts the host.
- `GET /web?revision=` — snapshot or `{ unchanged: true }`.
- `POST /web/prompt`, `POST /web/abort` — turn control.
- `POST /web/respond { requestId, optionId }` — answers one request; the
transport validates the option id and fails with `web_respond_failed`.

Switching a running Web Session to the TUI stops the Web process first; the
reverse disposes the PTY. Exactly one process may own a Session record.

## Browser

`agentSupportsWeb(agents, agent)` is the only gate for launch affordances.
Runtime identity affects copy (placeholder, stop label, wire tooltip) and
nothing else. The request card is pinned above the composer; see
[[docs/ui-interaction-and-motion.md]] for the interaction rules. Demo mode
mirrors the capability table in `ui/src/demo/fixtures/web-session.ts` and
scripts a permission turn for prompting runtimes; update it with any contract
change.

## Verification

- `npx tsc --noEmit`, `pnpm test`, `cd ui && npx tsc -b`.
- `src/workspaces/web-session-host.spec.ts` drives a fake child over stdio for
every wire; extend it when a transport learns a new message.
- `src/workspaces/adapters/web-command.spec.ts` pins each runtime's argv.
- `pnpm -F open-alice-ui dev:demo`: open a Claude/Codex/ACP quick chat, answer
the request card, stop mid-turn, and confirm the notice.
- Live acceptance needs installed runtimes and is not part of routine CI: for
each runtime, open one Session in Web, send a prompt that needs a tool,
answer the card, stop mid-turn, then reopen the same Session in the TUI and
confirm the native transcript is shared. State the gap when this was not run.
2 changes: 1 addition & 1 deletion docs/workspace-absorb.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ target Session the author of the source's old work.

Preview and apply inspect process-backed Workspace activity, not persisted
`state: running` flags or a bare counter. The review lists the exact open TUI,
WebPi, and headless turns. An exited child process is pruned from the guard even
Web, and headless turns. An exited child process is pruned from the guard even
if an outer cleanup promise was lost, preventing a zombie “someone is working”
blocker.

Expand Down
Loading