docs(ticket13): inventaire transport backend et frontend (B0)
Lot B0 du chantier server/client mode : cartographie des surfaces de transport existantes, préalable à l'extraction du cœur backend commun. - docs/ticket13-b0-backend-transport-inventory.md : inventaire backend. - docs/ticket13-f0-frontend-transport-inventory.md : inventaire frontend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
708
docs/ticket13-b0-backend-transport-inventory.md
Normal file
708
docs/ticket13-b0-backend-transport-inventory.md
Normal file
@ -0,0 +1,708 @@
|
|||||||
|
# Ticket #13 B0 - Backend transport inventory
|
||||||
|
|
||||||
|
Date: 2026-07-15
|
||||||
|
Branch: `feature/ticket13-server-client-mode`
|
||||||
|
|
||||||
|
Scope: read-only inventory of the current Tauri backend transport surface in
|
||||||
|
`crates/app-tauri`. No production code change is part of B0.
|
||||||
|
|
||||||
|
Architect ticket #13 target: keep desktop Tauri and add server/client mode on a
|
||||||
|
shared backend core. Future web transport is HTTP request/response plus a
|
||||||
|
dedicated WebSocket for PTY/live streams.
|
||||||
|
|
||||||
|
## Source anchors
|
||||||
|
|
||||||
|
- Tauri command registration: `crates/app-tauri/src/lib.rs:153`
|
||||||
|
- Composition root and `AppState`: `crates/app-tauri/src/state.rs:738`,
|
||||||
|
`crates/app-tauri/src/state.rs:1058`
|
||||||
|
- PTY Tauri Channel bridge: `crates/app-tauri/src/pty.rs:1`
|
||||||
|
- Structured chat Tauri Channel bridge: `crates/app-tauri/src/chat.rs:1`
|
||||||
|
- Domain event relay: `crates/app-tauri/src/events.rs:23`,
|
||||||
|
`crates/app-tauri/src/events.rs:1107`
|
||||||
|
- Desktop window commands/events: `crates/app-tauri/src/commands.rs:2325`
|
||||||
|
- Shutdown/window lifecycle wiring: `crates/app-tauri/src/lib.rs:80`
|
||||||
|
|
||||||
|
## Classification rules
|
||||||
|
|
||||||
|
- `request/response`: one Tauri `invoke` maps to one backend result. HTTP
|
||||||
|
candidate.
|
||||||
|
- `stream`: command uses `tauri::ipc::Channel` or controls a live stream/session.
|
||||||
|
WebSocket candidate, sometimes with a small HTTP control equivalent.
|
||||||
|
- `event`: backend push currently emitted through Tauri global events. WebSocket
|
||||||
|
live/event frame candidate unless desktop-specific.
|
||||||
|
- `desktop-only`: OS windows, Tauri path/lifecycle/dialog composition. Not part
|
||||||
|
of the web client transport.
|
||||||
|
|
||||||
|
## Exposed Tauri commands
|
||||||
|
|
||||||
|
### Core/projects/permissions
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `health` | request/response | `State<AppState>.health` |
|
||||||
|
| `create_project` | request/response | `create_project` use case |
|
||||||
|
| `open_project` | request/response | `open_project`, plus open-time reconciliation/wiring |
|
||||||
|
| `close_project` | request/response | `close_project` |
|
||||||
|
| `list_projects` | request/response | `list_projects` |
|
||||||
|
| `read_project_context` | request/response | project FS read use case |
|
||||||
|
| `update_project_context` | request/response | project FS write use case |
|
||||||
|
| `get_project_permissions` | request/response | permission read use case |
|
||||||
|
| `update_project_permissions` | request/response | permission write use case |
|
||||||
|
| `update_agent_permissions` | request/response | permission override write use case |
|
||||||
|
| `resolve_agent_permissions` | request/response | effective permission read use case |
|
||||||
|
|
||||||
|
### PTY terminals
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `open_terminal` | stream | Spawns PTY, registers `Channel<PtyChunk>`, starts output pump |
|
||||||
|
| `write_terminal` | stream/control | Writes bytes to live PTY |
|
||||||
|
| `resize_terminal` | stream/control | Resizes live PTY |
|
||||||
|
| `close_terminal` | stream/control | Kills PTY and unregisters channel |
|
||||||
|
| `reattach_terminal` | stream | Reads scrollback, replaces `Channel<PtyChunk>`, starts new pump |
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `PtyChunk = Vec<u8>` today (`crates/app-tauri/src/pty.rs:29`).
|
||||||
|
- The transport-specific registry is `PtyBridge`, stored in `AppState`.
|
||||||
|
- High-frequency PTY bytes are explicitly excluded from global Tauri events.
|
||||||
|
|
||||||
|
### Layouts
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `load_layout` | request/response | layout read use case |
|
||||||
|
| `mutate_layout` | request/response | layout mutation use case, emits `LayoutChanged` |
|
||||||
|
| `list_layouts` | request/response | named layout list |
|
||||||
|
| `create_layout` | request/response | named layout create |
|
||||||
|
| `rename_layout` | request/response | named layout rename |
|
||||||
|
| `delete_layout` | request/response | named layout delete |
|
||||||
|
| `set_active_layout` | request/response | active layout write |
|
||||||
|
|
||||||
|
### First-run, profiles, local models, embedders
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `first_run_state` | request/response | first-run read use case |
|
||||||
|
| `reference_profiles` | request/response | reference profile catalog |
|
||||||
|
| `detect_profiles` | request/response | runtime detection use case |
|
||||||
|
| `list_profiles` | request/response | profile store read |
|
||||||
|
| `save_profile` | request/response | profile upsert |
|
||||||
|
| `clone_opencode_profile_from_seed` | request/response | profile clone |
|
||||||
|
| `delete_profile` | request/response | profile delete |
|
||||||
|
| `configure_profiles` | request/response | first-run save |
|
||||||
|
| `list_model_servers` | request/response | local model server config read |
|
||||||
|
| `save_model_server` | request/response | local model server config write |
|
||||||
|
| `preview_model_server_command` | request/response | pure command preview |
|
||||||
|
| `delete_model_server` | request/response | local model server config delete |
|
||||||
|
| `list_embedder_profiles` | request/response | embedder profile read |
|
||||||
|
| `save_embedder_profile` | request/response | embedder profile write |
|
||||||
|
| `delete_embedder_profile` | request/response | embedder profile delete |
|
||||||
|
| `describe_embedder_engines` | request/response | embedder engine capabilities |
|
||||||
|
| `dismiss_embedder_suggestion` | request/response | suggestion state write |
|
||||||
|
|
||||||
|
### Agents, live state, chat and resume
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `create_agent` | request/response | agent manifest/context creation |
|
||||||
|
| `list_agents` | request/response | manifest read |
|
||||||
|
| `get_project_work_state` | request/response | read-model aggregation |
|
||||||
|
| `read_conversation_page` | request/response | paginated transcript read |
|
||||||
|
| `list_live_agents` | request/response | in-memory live registry read |
|
||||||
|
| `attach_live_agent` | request/response | live session rebind use case |
|
||||||
|
| `stop_live_agent` | request/response | live session stop use case |
|
||||||
|
| `read_agent_context` | request/response | agent Markdown read |
|
||||||
|
| `update_agent_context` | request/response | agent Markdown write |
|
||||||
|
| `delete_agent` | request/response | manifest/context delete |
|
||||||
|
| `inspect_conversation` | request/response | best-effort conversation inspection |
|
||||||
|
| `launch_agent` | stream | Spawns/reuses PTY or structured session, may register `Channel<PtyChunk>` |
|
||||||
|
| `change_agent_profile` | request/response | hot-swap use case; may affect live session |
|
||||||
|
| `agent_send` | stream | Structured chat turn, registers `Channel<ReplyChunk>`, pumps reply stream |
|
||||||
|
| `cancel_resume` | request/response | cancels scheduled auto-resume |
|
||||||
|
| `set_resume_at` | request/response | human-set resume time |
|
||||||
|
| `interrupt_agent` | request/response | orchestrator interrupt |
|
||||||
|
| `delegation_delivered` | request/response | frontend write-portal ack |
|
||||||
|
| `set_front_attached` | request/response | frontend mounted/unmounted signal |
|
||||||
|
| `reattach_agent_chat` | stream | Replaces `Channel<ReplyChunk>`, returns chat scrollback |
|
||||||
|
| `close_agent_session` | request/response | closes live agent/structured state |
|
||||||
|
| `list_resumable_agents` | request/response | resumable agent inventory |
|
||||||
|
|
||||||
|
Structured chat stream:
|
||||||
|
|
||||||
|
- `ReplyChunk` is tagged by `kind`: `textDelta`, `toolActivity`, `final`,
|
||||||
|
`error` (`crates/app-tauri/src/dto.rs:1760`).
|
||||||
|
- `ChatBridge` retains bounded scrollback and the current optional channel.
|
||||||
|
- `agent_send` is a turn command plus streaming response; in HTTP/WS terms it
|
||||||
|
should become a WS client frame that starts a turn on an attached session.
|
||||||
|
|
||||||
|
### Templates
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `create_template` | request/response | template create |
|
||||||
|
| `update_template` | request/response | template update, emits drift/update events |
|
||||||
|
| `list_templates` | request/response | template list |
|
||||||
|
| `delete_template` | request/response | template delete |
|
||||||
|
| `create_agent_from_template` | request/response | agent creation from template |
|
||||||
|
| `detect_agent_drift` | request/response | drift inventory |
|
||||||
|
| `sync_agent_with_template` | request/response | sync write |
|
||||||
|
|
||||||
|
### Git
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `git_status` | request/response | git status use case |
|
||||||
|
| `git_stage` | request/response | git stage use case |
|
||||||
|
| `git_unstage` | request/response | git unstage use case |
|
||||||
|
| `git_commit` | request/response | git commit use case |
|
||||||
|
| `git_branches` | request/response | branch list |
|
||||||
|
| `git_checkout` | request/response | branch checkout |
|
||||||
|
| `git_log` | request/response | log read |
|
||||||
|
| `git_init` | request/response | repo init |
|
||||||
|
| `git_graph` | request/response | graph read |
|
||||||
|
|
||||||
|
### Tickets and sprints
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ticket_create` | request/response | ticket create |
|
||||||
|
| `ticket_read` | request/response | ticket read |
|
||||||
|
| `ticket_delete` | request/response | ticket delete |
|
||||||
|
| `open_ticket_chat` | request/response | ephemeral ticket assistant open |
|
||||||
|
| `close_ticket_chat` | request/response | ephemeral ticket assistant close |
|
||||||
|
| `ticket_list` | request/response | ticket list |
|
||||||
|
| `ticket_update` | request/response | ticket update |
|
||||||
|
| `ticket_read_carnet` | request/response | carnet read |
|
||||||
|
| `ticket_update_carnet` | request/response | carnet write |
|
||||||
|
| `ticket_link` | request/response | link tickets |
|
||||||
|
| `ticket_unlink` | request/response | unlink tickets |
|
||||||
|
| `ticket_assign` | request/response | assign/unassign ticket agent |
|
||||||
|
| `sprint_create` | request/response | sprint create |
|
||||||
|
| `sprint_list` | request/response | sprint list |
|
||||||
|
| `sprint_rename` | request/response | sprint rename |
|
||||||
|
| `sprint_reorder` | request/response | sprint reorder |
|
||||||
|
| `sprint_delete` | request/response | sprint delete |
|
||||||
|
| `ticket_assign_sprint` | request/response | ticket sprint assignment |
|
||||||
|
| `ticket_unassign_sprint` | request/response | ticket sprint removal |
|
||||||
|
|
||||||
|
### Skills and memory
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `create_skill` | request/response | skill create |
|
||||||
|
| `update_skill` | request/response | skill update |
|
||||||
|
| `list_skills` | request/response | skill list |
|
||||||
|
| `delete_skill` | request/response | skill delete |
|
||||||
|
| `assign_skill_to_agent` | request/response | skill assignment |
|
||||||
|
| `unassign_skill_from_agent` | request/response | skill unassignment |
|
||||||
|
| `create_memory` | request/response | memory create |
|
||||||
|
| `update_memory` | request/response | memory update |
|
||||||
|
| `list_memories` | request/response | memory list |
|
||||||
|
| `get_memory` | request/response | memory read |
|
||||||
|
| `delete_memory` | request/response | memory delete |
|
||||||
|
| `read_memory_index` | request/response | memory index read |
|
||||||
|
| `recall_memory` | request/response | memory recall |
|
||||||
|
| `resolve_memory_links` | request/response | memory link resolution |
|
||||||
|
|
||||||
|
### Background tasks
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `spawn_background_command` | request/response | creates command-backed background task |
|
||||||
|
| `cancel_background_task` | request/response | cancels task |
|
||||||
|
| `retry_background_task` | request/response | retries task |
|
||||||
|
| `list_background_tasks` | request/response | task read-model list |
|
||||||
|
|
||||||
|
Lifecycle is pushed through `BackgroundTaskChanged` domain events; initial list is
|
||||||
|
request/response.
|
||||||
|
|
||||||
|
### Desktop windows and focus
|
||||||
|
|
||||||
|
| Command | Class | Current boundary |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `set_focused_project` | desktop-only + event | Tauri window/panel focus state; emits `focused-project://changed` |
|
||||||
|
| `get_focused_project` | desktop-only | Tauri panel state read |
|
||||||
|
| `list_open_view_windows` | desktop-only | `AppHandle.webview_windows()` |
|
||||||
|
| `open_view_window` | desktop-only | `WebviewWindowBuilder` |
|
||||||
|
| `close_view_window` | desktop-only | closes Tauri OS window |
|
||||||
|
| `move_tab_to_new_window` | desktop-only | use case plus `WebviewWindowBuilder` |
|
||||||
|
|
||||||
|
These do not migrate as-is to web. The shared backend may still keep workspace
|
||||||
|
topology use cases, but the OS-window adapter remains desktop-only.
|
||||||
|
|
||||||
|
## Tauri Channel usage
|
||||||
|
|
||||||
|
| Location | Channel type | Producer | Consumer | Web mapping |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `open_terminal` | `Channel<PtyChunk>` | PTY output pump | xterm view | WS `output` frames after `open_terminal` |
|
||||||
|
| `reattach_terminal` | `Channel<PtyChunk>` | PTY output pump | xterm view | WS `attached` + `output` frames after `attach_terminal` |
|
||||||
|
| `launch_agent` | `Channel<PtyChunk>` | PTY output pump for raw CLI agents | xterm/chat terminal view | WS `output` frames; structured sessions do not use this channel |
|
||||||
|
| `agent_send` | `Channel<ReplyChunk>` | structured reply pump | chat view | WS `chat.output` or generic live frames |
|
||||||
|
| `reattach_agent_chat` | `Channel<ReplyChunk>` | structured reply pump | chat view | WS attached/replay for structured session |
|
||||||
|
|
||||||
|
Transport-owned registries:
|
||||||
|
|
||||||
|
- `PtyBridge`: session id -> `(generation, Channel<PtyChunk>)`.
|
||||||
|
- `ChatBridge`: session id -> `{generation, Option<Channel<ReplyChunk>>, scrollback}`.
|
||||||
|
|
||||||
|
These should not descend into domain/application. Their backend-core equivalent
|
||||||
|
should be a transport-neutral outbound sink/subscription registry.
|
||||||
|
|
||||||
|
## Events emitted today
|
||||||
|
|
||||||
|
### Global domain event relay
|
||||||
|
|
||||||
|
`events::spawn_relay` subscribes to `TokioBroadcastEventBus` and emits:
|
||||||
|
|
||||||
|
- Tauri event name `domain://event`
|
||||||
|
- Payload `DomainEventDto`, tagged by `type`
|
||||||
|
- Skips `DomainEvent::PtyOutput`; PTY bytes use per-session channels
|
||||||
|
|
||||||
|
Current `DomainEventDto` variants:
|
||||||
|
|
||||||
|
- Project/lifecycle: `projectCreated`, `layoutChanged`, `remoteConnected`
|
||||||
|
- Agents: `agentLaunched`, `agentLaunchFailed`, `agentExited`,
|
||||||
|
`agentBusyChanged`, `agentProfileChanged`, `agentLivenessChanged`,
|
||||||
|
`agentRateLimited`, `agentResumeScheduled`, `agentResumeCancelled`,
|
||||||
|
`agentResumed`, `agentRateLimitSuspected`
|
||||||
|
- Delegation/orchestration: `delegationReady`, `agentReplied`,
|
||||||
|
`agentAnnouncement`, `orchestratorRequestProcessed`, `orchestratorChanged`
|
||||||
|
- Background work: `backgroundTaskChanged`, `agentInboxChanged`,
|
||||||
|
`agentWakeChanged`
|
||||||
|
- Templates/skills: `templateUpdated`, `agentDriftDetected`, `agentSynced`,
|
||||||
|
`skillAssigned`
|
||||||
|
- Git: `gitStateChanged`
|
||||||
|
- Tickets/sprints: `issueCreated`, `issueUpdated`, `issueDeleted`,
|
||||||
|
`issueStatusChanged`, `issuePriorityChanged`, `issueCarnetUpdated`,
|
||||||
|
`issueLinked`, `issueUnlinked`, `issueAgentAssigned`,
|
||||||
|
`issueAgentUnassigned`, `sprintCreated`, `sprintRenamed`,
|
||||||
|
`sprintReordered`, `sprintDeleted`, `issueSprintChanged`
|
||||||
|
- Memory/embedder: `memorySaved`, `memoryDeleted`, `memoryIndexRebuilt`,
|
||||||
|
`embedderSuggested`
|
||||||
|
- Model server: `modelServerStatusChanged`
|
||||||
|
- PTY placeholder: `ptyOutput` exists in DTO but is not emitted by the global
|
||||||
|
relay
|
||||||
|
|
||||||
|
### Dedicated global events
|
||||||
|
|
||||||
|
- `model_server_status_changed`: emitted in addition to `domain://event` for
|
||||||
|
`ModelServerStatusChanged`.
|
||||||
|
- `agent_launch_failed`: emitted in addition to `domain://event` for
|
||||||
|
`AgentLaunchFailed`.
|
||||||
|
- `view-window://lifecycle`: desktop-only, payload `{kind, panel, label}`.
|
||||||
|
- `focused-project://changed`: desktop-only, payload `{project}`.
|
||||||
|
|
||||||
|
For web, the domain-event set maps naturally to a low-frequency live WS stream.
|
||||||
|
The duplicate dedicated events should be normalized in the shared core contract
|
||||||
|
or kept as compatibility aliases only in the Tauri adapter.
|
||||||
|
|
||||||
|
## `tauri::State<AppState>` composition root inventory
|
||||||
|
|
||||||
|
`AppState::build(app_data_dir)` is the current composition root. It constructs
|
||||||
|
concrete adapters and use cases, then Tauri stores it through `app.manage`.
|
||||||
|
|
||||||
|
### Concrete adapters and infra state currently built there
|
||||||
|
|
||||||
|
- `TokioBroadcastEventBus`
|
||||||
|
- `SystemClock`
|
||||||
|
- `UuidGenerator`
|
||||||
|
- `LocalFileSystem`
|
||||||
|
- `FsProjectStore`
|
||||||
|
- `FsWindowStateStore`
|
||||||
|
- `PortablePtyAdapter` with sandbox enforcer
|
||||||
|
- `TerminalSessions`
|
||||||
|
- `StructuredSessions`
|
||||||
|
- `StructuredSessionFactory`
|
||||||
|
- `LocalProcessSpawner`
|
||||||
|
- `CliAgentRuntime`
|
||||||
|
- `FsProfileStore`
|
||||||
|
- local model server manager/use cases
|
||||||
|
- issue/ticket stores and providers
|
||||||
|
- `PtyBridge` and `ChatBridge` transport bridges
|
||||||
|
- profile/template/git/skill/memory/embedder stores and use cases
|
||||||
|
- orchestrator service, watchers and per-project MCP servers
|
||||||
|
- session-limit service, resume contexts and turn watcher
|
||||||
|
- background task runner/store
|
||||||
|
- focused project mutex
|
||||||
|
- home/app-data derived paths
|
||||||
|
|
||||||
|
### Boundary that must be extracted
|
||||||
|
|
||||||
|
Current shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
React -> @tauri invoke/listen/Channel
|
||||||
|
-> app-tauri commands/events/bridges
|
||||||
|
-> AppState concrete composition root
|
||||||
|
-> application use cases + infrastructure adapters
|
||||||
|
```
|
||||||
|
|
||||||
|
Target shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
React -> TS gateways
|
||||||
|
-> Tauri adapter OR HTTP+WS adapter
|
||||||
|
-> driving adapter calls shared backend core facade
|
||||||
|
-> application use cases + infrastructure adapters
|
||||||
|
```
|
||||||
|
|
||||||
|
The extraction line is inside `app-tauri::state`:
|
||||||
|
|
||||||
|
- Move reusable construction into a shared backend core crate/facade, e.g.
|
||||||
|
`BackendCore::build(config)`.
|
||||||
|
- Keep Tauri-only concerns in `app-tauri`: resolving `app_data_dir` via Tauri
|
||||||
|
path API, `app.manage`, command registration, `AppHandle`/`WebviewWindow`,
|
||||||
|
shutdown hooks, Tauri event emission, Tauri `Channel` bridges, dialog plugin.
|
||||||
|
- The future web server adapter should own HTTP route registration, TLS/pairing
|
||||||
|
integration, WS connection lifecycle, CORS/origin checks and WS sink mapping.
|
||||||
|
- The core should expose transport-neutral methods for the request/response
|
||||||
|
use cases and transport-neutral subscription/sink APIs for PTY/chat/live
|
||||||
|
output.
|
||||||
|
|
||||||
|
Do not move `PtyBridge` or `ChatBridge` as-is. They are Tauri transport
|
||||||
|
adapters. Move the underlying concept: attach/detach a sink to a live
|
||||||
|
session, replay bounded scrollback, and deliver ordered output.
|
||||||
|
|
||||||
|
## HTTP DTO first draft
|
||||||
|
|
||||||
|
Naming below keeps current DTO semantics and camelCase fields. Exact paths are a
|
||||||
|
proposal for B1/B3; they can be namespaced by project where useful.
|
||||||
|
|
||||||
|
### Request/response examples
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/health
|
||||||
|
POST /api/projects
|
||||||
|
GET /api/projects
|
||||||
|
POST /api/projects/{projectId}/open
|
||||||
|
POST /api/projects/{projectId}/close
|
||||||
|
GET /api/projects/{projectId}/context
|
||||||
|
PUT /api/projects/{projectId}/context
|
||||||
|
GET /api/projects/{projectId}/layout
|
||||||
|
POST /api/projects/{projectId}/layout/mutate
|
||||||
|
GET /api/projects/{projectId}/agents
|
||||||
|
POST /api/projects/{projectId}/agents
|
||||||
|
GET /api/projects/{projectId}/agents/{agentId}/context
|
||||||
|
PUT /api/projects/{projectId}/agents/{agentId}/context
|
||||||
|
DELETE /api/projects/{projectId}/agents/{agentId}
|
||||||
|
GET /api/projects/{projectId}/work-state
|
||||||
|
GET /api/projects/{projectId}/conversation-page
|
||||||
|
GET /api/projects/{projectId}/git/status
|
||||||
|
POST /api/projects/{projectId}/git/stage
|
||||||
|
POST /api/projects/{projectId}/git/commit
|
||||||
|
GET /api/tickets
|
||||||
|
POST /api/tickets
|
||||||
|
GET /api/tickets/{ref}
|
||||||
|
PATCH /api/tickets/{ref}
|
||||||
|
GET /api/templates
|
||||||
|
POST /api/templates
|
||||||
|
GET /api/background-tasks
|
||||||
|
POST /api/background-tasks
|
||||||
|
```
|
||||||
|
|
||||||
|
Errors should preserve `ErrorDto`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "NOT_FOUND",
|
||||||
|
"message": "project ..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## WebSocket first draft
|
||||||
|
|
||||||
|
One dedicated WS endpoint is enough for B5 V1:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /ws/live?token=... (token normally via Authorization/cookie, not URL in prod)
|
||||||
|
```
|
||||||
|
|
||||||
|
Authentication note from ticket #13: pairing/token must travel only over HTTPS
|
||||||
|
and not as a URL secret. The query above is only a sketch; production should use
|
||||||
|
`Authorization: Bearer` during WS upgrade or an HTTPS-only secure cookie.
|
||||||
|
|
||||||
|
### Common envelope
|
||||||
|
|
||||||
|
Client to server:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "client-msg-uuid",
|
||||||
|
"kind": "terminal.input",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Server to client:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "server-msg-uuid",
|
||||||
|
"replyTo": "client-msg-uuid",
|
||||||
|
"kind": "terminal.status",
|
||||||
|
"payload": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`replyTo` is present for command acknowledgements, absent for unsolicited live
|
||||||
|
events/output.
|
||||||
|
|
||||||
|
### PTY client -> server frames
|
||||||
|
|
||||||
|
`attach_terminal`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"kind": "terminal.attach",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"lastSeq": 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`open_terminal`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"kind": "terminal.open",
|
||||||
|
"payload": {
|
||||||
|
"projectId": "uuid",
|
||||||
|
"nodeId": "uuid-or-null",
|
||||||
|
"cwd": "/abs/path",
|
||||||
|
"rows": 30,
|
||||||
|
"cols": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`launch_agent`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"kind": "agent.launch",
|
||||||
|
"payload": {
|
||||||
|
"projectId": "uuid",
|
||||||
|
"agentId": "uuid",
|
||||||
|
"nodeId": "uuid-or-null",
|
||||||
|
"rows": 30,
|
||||||
|
"cols": 120,
|
||||||
|
"conversationId": "engine-conversation-id-or-null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`input`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"kind": "terminal.input",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"bytesBase64": "DQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`resize`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"kind": "terminal.resize",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"rows": 40,
|
||||||
|
"cols": 140
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`detach`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "6",
|
||||||
|
"kind": "terminal.detach",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`close`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "7",
|
||||||
|
"kind": "terminal.close",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ping`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "8",
|
||||||
|
"kind": "ping",
|
||||||
|
"payload": {
|
||||||
|
"atMs": 1784123456789
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PTY server -> client frames
|
||||||
|
|
||||||
|
`attached`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": "terminal.attached",
|
||||||
|
"replyTo": "1",
|
||||||
|
"payload": {
|
||||||
|
"session": { "sessionId": "uuid", "projectId": "uuid", "nodeId": "uuid", "rows": 30, "cols": 120 },
|
||||||
|
"nextSeq": 43,
|
||||||
|
"scrollback": [
|
||||||
|
{ "seq": 1, "bytesBase64": "..." }
|
||||||
|
],
|
||||||
|
"gap": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`output`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": "terminal.output",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"seq": 43,
|
||||||
|
"bytesBase64": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`status`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": "terminal.status",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"status": "running"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggested status values: `starting`, `running`, `exited`, `closed`,
|
||||||
|
`detached`, `reattached`.
|
||||||
|
|
||||||
|
`error`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": "error",
|
||||||
|
"replyTo": "7",
|
||||||
|
"payload": {
|
||||||
|
"code": "NOT_FOUND",
|
||||||
|
"message": "terminal session ..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`pong`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": "pong",
|
||||||
|
"replyTo": "8",
|
||||||
|
"payload": {
|
||||||
|
"atMs": 1784123456789
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Structured chat frames
|
||||||
|
|
||||||
|
The ticket #13 PTY contract does not require structured chat in B5, but the
|
||||||
|
current backend already has a second stream. To avoid a later incompatibility,
|
||||||
|
reserve a parallel namespace:
|
||||||
|
|
||||||
|
Client:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "id": "9", "kind": "chat.attach", "payload": { "sessionId": "uuid" } }
|
||||||
|
{ "id": "10", "kind": "chat.send", "payload": { "sessionId": "uuid", "prompt": "..." } }
|
||||||
|
{ "id": "11", "kind": "chat.detach", "payload": { "sessionId": "uuid" } }
|
||||||
|
```
|
||||||
|
|
||||||
|
Server:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": "chat.attached",
|
||||||
|
"replyTo": "9",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"scrollback": [
|
||||||
|
{ "kind": "textDelta", "text": "..." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": "chat.output",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "uuid",
|
||||||
|
"chunk": { "kind": "final", "content": "..." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Low-frequency live events
|
||||||
|
|
||||||
|
Domain events can be pushed on the same WS connection:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": "event.domain",
|
||||||
|
"payload": {
|
||||||
|
"type": "agentBusyChanged",
|
||||||
|
"agentId": "uuid",
|
||||||
|
"busy": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This replaces Tauri `listen("domain://event")` for web. Desktop can keep the
|
||||||
|
existing event relay.
|
||||||
|
|
||||||
|
## Plan impact / risks found
|
||||||
|
|
||||||
|
No finding invalidates the ticket #13 lot plan.
|
||||||
|
|
||||||
|
Points to decide early in B1/B2:
|
||||||
|
|
||||||
|
- The duplicate Tauri registration of `detect_agent_drift` in
|
||||||
|
`generate_handler!` is harmless for B0 but should be cleaned in a separate fix.
|
||||||
|
- `open_project` currently also triggers open-time reconciliation and per-project
|
||||||
|
runtime side effects. The shared backend core facade should make those explicit
|
||||||
|
methods, not hide them in transport commands.
|
||||||
|
- `launch_agent` has both raw PTY and structured-session branches. B5 PTY V1 can
|
||||||
|
implement raw PTY first, but the WS namespace should reserve chat/live frames.
|
||||||
|
- `PtyBridge` and `ChatBridge` contain transport scrollback/replay behavior.
|
||||||
|
Extract the behavior as a core subscription contract; do not leak Tauri
|
||||||
|
`Channel` into the future core.
|
||||||
|
- Desktop window/focus commands are cleanly separable as desktop-only. Workspace
|
||||||
|
topology can remain shared, OS-window creation cannot.
|
||||||
158
docs/ticket13-f0-frontend-transport-inventory.md
Normal file
158
docs/ticket13-f0-frontend-transport-inventory.md
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
# Ticket #13 — Lot B0/F0 : inventaire du transport frontend (TS/React)
|
||||||
|
|
||||||
|
> Livrable d'inventaire. **Aucun code de feature modifié.** Miroir frontend de
|
||||||
|
> l'inventaire backend (`ticket13-b0-backend-transport-inventory.md`).
|
||||||
|
> Objectif : cartographier tous les accès `@tauri-apps/api`, les classer par nature
|
||||||
|
> (request/response · stream · event · desktop-only), et localiser la frontière
|
||||||
|
> « gateways TS transport-neutres » du lot F1 (adapter Tauri desktop | adapter
|
||||||
|
> HTTP+WS web).
|
||||||
|
|
||||||
|
## 0. Constat majeur — la frontière F1 existe déjà (à ~90 %)
|
||||||
|
|
||||||
|
Le frontend est **déjà** structuré exactement comme le plan cible le demande :
|
||||||
|
|
||||||
|
- **Ports transport-neutres** : `src/ports/index.ts` déclare 21 gateways (interfaces
|
||||||
|
TS) décrivant *ce dont l'UI a besoin*, sans aucune référence à Tauri. Toute la
|
||||||
|
couche `features/` + `app/` dépend de ces ports via DI (`useGateways()`).
|
||||||
|
- **Adapters = seul lieu Tauri** : `src/adapters/*` implémente les ports via
|
||||||
|
`@tauri-apps/api`. C'est le **seul** répertoire autorisé à importer Tauri.
|
||||||
|
- **Garde d'architecture L1 active** : `src/app/no-direct-invoke.test.ts` échoue en CI
|
||||||
|
si un fichier hors `src/adapters` importe `@tauri-apps/api` **ou** appelle `invoke(`.
|
||||||
|
Cette garde est notre filet de sécurité pour F1 : elle garantit qu'aucun composant
|
||||||
|
n'appelle Tauri en dur — donc **aucun composant métier à réécrire** pour le web.
|
||||||
|
- **Composition déjà bifurquée** : `src/app/di.tsx` → `resolveGateways()` choisit
|
||||||
|
`createTauriGateways()` vs `createMockGateways()` selon `VITE_USE_MOCK`. F1 ajoute
|
||||||
|
simplement une **3ᵉ implémentation** (`createHttpWsGateways()`) au même seam.
|
||||||
|
|
||||||
|
**Conséquence sur le plan de lots** : F1 n'est *pas* une extraction de frontière
|
||||||
|
(elle est faite), mais l'écriture d'un **second jeu d'adapters** derrière des ports
|
||||||
|
inchangés. Le risque est concentré dans **3 adapters à flux (Channel)** et **3 usages
|
||||||
|
desktop-only**, pas dans les 15 gateways request/response (mécaniques).
|
||||||
|
|
||||||
|
## 1. Inventaire exhaustif des accès `@tauri-apps/api`
|
||||||
|
|
||||||
|
Aucun accès hors `src/adapters`. Détail par fichier adapter :
|
||||||
|
|
||||||
|
| Adapter | API Tauri utilisée | Nature | Commentaire transport |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `system.ts` | `invoke` (`health`) | request/response | trivial HTTP |
|
||||||
|
| `system.ts` | `listen("domain://event")` | **event** | bus d'événements domaine global → **WS** (fan-out serveur→client) |
|
||||||
|
| `system.ts` | `open()` de `@tauri-apps/plugin-dialog` | **desktop-only** | picker dossier natif — **pas de sens sur web** (voir §4) |
|
||||||
|
| `agent.ts` | `invoke` (list/create/change/read/update/delete/…) | request/response | HTTP |
|
||||||
|
| `agent.ts` | `Channel<number[]>` (`launch_agent`, `reattach_terminal`) | **stream** | flux PTY agent → **WS** (contrat PTY du carnet) |
|
||||||
|
| `terminal.ts` | `Channel<number[]>` (`open_terminal`, `reattach_terminal`) | **stream** | flux PTY terminal → **WS** (cœur de F3) |
|
||||||
|
| `terminal.ts` | `invoke` (`write_/resize_/close_terminal`) | request/response (contrôle PTY) | messages WS client→serveur (input/resize/close) |
|
||||||
|
| `ticket.ts` | `Channel<ReplyChunk>` (`agent_send`) | **stream** | flux de réponse chat assistant ticket → **WS** |
|
||||||
|
| `ticket.ts` | `invoke` (~20 commandes `ticket_*`) | request/response | HTTP |
|
||||||
|
| `window.ts` | `invoke` (`open_/close_/list_view_window`) | **desktop-only** | cycle de vie fenêtre OS (WebviewWindow) — voir §4 |
|
||||||
|
| `window.ts` | `listen("view-window://lifecycle")` | **desktop-only / event** | fermeture fenêtre OS — voir §4 |
|
||||||
|
| `focusedProject.ts` | `invoke` + `listen("focused-project://changed")` | **desktop-only / event** | canal fenêtre principale ⇄ fenêtre détachée — voir §4 |
|
||||||
|
| `project.ts`, `layout.ts`, `git.ts`, `profile.ts`, `modelServer.ts`, `template.ts`, `skill.ts`, `memory.ts`, `embedder.ts`, `permission.ts`, `workState.ts`, `conversation.ts`, `input.ts` | `invoke` seul | request/response | HTTP mécanique |
|
||||||
|
| `uiPreferences.ts` | *(aucune)* — `localStorage` | frontend-pur | déjà transport-neutre, marche tel quel sur web |
|
||||||
|
|
||||||
|
## 2. Classement par nature de transport
|
||||||
|
|
||||||
|
### 2a. `request/response` (le gros du volume — mapping HTTP direct)
|
||||||
|
Tous les `invoke()` non-Channel. 15 gateways sur 21 sont **exclusivement**
|
||||||
|
request/response : `project, layout, git, profile, modelServer, template, skill,
|
||||||
|
memory, embedder, permission, workState, conversation, input`, + le `health` de
|
||||||
|
`system` et les ~20 commandes `ticket_*`.
|
||||||
|
→ **Aucune décision d'archi** : un adapter HTTP générique (POST `{command, args}` ou
|
||||||
|
route par commande) suffit. C'est le socle du **premier livrable B4/F2** (web ouvre un
|
||||||
|
projet + read-only), qui n'a besoin d'aucun stream.
|
||||||
|
|
||||||
|
### 2b. `stream` (Channel Tauri → WebSocket) — **le vrai chantier F1/F3**
|
||||||
|
Trois flux, tous modélisés côté port par un **callback `onData`/`onChunk`** (le port
|
||||||
|
ne connaît jamais `Channel`) :
|
||||||
|
|
||||||
|
1. **PTY terminal** — `terminal.ts` `openTerminal`/`reattach` : `Channel<number[]>`,
|
||||||
|
octets bruts → `term.write`. **Cœur de F3 (xterm sur WS).**
|
||||||
|
2. **PTY agent** — `agent.ts` `launchAgent`/`reattach` : même `Channel<number[]>`,
|
||||||
|
réutilise `makeTerminalHandle`. Même transport que le PTY terminal (le carnet dit
|
||||||
|
« agents via WS PTY », lot B6).
|
||||||
|
3. **Chat assistant ticket** — `ticket.ts` `sendTicketChat` : `Channel<ReplyChunk>`,
|
||||||
|
flux de réponse LLM structuré (`agent_send`). Stream applicatif, pas PTY.
|
||||||
|
|
||||||
|
Point de contrat clé : le **handle** (`TerminalHandle`) porte déjà `write/resize/
|
||||||
|
detach/close` **et** la sémantique detach≠close (détacher la vue sans tuer le PTY).
|
||||||
|
Cette sémantique **correspond exactement** au contrat PTY WebSocket du carnet
|
||||||
|
(`attach_terminal`/`detach`, PTY autorité serveur, scrollback borné). L'adapter WS
|
||||||
|
implémentera `makeTerminalHandle` en émettant `input/resize/detach/close` sur la socket
|
||||||
|
et en poussant `output(seq,bytes)` dans `onData`. **Le port n'a pas à changer.**
|
||||||
|
|
||||||
|
### 2c. `event` (listen Tauri → WebSocket serveur→client)
|
||||||
|
Un seul événement *portable* : `system.ts` `onDomainEvent("domain://event")` — le bus
|
||||||
|
d'événements domaine (agent launched, rate-limited, backgroundTaskChanged, issueDeleted,
|
||||||
|
progress modèle…). Sur web ⇒ **canal WS serveur→client** (ou multiplexé sur la même
|
||||||
|
socket que le PTY selon l'arbitrage HTTP+WS séparés vs WS unique, laissé à Architect).
|
||||||
|
Les deux autres `listen` (`view-window://lifecycle`, `focused-project://changed`) sont
|
||||||
|
**desktop-only** (§4), pas des events métier.
|
||||||
|
|
||||||
|
### 2d. `desktop-only` (pas de portage web direct — voir §4)
|
||||||
|
`system.pickFolder` (dialog natif), tout `window.ts` (WebviewWindow OS),
|
||||||
|
`focusedProject.ts` (coordination multi-fenêtres OS).
|
||||||
|
|
||||||
|
## 3. Frontière F1 : où placer les deux implémentations
|
||||||
|
|
||||||
|
**La frontière est déjà `src/ports/` ⇄ `src/adapters/`.** F1 = ajouter un dossier
|
||||||
|
`src/adapters/http/` (ou `webws/`) avec une implémentation par port, et un
|
||||||
|
`createHttpWsGateways()` branché dans `resolveGateways()`.
|
||||||
|
|
||||||
|
Découpage recommandé des adapters web par priorité (aligné sur les lots du carnet) :
|
||||||
|
|
||||||
|
- **F2 (premier livrable, request/response only)** : adapters HTTP pour `system.health`,
|
||||||
|
`project`, `layout` (read), `workState`, `ticket` (read), `agent.listAgents` +
|
||||||
|
`onDomainEvent` sur WS. Suffit pour « ouvrir un projet + état read-only ».
|
||||||
|
- **F3 (xterm sur WS)** : adapter WS pour `terminal` (+ réécriture de
|
||||||
|
`makeTerminalHandle` en variante socket). `TerminalView.tsx` **ne change pas** : il ne
|
||||||
|
connaît que le port et un `open/reattach` injecté.
|
||||||
|
- **B6/F(agents)** : `agent.launchAgent/reattach` sur le même transport WS PTY.
|
||||||
|
- **Chat ticket** : `ticket.sendTicketChat` sur WS applicatif.
|
||||||
|
- **desktop-only** : `window`, `focusedProject`, `pickFolder` → implémentations web
|
||||||
|
dégradées/alternatives (§4).
|
||||||
|
|
||||||
|
**Aucun composant métier (`features/`, `app/`) n'appelle Tauri** — garanti par la garde
|
||||||
|
L1. Donc F1 ne touche **que** `src/adapters/`, `src/app/di.tsx` (3-way resolve) et
|
||||||
|
`vite`/build (cible web sans Tauri). C'est la bonne nouvelle du plan.
|
||||||
|
|
||||||
|
## 4. Points desktop-only — décisions à cadrer (Architect)
|
||||||
|
|
||||||
|
Ces trois surfaces n'ont pas d'équivalent WS trivial ; elles ne bloquent **pas** le
|
||||||
|
premier livrable (read-only) mais doivent être tranchées avant les lots concernés :
|
||||||
|
|
||||||
|
1. **`system.pickFolder`** (créer/ouvrir un projet par un dossier local). Sur le
|
||||||
|
serveur, le « dossier » est **sur la machine serveur**, pas sur le client web. ⇒ Il
|
||||||
|
faut un **file-picker serveur** (browse arborescence serveur via HTTP) au lieu du
|
||||||
|
dialog natif OS. Impacte la création/ouverture de projet côté web. **À cadrer.**
|
||||||
|
2. **`window.ts` (détacher un panneau en fenêtre OS)** : concept purement desktop
|
||||||
|
(Tauri `WebviewWindow`). Équivalent web = nouvel **onglet/`window.open`** navigateur,
|
||||||
|
sémantique différente (pas de registre anti-doublon OS). V1 web : dégrader en
|
||||||
|
**no-op** ou en onglet navigateur, la garde L1 permet une impl web distincte sans
|
||||||
|
toucher les composants. **À cadrer (probablement hors-scope V1 web).**
|
||||||
|
3. **`focusedProject.ts`** : coordination fenêtre principale ⇄ fenêtres détachées. Sans
|
||||||
|
fenêtres détachées (point 2), le canal se réduit à un état local. V1 web :
|
||||||
|
implémentation triviale in-memory / `BroadcastChannel` si multi-onglets. **À cadrer.**
|
||||||
|
|
||||||
|
## 5. Signaux pour le plan de lots / contrat de transport
|
||||||
|
|
||||||
|
- ✅ **Rien ne remet en cause le plan.** La frontière gateways transport-neutres du
|
||||||
|
carnet est **déjà réalisée et testée** (garde L1). F1 est un ajout d'adapters, pas un
|
||||||
|
refactor de composants. Gain de risque majeur.
|
||||||
|
- ✅ **Le contrat PTY WebSocket du carnet colle au port existant** : `TerminalHandle`
|
||||||
|
(write/resize/detach/close, detach≠close, scrollback au reattach) mappe 1-pour-1 sur
|
||||||
|
`attach_terminal/input/resize/detach/close` + `attached(scrollback)/output(seq)`.
|
||||||
|
Aucune évolution de port nécessaire pour F3.
|
||||||
|
- ⚠️ **3 flux Channel** (PTY terminal, PTY agent, chat ticket) partagent la même
|
||||||
|
mécanique `onData`/callback — un **transport WS commun** peut les servir tous les
|
||||||
|
trois. À confirmer avec le choix « HTTP+WS séparés vs WS multiplexé » (délégué
|
||||||
|
Architect).
|
||||||
|
- ⚠️ **`pickFolder`** est le seul point request/response qui **change de sémantique** sur
|
||||||
|
web (dossier serveur ≠ dossier client). À arbitrer avant le lot création/ouverture de
|
||||||
|
projet web.
|
||||||
|
- ⚠️ **`window`/`focusedProject`** (multi-fenêtres OS) : probablement **hors V1 web** ;
|
||||||
|
à confirmer pour ne pas gonfler le périmètre.
|
||||||
|
- ℹ️ **`uiPreferences`** (localStorage) marche déjà tel quel sur web — aucun adapter à
|
||||||
|
écrire.
|
||||||
|
- ℹ️ **Convention DTO** : commandes snake_case, payloads camelCase, souvent enveloppés
|
||||||
|
dans `{ request: {...} }`. L'adapter HTTP doit préserver cette enveloppe pour parler au
|
||||||
|
même cœur backend (le miroir backend confirme ce point de contrat).
|
||||||
Reference in New Issue
Block a user