diff --git a/crates/yoi/src/main.rs b/crates/yoi/src/main.rs index f30737f3..03446437 100644 --- a/crates/yoi/src/main.rs +++ b/crates/yoi/src/main.rs @@ -220,7 +220,7 @@ fn resolve_tui_target( connection_resolver: &R, command: CliCommand, selection: &TargetSelection, - workspace_root: &Path, + _workspace_root: &Path, ) -> Result, ParseError> { if selection.explicit_local { return resolve_connection_aware_cli_connection( @@ -240,16 +240,12 @@ fn resolve_tui_target( return Ok(target); } - let workspace_id = match selection.workspace_id.clone() { - Some(workspace_id) => Some(workspace_id), - None => resolve_workspace_id_from_root(workspace_root)?, - }; resolve_connection_aware_cli_connection( connection_resolver, command, selection.explicit_local, selection.backend_url.clone(), - workspace_id.as_deref(), + selection.workspace_id.as_deref(), ) } @@ -1050,12 +1046,6 @@ fn current_dir() -> Result { .map_err(|e| ParseError(format!("failed to resolve current directory: {e}"))) } -#[derive(Debug, Deserialize)] -struct WorkspaceIdentityFile { - #[serde(alias = "workspace_id")] - id: String, -} - #[derive(Debug, Default)] struct ClientConfigFile { default_backend: Option, @@ -1117,34 +1107,6 @@ impl ClientConfigFile { } } -fn resolve_workspace_id_from_root(workspace_root: &Path) -> Result, ParseError> { - let mut current = if workspace_root.is_absolute() { - workspace_root.to_path_buf() - } else { - current_dir()?.join(workspace_root) - }; - loop { - let path = current.join(".yoi").join("workspace.toml"); - if path.is_file() { - let contents = fs::read_to_string(&path) - .map_err(|e| ParseError(format!("failed to read {}: {e}", path.display())))?; - let identity: WorkspaceIdentityFile = toml::from_str(&contents) - .map_err(|e| ParseError(format!("failed to parse {}: {e}", path.display())))?; - let id = identity.id.trim(); - if id.is_empty() { - return Err(ParseError(format!( - "{} must contain a non-empty workspace id", - path.display() - ))); - } - return Ok(Some(id.to_string())); - } - if !current.pop() { - return Ok(None); - } - } -} - fn resolve_backend_url( explicit_backend_url: Option, workspace_id: Option<&str>, @@ -1219,13 +1181,13 @@ fn read_client_config_overlay(path: &Path) -> Result } fn client_global_config_path() -> Option { - manifest::paths::data_dir().map(|dir| dir.join("client").join("config.toml")) + manifest::paths::config_dir().map(|dir| dir.join("client.toml")) } fn client_config_location_message() -> String { match client_global_config_path() { Some(path) => path.display().to_string(), - None => "/client/config.toml".to_string(), + None => "/client.toml".to_string(), } } @@ -2028,6 +1990,8 @@ backend = "shared" match parse_args_from([ "--backend", "http://127.0.0.1:8787", + "--workspace-id", + "workspace-a", "--runtime-id", "runtime-a", "--worker-id", @@ -2447,7 +2411,7 @@ backend = "shared" } #[test] - fn default_backend_target_inherits_workspace_identity_from_workspace_root() { + fn default_backend_target_does_not_read_repository_workspace_identity() { let workspace = tempfile::tempdir().unwrap(); std::fs::create_dir_all(workspace.path().join(".yoi")).unwrap(); std::fs::write( @@ -2467,12 +2431,10 @@ backend = "shared" ) .unwrap(); + assert_eq!(target.kind(), TargetKind::Backend); assert_eq!( - target.resolve().unwrap(), - client::ResolvedTarget::Backend { - base_url: "http://default-backend.example".to_string(), - workspace_id: "workspace-from-root".to_string(), - } + target.resolve().unwrap_err().to_string(), + "invalid Backend target: workspace selection is required for Backend product-state operations", ); } diff --git a/docs/design/standalone-agent-host.md b/docs/design/standalone-agent-host.md index 3eff3604..c8ed3e7f 100644 --- a/docs/design/standalone-agent-host.md +++ b/docs/design/standalone-agent-host.md @@ -31,7 +31,7 @@ worker ## CLI / TUI routing -- `yoi` の connection-aware command は `TargetKind::Standalone | Backend` の二択で dispatch する。`--local` と client config の `default_connection = "local"` は Standalone を選ぶ入力であり、旧 LocalBackend を有効化しない。 Client config は repository `.yoi/client.config.toml` を読まず、repository `.yoi/workspace.toml` は Backend Workspace identity が必要な場合だけ参照する。 +- `yoi` の connection-aware command は `TargetKind::Standalone | Backend` の二択で dispatch する。`--local` は Standalone を、明示的な CLI selector または `$XDG_CONFIG_HOME/yoi/client.toml` の `default_backend` は Backend を選ぶ。repository-local `.yoi/workspace.toml` / `.yoi/client.config.toml` は connection や Workspace selection の authority ではなく、Backend operation の Workspace は明示的な Workspace selector または Server DB catalog から選択する。 - Standalone の通常起動は `StandaloneHost`、restore は専用 `StandaloneStore` の session picker を使う。Workspace Worker list、PID、Unix socket、subprocess は探索しない。 - `workers`、Backend Worker restore、Workspace panel、Ticket、Objective は Backend authority を要求する。Standalone から repository-local filesystem backend へ fallback しない。 - `yoi worker` は Runtime や明示的な process-owned integration が使う direct Worker entrypoint として残るが、通常の `yoi` / TUI 起動経路からは呼び出さない。 diff --git a/docs/development/work-items.md b/docs/development/work-items.md index 34bec74f..7e95cdbb 100644 --- a/docs/development/work-items.md +++ b/docs/development/work-items.md @@ -108,62 +108,9 @@ The first version intentionally does not implement roadmap scheduling, milestone ## Ticket configuration -Workspace Ticket policy is configured by the tracked workspace settings file `.yoi/workspace.toml` under the `[ticket]` table. The old `.yoi/ticket.config.toml` file is obsolete: current code only reads it as a narrow read-only migration fallback when `.yoi/workspace.toml` has no `[ticket]` table. Workspace settings take precedence as soon as `[ticket]` exists. +Workspace Ticket data and workflow authority live in the Workspace Server's SQLite control-plane store. Repository-local `.yoi/workspace.toml` and `.yoi/ticket.config.toml` are not Ticket, Workspace identity, Backend connection, or role-launch authority. -MVP shape: - -```toml -[ticket] -language = "Japanese" - -[ticket.backend] -provider = "builtin:yoi_local" -root = ".yoi/tickets" - -[ticket.roles.intake] -profile = "project:intake" -launch_prompt = "ticket.intake.launch" - -[ticket.roles.orchestrator] -profile = "project:orchestrator" -launch_prompt = "ticket.orchestrator.launch" - -[ticket.roles.coder] -profile = "project:coder" -launch_prompt = "ticket.coder.launch" - -[ticket.roles.reviewer] -profile = "project:reviewer" -launch_prompt = "ticket.reviewer.launch" -``` - -Fixed roles are: - -- `intake` -- `orchestrator` -- `coder` -- `reviewer` - -This is not an arbitrary role registry. The fixed roles are the roles required by Ticket orchestration. -Stale `[ticket.roles.investigator]` config is rejected as an unsupported fixed role; remove it and, -when a spike is useful, let the Orchestrator create an ordinary task-specific read-only helper Worker. - -`profile` selects the Worker runtime Profile for that role. The selected Profile owns durable role/system behavior. Workspace Ticket settings do not have a role-level `system_instruction` field. - -`launch_prompt` is a per-action first-run prompt reference for future prompt resolution. Current launcher behavior exposes the ref but does not treat it as system instruction. - -Role launch prompts are plain history input. State and phase-specific prompt injection are future work; any dynamic prompt content must be committed as history before it affects model context. - -`provider = "builtin:yoi_local"` selects Yoi's built-in local Ticket backend. `root = ".yoi/tickets"` is the canonical local storage root for this repository. Legacy `kind = "local"` is accepted only as a short transitional alias; new configs should use `provider`. - -If `.yoi/workspace.toml` has no `[ticket]` table and no legacy fallback file exists, defaults are: - -- backend provider: `builtin:yoi_local` -- backend root: `/.yoi/tickets` -- all role profiles: `inherit` -- no launch prompt refs - -Important: top-level Ticket role launches cannot execute `profile = "inherit"` because top-level launch has no parent Profile to inherit from. Configure concrete role profiles in `.yoi/workspace.toml` under `[ticket.roles.*]` before using `yoi panel` role-launch actions. +Fixed Ticket workflow roles are `intake`, `orchestrator`, `coder`, and `reviewer`. The Server resolves the selected Profile and launch material from the active Workspace configuration authority, and Runtime receives the resulting immutable launch snapshot. A repository checkout may still contain ordinary project files, but neither the client nor Runtime may infer Workspace identity, Backend routing, role Profile, or Ticket storage from repository-local `.yoi` files. ## Ticket lifecycle @@ -267,7 +214,7 @@ Close with a resolution that summarizes what changed, key commits, validation, r `yoi panel` is the active Ticket/Intake/Orchestrator Dashboard. It owns fixed Ticket role-launch actions and uses the shared client Ticket role launcher. The single-Worker Console no longer supports `:ticket ...` commands; typing them in command mode is treated like any other unknown command. -Role actions map to the same fixed roles configured in `.yoi/workspace.toml` under `[ticket.roles]`: +Role actions map to the fixed Workspace Ticket roles: - intake launches the intake role without an existing Ticket and requires freeform context. - route launches the orchestrator role for an existing Ticket. @@ -278,60 +225,14 @@ All actions are explicit and user-triggered. They are not a scheduler, queue, sp ### Dashboard execution path -The role-launch path is: +The Dashboard sends the selected action and Ticket context to the Workspace Server. The Server validates Workspace access, resolves the current Server DB Workspace/Ticket authority and active Profile projection, launches or restores the role Worker through the shared Worker path, commits the typed initial input, and returns durable acceptance evidence. The client does not inspect repository-local `.yoi` files, choose a Ticket storage directory, or construct Runtime launch authority. -```text -User triggers a Ticket action in yoi panel - -> Dashboard builds a TicketRoleLaunchContext - -> client Ticket role launcher reads .yoi/workspace.toml [ticket] settings - -> launcher selects the role Profile - -> launcher spawns the role Worker - -> launcher sends Method::Submit with Text segments - -> launcher waits for run-acceptance evidence - -> Dashboard reports success/failure -``` - -The launched Worker receives dynamic Ticket/action context as its first committed run input. The Dashboard does not inject hidden context, does not write Ticket files directly, and does not construct prompt segments by hand. - -The first run input contains: - -- the selected fixed role; -- Ticket id when the action targets an existing Ticket; -- freeform user instruction/context from the action; -- configured `launch_prompt` reference if present, as an unresolved reference for future prompt resolution. - -The selected Profile supplies durable system/role behavior. Workspace Ticket settings do not override system instruction. - -### Dashboard setup - -Because top-level role launches cannot inherit a parent Profile, configure concrete role profiles before using Dashboard role actions: - -```toml -# .yoi/workspace.toml - -[ticket.backend] -provider = "builtin:yoi_local" -root = ".yoi/tickets" - -[ticket.roles.intake] -profile = "project:intake" - -[ticket.roles.orchestrator] -profile = "project:orchestrator" - -[ticket.roles.coder] -profile = "project:coder" - -[ticket.roles.reviewer] -profile = "project:reviewer" -``` - -If a role still uses `profile = "inherit"`, the Dashboard fails closed with a diagnostic explaining that a concrete profile is required. +The launched Worker receives dynamic Ticket/action context as its first committed run input. The selected Profile supplies durable system/role behavior. Workspace Ticket metadata does not override system instruction. ### Dashboard troubleshooting -- `profile = "inherit"`: configure a concrete role Profile in `.yoi/workspace.toml` under `[ticket.roles.]`. -- malformed workspace Ticket settings: fix the `[ticket]` table in `.yoi/workspace.toml` and retry. +- unresolved Workspace selection: select an accessible Workspace from the Server catalog or pass an explicit Workspace selector. +- unavailable role Profile: update the active Workspace configuration and retry after the Server projects the new revision. - missing Ticket id for route, implement, or review actions: provide the target Ticket. - launch success but no visible completion: attach to or inspect the launched Worker; completion notifications are hints, not authority.