refactor: rename execution workspace to working directory
This commit is contained in:
parent
0cad2308cc
commit
40463b6133
|
|
@ -20,14 +20,14 @@ Yoi を、単一のローカル開発ディレクトリで動くエージェン
|
||||||
|
|
||||||
- Workspace: チームまたはプロジェクトの管理単位。Ticket、Objective、Memory、Knowledge、Artifact、Policy、Actor、Repository、Runtime state を持つ。Git Repository root ではない。
|
- Workspace: チームまたはプロジェクトの管理単位。Ticket、Objective、Memory、Knowledge、Artifact、Policy、Actor、Repository、Runtime state を持つ。Git Repository root ではない。
|
||||||
- Control plane: Workspace の正本を持ち、Web UI / API / CLI から操作される管理システム。
|
- Control plane: Workspace の正本を持ち、Web UI / API / CLI から操作される管理システム。
|
||||||
- Runtime: Worker 群を束ねる実行基盤。Worker lifecycle、sandbox、mount、cache、checkout/worktree/container filesystem などの Execution Workspace materialization、event/control plane を管理する。将来的には 1 つの Runtime が複数 Workspace / Repository の Worker を抱えられる。
|
- Runtime: Worker 群を束ねる実行基盤。Worker lifecycle、sandbox、mount、cache、checkout/worktree/container filesystem などの working directory materialization、event/control plane を管理する。将来的には 1 つの Runtime が複数 Workspace / Repository の Worker を抱えられる。
|
||||||
- Worker: Runtime が管理する 1 つの agent/session/process。Runtime が用意した Execution Workspace と authority の中で動く。
|
- Worker: Runtime が管理する 1 つの agent/session/process。Runtime が用意した working directory と authority の中で動く。
|
||||||
- Repository: Workspace に接続される source/storage。コード、ドキュメント、local directory、object storage、artifact store、dataset などを含む。Git Repository も Repository の一種であり、基本的には filesystem path ではなく URI / URL で識別する。
|
- Repository: Workspace に接続される source/storage。コード、ドキュメント、local directory、object storage、artifact store、dataset などを含む。Git Repository も Repository の一種であり、基本的には filesystem path ではなく URI / URL で識別する。
|
||||||
- RepositoryId: Workspace 内でどの Repository を対象にするかを指す安定 identifier。Git hash、branch、path ではない。
|
- RepositoryId: Workspace 内でどの Repository を対象にするかを指す安定 identifier。Git hash、branch、path ではない。
|
||||||
- Repository provider: Repository の種類ごとの実装。Git、local filesystem、object store、artifact store、将来の non-Git VCS など。
|
- Repository provider: Repository の種類ごとの実装。Git、local filesystem、object store、artifact store、将来の non-Git VCS など。
|
||||||
- RepositorySelector: Repository provider に渡す未解決の地点指定。branch/tag/PR/revspec/bookmark/revset/path@revision/object version/latest など provider-specific な symbolic / mutable / query-like locator であり、それ自体は再現性の authority ではない。
|
- RepositorySelector: Repository provider に渡す未解決の地点指定。branch/tag/PR/revspec/bookmark/revset/path@revision/object version/latest など provider-specific な symbolic / mutable / query-like locator であり、それ自体は再現性の authority ではない。
|
||||||
- RepositoryPoint: RepositorySelector をある時点で解決した具体地点。Git commit/tree、Mercurial changeset、SVN revision、object store version/manifest digest、file snapshot など provider ごとの immutable / reproducible point を表し、Artifact/evidence に残す。
|
- RepositoryPoint: RepositorySelector をある時点で解決した具体地点。Git commit/tree、Mercurial changeset、SVN revision、object store version/manifest digest、file snapshot など provider ごとの immutable / reproducible point を表し、Artifact/evidence に残す。
|
||||||
- Execution Workspace: Runtime が Worker のために作る作業環境。1 つ以上の RepositoryPoint から materialize される作業用ディレクトリ、container filesystem、sandbox mount の集合であり、Git worktree、clone、sparse checkout などはこれを作る手段である。
|
- working directory: Runtime が Worker のために作る作業環境。1 つ以上の RepositoryPoint から materialize される作業用ディレクトリ、container filesystem、sandbox mount の集合であり、Git worktree、clone、sparse checkout などはこれを作る手段である。
|
||||||
- Ticket: チームで扱う作業単位。目的、要件、判断、議論、完了条件、関係、証跡を持つ。
|
- Ticket: チームで扱う作業単位。目的、要件、判断、議論、完了条件、関係、証跡を持つ。
|
||||||
- Objective: 複数の Ticket を束ねる長期目標や設計方針。
|
- Objective: 複数の Ticket を束ねる長期目標や設計方針。
|
||||||
- Artifact: Ticket や Worker 実行に紐づく成果物や証跡。diff、log、validation result、review result、report など。
|
- Artifact: Ticket や Worker 実行に紐づく成果物や証跡。diff、log、validation result、review result、report など。
|
||||||
|
|
@ -46,7 +46,7 @@ Yoi を、単一のローカル開発ディレクトリで動くエージェン
|
||||||
- 管理システムと Runtime を分ける。
|
- 管理システムと Runtime を分ける。
|
||||||
- まず Web から Ticket、Objective、Memory、Knowledge、Artifact、Runtime / Worker state を見られるようにする。
|
- まず Web から Ticket、Objective、Memory、Knowledge、Artifact、Runtime / Worker state を見られるようにする。
|
||||||
- 最初はローカル Runtime を使い、後でリモート Runtime、クラウド Runtime、runtime pool、resource allocation、quota、billing、sandboxing に拡張する。
|
- 最初はローカル Runtime を使い、後でリモート Runtime、クラウド Runtime、runtime pool、resource allocation、quota、billing、sandboxing に拡張する。
|
||||||
- Git ホスティング機能を取り込むのではなく、Git Repository / worktree / clone は Repository provider と Execution Workspace materialization の手段として扱う。
|
- Git ホスティング機能を取り込むのではなく、Git Repository / worktree / clone は Repository provider と working directory materialization の手段として扱う。
|
||||||
|
|
||||||
OSS として Control plane、Runtime、Web frontend、protocol を公開しつつ、managed service では hosted control plane、runtime fleet、リソース柔軟性、team auth、backup、audit、availability、multi-tenant operations で価値を出す。
|
OSS として Control plane、Runtime、Web frontend、protocol を公開しつつ、managed service では hosted control plane、runtime fleet、リソース柔軟性、team auth、backup、audit、availability、multi-tenant operations で価値を出す。
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ Ticket と Objective は Repository 配下に置かず、Workspace 配下に平
|
||||||
|
|
||||||
RepositorySelector は Git branch/tag の抽象化ではない。Selector は provider-specific な未解決 locator であり、Git provider なら branch/tag/ref/revspec/PR/commit、Mercurial provider なら bookmark/revset/changeset、SVN provider なら path/revision、object store provider なら prefix/version/latest などを解釈する。実行時には Control plane または Repository provider が Selector を RepositoryPoint に解決し、Runtime はその RepositoryPoint を materialize する。
|
RepositorySelector は Git branch/tag の抽象化ではない。Selector は provider-specific な未解決 locator であり、Git provider なら branch/tag/ref/revspec/PR/commit、Mercurial provider なら bookmark/revset/changeset、SVN provider なら path/revision、object store provider なら prefix/version/latest などを解釈する。実行時には Control plane または Repository provider が Selector を RepositoryPoint に解決し、Runtime はその RepositoryPoint を materialize する。
|
||||||
|
|
||||||
Worker launch request は Ticket の target selector を concrete RepositoryPoint に解決し、その RepositoryPoint から Runtime が Execution Workspace を materialize する。Git worktree 相当の機能は、この Execution Workspace を作るための実装戦略として扱う。
|
Worker launch request は Ticket の target selector を concrete RepositoryPoint に解決し、その RepositoryPoint から Runtime が working directory を materialize する。Git worktree 相当の機能は、この working directory を作るための実装戦略として扱う。
|
||||||
|
|
||||||
Backend は cwd や `--workspace` を暗黙の Repository として扱わない。`--workspace` は当面 workspace config root / local descriptor root を指すだけであり、Repository registry は明示設定された Workspace config から構築する。短期的には `.yoi/workspace-backend.local.toml` の `[[repositories]]` を local descriptor として使い、`uri = "."` のような local repository も明示 entry として登録する。`./` を暗黙 Repository として自動採用しない。
|
Backend は cwd や `--workspace` を暗黙の Repository として扱わない。`--workspace` は当面 workspace config root / local descriptor root を指すだけであり、Repository registry は明示設定された Workspace config から構築する。短期的には `.yoi/workspace-backend.local.toml` の `[[repositories]]` を local descriptor として使い、`uri = "."` のような local repository も明示 entry として登録する。`./` を暗黙 Repository として自動採用しない。
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ Ticket target は intent/selector であり、実行再現性のための immuta
|
||||||
Ticket
|
Ticket
|
||||||
-> target selectors: Repository + ref selector + path + intent
|
-> target selectors: Repository + ref selector + path + intent
|
||||||
-> resolved RepositoryPoint
|
-> resolved RepositoryPoint
|
||||||
-> Execution Workspace
|
-> working directory
|
||||||
-> WorkerRef / Artifact / Evidence
|
-> WorkerRef / Artifact / Evidence
|
||||||
-> Review / Decision
|
-> Review / Decision
|
||||||
-> Audit / Notification
|
-> Audit / Notification
|
||||||
|
|
@ -119,7 +119,7 @@ Ticket には次の概念が必要になる。
|
||||||
- Actor identity: human / agent / system / service account.
|
- Actor identity: human / agent / system / service account.
|
||||||
- Assignment / owner / reviewer / watcher.
|
- Assignment / owner / reviewer / watcher.
|
||||||
- Typed thread events: comment, decision, plan, review, implementation report, state transition.
|
- Typed thread events: comment, decision, plan, review, implementation report, state transition.
|
||||||
- Linked Objective / Artifact / WorkerRef / Repository / RepositoryPoint / Execution Workspace.
|
- Linked Objective / Artifact / WorkerRef / Repository / RepositoryPoint / working directory.
|
||||||
- Permission / visibility.
|
- Permission / visibility.
|
||||||
- Audit trail.
|
- Audit trail.
|
||||||
- Notification / mention.
|
- Notification / mention.
|
||||||
|
|
@ -170,17 +170,17 @@ Ticket / user intent
|
||||||
-> RepositoryId + RepositorySelector + path scope + required authority
|
-> RepositoryId + RepositorySelector + path scope + required authority
|
||||||
-> resolved RepositoryPoint
|
-> resolved RepositoryPoint
|
||||||
-> WorkerLaunchRequest / ConfigBundle / AuthorityBundle
|
-> WorkerLaunchRequest / ConfigBundle / AuthorityBundle
|
||||||
-> Runtime ExecutionWorkspaceMaterializer
|
-> Runtime WorkingDirectoryMaterializer
|
||||||
-> Execution Workspace allocation
|
-> working directory allocation
|
||||||
-> Worker process
|
-> Worker process
|
||||||
-> WorkerRef + Artifact/evidence
|
-> WorkerRef + Artifact/evidence
|
||||||
```
|
```
|
||||||
|
|
||||||
Control plane は Workspace authority、Ticket target、Repository registry、Actor permission、設定 bundle の正本を持つ。Control plane または Repository provider は RepositorySelector を RepositoryPoint に解決し、どの地点を対象にしたかを evidence に残す。Runtime は RepositoryPoint、materialization policy、sandbox policy、mount/cache/secret policy、Worker config を受け取り、Runtime-local な Execution Workspace を確保して Worker を起動する。
|
Control plane は Workspace authority、Ticket target、Repository registry、Actor permission、設定 bundle の正本を持つ。Control plane または Repository provider は RepositorySelector を RepositoryPoint に解決し、どの地点を対象にしたかを evidence に残す。Runtime は RepositoryPoint、materialization policy、sandbox policy、mount/cache/secret policy、Worker config を受け取り、Runtime-local な working directory を確保して Worker を起動する。
|
||||||
|
|
||||||
この境界では、Worker は host filesystem path や Repository credential を自分で発見しない。Worker は Runtime が materialize した workspace root、mount、環境変数、tool authority、config bundle だけを見る。Runtime は Execution Workspace の lifecycle、cleanup、cache reuse、namespace、quota、sandbox boundary、event collection を管理する。Control plane / Browser-facing API は raw host path、secret、socket、internal runtime path を authority-bearing internals として扱い、必要な evidence だけを Artifact として公開する。
|
この境界では、Worker は host filesystem path や Repository credential を自分で発見しない。Worker は Runtime が materialize した workspace root、mount、環境変数、tool authority、config bundle だけを見る。Runtime は working directory の lifecycle、cleanup、cache reuse、namespace、quota、sandbox boundary、event collection を管理する。Control plane / Browser-facing API は raw host path、secret、socket、internal runtime path を authority-bearing internals として扱い、必要な evidence だけを Artifact として公開する。
|
||||||
|
|
||||||
v0 materializer は existing local root を明示的な Execution Workspace として返してよい。ただし型と呼び出し順序は、後で Git worktree、clone、sparse checkout、container filesystem、remote object snapshot、multi-repository mount に置き換えられる形にする。Runtime process 起動時の `--workspace` はこの v0 materializer の legacy bootstrap input であり、Runtime identity や long-term workspace binding ではない。
|
v0 materializer は existing local root を明示的な working directory として返してよい。ただし型と呼び出し順序は、後で Git worktree、clone、sparse checkout、container filesystem、remote object snapshot、multi-repository mount に置き換えられる形にする。Runtime process 起動時の `--workspace` はこの v0 materializer の legacy bootstrap input であり、Runtime identity や long-term workspace binding ではない。
|
||||||
|
|
||||||
その後で、remote Runtime、self-hosted Runtime、hosted cloud runtime fleet、runtime pool、resource allocation、quota、billing、sandbox、network policy、secret distribution を追加する。
|
その後で、remote Runtime、self-hosted Runtime、hosted cloud runtime fleet、runtime pool、resource allocation、quota、billing、sandbox、network policy、secret distribution を追加する。
|
||||||
|
|
||||||
|
|
@ -204,7 +204,7 @@ Web UI は Ticket、Objective、Memory、Knowledge、Runtime、Worker、Artifact
|
||||||
|
|
||||||
### 7. 多重起動コストと runtime placement を見直す
|
### 7. 多重起動コストと runtime placement を見直す
|
||||||
|
|
||||||
Cloud/remote execution を成立させるには、多数のエージェント実行を安く管理できる必要がある。logical Worker session と Runtime process/resource placement を分ける。Runtime は Git Repository root や Workspace path に固定されず、request ごとに必要な Execution Workspace を materialize できる実行基盤として扱う。
|
Cloud/remote execution を成立させるには、多数のエージェント実行を安く管理できる必要がある。logical Worker session と Runtime process/resource placement を分ける。Runtime は Git Repository root や Workspace path に固定されず、request ごとに必要な working directory を materialize できる実行基盤として扱う。
|
||||||
|
|
||||||
初期 Workspace DB では、Worker を canonical table として永続化しない。Runtime / Worker 一覧は backend-local runtime inspection や将来の Runtime protocol から逐次取得する live view とし、Ticket に関わった Worker は Ticket thread events と WorkerRef snapshot / TicketWorkerLink として記録する。
|
初期 Workspace DB では、Worker を canonical table として永続化しない。Runtime / Worker 一覧は backend-local runtime inspection や将来の Runtime protocol から逐次取得する live view とし、Ticket に関わった Worker は Ticket thread events と WorkerRef snapshot / TicketWorkerLink として記録する。
|
||||||
|
|
||||||
|
|
@ -214,7 +214,7 @@ Worker の一元管理、データ永続化、アーカイブは将来的には
|
||||||
|
|
||||||
- Worker identity と Runtime process/resource placement の分離。
|
- Worker identity と Runtime process/resource placement の分離。
|
||||||
- 1 Runtime が複数 Workspace / Repository の Worker を抱える場合の namespace、quota、cleanup、audit boundary。
|
- 1 Runtime が複数 Workspace / Repository の Worker を抱える場合の namespace、quota、cleanup、audit boundary。
|
||||||
- Runtime-side Execution Workspace materialization、sandbox、mount、checkout/worktree/container filesystem の責務。
|
- Runtime-side working directory materialization、sandbox、mount、checkout/worktree/container filesystem の責務。
|
||||||
- Provider client、tool registry、resource cache の共有可能性。
|
- Provider client、tool registry、resource cache の共有可能性。
|
||||||
- Prompt/resource/profile/config bundle resolution cache。
|
- Prompt/resource/profile/config bundle resolution cache。
|
||||||
- Model call multiplexing and scheduling。
|
- Model call multiplexing and scheduling。
|
||||||
|
|
@ -226,7 +226,7 @@ Worker の一元管理、データ永続化、アーカイブは将来的には
|
||||||
## Initial phases / candidate tickets
|
## Initial phases / candidate tickets
|
||||||
|
|
||||||
1. **Vocabulary / architecture record**
|
1. **Vocabulary / architecture record**
|
||||||
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / Execution Workspace / Runtime / Worker / Control Plane / Ticket / Memory / Knowledge の用語と境界を固める。
|
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / working directory / Runtime / Worker / Control Plane / Ticket / Memory / Knowledge の用語と境界を固める。
|
||||||
2. **Team-space canonical data model**
|
2. **Team-space canonical data model**
|
||||||
- Ticket / Objective / Target / Artifact / Actor / Permission / Audit / Memory / Knowledge の entity/event model を設計する。
|
- Ticket / Objective / Target / Artifact / Actor / Permission / Audit / Memory / Knowledge の entity/event model を設計する。
|
||||||
3. **Ticket evidence model**
|
3. **Ticket evidence model**
|
||||||
|
|
@ -238,8 +238,8 @@ Worker の一元管理、データ永続化、アーカイブは将来的には
|
||||||
6. **Web control plane MVP design**
|
6. **Web control plane MVP design**
|
||||||
- read-only Ticket / Objective / Memory / Knowledge / Runtime / Worker state UI/API の範囲を決める。
|
- read-only Ticket / Objective / Memory / Knowledge / Runtime / Worker state UI/API の範囲を決める。
|
||||||
7. **Local Runtime protocol design**
|
7. **Local Runtime protocol design**
|
||||||
- Web/control plane から local Runtime に安全な操作を送り、Runtime が Worker lifecycle と Execution Workspace materialization を担う protocol と authority boundary を設計する。
|
- Web/control plane から local Runtime に安全な操作を送り、Runtime が Worker lifecycle と working directory materialization を担う protocol と authority boundary を設計する。
|
||||||
8. **Repository and Execution Workspace materialization model**
|
8. **Repository and working directory materialization model**
|
||||||
- Repository URI、Repository provider capability、RepositorySelector resolution、RepositoryPoint evidence、Git worktree / clone / sparse checkout / future source backend を Runtime-side materialization strategy として抽象化する。
|
- Repository URI、Repository provider capability、RepositorySelector resolution、RepositoryPoint evidence、Git worktree / clone / sparse checkout / future source backend を Runtime-side materialization strategy として抽象化する。
|
||||||
9. **Remote/hosted runtime foundation**
|
9. **Remote/hosted runtime foundation**
|
||||||
- runtime registration, heartbeat, capability advertisement, job assignment, logs/events, secrets, sandbox/resource policy を設計する。
|
- runtime registration, heartbeat, capability advertisement, job assignment, logs/events, secrets, sandbox/resource policy を設計する。
|
||||||
|
|
@ -258,7 +258,7 @@ Worker の一元管理、データ永続化、アーカイブは将来的には
|
||||||
|
|
||||||
## Success criteria / exit conditions
|
## Success criteria / exit conditions
|
||||||
|
|
||||||
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / Execution Workspace / Runtime / Worker / Control Plane / Ticket / Memory / Knowledge の境界が文書化されている。
|
- Workspace / RepositoryId / RepositorySelector / RepositoryPoint / working directory / Runtime / Worker / Control Plane / Ticket / Memory / Knowledge の境界が文書化されている。
|
||||||
- Ticket が team coordination record として、target selector / Artifact / Actor / Permission / Audit と分離された model を持つ。
|
- Ticket が team coordination record として、target selector / Artifact / Actor / Permission / Audit と分離された model を持つ。
|
||||||
- `.yoi` local backend は compatibility/local backend として整理され、server-side canonical backend の設計を阻害しない。
|
- `.yoi` local backend は compatibility/local backend として整理され、server-side canonical backend の設計を阻害しない。
|
||||||
- Web UI/API が Ticket / Objective / Runtime / Worker state を中心とした read-only view を提供できる設計または MVP を持つ。Memory / Knowledge は既存 record の表示または将来 placeholder に留め、本格再設計をこの段階の必須条件にしない。
|
- Web UI/API が Ticket / Objective / Runtime / Worker state を中心とした read-only view を提供できる設計または MVP を持つ。Memory / Knowledge は既存 record の表示または将来 placeholder に留め、本格再設計をこの段階の必須条件にしない。
|
||||||
|
|
@ -266,7 +266,7 @@ Worker の一元管理、データ永続化、アーカイブは将来的には
|
||||||
- Runtime は single Workspace / Git repository root 専用 process ではなく、sandbox/authority が成立すれば複数 Workspace / Repository の Worker を抱えられる execution substrate として設計されている。
|
- Runtime は single Workspace / Git repository root 専用 process ではなく、sandbox/authority が成立すれば複数 Workspace / Repository の Worker を抱えられる execution substrate として設計されている。
|
||||||
- Git Repository root に依存しない Workspace model があり、Git Repository は Repository provider の一種として扱われている。
|
- Git Repository root に依存しない Workspace model があり、Git Repository は Repository provider の一種として扱われている。
|
||||||
- Ticket と Objective は Workspace 配下に平たく存在し、Repository への所属ではなく RepositoryId / RepositorySelector / path scope / intent で対象を表現する。
|
- Ticket と Objective は Workspace 配下に平たく存在し、Repository への所属ではなく RepositoryId / RepositorySelector / path scope / intent で対象を表現する。
|
||||||
- Git worktree 相当は Execution Workspace materialization strategy として扱われ、Artifact/evidence が concrete RepositoryPoint を記録する。
|
- Git worktree 相当は working directory materialization strategy として扱われ、Artifact/evidence が concrete RepositoryPoint を記録する。
|
||||||
- Memory / Knowledge は Ticket / Artifact の authority を置き換えない record として platform contract だけを持つ。本格的な意味論・抽出・承認・検索・staleness 処理は、Memory の保存先を Workspace backend / control plane record に移すタイミングで回収する。
|
- Memory / Knowledge は Ticket / Artifact の authority を置き換えない record として platform contract だけを持つ。本格的な意味論・抽出・承認・検索・staleness 処理は、Memory の保存先を Workspace backend / control plane record に移すタイミングで回収する。
|
||||||
- Hosted Runtime / resource allocation / SaaS offering に進むための後続 Ticket が切れる状態になっている。
|
- Hosted Runtime / resource allocation / SaaS offering に進むための後続 Ticket が切れる状態になっている。
|
||||||
- 既存 local dogfooding runtime を壊さず、local use と remote-capable architecture が両立している。
|
- 既存 local dogfooding runtime を壊さず、local use と remote-capable architecture が両立している。
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: "Runtime execution workspace materialization and sandboxed agent environments"
|
title: "Runtime working directory materialization and sandboxed agent environments"
|
||||||
state: "active"
|
state: "active"
|
||||||
created_at: "2026-07-06T16:28:12Z"
|
created_at: "2026-07-06T16:28:12Z"
|
||||||
updated_at: "2026-07-06T16:28:12Z"
|
updated_at: "2026-07-06T16:28:12Z"
|
||||||
|
|
@ -8,11 +8,11 @@ linked_tickets: ["00001KWPC13WQ", "00001KWMBAA6V"]
|
||||||
|
|
||||||
## Goal
|
## Goal
|
||||||
|
|
||||||
Runtime が Worker ごとに安全で安価な作業環境を用意できるようにする。Yoi の Runtime は、単に既存ディレクトリで Worker process を起動する launcher ではなく、RepositoryPoint から Execution Workspace を materialize し、sandbox / mount / cache / cleanup / evidence を管理する実行基盤になる。
|
Runtime が Worker ごとに安全で安価な作業環境を用意できるようにする。Yoi の Runtime は、単に既存ディレクトリで Worker process を起動する launcher ではなく、RepositoryPoint から working directory を materialize し、sandbox / mount / cache / cleanup / evidence を管理する実行基盤になる。
|
||||||
|
|
||||||
この Objective の中心は、Worktree ライクな作業ディレクトリ管理、Repository cache、Execution Workspace allocation、sandboxed agent environment の境界を設計し、将来的に 1 つの Runtime が複数 Workspace / Repository の Worker を抱えられるようにすることである。
|
この Objective の中心は、Worktree ライクな作業ディレクトリ管理、Repository cache、working directory allocation、sandboxed agent environment の境界を設計し、将来的に 1 つの Runtime が複数 Workspace / Repository の Worker を抱えられるようにすることである。
|
||||||
|
|
||||||
初期実装では Git/local repository を主対象にしてよい。ただし設計は Git worktree 固定にしない。Git worktree、bare object cache、sparse checkout、copy-on-write snapshot、reflink copy、APFS clonefile、btrfs snapshot、overlay filesystem、container filesystem、remote object snapshot は、すべて Execution Workspace materialization strategy の候補として扱う。
|
初期実装では Git/local repository を主対象にしてよい。ただし設計は Git worktree 固定にしない。Git worktree、bare object cache、sparse checkout、copy-on-write snapshot、reflink copy、APFS clonefile、btrfs snapshot、overlay filesystem、container filesystem、remote object snapshot は、すべて working directory materialization strategy の候補として扱う。
|
||||||
|
|
||||||
## Motivation / background
|
## Motivation / background
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ Runtime が Worker ごとに安全で安価な作業環境を用意できるよ
|
||||||
- Worker ごとに full clone すると容量と時間のコストが大きすぎる。
|
- Worker ごとに full clone すると容量と時間のコストが大きすぎる。
|
||||||
- `.yoi` / Backend fs-store / Workspace descriptor と、Worker 実行用 checkout / scratch / build output の lifecycle が混ざりやすい。
|
- `.yoi` / Backend fs-store / Workspace descriptor と、Worker 実行用 checkout / scratch / build output の lifecycle が混ざりやすい。
|
||||||
|
|
||||||
Yoi は Workspace / Repository / Runtime を分ける方針になっている。Backend は Repository registry、RepositorySelector、RepositoryPoint、Ticket/Artifact evidence の authority を持つ。一方 Runtime は RepositoryPoint を受け取り、Worker が使う Execution Workspace を用意する責務を持つべきである。
|
Yoi は Workspace / Repository / Runtime を分ける方針になっている。Backend は Repository registry、RepositorySelector、RepositoryPoint、Ticket/Artifact evidence の authority を持つ。一方 Runtime は RepositoryPoint を受け取り、Worker が使う working directory を用意する責務を持つべきである。
|
||||||
|
|
||||||
したがって、Repository を持ってくる実体ディレクトリは Backend / Workspace store ではなく Runtime 管理領域に置く。`./.yoi` は local descriptor / compatibility / project record surface であり、Worker ごとの checkout、worktree、snapshot、sandbox root、build cache、dependency cache を置く場所ではない。
|
したがって、Repository を持ってくる実体ディレクトリは Backend / Workspace store ではなく Runtime 管理領域に置く。`./.yoi` は local descriptor / compatibility / project record surface であり、Worker ごとの checkout、worktree、snapshot、sandbox root、build cache、dependency cache を置く場所ではない。
|
||||||
|
|
||||||
|
|
@ -32,11 +32,11 @@ Yoi は Workspace / Repository / Runtime を分ける方針になっている。
|
||||||
|
|
||||||
## Glossary
|
## Glossary
|
||||||
|
|
||||||
- Runtime root: Runtime が自身の store、cache、Worker metadata、Execution Workspace allocation を管理する root。長期的には `~/.yoi/runtimes/<runtime-id>/` 配下など、Workspace backend store とは別に置く。
|
- Runtime root: Runtime が自身の store、cache、Worker metadata、working directory allocation を管理する root。長期的には `~/.yoi/runtimes/<runtime-id>/` 配下など、Workspace backend store とは別に置く。
|
||||||
- Repository cache: Runtime-local の共有 source/cache。Git なら bare mirror / object cache / packfile cache など。重く、長寿命で、複数 Worker allocation から共有される。
|
- Repository cache: Runtime-local の共有 source/cache。Git なら bare mirror / object cache / packfile cache など。重く、長寿命で、複数 Worker allocation から共有される。
|
||||||
- Execution Workspace: Worker ごとの作業環境。短寿命で、Worker が読み書きする root / mounts / scratch / overlay を含む。
|
- working directory: Worker ごとの作業環境。短寿命で、Worker が読み書きする root / mounts / scratch / overlay を含む。
|
||||||
- Materialization strategy: RepositoryPoint から Execution Workspace を作る実装戦略。Git detached worktree、sparse checkout、CoW snapshot、reflink copy、overlay、container filesystem など。
|
- Materialization strategy: RepositoryPoint から working directory を作る実装戦略。Git detached worktree、sparse checkout、CoW snapshot、reflink copy、overlay、container filesystem など。
|
||||||
- ExecutionWorkspaceAllocation: Runtime が払い出した作業環境の record。allocation id、worker id、workspace id、repository point、materializer kind、root、mounts、cleanup policy、status を持つ。
|
- WorkingDirectoryAllocation: Runtime が払い出した作業環境の record。allocation id、worker id、workspace id、repository point、materializer kind、root、mounts、cleanup policy、status を持つ。
|
||||||
- Sandbox policy: Worker が見られる filesystem、network、process、secret、tool authority の境界を表す policy。
|
- Sandbox policy: Worker が見られる filesystem、network、process、secret、tool authority の境界を表す policy。
|
||||||
- Dirty state policy: local uncommitted changes を Worker environment に含めるかどうかの方針。clean point only、patch artifact apply、snapshot current worktree など。
|
- Dirty state policy: local uncommitted changes を Worker environment に含めるかどうかの方針。clean point only、patch artifact apply、snapshot current worktree など。
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ Runtime root
|
||||||
- config bundles
|
- config bundles
|
||||||
- worker metadata / transcript references
|
- worker metadata / transcript references
|
||||||
- repository-cache
|
- repository-cache
|
||||||
- execution-workspaces
|
- working-directories
|
||||||
- sandbox state
|
- sandbox state
|
||||||
- build/dependency cache
|
- build/dependency cache
|
||||||
- cleanup ledger
|
- cleanup ledger
|
||||||
|
|
@ -94,7 +94,7 @@ Runtime root
|
||||||
git/
|
git/
|
||||||
<repository-cache-key>/
|
<repository-cache-key>/
|
||||||
bare.git
|
bare.git
|
||||||
execution-workspaces/
|
working-directories/
|
||||||
<allocation-id>/
|
<allocation-id>/
|
||||||
root/
|
root/
|
||||||
mounts/
|
mounts/
|
||||||
|
|
@ -106,14 +106,14 @@ Runtime root
|
||||||
|
|
||||||
### 2. clone ではなく materialize と呼ぶ
|
### 2. clone ではなく materialize と呼ぶ
|
||||||
|
|
||||||
Runtime は Repository を毎回 clone するのではない。Runtime は RepositoryPoint を Worker 用の Execution Workspace として materialize する。
|
Runtime は Repository を毎回 clone するのではない。Runtime は RepositoryPoint を Worker 用の working directory として materialize する。
|
||||||
|
|
||||||
```text
|
```text
|
||||||
RepositoryId + RepositorySelector
|
RepositoryId + RepositorySelector
|
||||||
-> resolved RepositoryPoint
|
-> resolved RepositoryPoint
|
||||||
-> Runtime repository-cache
|
-> Runtime repository-cache
|
||||||
-> ExecutionWorkspaceMaterializer
|
-> WorkingDirectoryMaterializer
|
||||||
-> ExecutionWorkspaceAllocation
|
-> WorkingDirectoryAllocation
|
||||||
-> Worker process
|
-> Worker process
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -130,13 +130,13 @@ Git の場合でも materialization strategy は複数あり得る。
|
||||||
|
|
||||||
### 3. Repository cache と Worker workspace を分離する
|
### 3. Repository cache と Worker workspace を分離する
|
||||||
|
|
||||||
full clone を Worker ごとに作らない。重い source/object data は Runtime-local repository cache に集約し、Worker ごとの Execution Workspace は cheap allocation にする。
|
full clone を Worker ごとに作らない。重い source/object data は Runtime-local repository cache に集約し、Worker ごとの working directory は cheap allocation にする。
|
||||||
|
|
||||||
Git v0 の方向:
|
Git v0 の方向:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
repository-cache/git/<repo-key>/bare.git
|
repository-cache/git/<repo-key>/bare.git
|
||||||
execution-workspaces/<allocation-id>/root/<repository-id>/
|
working-directories/<allocation-id>/root/<repository-id>/
|
||||||
```
|
```
|
||||||
|
|
||||||
初回:
|
初回:
|
||||||
|
|
@ -174,7 +174,7 @@ Dirty state を含める場合、Artifact/evidence には source RepositoryPoint
|
||||||
|
|
||||||
### 5. Sandbox を materialization と同じ境界で扱う
|
### 5. Sandbox を materialization と同じ境界で扱う
|
||||||
|
|
||||||
Execution Workspace は単なる directory path ではなく、Worker が見てよい filesystem view である。Runtime は Execution Workspace allocation と同時に sandbox/mount/authority を構築する。
|
working directory は単なる directory path ではなく、Worker が見てよい filesystem view である。Runtime は working directory allocation と同時に sandbox/mount/authority を構築する。
|
||||||
|
|
||||||
Worker に渡すもの:
|
Worker に渡すもの:
|
||||||
|
|
||||||
|
|
@ -197,14 +197,14 @@ Worker が自分で発見してはいけないもの:
|
||||||
|
|
||||||
Sandbox v0 は strong isolation でなくてもよい。ただし型と lifecycle は、後で container sandbox、namespace, mount filtering, network policy, secret boundary に拡張できる形にする。
|
Sandbox v0 は strong isolation でなくてもよい。ただし型と lifecycle は、後で container sandbox、namespace, mount filtering, network policy, secret boundary に拡張できる形にする。
|
||||||
|
|
||||||
### 6. Execution Workspace registry を持つ
|
### 6. working directory registry を持つ
|
||||||
|
|
||||||
Runtime は materialized workspace を filesystem だけでなく registry でも管理する。
|
Runtime は materialized workspace を filesystem だけでなく registry でも管理する。
|
||||||
|
|
||||||
必要な record:
|
必要な record:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ExecutionWorkspaceAllocation
|
WorkingDirectoryAllocation
|
||||||
id
|
id
|
||||||
runtime_id
|
runtime_id
|
||||||
worker_id
|
worker_id
|
||||||
|
|
@ -245,26 +245,26 @@ Rift の filtered CoW creation のように、重い artifacts を除外しな
|
||||||
|
|
||||||
### Phase 1: Materialization boundary and legacy allocation record
|
### Phase 1: Materialization boundary and legacy allocation record
|
||||||
|
|
||||||
- `CreateWorkerRequest` に Execution Workspace request / target placeholder を追加する。
|
- `CreateWorkerRequest` に working directory request / target placeholder を追加する。
|
||||||
- `ExecutionWorkspaceMaterializer` trait を `worker-runtime` に追加する。
|
- `WorkingDirectoryMaterializer` trait を `worker-runtime` に追加する。
|
||||||
- `WorkerRuntimeExecutionBackend` が Worker spawn 前に materializer を呼ぶ順序にする。
|
- `WorkerRuntimeExecutionBackend` が Worker spawn 前に materializer を呼ぶ順序にする。
|
||||||
- v0 materializer は existing local root を explicit allocation として返してよいが、`direct_legacy_mount` として明示し、通常設計の final form と混同しない。
|
- v0 materializer は existing local root を explicit allocation として返してよいが、`direct_legacy_mount` として明示し、通常設計の final form と混同しない。
|
||||||
- Allocation record / cleanup policy / diagnostics の型を先に作る。
|
- Allocation record / cleanup policy / diagnostics の型を先に作る。
|
||||||
- Worker が source repository root を直接 scope として要求する経路を deprecated/legacy に閉じ込める。
|
- Worker が source repository root を直接 scope として要求する経路を deprecated/legacy に閉じ込める。
|
||||||
|
|
||||||
### Phase 2: Runtime root and execution workspace storage
|
### Phase 2: Runtime root and working directory storage
|
||||||
|
|
||||||
- `--runtime-root` を導入し、Runtime state / repository-cache / execution-workspaces / worker metadata / worker runtime dirs を Runtime root 配下へ寄せる。
|
- `--runtime-root` を導入し、Runtime state / repository-cache / working-directories / worker metadata / worker runtime dirs を Runtime root 配下へ寄せる。
|
||||||
- `--workspace` は legacy bootstrap input としてだけ扱い、Runtime identity / long-term workspace binding から外す。
|
- `--workspace` は legacy bootstrap input としてだけ扱い、Runtime identity / long-term workspace binding から外す。
|
||||||
- Runtime root default は user data 配下にする。
|
- Runtime root default は user data 配下にする。
|
||||||
- Execution Workspace allocation registry を Runtime root に保存する。
|
- working directory allocation registry を Runtime root に保存する。
|
||||||
|
|
||||||
### Phase 3: Git cached detached worktree materializer
|
### Phase 3: Git cached detached worktree materializer
|
||||||
|
|
||||||
- Git repository cache を Runtime root に作る。
|
- Git repository cache を Runtime root に作る。
|
||||||
- RepositorySelector を RepositoryPoint に解決する呼び出し境界を作る。
|
- RepositorySelector を RepositoryPoint に解決する呼び出し境界を作る。
|
||||||
- resolved commit/tree を evidence として残す。
|
- resolved commit/tree を evidence として残す。
|
||||||
- Worker ごとに detached worktree を `execution-workspaces/<allocation-id>/root/<repository-id>` に作る。
|
- Worker ごとに detached worktree を `working-directories/<allocation-id>/root/<repository-id>` に作る。
|
||||||
- Worker stop / cleanup 時に `git worktree remove` と registry cleanup を行う。
|
- Worker stop / cleanup 時に `git worktree remove` と registry cleanup を行う。
|
||||||
- dirty state は v0 では `clean_point_only` を default にし、dirty local workspace は明示 diagnostic で拒否する。
|
- dirty state は v0 では `clean_point_only` を default にし、dirty local workspace は明示 diagnostic で拒否する。
|
||||||
|
|
||||||
|
|
@ -284,13 +284,13 @@ Rift の filtered CoW creation のように、重い artifacts を除外しな
|
||||||
|
|
||||||
- container filesystem / mount namespace / network policy / secret handle / tool authority を Runtime allocation と統合する。
|
- container filesystem / mount namespace / network policy / secret handle / tool authority を Runtime allocation と統合する。
|
||||||
- Runtime が複数 Workspace / Repository の Worker を同時に抱える場合の namespace、quota、cleanup、audit boundary を固める。
|
- Runtime が複数 Workspace / Repository の Worker を同時に抱える場合の namespace、quota、cleanup、audit boundary を固める。
|
||||||
- remote/self-hosted/hosted Runtime fleet で repository cache と execution workspace storage をどう扱うかを設計する。
|
- remote/self-hosted/hosted Runtime fleet で repository cache と working directory storage をどう扱うかを設計する。
|
||||||
|
|
||||||
## Non-goals
|
## Non-goals
|
||||||
|
|
||||||
- v0 で完全な container sandbox を実装すること。
|
- v0 で完全な container sandbox を実装すること。
|
||||||
- Worker ごとに full clone すること。
|
- Worker ごとに full clone すること。
|
||||||
- `.yoi` や Backend workspace store に Worker execution workspace を置くこと。
|
- `.yoi` や Backend workspace store に Worker working directory を置くこと。
|
||||||
- Git worktree を唯一の materialization strategy として固定すること。
|
- Git worktree を唯一の materialization strategy として固定すること。
|
||||||
- Dirty local changes を暗黙に Worker に渡すこと。
|
- Dirty local changes を暗黙に Worker に渡すこと。
|
||||||
- Browser-facing API に raw host path、secret、Runtime internal store path を公開すること。
|
- Browser-facing API に raw host path、secret、Runtime internal store path を公開すること。
|
||||||
|
|
@ -298,10 +298,10 @@ Rift の filtered CoW creation のように、重い artifacts を除外しな
|
||||||
|
|
||||||
## Success criteria / exit conditions
|
## Success criteria / exit conditions
|
||||||
|
|
||||||
- Runtime root、Repository cache、Execution Workspace、Backend/Workspace store の境界が文書化されている。
|
- Runtime root、Repository cache、working directory、Backend/Workspace store の境界が文書化されている。
|
||||||
- Runtime が Worker spawn 前に Execution Workspace materializer を呼ぶ型と順序を持つ。
|
- Runtime が Worker spawn 前に working directory materializer を呼ぶ型と順序を持つ。
|
||||||
- Worker は source repository root ではなく materialized Execution Workspace を scope として起動する。
|
- Worker は source repository root ではなく materialized working directory を scope として起動する。
|
||||||
- Execution Workspace allocation が Runtime registry に記録され、cleanup policy を持つ。
|
- working directory allocation が Runtime registry に記録され、cleanup policy を持つ。
|
||||||
- Git/local repository の v0 materializer が full clone 連発ではなく shared cache / detached worktree / cheap allocation の方向に進んでいる。
|
- Git/local repository の v0 materializer が full clone 連発ではなく shared cache / detached worktree / cheap allocation の方向に進んでいる。
|
||||||
- dirty state policy が明示され、clean point、patch artifact、snapshot のどれを使ったか evidence に残せる。
|
- dirty state policy が明示され、clean point、patch artifact、snapshot のどれを使ったか evidence に残せる。
|
||||||
- Runtime process 起動時の `--workspace` は legacy bootstrap input として隔離され、Runtime identity や single workspace binding とみなされない。
|
- Runtime process 起動時の `--workspace` は legacy bootstrap input として隔離され、Runtime identity や single workspace binding とみなされない。
|
||||||
|
|
@ -315,10 +315,10 @@ Rift の filtered CoW creation のように、重い artifacts を除外しな
|
||||||
## Decision context
|
## Decision context
|
||||||
|
|
||||||
- Runtime は Worker 群を束ねる実行基盤であり、Worker 用の作業環境を用意する責務を持つ。
|
- Runtime は Worker 群を束ねる実行基盤であり、Worker 用の作業環境を用意する責務を持つ。
|
||||||
- Repository は clone されるものではなく、RepositoryPoint から Worker 用 Execution Workspace として materialize される。
|
- Repository は clone されるものではなく、RepositoryPoint から Worker 用 working directory として materialize される。
|
||||||
- Runtime execution storage は Backend/Workspace store と分離する。`.yoi` は Worker execution workspace 置き場ではない。
|
- Runtime execution storage は Backend/Workspace store と分離する。`.yoi` は Worker working directory 置き場ではない。
|
||||||
- full clone を Worker ごとに作らない。Runtime-local repository cache と Worker-local cheap workspace allocation を分ける。
|
- full clone を Worker ごとに作らない。Runtime-local repository cache と Worker-local cheap workspace allocation を分ける。
|
||||||
- Git detached worktree は v0 materialization strategy として有力だが、Git worktree 固定の設計にはしない。
|
- Git detached worktree は v0 materialization strategy として有力だが、Git worktree 固定の設計にはしない。
|
||||||
- CoW snapshot / reflink / btrfs snapshot / APFS clonefile / Rift-like workspace creation は、将来の materializer backend として検討する。
|
- CoW snapshot / reflink / btrfs snapshot / APFS clonefile / Rift-like workspace creation は、将来の materializer backend として検討する。
|
||||||
- Dirty local state は暗黙に渡さず、policy と evidence を持って扱う。
|
- Dirty local state は暗黙に渡さず、policy と evidence を持って扱う。
|
||||||
- Sandbox は後付けの別機能ではなく、Execution Workspace allocation と同じ境界で扱う。
|
- Sandbox は後付けの別機能ではなく、working directory allocation と同じ境界で扱う。
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ queued_at: '2026-07-04T19:34:04Z'
|
||||||
|
|
||||||
## 非目標
|
## 非目標
|
||||||
|
|
||||||
- Runtime materialization / Execution Workspace 作成をこのチケットで実装すること。
|
- Runtime materialization / working directory 作成をこのチケットで実装すること。
|
||||||
- multi-workspace Backend store を `~/.yoi/` に完全移行すること。
|
- multi-workspace Backend store を `~/.yoi/` に完全移行すること。
|
||||||
- Repository credential / clone / fetch / write operation を実装すること。
|
- Repository credential / clone / fetch / write operation を実装すること。
|
||||||
- Ticket target から RepositoryPoint を解決して Worker launch に渡すこと。
|
- Ticket target から RepositoryPoint を解決して Worker launch に渡すこと。
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ Implementation latitude:
|
||||||
- focused tests の構成は backend helper/route-level と web model/component-levelの現実的な範囲で選んでよい。
|
- focused tests の構成は backend helper/route-level と web model/component-levelの現実的な範囲で選んでよい。
|
||||||
|
|
||||||
Escalate if:
|
Escalate if:
|
||||||
- credential/clone/fetch/write operation、Runtime materialization、Execution Workspace 作成、multi-workspace store 移行、Ticket target からの RepositoryPoint 解決が必要になる場合。
|
- credential/clone/fetch/write operation、Runtime materialization、working directory 作成、multi-workspace store 移行、Ticket target からの RepositoryPoint 解決が必要になる場合。
|
||||||
- Browser-facing API に secret/auth ref/internal absolute paths を出さないと実装できない場合。
|
- Browser-facing API に secret/auth ref/internal absolute paths を出さないと実装できない場合。
|
||||||
- `RepositorySelector` / `RepositoryPoint` の概念境界を設計変更する必要が出た場合。
|
- `RepositorySelector` / `RepositoryPoint` の概念境界を設計変更する必要が出た場合。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"id":"orch-plan-20260706-182951-1","ticket_id":"00001KWW9DYH4","kind":"accepted_plan","accepted_plan":{"summary":"Ticket 00001KWW9DYH4 は独立 queued Ticket で blocker なし。Repository registry の次段として、local Git configured repository から Runtime root 配下に Worker ごとの detached Execution Workspace を materialize する v0 を実装する。human authorized routing 済みのため queued->inprogress acceptance 後に worktree + coder/reviewer loop へ進める。","branch":"work/00001KWW9DYH4-execution-workspace-materializer","worktree":"/home/hare/Projects/yoi/.worktree/00001KWW9DYH4-execution-workspace-materializer","role_plan":"単一 sibling Coder Pod に implementation worktree を委譲し、worker-runtime の Execution Workspace materialization boundary / local Git detached worktree allocation / cleanup evidence / tests を実装する。完了後、別 sibling Reviewer Pod で Ticket IntentPacket / Objective boundary / acceptance criteria に照らして read-only review する。"},"author":"orchestrator","at":"2026-07-06T18:29:51Z"}
|
{"id":"orch-plan-20260706-182951-1","ticket_id":"00001KWW9DYH4","kind":"accepted_plan","accepted_plan":{"summary":"Ticket 00001KWW9DYH4 は独立 queued Ticket で blocker なし。Repository registry の次段として、local Git configured repository から Runtime root 配下に Worker ごとの detached working directory を materialize する v0 を実装する。human authorized routing 済みのため queued->inprogress acceptance 後に worktree + coder/reviewer loop へ進める。","branch":"work/00001KWW9DYH4-execution-workspace-materializer","worktree":"/home/hare/Projects/yoi/.worktree/00001KWW9DYH4-execution-workspace-materializer","role_plan":"単一 sibling Coder Pod に implementation worktree を委譲し、worker-runtime の working directory materialization boundary / local Git detached worktree allocation / cleanup evidence / tests を実装する。完了後、別 sibling Reviewer Pod で Ticket IntentPacket / Objective boundary / acceptance criteria に照らして read-only review する。"},"author":"orchestrator","at":"2026-07-06T18:29:51Z"}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 'Add local Git worktree Execution Workspace materializer'
|
title: 'Add local Git worktree working directory materializer'
|
||||||
state: 'closed'
|
state: 'closed'
|
||||||
created_at: '2026-07-06T18:00:46Z'
|
created_at: '2026-07-06T18:00:46Z'
|
||||||
updated_at: '2026-07-06T19:24:25Z'
|
updated_at: '2026-07-06T19:24:25Z'
|
||||||
|
|
@ -10,29 +10,29 @@ queued_at: '2026-07-06T18:28:26Z'
|
||||||
|
|
||||||
## 背景
|
## 背景
|
||||||
|
|
||||||
現在の Runtime は `--workspace` で与えられた単一 root を Worker の workspace scope として使っている。そのため、同じ repository root を使う Worker を複数 spawn すると worker allocation conflict が起きる。また、Runtime が source repository root を直接 Worker に渡しており、Execution Workspace materialization / cleanup / evidence / sandbox の境界がまだない。
|
現在の Runtime は `--workspace` で与えられた単一 root を Worker の workspace scope として使っている。そのため、同じ repository root を使う Worker を複数 spawn すると worker allocation conflict が起きる。また、Runtime が source repository root を直接 Worker に渡しており、working directory materialization / cleanup / evidence / sandbox の境界がまだない。
|
||||||
|
|
||||||
Objective `00001KWW44EXK` では、Runtime が RepositoryPoint から Worker ごとの Execution Workspace を materialize し、Worker は source repository root ではなく materialized workspace を scope として起動する方針を定めた。この Ticket ではその v0 として、remote/cache-backed Git clone は扱わず、Repository `uri` が local Git repository を指し、そこから `git worktree add --detach` できる前提で最小の materializer を実装する。
|
Objective `00001KWW44EXK` では、Runtime が RepositoryPoint から Worker ごとの working directory を materialize し、Worker は source repository root ではなく materialized workspace を scope として起動する方針を定めた。この Ticket ではその v0 として、remote/cache-backed Git clone は扱わず、Repository `uri` が local Git repository を指し、そこから `git worktree add --detach` できる前提で最小の materializer を実装する。
|
||||||
|
|
||||||
ローカル origin の Git clone / Runtime repository-cache / bare mirror cache はこの Ticket では非目標とする。v0 は local source repository 自体を source cache 相当として扱い、Runtime root 配下に Worker ごとの detached worktree を作る。
|
ローカル origin の Git clone / Runtime repository-cache / bare mirror cache はこの Ticket では非目標とする。v0 は local source repository 自体を source cache 相当として扱い、Runtime root 配下に Worker ごとの detached worktree を作る。
|
||||||
|
|
||||||
## 要件
|
## 要件
|
||||||
|
|
||||||
- Runtime は Worker spawn 前に Execution Workspace materializer を呼ぶ境界を持つ。
|
- Runtime は Worker spawn 前に working directory materializer を呼ぶ境界を持つ。
|
||||||
- v0 materializer は configured local Git repository から detached worktree を Runtime root 配下に作る。
|
- v0 materializer は configured local Git repository から detached worktree を Runtime root 配下に作る。
|
||||||
- Worker は source repository root ではなく、materialized worktree path を workspace scope / cwd として起動する。
|
- Worker は source repository root ではなく、materialized worktree path を workspace scope / cwd として起動する。
|
||||||
- `RepositoryId` / `RepositorySelector` / resolved `RepositoryPoint` の将来境界を壊さない型にする。
|
- `RepositoryId` / `RepositorySelector` / resolved `RepositoryPoint` の将来境界を壊さない型にする。
|
||||||
- この Ticket では local URI の Git repository のみ対応する。remote URI、bare mirror cache、clone/fetch、credential resolution は扱わない。
|
- この Ticket では local URI の Git repository のみ対応する。remote URI、bare mirror cache、clone/fetch、credential resolution は扱わない。
|
||||||
- worktree は branch 名ではなく resolved commit に対する detached worktree として作る。v0 では branch を作らず、Worker の変更は detached worktree 上の diff / patch artifact として回収する。branch 作成は後続の merge/export policy として扱う。
|
- worktree は branch 名ではなく resolved commit に対する detached worktree として作る。v0 では branch を作らず、Worker の変更は detached worktree 上の diff / patch artifact として回収する。branch 作成は後続の merge/export policy として扱う。
|
||||||
- dirty local changes は暗黙に含めない。v0 は `clean_point_only` とし、必要なら dirty state diagnostic を返す。
|
- dirty local changes は暗黙に含めない。v0 は `clean_point_only` とし、必要なら dirty state diagnostic を返す。
|
||||||
- Execution Workspace は `.yoi` や Backend workspace store ではなく Runtime root 配下に作る。
|
- working directory は `.yoi` や Backend workspace store ではなく Runtime root 配下に作る。
|
||||||
- Worker stop / cleanup 時に worktree cleanup できる方針または最低限の cleanup record を持つ。
|
- Worker stop / cleanup 時に worktree cleanup できる方針または最低限の cleanup record を持つ。
|
||||||
- Browser-facing/API-facing diagnostics は raw host path を不必要に漏らさない。
|
- Browser-facing/API-facing diagnostics は raw host path を不必要に漏らさない。
|
||||||
|
|
||||||
## 受け入れ条件
|
## 受け入れ条件
|
||||||
|
|
||||||
- `CreateWorkerRequest` または Runtime-side spawn path に Execution Workspace materialization request / allocation 境界が追加されている。
|
- `CreateWorkerRequest` または Runtime-side spawn path に working directory materialization request / allocation 境界が追加されている。
|
||||||
- local Git repository `uri = "."` 相当から resolved commit を取得し、Runtime root 配下の `execution-workspaces/<allocation-id>/root/<repository-id>` に `git worktree add --detach` できる。
|
- local Git repository `uri = "."` 相当から resolved commit を取得し、Runtime root 配下の `working-directories/<allocation-id>/root/<repository-id>` に `git worktree add --detach` できる。
|
||||||
- Worker 用の Git branch は v0 materialization では作成されず、変更回収は detached worktree の diff / patch artifact として扱われる。
|
- Worker 用の Git branch は v0 materialization では作成されず、変更回収は detached worktree の diff / patch artifact として扱われる。
|
||||||
- Worker spawn 時、Worker scope/cwd は source repository root ではなく materialized worktree を指す。
|
- Worker spawn 時、Worker scope/cwd は source repository root ではなく materialized worktree を指す。
|
||||||
- 同じ local Git repository を対象に複数 Worker を spawn しても、source root scope の allocation conflict で失敗しない。
|
- 同じ local Git repository を対象に複数 Worker を spawn しても、source root scope の allocation conflict で失敗しない。
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
完了。
|
完了。
|
||||||
|
|
||||||
実装内容:
|
実装内容:
|
||||||
- Runtime-side Execution Workspace materialization boundary を追加した。
|
- Runtime-side working directory materialization boundary を追加した。
|
||||||
- v0 local Git materializer として、configured repository から resolved commit / tree evidence を取り、Runtime root 配下 `execution-workspaces/<allocation-id>/root/<repository-id>` に detached Git worktree を作成するようにした。
|
- v0 local Git materializer として、configured repository から resolved commit / tree evidence を取り、Runtime root 配下 `working-directories/<allocation-id>/root/<repository-id>` に detached Git worktree を作成するようにした。
|
||||||
- Worker spawn 時の workspace root / cwd / scope は source repository root ではなく materialized worktree path を使うようにした。
|
- Worker spawn 時の workspace root / cwd / scope は source repository root ではなく materialized worktree path を使うようにした。
|
||||||
- 同一 source repository に対する複数 Worker が distinct materialized paths を使えるようにした。
|
- 同一 source repository に対する複数 Worker が distinct materialized paths を使えるようにした。
|
||||||
- dirty source state は `clean_point_only` policy で拒否し、remote URI / non-Git provider / unsupported policy は typed diagnostics で fail closed するようにした。
|
- dirty source state は `clean_point_only` policy で拒否し、remote URI / non-Git provider / unsupported policy は typed diagnostics で fail closed するようにした。
|
||||||
- allocation evidence / cleanup target / cleanup policy / status を記録し、Worker stop cleanup hook で worktree cleanup と record update を行うようにした。
|
- allocation evidence / cleanup target / cleanup policy / status を記録し、Worker stop cleanup hook で worktree cleanup と record update を行うようにした。
|
||||||
- Browser/API-facing spawn boundary は raw `ExecutionWorkspaceRequest` / `local_path` を受け取らないようにし、safe `repository_id` / optional `selector` から host/server 内部で materialization request を構築するようにした。
|
- Browser/API-facing spawn boundary は raw `WorkingDirectoryRequest` / `local_path` を受け取らないようにし、safe `repository_id` / optional `selector` から host/server 内部で materialization request を構築するようにした。
|
||||||
- Focused worker-runtime / workspace-server tests を追加した。
|
- Focused worker-runtime / workspace-server tests を追加した。
|
||||||
|
|
||||||
主な commit / merge:
|
主な commit / merge:
|
||||||
- implementation: `8b7a5da0 feat: materialize execution workspaces`
|
- implementation: `8b7a5da0 feat: materialize working directories`
|
||||||
- review fix: `c90ebf24 fix: keep execution workspace paths internal`
|
- review fix: `c90ebf24 fix: keep working directory paths internal`
|
||||||
- merge into orchestration: `c4cdf1c3 merge: execution workspace materializer`
|
- merge into orchestration: `c4cdf1c3 merge: working directory materializer`
|
||||||
|
|
||||||
Review:
|
Review:
|
||||||
- 初回 external review は request_changes。
|
- 初回 external review は request_changes。
|
||||||
|
|
|
||||||
|
|
@ -39,15 +39,15 @@ Ticket を `workspace-panel` が queued にしました。
|
||||||
Routing decision: implementation_ready
|
Routing decision: implementation_ready
|
||||||
|
|
||||||
Reason:
|
Reason:
|
||||||
- Ticket は local Git worktree Execution Workspace materializer の v0 範囲、非目標、acceptance criteria が具体化されている。
|
- Ticket は local Git worktree working directory materializer の v0 範囲、非目標、acceptance criteria が具体化されている。
|
||||||
- Objective `00001KWW44EXK` の境界(Runtime root / Execution Workspace / RepositoryPoint / materialization strategy / cleanup registry / dirty state policy)と整合している。
|
- Objective `00001KWW44EXK` の境界(Runtime root / working directory / RepositoryPoint / materialization strategy / cleanup registry / dirty state policy)と整合している。
|
||||||
- `remote URI`, clone/fetch/cache, credential resolution, CoW/sandbox 強化、dirty snapshot/apply は非目標として明確。
|
- `remote URI`, clone/fetch/cache, credential resolution, CoW/sandbox 強化、dirty snapshot/apply は非目標として明確。
|
||||||
- typed relation blocker は 0 件、OrchestrationPlan record は 0 件。
|
- typed relation blocker は 0 件、OrchestrationPlan record は 0 件。
|
||||||
- queued notification は human authorized routing であり、今回の routing acceptance 後にのみ implementation side effect へ進める。
|
- queued notification は human authorized routing であり、今回の routing acceptance 後にのみ implementation side effect へ進める。
|
||||||
|
|
||||||
Evidence checked:
|
Evidence checked:
|
||||||
- Ticket body / thread / artifacts。
|
- Ticket body / thread / artifacts。
|
||||||
- Objective `00001KWW44EXK` body(Runtime execution workspace materialization direction, Runtime root separation, detached worktree strategy, clean point policy, cleanup registry)。
|
- Objective `00001KWW44EXK` body(Runtime working directory materialization direction, Runtime root separation, detached worktree strategy, clean point policy, cleanup registry)。
|
||||||
- `TicketRelationQuery(00001KWW9DYH4)`: 0 件。
|
- `TicketRelationQuery(00001KWW9DYH4)`: 0 件。
|
||||||
- `TicketOrchestrationPlanQuery(00001KWW9DYH4)`: 0 件。
|
- `TicketOrchestrationPlanQuery(00001KWW9DYH4)`: 0 件。
|
||||||
- Orchestrator worktree git status: clean on `orchestration`。
|
- Orchestrator worktree git status: clean on `orchestration`。
|
||||||
|
|
@ -59,11 +59,11 @@ Evidence checked:
|
||||||
IntentPacket:
|
IntentPacket:
|
||||||
|
|
||||||
Intent:
|
Intent:
|
||||||
- Runtime Worker spawn の直前に Execution Workspace materialization boundary を追加し、configured local Git repository から Worker ごとの detached worktree を Runtime root 配下に作る v0 materializer を実装する。
|
- Runtime Worker spawn の直前に working directory materialization boundary を追加し、configured local Git repository から Worker ごとの detached worktree を Runtime root 配下に作る v0 materializer を実装する。
|
||||||
- Worker の workspace scope / cwd を source repository root ではなく materialized worktree path にすることで、同じ local Git repository を対象に複数 Worker を spawn しても source root scope allocation conflict で失敗しないようにする。
|
- Worker の workspace scope / cwd を source repository root ではなく materialized worktree path にすることで、同じ local Git repository を対象に複数 Worker を spawn しても source root scope allocation conflict で失敗しないようにする。
|
||||||
|
|
||||||
Binding decisions / invariants:
|
Binding decisions / invariants:
|
||||||
- Execution Workspace は `.yoi` / Backend workspace store ではなく Runtime root 配下に作る。
|
- working directory は `.yoi` / Backend workspace store ではなく Runtime root 配下に作る。
|
||||||
- v0 は local Git repository URI のみ対応する。remote URI / non-Git provider / clone/fetch/cache / credentials は typed diagnostic で拒否または非対応扱い。
|
- v0 は local Git repository URI のみ対応する。remote URI / non-Git provider / clone/fetch/cache / credentials は typed diagnostic で拒否または非対応扱い。
|
||||||
- worktree は branch 名ではなく resolved commit に対する detached worktree として作る。v0 materialization では Worker 用 branch を作らない。
|
- worktree は branch 名ではなく resolved commit に対する detached worktree として作る。v0 materialization では Worker 用 branch を作らない。
|
||||||
- dirty local changes は暗黙に含めない。v0 は `clean_point_only` とし、dirty state は typed diagnostic / test evidence で明示する。
|
- dirty local changes は暗黙に含めない。v0 は `clean_point_only` とし、dirty state は typed diagnostic / test evidence で明示する。
|
||||||
|
|
@ -73,8 +73,8 @@ Binding decisions / invariants:
|
||||||
- `RepositoryId` / `RepositorySelector` / `RepositoryPoint` の将来境界を壊さず、Git branch/tag/hash 固定の抽象にしない。
|
- `RepositoryId` / `RepositorySelector` / `RepositoryPoint` の将来境界を壊さず、Git branch/tag/hash 固定の抽象にしない。
|
||||||
|
|
||||||
Requirements / acceptance criteria:
|
Requirements / acceptance criteria:
|
||||||
- `CreateWorkerRequest` または Runtime-side spawn path に Execution Workspace materialization request / allocation 境界が追加されている。
|
- `CreateWorkerRequest` または Runtime-side spawn path に working directory materialization request / allocation 境界が追加されている。
|
||||||
- local Git repository `uri = "."` 相当から resolved commit を取得し、Runtime root 配下の `execution-workspaces/<allocation-id>/root/<repository-id>` に `git worktree add --detach` できる。
|
- local Git repository `uri = "."` 相当から resolved commit を取得し、Runtime root 配下の `working-directories/<allocation-id>/root/<repository-id>` に `git worktree add --detach` できる。
|
||||||
- Worker spawn 時の scope/cwd は source repository root ではなく materialized worktree を指す。
|
- Worker spawn 時の scope/cwd は source repository root ではなく materialized worktree を指す。
|
||||||
- 同じ local Git repository を対象に複数 Worker を spawn しても source root scope conflict で失敗しない。
|
- 同じ local Git repository を対象に複数 Worker を spawn しても source root scope conflict で失敗しない。
|
||||||
- unsupported non-local URI / non-Git provider は typed diagnostic。
|
- unsupported non-local URI / non-Git provider は typed diagnostic。
|
||||||
|
|
@ -124,7 +124,7 @@ Queued acceptance recorded after Workspace Dashboard Queue notification authoriz
|
||||||
|
|
||||||
Checked context:
|
Checked context:
|
||||||
- Ticket body / thread / artifacts。
|
- Ticket body / thread / artifacts。
|
||||||
- Objective `00001KWW44EXK` の Execution Workspace materialization strategy / dirty policy / Runtime root separation / cleanup registry context。
|
- Objective `00001KWW44EXK` の working directory materialization strategy / dirty policy / Runtime root separation / cleanup registry context。
|
||||||
- `TicketRelationQuery(00001KWW9DYH4)`: blocking relation 0 件。
|
- `TicketRelationQuery(00001KWW9DYH4)`: blocking relation 0 件。
|
||||||
- `TicketOrchestrationPlanQuery(00001KWW9DYH4)`: prior record 0 件。今回 accepted_plan を記録済み。
|
- `TicketOrchestrationPlanQuery(00001KWW9DYH4)`: prior record 0 件。今回 accepted_plan を記録済み。
|
||||||
- Orchestrator worktree git status: clean on `orchestration`。
|
- Orchestrator worktree git status: clean on `orchestration`。
|
||||||
|
|
@ -161,22 +161,22 @@ Implementation routing update:
|
||||||
|
|
||||||
Implementation progress report:
|
Implementation progress report:
|
||||||
|
|
||||||
- Coder Pod completed implementation and committed `8b7a5da0 feat: materialize execution workspaces` on branch `work/00001KWW9DYH4-execution-workspace-materializer`.
|
- Coder Pod completed implementation and committed `8b7a5da0 feat: materialize working directories` on branch `work/00001KWW9DYH4-execution-workspace-materializer`.
|
||||||
- Worktree status was clean after commit.
|
- Worktree status was clean after commit.
|
||||||
- Changed files include:
|
- Changed files include:
|
||||||
- `crates/worker-runtime/src/execution_workspace.rs` (new)
|
- `crates/worker-runtime/src/working_directory.rs` (new)
|
||||||
- `crates/worker-runtime/src/{catalog.rs,execution.rs,http_server.rs,lib.rs,main.rs,runtime.rs,worker_backend.rs}`
|
- `crates/worker-runtime/src/{catalog.rs,execution.rs,http_server.rs,lib.rs,main.rs,runtime.rs,worker_backend.rs}`
|
||||||
- `crates/workspace-server/src/{companion.rs,hosts.rs,server.rs}`
|
- `crates/workspace-server/src/{companion.rs,hosts.rs,server.rs}`
|
||||||
- Implementation summary:
|
- Implementation summary:
|
||||||
- Runtime-side Execution Workspace request/status types and local Git worktree materializer。
|
- Runtime-side working directory request/status types and local Git worktree materializer。
|
||||||
- detached Git worktree creation under Runtime root: `execution-workspaces/<allocation-id>/root/<repository-id>`。
|
- detached Git worktree creation under Runtime root: `working-directories/<allocation-id>/root/<repository-id>`。
|
||||||
- clean HEAD/selector commit and tree evidence resolution。
|
- clean HEAD/selector commit and tree evidence resolution。
|
||||||
- dirty source rejection under `clean_point_only`。
|
- dirty source rejection under `clean_point_only`。
|
||||||
- typed diagnostics for remote URI / non-Git provider / unsupported policy。
|
- typed diagnostics for remote URI / non-Git provider / unsupported policy。
|
||||||
- materialization/cleanup evidence persistence and best-effort worktree cleanup on Worker stop。
|
- materialization/cleanup evidence persistence and best-effort worktree cleanup on Worker stop。
|
||||||
- Worker spawn cwd/scope uses materialized worktree path, not source repository root。
|
- Worker spawn cwd/scope uses materialized worktree path, not source repository root。
|
||||||
- workspace-server embedded runtime startup and Worker spawn pass configured repositories into materialization request。
|
- workspace-server embedded runtime startup and Worker spawn pass configured repositories into materialization request。
|
||||||
- API-facing execution workspace status summaries remain sanitized/path-free。
|
- API-facing working directory status summaries remain sanitized/path-free。
|
||||||
- Orchestrator reran validation in the implementation worktree:
|
- Orchestrator reran validation in the implementation worktree:
|
||||||
- `git diff --check`: pass
|
- `git diff --check`: pass
|
||||||
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass(34 lib tests + 5 main tests + doc tests)
|
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass(34 lib tests + 5 main tests + doc tests)
|
||||||
|
|
@ -197,16 +197,16 @@ Implementation progress report:
|
||||||
External review result: request_changes
|
External review result: request_changes
|
||||||
|
|
||||||
Blocker:
|
Blocker:
|
||||||
- 実装が Browser/API-facing spawn boundary を広げ、API caller から full `ExecutionWorkspaceRequest`(`ExecutionWorkspaceRepository.local_path: Option<PathBuf>` を含む)を受け取れる形になっている。
|
- 実装が Browser/API-facing spawn boundary を広げ、API caller から full `WorkingDirectoryRequest`(`WorkingDirectoryRepository.local_path: Option<PathBuf>` を含む)を受け取れる形になっている。
|
||||||
- `crates/workspace-server/src/hosts.rs:263-283` は `WorkerSpawnRequest` を browser-safe として raw workspace roots/storage paths を受け取らない契約にしているが、現在は `execution_workspace: Option<ExecutionWorkspaceRequest>` を含む。
|
- `crates/workspace-server/src/hosts.rs:263-283` は `WorkerSpawnRequest` を browser-safe として raw workspace roots/storage paths を受け取らない契約にしているが、現在は `working_directory: Option<WorkingDirectoryRequest>` を含む。
|
||||||
- `crates/worker-runtime/src/catalog.rs:58-66` の request は raw `local_path` を運べる。
|
- `crates/worker-runtime/src/catalog.rs:58-66` の request は raw `local_path` を運べる。
|
||||||
- `/api/runtimes/{runtime_id}/workers` はこの request shape を受け取り、embedded/remote host spawn path が `CreateWorkerRequest` に pass-through している。
|
- `/api/runtimes/{runtime_id}/workers` はこの request shape を受け取り、embedded/remote host spawn path が `CreateWorkerRequest` に pass-through している。
|
||||||
- これは Ticket / routing invariant の「raw source/internal paths を含む public launch boundary 拡張を避ける。必要なら escalate」に反し、既存コード上の browser-safe contract とも衝突している。
|
- これは Ticket / routing invariant の「raw source/internal paths を含む public launch boundary 拡張を避ける。必要なら escalate」に反し、既存コード上の browser-safe contract とも衝突している。
|
||||||
- safer `/api/workers` path は server-side repository config から `repository_id` により解決しているため、generic API も caller-supplied raw `local_path` authority を受け取らない形にするか、Execution Workspace materialization request の組み立てを host/runtime 内部境界に閉じる必要がある。
|
- safer `/api/workers` path は server-side repository config から `repository_id` により解決しているため、generic API も caller-supplied raw `local_path` authority を受け取らない形にするか、working directory materialization request の組み立てを host/runtime 内部境界に閉じる必要がある。
|
||||||
|
|
||||||
Evidence supporting the rest of the implementation:
|
Evidence supporting the rest of the implementation:
|
||||||
- Runtime/backend materialization boundary は存在する。`Runtime::create_worker` が request を backend に渡し、`WorkerRuntimeExecutionBackend::spawn_worker` が spawn 前に materializer を呼ぶ。
|
- Runtime/backend materialization boundary は存在する。`Runtime::create_worker` が request を backend に渡し、`WorkerRuntimeExecutionBackend::spawn_worker` が spawn 前に materializer を呼ぶ。
|
||||||
- local materializer は `execution-workspaces/<allocation-id>/root/<repository-id>` 配下に resolved commit の detached worktree を作成し、branch checkout はしない。
|
- local materializer は `working-directories/<allocation-id>/root/<repository-id>` 配下に resolved commit の detached worktree を作成し、branch checkout はしない。
|
||||||
- Worker `workspace_root` / `cwd` は `Worker::from_manifest_with_context` 前に materialized binding に差し替えられている。
|
- Worker `workspace_root` / `cwd` は `Worker::from_manifest_with_context` 前に materialized binding に差し替えられている。
|
||||||
- dirty source は `clean_point_only` で拒否され、remote/non-Git は typed diagnostic code を返す。
|
- dirty source は `clean_point_only` で拒否され、remote/non-Git は typed diagnostic code を返す。
|
||||||
- allocation evidence/status/cleanup target/policy は記録・永続化され、worker stop cleanup は worktree removal / cleanup-pending 更新を行う。
|
- allocation evidence/status/cleanup target/policy は記録・永続化され、worker stop cleanup は worktree removal / cleanup-pending 更新を行う。
|
||||||
|
|
@ -228,15 +228,15 @@ Non-blocking follow-ups:
|
||||||
|
|
||||||
Review-fix implementation report:
|
Review-fix implementation report:
|
||||||
|
|
||||||
- Coder Pod completed requested changes and committed follow-up `c90ebf24 fix: keep execution workspace paths internal` on branch `work/00001KWW9DYH4-execution-workspace-materializer`.
|
- Coder Pod completed requested changes and committed follow-up `c90ebf24 fix: keep working directory paths internal` on branch `work/00001KWW9DYH4-execution-workspace-materializer`.
|
||||||
- Worktree status was clean after commit.
|
- Worktree status was clean after commit.
|
||||||
- Fix summary:
|
- Fix summary:
|
||||||
- Browser/API-facing `WorkerSpawnRequest` no longer deserializes full `ExecutionWorkspaceRequest` or caller-supplied raw `local_path`.
|
- Browser/API-facing `WorkerSpawnRequest` no longer deserializes full `WorkingDirectoryRequest` or caller-supplied raw `local_path`.
|
||||||
- Added browser-safe execution workspace request shape with `repository_id` and optional `selector` only。
|
- Added browser-safe working directory request shape with `repository_id` and optional `selector` only。
|
||||||
- Added `deny_unknown_fields` so raw fields such as `execution_workspace.local_path` and unexpected fields are rejected at the API boundary。
|
- Added `deny_unknown_fields` so raw fields such as `working_directory.local_path` and unexpected fields are rejected at the API boundary。
|
||||||
- Server-side host code resolves configured repository registry into internal `ExecutionWorkspaceRequest`; raw `local_path` construction stays behind trusted host/server boundary。
|
- Server-side host code resolves configured repository registry into internal `WorkingDirectoryRequest`; raw `local_path` construction stays behind trusted host/server boundary。
|
||||||
- Existing `/api/workers` repository-id safe behavior remains preserved。
|
- Existing `/api/workers` repository-id safe behavior remains preserved。
|
||||||
- Added focused API tests for rejecting raw `execution_workspace.local_path` and accepting safe repository-id/selector materialization without exposing raw workspace path。
|
- Added focused API tests for rejecting raw `working_directory.local_path` and accepting safe repository-id/selector materialization without exposing raw workspace path。
|
||||||
- Orchestrator reran validation in the implementation worktree:
|
- Orchestrator reran validation in the implementation worktree:
|
||||||
- `git diff --check`: pass
|
- `git diff --check`: pass
|
||||||
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass(34 lib tests + 5 main tests + doc tests)
|
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass(34 lib tests + 5 main tests + doc tests)
|
||||||
|
|
@ -263,15 +263,15 @@ Evidence reviewed:
|
||||||
- Fix commit `c90ebf24` and combined implementation after `8b7a5da0`。
|
- Fix commit `c90ebf24` and combined implementation after `8b7a5da0`。
|
||||||
|
|
||||||
Findings:
|
Findings:
|
||||||
- Prior blocker is resolved. Browser/API `WorkerSpawnRequest` now exposes only `execution_workspace: { repository_id, selector? }` via `WorkerSpawnExecutionWorkspaceRequest` with `deny_unknown_fields`; internal `resolved_execution_workspace` is `#[serde(skip, default)]`。
|
- Prior blocker is resolved. Browser/API `WorkerSpawnRequest` now exposes only `working_directory: { repository_id, selector? }` via `WorkerSpawnWorkingDirectoryRequest` with `deny_unknown_fields`; internal `resolved_working_directory` is `#[serde(skip, default)]`。
|
||||||
- `/api/runtimes/{runtime_id}/workers` resolves safe repository id/selector server-side from configured repositories into an internal `ExecutionWorkspaceRequest`。
|
- `/api/runtimes/{runtime_id}/workers` resolves safe repository id/selector server-side from configured repositories into an internal `WorkingDirectoryRequest`。
|
||||||
- `/api/workers` remains safe: `BrowserCreateWorkerRequest` only accepts optional `repository_id`; server-side code builds the internal request。
|
- `/api/workers` remains safe: `BrowserCreateWorkerRequest` only accepts optional `repository_id`; server-side code builds the internal request。
|
||||||
- Fix tests explicitly reject caller-supplied `local_path` and accept safe `repository_id`/`selector`, with response path redaction asserted。
|
- Fix tests explicitly reject caller-supplied `local_path` and accept safe `repository_id`/`selector`, with response path redaction asserted。
|
||||||
- Response/status projections remain browser-safe: execution workspace summaries carry ids/commit/tree/cleanup metadata, not raw workspace/source paths。
|
- Response/status projections remain browser-safe: working directory summaries carry ids/commit/tree/cleanup metadata, not raw workspace/source paths。
|
||||||
- Original materializer acceptance remains satisfied:
|
- Original materializer acceptance remains satisfied:
|
||||||
- local Git only, remote/non-Git/dirty source rejected with typed diagnostics。
|
- local Git only, remote/non-Git/dirty source rejected with typed diagnostics。
|
||||||
- selector resolves to commit/tree。
|
- selector resolves to commit/tree。
|
||||||
- detached `git worktree add --detach` is created under Runtime root `execution-workspaces/<allocation-id>/root/<repository-id>`。
|
- detached `git worktree add --detach` is created under Runtime root `working-directories/<allocation-id>/root/<repository-id>`。
|
||||||
- worker factory uses materialized workspace root/cwd instead of source root。
|
- worker factory uses materialized workspace root/cwd instead of source root。
|
||||||
- allocation evidence and cleanup target are recorded。
|
- allocation evidence and cleanup target are recorded。
|
||||||
- cleanup hook removes worktree and updates record。
|
- cleanup hook removes worktree and updates record。
|
||||||
|
|
@ -296,7 +296,7 @@ No merge-blocking issues found.
|
||||||
Merge/validation acceptance:
|
Merge/validation acceptance:
|
||||||
|
|
||||||
- Follow-up external review approved the implementation after blocker fix。
|
- Follow-up external review approved the implementation after blocker fix。
|
||||||
- Implementation branch `work/00001KWW9DYH4-execution-workspace-materializer` was merged into the orchestration branch with merge commit `c4cdf1c3 merge: execution workspace materializer`。
|
- Implementation branch `work/00001KWW9DYH4-execution-workspace-materializer` was merged into the orchestration branch with merge commit `c4cdf1c3 merge: working directory materializer`。
|
||||||
- Final validation in Orchestrator worktree passed:
|
- Final validation in Orchestrator worktree passed:
|
||||||
- `git diff --check`: pass
|
- `git diff --check`: pass
|
||||||
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass(34 lib tests + 5 main tests + doc tests)
|
- `cargo test -p worker-runtime --features ws-server,fs-store`: pass(34 lib tests + 5 main tests + doc tests)
|
||||||
|
|
@ -308,8 +308,8 @@ Merge/validation acceptance:
|
||||||
- `nix build .#yoi --no-link` は Cargo.lock / dependency / resource packaging / Nix 変更ではないため未実行。
|
- `nix build .#yoi --no-link` は Cargo.lock / dependency / resource packaging / Nix 変更ではないため未実行。
|
||||||
|
|
||||||
Implementation commits:
|
Implementation commits:
|
||||||
- `8b7a5da0 feat: materialize execution workspaces`
|
- `8b7a5da0 feat: materialize working directories`
|
||||||
- `c90ebf24 fix: keep execution workspace paths internal`
|
- `c90ebf24 fix: keep working directory paths internal`
|
||||||
|
|
||||||
No merge-blocking issues remain.
|
No merge-blocking issues remain.
|
||||||
|
|
||||||
|
|
@ -331,19 +331,19 @@ Ticket を closed にしました。
|
||||||
完了。
|
完了。
|
||||||
|
|
||||||
実装内容:
|
実装内容:
|
||||||
- Runtime-side Execution Workspace materialization boundary を追加した。
|
- Runtime-side working directory materialization boundary を追加した。
|
||||||
- v0 local Git materializer として、configured repository から resolved commit / tree evidence を取り、Runtime root 配下 `execution-workspaces/<allocation-id>/root/<repository-id>` に detached Git worktree を作成するようにした。
|
- v0 local Git materializer として、configured repository から resolved commit / tree evidence を取り、Runtime root 配下 `working-directories/<allocation-id>/root/<repository-id>` に detached Git worktree を作成するようにした。
|
||||||
- Worker spawn 時の workspace root / cwd / scope は source repository root ではなく materialized worktree path を使うようにした。
|
- Worker spawn 時の workspace root / cwd / scope は source repository root ではなく materialized worktree path を使うようにした。
|
||||||
- 同一 source repository に対する複数 Worker が distinct materialized paths を使えるようにした。
|
- 同一 source repository に対する複数 Worker が distinct materialized paths を使えるようにした。
|
||||||
- dirty source state は `clean_point_only` policy で拒否し、remote URI / non-Git provider / unsupported policy は typed diagnostics で fail closed するようにした。
|
- dirty source state は `clean_point_only` policy で拒否し、remote URI / non-Git provider / unsupported policy は typed diagnostics で fail closed するようにした。
|
||||||
- allocation evidence / cleanup target / cleanup policy / status を記録し、Worker stop cleanup hook で worktree cleanup と record update を行うようにした。
|
- allocation evidence / cleanup target / cleanup policy / status を記録し、Worker stop cleanup hook で worktree cleanup と record update を行うようにした。
|
||||||
- Browser/API-facing spawn boundary は raw `ExecutionWorkspaceRequest` / `local_path` を受け取らないようにし、safe `repository_id` / optional `selector` から host/server 内部で materialization request を構築するようにした。
|
- Browser/API-facing spawn boundary は raw `WorkingDirectoryRequest` / `local_path` を受け取らないようにし、safe `repository_id` / optional `selector` から host/server 内部で materialization request を構築するようにした。
|
||||||
- Focused worker-runtime / workspace-server tests を追加した。
|
- Focused worker-runtime / workspace-server tests を追加した。
|
||||||
|
|
||||||
主な commit / merge:
|
主な commit / merge:
|
||||||
- implementation: `8b7a5da0 feat: materialize execution workspaces`
|
- implementation: `8b7a5da0 feat: materialize working directories`
|
||||||
- review fix: `c90ebf24 fix: keep execution workspace paths internal`
|
- review fix: `c90ebf24 fix: keep working directory paths internal`
|
||||||
- merge into orchestration: `c4cdf1c3 merge: execution workspace materializer`
|
- merge into orchestration: `c4cdf1c3 merge: working directory materializer`
|
||||||
|
|
||||||
Review:
|
Review:
|
||||||
- 初回 external review は request_changes。
|
- 初回 external review は request_changes。
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ Evidence checked:
|
||||||
- `TicketOrchestrationPlanQuery(00001KWWE8E04)`: 0 件。
|
- `TicketOrchestrationPlanQuery(00001KWWE8E04)`: 0 件。
|
||||||
- Orchestrator worktree git status: clean on `orchestration`。
|
- Orchestrator worktree git status: clean on `orchestration`。
|
||||||
- queued Ticket 一覧: この Ticket 1件のみ。inprogress は 0 件。
|
- queued Ticket 一覧: この Ticket 1件のみ。inprogress は 0 件。
|
||||||
- visible Pods: previous execution-workspace child Pods が idle で残っているが、`StopPod` 不使用方針のため停止せず、新規 unique Pod を使う。capacity blocker ではない。
|
- visible Pods: previous working-directory child Pods が idle で残っているが、`StopPod` 不使用方針のため停止せず、新規 unique Pod を使う。capacity blocker ではない。
|
||||||
- `TicketDoctor`: 0 errors / 既存 diagnostics のみ。
|
- `TicketDoctor`: 0 errors / 既存 diagnostics のみ。
|
||||||
- Bounded code map: `crates/workspace-server/src/{identity.rs,server.rs,hosts.rs,config.rs,records.rs,repositories.rs}`, `web/workspace/src/routes/**`, `web/workspace/src/lib/workspace-sidebar/**`, workspace settings/sidebar/console helpers。
|
- Bounded code map: `crates/workspace-server/src/{identity.rs,server.rs,hosts.rs,config.rs,records.rs,repositories.rs}`, `web/workspace/src/routes/**`, `web/workspace/src/lib/workspace-sidebar/**`, workspace settings/sidebar/console helpers。
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"id":"orch-plan-20260707-134133-1","ticket_id":"00001KWY8EHEJ","kind":"accepted_plan","accepted_plan":{"summary":"Ticket 00001KWY8EHEJ は独立 queued Ticket で blocker なし。先行 Runtime materializer と workspace-id scoped API を前提に、Browser-managed pre-allocated Execution Workspace を Worker spawn に接続する。human authorized routing 済みのため queued->inprogress acceptance 後に worktree + coder/reviewer loop へ進める。","branch":"work/00001KWY8EHEJ-browser-execution-workspaces","worktree":"/home/hare/Projects/yoi/.worktree/00001KWY8EHEJ-browser-execution-workspaces","role_plan":"単一 sibling Coder Pod に implementation worktree を委譲し、workspace-scoped Browser-managed Execution Workspace API / Worker launch UI integration / allocation-id spawn connection / relative_cwd validation / cleanup/status tests を実装する。完了後、別 sibling Reviewer Pod で Ticket IntentPacket / authority boundary / acceptance criteria に照らして read-only review する。"},"author":"orchestrator","at":"2026-07-07T13:41:33Z"}
|
{"id":"orch-plan-20260707-134133-1","ticket_id":"00001KWY8EHEJ","kind":"accepted_plan","accepted_plan":{"summary":"Ticket 00001KWY8EHEJ は独立 queued Ticket で blocker なし。先行 Runtime materializer と workspace-id scoped API を前提に、Browser-managed pre-allocated working directory を Worker spawn に接続する。human authorized routing 済みのため queued->inprogress acceptance 後に worktree + coder/reviewer loop へ進める。","branch":"work/00001KWY8EHEJ-browser-working-directories","worktree":"/home/hare/Projects/yoi/.worktree/00001KWY8EHEJ-browser-working-directories","role_plan":"単一 sibling Coder Pod に implementation worktree を委譲し、workspace-scoped Browser-managed working directory API / Worker launch UI integration / allocation-id spawn connection / relative_cwd validation / cleanup/status tests を実装する。完了後、別 sibling Reviewer Pod で Ticket IntentPacket / authority boundary / acceptance criteria に照らして read-only review する。"},"author":"orchestrator","at":"2026-07-07T13:41:33Z"}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 'Enable Browser-managed execution workspaces for Worker spawn'
|
title: 'Enable Browser-managed working directories for Worker spawn'
|
||||||
state: 'closed'
|
state: 'closed'
|
||||||
created_at: '2026-07-07T12:22:06Z'
|
created_at: '2026-07-07T12:22:06Z'
|
||||||
updated_at: '2026-07-07T14:58:35Z'
|
updated_at: '2026-07-07T14:58:35Z'
|
||||||
|
|
@ -10,31 +10,31 @@ queued_at: '2026-07-07T13:40:23Z'
|
||||||
|
|
||||||
## 背景
|
## 背景
|
||||||
|
|
||||||
Runtime 側には `CreateWorkerRequest.execution_workspace` と local Git worktree materializer があり、Worker 作成時に RepositoryPoint 相当の入力から detached worktree を materialize して Worker の workspace/cwd/scope にする境界が既にある。一方、Browser UI からは作業環境を事前に用意・確認し、それを Worker spawn 操作で選択する経路がまだ無い。
|
Runtime 側には `CreateWorkerRequest.working_directory` と local Git worktree materializer があり、Worker 作成時に RepositoryPoint 相当の入力から detached worktree を materialize して Worker の workspace/cwd/scope にする境界が既にある。一方、Browser UI からは作業環境を事前に用意・確認し、それを Worker spawn 操作で選択する経路がまだ無い。
|
||||||
|
|
||||||
Browser から raw host path や内部 materialization request を直接指定させるのではなく、Workspace Backend が repository / selector / policy を検証して Execution Workspace を作成し、Browser には allocation id と安全な summary だけを返す形にする。
|
Browser から raw host path や内部 materialization request を直接指定させるのではなく、Workspace Backend が repository / selector / policy を検証して working directory を作成し、Browser には allocation id と安全な summary だけを返す形にする。
|
||||||
|
|
||||||
## 要件
|
## 要件
|
||||||
|
|
||||||
- Browser-facing API で Execution Workspace を作成・一覧・詳細確認できる。
|
- Browser-facing API で working directory を作成・一覧・詳細確認できる。
|
||||||
- v0 の materializer は既存方針どおり local Git repository からの detached worktree とし、remote clone/cache や dirty source inclusion は扱わない。
|
- v0 の materializer は既存方針どおり local Git repository からの detached worktree とし、remote clone/cache や dirty source inclusion は扱わない。
|
||||||
- Browser-facing payload に backend-private absolute path、raw materialization request、内部 runtime path を露出しない。
|
- Browser-facing payload に backend-private absolute path、raw materialization request、内部 runtime path を露出しない。
|
||||||
- Worker spawn form/API から、作成済み Execution Workspace を選択して Worker 作成に利用できる。
|
- Worker spawn form/API から、作成済み working directory を選択して Worker 作成に利用できる。
|
||||||
- Worker が利用する cwd 指定は host absolute path ではなく、materialized workspace root からの安全な `relative_cwd` として扱う。
|
- Worker が利用する cwd 指定は host absolute path ではなく、materialized workspace root からの安全な `relative_cwd` として扱う。
|
||||||
- `relative_cwd` は absolute path、`..` escape、symlink escape、存在しない/不正な working directory を拒否する。
|
- `relative_cwd` は absolute path、`..` escape、symlink escape、存在しない/不正な working directory を拒否する。
|
||||||
- Worker detail / list には execution workspace の safe summary を表示する。
|
- Worker detail / list には working directory の safe summary を表示する。
|
||||||
- cleanup policy と手動 cleanup 操作の v0 方針を明示する。
|
- cleanup policy と手動 cleanup 操作の v0 方針を明示する。
|
||||||
|
|
||||||
## 受け入れ条件
|
## 受け入れ条件
|
||||||
|
|
||||||
- `POST /api/w/<workspace-id>/execution-workspaces` 相当の API で configured repository と selector から Execution Workspace を作成できる。
|
- `POST /api/w/<workspace-id>/working-directories` 相当の API で configured repository と selector から working directory を作成できる。
|
||||||
- `GET /api/w/<workspace-id>/execution-workspaces` 相当の API で作成済み Execution Workspace の safe summary を一覧できる。
|
- `GET /api/w/<workspace-id>/working-directories` 相当の API で作成済み working directory の safe summary を一覧できる。
|
||||||
- Worker launch UI で Execution Workspace を選択でき、spawn request が raw path ではなく safe id/selector 情報を送る。
|
- Worker launch UI で working directory を選択でき、spawn request が raw path ではなく safe id/selector 情報を送る。
|
||||||
- Worker spawn 時に選択した Execution Workspace が Runtime の `CreateWorkerRequest.execution_workspace` または同等の backend-resolved request に接続される。
|
- Worker spawn 時に選択した working directory が Runtime の `CreateWorkerRequest.working_directory` または同等の backend-resolved request に接続される。
|
||||||
- 作成された Worker の workspace/cwd/scope が materialized worktree 配下になり、Browser/API には内部 path が漏れない。
|
- 作成された Worker の workspace/cwd/scope が materialized worktree 配下になり、Browser/API には内部 path が漏れない。
|
||||||
- 不正な repository、dirty unsupported source、remote unsupported source、invalid `relative_cwd` は typed diagnostic として拒否される。
|
- 不正な repository、dirty unsupported source、remote unsupported source、invalid `relative_cwd` は typed diagnostic として拒否される。
|
||||||
- frontend check/test、workspace-server tests、`yoi ticket doctor`、`nix build .#yoi` が通る。
|
- frontend check/test、workspace-server tests、`yoi ticket doctor`、`nix build .#yoi` が通る。
|
||||||
|
|
||||||
## 設計メモ
|
## 設計メモ
|
||||||
|
|
||||||
初期実装は pre-allocate 方式を優先する。つまり Browser で Execution Workspace を明示的に作成し、Worker spawn では `allocation_id` と任意の `relative_cwd` を選択する。単発 spawn request だけで暗黙 materialize する経路は、UI/cleanup/diagnostics が固まるまで後回しにする。
|
初期実装は pre-allocate 方式を優先する。つまり Browser で working directory を明示的に作成し、Worker spawn では `allocation_id` と任意の `relative_cwd` を選択する。単発 spawn request だけで暗黙 materialize する経路は、UI/cleanup/diagnostics が固まるまで後回しにする。
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,24 @@
|
||||||
完了。
|
完了。
|
||||||
|
|
||||||
実装内容:
|
実装内容:
|
||||||
- Browser-managed Execution Workspace API を scoped Workspace API に追加した。
|
- Browser-managed working directory API を scoped Workspace API に追加した。
|
||||||
- `POST /api/w/<workspace-id>/execution-workspaces`
|
- `POST /api/w/<workspace-id>/working-directories`
|
||||||
- `GET /api/w/<workspace-id>/execution-workspaces`
|
- `GET /api/w/<workspace-id>/working-directories`
|
||||||
- detail / cleanup endpoint
|
- detail / cleanup endpoint
|
||||||
- Browser create payload は configured `repository_id` / selector / policy を使い、raw host path / raw internal `ExecutionWorkspaceRequest` / internal runtime path を受け取らないようにした。
|
- Browser create payload は configured `repository_id` / selector / policy を使い、raw host path / raw internal `WorkingDirectoryRequest` / internal runtime path を受け取らないようにした。
|
||||||
- Browser response は allocation id、repository id、selector/commit evidence、status、cleanup policy など safe summary のみを返すようにした。
|
- Browser response は allocation id、repository id、selector/commit evidence、status、cleanup policy など safe summary のみを返すようにした。
|
||||||
- Worker launch UI で Execution Workspace を create/select できるようにした。
|
- Worker launch UI で working directory を create/select できるようにした。
|
||||||
- Worker spawn は selected allocation id + optional `relative_cwd` を使い、Runtime materialized workspace に接続するようにした。
|
- Worker spawn は selected allocation id + optional `relative_cwd` を使い、Runtime materialized workspace に接続するようにした。
|
||||||
- `relative_cwd` は materialized root 相対として検証し、absolute path、`..` escape、symlink escape、nonexistent / non-directory を拒否するようにした。
|
- `relative_cwd` は materialized root 相対として検証し、absolute path、`..` escape、symlink escape、nonexistent / non-directory を拒否するようにした。
|
||||||
- invalid `relative_cwd` は Browser-facing API で typed `execution_workspace_relative_cwd_*` diagnostic として返るようにした。
|
- invalid `relative_cwd` は Browser-facing API で typed `working_directory_relative_cwd_*` diagnostic として返るようにした。
|
||||||
- Worker list/detail に execution workspace safe summary を含めた。
|
- Worker list/detail に working directory safe summary を含めた。
|
||||||
- cleanup は allocation root に bounded され、typed/sanitized diagnostic を返すようにした。
|
- cleanup は allocation root に bounded され、typed/sanitized diagnostic を返すようにした。
|
||||||
- Focused backend/runtime/web tests を追加した。
|
- Focused backend/runtime/web tests を追加した。
|
||||||
|
|
||||||
主な commit / merge:
|
主な commit / merge:
|
||||||
- implementation: `684b19e8 feat: add browser execution workspaces`
|
- implementation: `684b19e8 feat: add browser working directories`
|
||||||
- review fix: `9adb0fae fix: preserve execution workspace cwd diagnostics`
|
- review fix: `9adb0fae fix: preserve working directory cwd diagnostics`
|
||||||
- merge into orchestration: `b52986e5 merge: browser execution workspaces`
|
- merge into orchestration: `b52986e5 merge: browser working directories`
|
||||||
|
|
||||||
Review:
|
Review:
|
||||||
- 初回 external review は request_changes。
|
- 初回 external review は request_changes。
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ Ticket を `workspace-panel` が queued にしました。
|
||||||
Routing decision: implementation_ready
|
Routing decision: implementation_ready
|
||||||
|
|
||||||
Reason:
|
Reason:
|
||||||
- Ticket は Browser-managed Execution Workspace の pre-allocate 方式、API/UI/spawn 接続、`relative_cwd` validation、path leak invariant、cleanup policy の v0 方針を具体化している。
|
- Ticket は Browser-managed working directory の pre-allocate 方式、API/UI/spawn 接続、`relative_cwd` validation、path leak invariant、cleanup policy の v0 方針を具体化している。
|
||||||
- 先行 Runtime-side materializer / Repository registry / workspace-id scoped API が orchestration branch に統合済みで、Browser-facing API を `/api/w/<workspace-id>/...` に追加する実装基盤がある。
|
- 先行 Runtime-side materializer / Repository registry / workspace-id scoped API が orchestration branch に統合済みで、Browser-facing API を `/api/w/<workspace-id>/...` に追加する実装基盤がある。
|
||||||
- raw host path / internal materialization request を Browser payload に露出しない binding invariant が明確で、Worker spawn は allocation id + safe `relative_cwd` で接続する要求になっている。
|
- raw host path / internal materialization request を Browser payload に露出しない binding invariant が明確で、Worker spawn は allocation id + safe `relative_cwd` で接続する要求になっている。
|
||||||
- typed relation blocker は 0 件、OrchestrationPlan record は 0 件。
|
- typed relation blocker は 0 件、OrchestrationPlan record は 0 件。
|
||||||
|
|
@ -53,17 +53,17 @@ Evidence checked:
|
||||||
- queued Ticket 一覧: この Ticket 1件のみ。inprogress は 0 件。
|
- queued Ticket 一覧: この Ticket 1件のみ。inprogress は 0 件。
|
||||||
- visible Pods: previous child Pods が idle で残っているが、`StopPod` 不使用方針のため停止せず、新規 unique Pod を使う。capacity blocker ではない。
|
- visible Pods: previous child Pods が idle で残っているが、`StopPod` 不使用方針のため停止せず、新規 unique Pod を使う。capacity blocker ではない。
|
||||||
- `TicketDoctor`: 0 errors / 既存 diagnostics のみ。
|
- `TicketDoctor`: 0 errors / 既存 diagnostics のみ。
|
||||||
- Bounded code map: `crates/worker-runtime/src/{catalog.rs,execution_workspace.rs,runtime.rs,worker_backend.rs,http_server.rs}`, `crates/workspace-server/src/{server.rs,hosts.rs,repositories.rs,config.rs}`, `web/workspace/src/lib/workspace-sidebar/**`, scoped routes/API under `web/workspace/src/routes/w/[workspaceId]/**`。
|
- Bounded code map: `crates/worker-runtime/src/{catalog.rs,working_directory.rs,runtime.rs,worker_backend.rs,http_server.rs}`, `crates/workspace-server/src/{server.rs,hosts.rs,repositories.rs,config.rs}`, `web/workspace/src/lib/workspace-sidebar/**`, scoped routes/API under `web/workspace/src/routes/w/[workspaceId]/**`。
|
||||||
|
|
||||||
IntentPacket:
|
IntentPacket:
|
||||||
|
|
||||||
Intent:
|
Intent:
|
||||||
- Browser-facing API から configured repository + selector + policy により Execution Workspace を事前作成し、Browser には allocation id と safe summary だけを返す。
|
- Browser-facing API から configured repository + selector + policy により working directory を事前作成し、Browser には allocation id と safe summary だけを返す。
|
||||||
- Worker launch UI/API で作成済み Execution Workspace を選択し、Worker spawn 時に allocation id と safe `relative_cwd` を Runtime materializer / Worker spawn boundary に接続する。
|
- Worker launch UI/API で作成済み working directory を選択し、Worker spawn 時に allocation id と safe `relative_cwd` を Runtime materializer / Worker spawn boundary に接続する。
|
||||||
- Worker detail/list に execution workspace の safe summary を表示し、cleanup policy と手動 cleanup 操作の v0 方針を実装/明示する。
|
- Worker detail/list に working directory の safe summary を表示し、cleanup policy と手動 cleanup 操作の v0 方針を実装/明示する。
|
||||||
|
|
||||||
Binding decisions / invariants:
|
Binding decisions / invariants:
|
||||||
- Browser から raw host path、backend-private absolute path、raw `ExecutionWorkspaceRequest`、internal runtime path を送らせない/返さない。
|
- Browser から raw host path、backend-private absolute path、raw `WorkingDirectoryRequest`、internal runtime path を送らせない/返さない。
|
||||||
- pre-allocate 方式を優先する。単発 spawn request だけで暗黙 materialize する経路は後回し。
|
- pre-allocate 方式を優先する。単発 spawn request だけで暗黙 materialize する経路は後回し。
|
||||||
- v0 materializer は existing local Git detached worktree 方針を使う。remote clone/cache、dirty source inclusion は扱わない。
|
- v0 materializer は existing local Git detached worktree 方針を使う。remote clone/cache、dirty source inclusion は扱わない。
|
||||||
- Worker spawn は作成済み allocation id と任意の `relative_cwd` を選ぶ。
|
- Worker spawn は作成済み allocation id と任意の `relative_cwd` を選ぶ。
|
||||||
|
|
@ -73,19 +73,19 @@ Binding decisions / invariants:
|
||||||
- Workspace-scoped Browser API は `/api/w/<workspace-id>/...` を使い、Runtime `/v1/...` API を Browser contract として広げない。
|
- Workspace-scoped Browser API は `/api/w/<workspace-id>/...` を使い、Runtime `/v1/...` API を Browser contract として広げない。
|
||||||
|
|
||||||
Requirements / acceptance criteria:
|
Requirements / acceptance criteria:
|
||||||
- `POST /api/w/<workspace-id>/execution-workspaces` 相当で configured repository + selector から Execution Workspace を作成できる。
|
- `POST /api/w/<workspace-id>/working-directories` 相当で configured repository + selector から working directory を作成できる。
|
||||||
- `GET /api/w/<workspace-id>/execution-workspaces` 相当で safe summary list を取得できる。
|
- `GET /api/w/<workspace-id>/working-directories` 相当で safe summary list を取得できる。
|
||||||
- 必要なら detail / cleanup endpoint を scoped API に追加し、safe summary / typed diagnostic を返す。
|
- 必要なら detail / cleanup endpoint を scoped API に追加し、safe summary / typed diagnostic を返す。
|
||||||
- Worker launch UI で Execution Workspace を選択でき、raw path ではなく safe id/selector 情報を送る。
|
- Worker launch UI で working directory を選択でき、raw path ではなく safe id/selector 情報を送る。
|
||||||
- Worker spawn 時に選択 allocation が Runtime の materialized workspace に接続され、Worker cwd/scope は materialized worktree 配下になる。
|
- Worker spawn 時に選択 allocation が Runtime の materialized workspace に接続され、Worker cwd/scope は materialized worktree 配下になる。
|
||||||
- invalid repository / dirty unsupported / remote unsupported / invalid `relative_cwd` は typed diagnostic。
|
- invalid repository / dirty unsupported / remote unsupported / invalid `relative_cwd` は typed diagnostic。
|
||||||
- Worker detail/list に execution workspace safe summary を表示する。
|
- Worker detail/list に working directory safe summary を表示する。
|
||||||
- frontend check/test、workspace-server tests、`yoi ticket doctor`、`nix build .#yoi` を通す。
|
- frontend check/test、workspace-server tests、`yoi ticket doctor`、`nix build .#yoi` を通す。
|
||||||
|
|
||||||
Implementation latitude:
|
Implementation latitude:
|
||||||
- Allocation store の所在/形式、API endpoint 名、UI placement、cleanup status 名、test helper は既存 workspace-server / worker-runtime / web style に合わせてよい。
|
- Allocation store の所在/形式、API endpoint 名、UI placement、cleanup status 名、test helper は既存 workspace-server / worker-runtime / web style に合わせてよい。
|
||||||
- v0 では Runtime materializer の既存 allocation evidence を再利用し、Browser-managed allocation registry は必要最小限でよい。
|
- v0 では Runtime materializer の既存 allocation evidence を再利用し、Browser-managed allocation registry は必要最小限でよい。
|
||||||
- Worker launch UI は既存 New Worker form に Execution Workspace select / create link / refresh を追加する形でよい。
|
- Worker launch UI は既存 New Worker form に working directory select / create link / refresh を追加する形でよい。
|
||||||
- `relative_cwd` は empty/`.` を workspace root と扱ってよい。
|
- `relative_cwd` は empty/`.` を workspace root と扱ってよい。
|
||||||
|
|
||||||
Escalate if:
|
Escalate if:
|
||||||
|
|
@ -103,7 +103,7 @@ Validation:
|
||||||
- `nix build .#yoi --no-link`(Ticket acceptance に含まれるため実行する。実行不能なら理由を implementation report に明記する)
|
- `nix build .#yoi --no-link`(Ticket acceptance に含まれるため実行する。実行不能なら理由を implementation report に明記する)
|
||||||
|
|
||||||
Current code map:
|
Current code map:
|
||||||
- Runtime Execution Workspace: `crates/worker-runtime/src/catalog.rs`, `execution_workspace.rs`, `runtime.rs`, `worker_backend.rs`, `http_server.rs`。
|
- Runtime working directory: `crates/worker-runtime/src/catalog.rs`, `working_directory.rs`, `runtime.rs`, `worker_backend.rs`, `http_server.rs`。
|
||||||
- Workspace Backend host/API: `crates/workspace-server/src/server.rs`, `hosts.rs`, `repositories.rs`, `config.rs`。
|
- Workspace Backend host/API: `crates/workspace-server/src/server.rs`, `hosts.rs`, `repositories.rs`, `config.rs`。
|
||||||
- Worker launch UI and API helpers: `web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte`, worker launch model/tests, workspace API helper, scoped route layout。
|
- Worker launch UI and API helpers: `web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte`, worker launch model/tests, workspace API helper, scoped route layout。
|
||||||
- Worker detail/console: scoped worker console route and runtime worker detail/list projections。
|
- Worker detail/console: scoped worker console route and runtime worker detail/list projections。
|
||||||
|
|
@ -134,7 +134,7 @@ Checked context:
|
||||||
|
|
||||||
Acceptance basis:
|
Acceptance basis:
|
||||||
- concrete missing decision / information は残っていない。
|
- concrete missing decision / information は残っていない。
|
||||||
- Ticket の scope は Browser-managed pre-allocated Execution Workspace と Worker spawn 接続に閉じており、remote clone/cache, dirty source inclusion, raw path Browser contract, implicit one-shot materialization は非目標/後回しとして明確。
|
- Ticket の scope は Browser-managed pre-allocated working directory と Worker spawn 接続に閉じており、remote clone/cache, dirty source inclusion, raw path Browser contract, implicit one-shot materialization は非目標/後回しとして明確。
|
||||||
- side effect はこの `queued -> inprogress` acceptance 後に、worktree-workflow に沿って implementation worktree を作成してから開始する。
|
- side effect はこの `queued -> inprogress` acceptance 後に、worktree-workflow に沿って implementation worktree を作成してから開始する。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -146,8 +146,8 @@ Acceptance basis:
|
||||||
Implementation routing update:
|
Implementation routing update:
|
||||||
|
|
||||||
- Worktree-workflow に従い implementation worktree を作成した。
|
- Worktree-workflow に従い implementation worktree を作成した。
|
||||||
- Worktree: `/home/hare/Projects/yoi/.worktree/00001KWY8EHEJ-browser-execution-workspaces`
|
- Worktree: `/home/hare/Projects/yoi/.worktree/00001KWY8EHEJ-browser-working-directories`
|
||||||
- Branch: `work/00001KWY8EHEJ-browser-execution-workspaces`
|
- Branch: `work/00001KWY8EHEJ-browser-working-directories`
|
||||||
- Worktree には tracked `.yoi` project records が見えている。
|
- Worktree には tracked `.yoi` project records が見えている。
|
||||||
- `.yoi/memory` / `.yoi/runtime` / `.yoi/log` / `.yoi/logs` / `.yoi/local` / `.yoi/*.lock` / `.yoi/*secret*` は作成直後に存在しないことを確認した。
|
- `.yoi/memory` / `.yoi/runtime` / `.yoi/log` / `.yoi/logs` / `.yoi/local` / `.yoi/*.lock` / `.yoi/*secret*` は作成直後に存在しないことを確認した。
|
||||||
- Sibling Coder Pod `yoi-coder-00001KWY8EHEJ-browser-execws` を起動した。
|
- Sibling Coder Pod `yoi-coder-00001KWY8EHEJ-browser-execws` を起動した。
|
||||||
|
|
@ -162,20 +162,20 @@ Implementation routing update:
|
||||||
|
|
||||||
Implementation progress report:
|
Implementation progress report:
|
||||||
|
|
||||||
- Coder Pod completed implementation and committed `684b19e8 feat: add browser execution workspaces` on branch `work/00001KWY8EHEJ-browser-execution-workspaces`.
|
- Coder Pod completed implementation and committed `684b19e8 feat: add browser working directories` on branch `work/00001KWY8EHEJ-browser-working-directories`.
|
||||||
- Worktree status was clean after commit.
|
- Worktree status was clean after commit.
|
||||||
- Changed files include:
|
- Changed files include:
|
||||||
- `crates/worker-runtime/src/{catalog.rs,execution_workspace.rs,http_server.rs,runtime.rs,worker_backend.rs}`
|
- `crates/worker-runtime/src/{catalog.rs,working_directory.rs,http_server.rs,runtime.rs,worker_backend.rs}`
|
||||||
- `crates/workspace-server/src/{companion.rs,hosts.rs,server.rs}`
|
- `crates/workspace-server/src/{companion.rs,hosts.rs,server.rs}`
|
||||||
- `web/workspace/src/lib/workspace-sidebar/{WorkersNavSection.svelte,types.ts,worker-launch.ts,worker-launch.test.ts}`
|
- `web/workspace/src/lib/workspace-sidebar/{WorkersNavSection.svelte,types.ts,worker-launch.ts,worker-launch.test.ts}`
|
||||||
- Implementation summary:
|
- Implementation summary:
|
||||||
- Runtime-side preallocated Execution Workspace create/list/detail/cleanup and allocation binding at Worker spawn。
|
- Runtime-side preallocated working directory create/list/detail/cleanup and allocation binding at Worker spawn。
|
||||||
- `relative_cwd` validation rejects absolute paths, `..`, missing paths, files, and symlink escapes。
|
- `relative_cwd` validation rejects absolute paths, `..`, missing paths, files, and symlink escapes。
|
||||||
- scoped Browser API: `POST/GET /api/w/<workspace-id>/execution-workspaces`, detail, and cleanup。
|
- scoped Browser API: `POST/GET /api/w/<workspace-id>/working-directories`, detail, and cleanup。
|
||||||
- Browser API uses configured `repository_id` + selector + policy, not raw paths/internal `ExecutionWorkspaceRequest`。
|
- Browser API uses configured `repository_id` + selector + policy, not raw paths/internal `WorkingDirectoryRequest`。
|
||||||
- Browser responses expose safe summaries only。
|
- Browser responses expose safe summaries only。
|
||||||
- Worker spawn uses selected allocation id plus optional safe `relative_cwd`。
|
- Worker spawn uses selected allocation id plus optional safe `relative_cwd`。
|
||||||
- Worker list/detail projections include execution workspace safe summary。
|
- Worker list/detail projections include working directory safe summary。
|
||||||
- Workspace UI Worker launch flow can select existing allocations or create a new allocation before launch。
|
- Workspace UI Worker launch flow can select existing allocations or create a new allocation before launch。
|
||||||
- Orchestrator reran validation in the implementation worktree:
|
- Orchestrator reran validation in the implementation worktree:
|
||||||
- `git diff --check`: pass
|
- `git diff --check`: pass
|
||||||
|
|
@ -198,23 +198,23 @@ External review result: request_changes
|
||||||
|
|
||||||
Blocker:
|
Blocker:
|
||||||
|
|
||||||
1. Invalid `relative_cwd` is rejected internally, but the Browser-facing Worker spawn API does not return the required typed Execution Workspace diagnostic。
|
1. Invalid `relative_cwd` is rejected internally, but the Browser-facing Worker spawn API does not return the required typed working directory diagnostic。
|
||||||
- Ticket acceptance requires invalid `relative_cwd` to be rejected as a typed diagnostic。
|
- Ticket acceptance requires invalid `relative_cwd` to be rejected as a typed diagnostic。
|
||||||
- Materializer has specific typed codes:
|
- Materializer has specific typed codes:
|
||||||
- `execution_workspace_relative_cwd_invalid`
|
- `working_directory_relative_cwd_invalid`
|
||||||
- `execution_workspace_relative_cwd_unavailable`
|
- `working_directory_relative_cwd_unavailable`
|
||||||
- `execution_workspace_relative_cwd_escape_rejected`
|
- `working_directory_relative_cwd_escape_rejected`
|
||||||
- `crates/worker-runtime/src/worker_backend.rs` converts `ExecutionWorkspaceDiagnostic` to a plain string in `WorkerExecutionResult::rejected(...)`。
|
- `crates/worker-runtime/src/worker_backend.rs` converts `WorkingDirectoryDiagnostic` to a plain string in `WorkerExecutionResult::rejected(...)`。
|
||||||
- Workspace host/API then maps this to generic diagnostics/error codes, including generic embedded execution rejection and generic `workspace_worker_create_failed`。
|
- Workspace host/API then maps this to generic diagnostics/error codes, including generic embedded execution rejection and generic `workspace_worker_create_failed`。
|
||||||
- Result: Browser caller submitting invalid `relative_cwd` gets a generic worker-create/runtime rejection rather than a typed `execution_workspace_relative_cwd_*` diagnostic。
|
- Result: Browser caller submitting invalid `relative_cwd` gets a generic worker-create/runtime rejection rather than a typed `working_directory_relative_cwd_*` diagnostic。
|
||||||
- Browser/scoped API-level tests cover materializer bind validation but not the API rejection shape。
|
- Browser/scoped API-level tests cover materializer bind validation but not the API rejection shape。
|
||||||
|
|
||||||
Required fix:
|
Required fix:
|
||||||
- Preserve/propagate `ExecutionWorkspaceDiagnostic.code` through Worker spawn, or prevalidate allocation + `relative_cwd` in the Workspace API and return that typed diagnostic directly。
|
- Preserve/propagate `WorkingDirectoryDiagnostic.code` through Worker spawn, or prevalidate allocation + `relative_cwd` in the Workspace API and return that typed diagnostic directly。
|
||||||
- Add a scoped Browser API test for at least one invalid `relative_cwd` that asserts the typed `execution_workspace_relative_cwd_*` diagnostic is returned。
|
- Add a scoped Browser API test for at least one invalid `relative_cwd` that asserts the typed `working_directory_relative_cwd_*` diagnostic is returned。
|
||||||
|
|
||||||
Non-blocking follow-ups:
|
Non-blocking follow-ups:
|
||||||
- The implementation otherwise appears aligned with the pre-allocation design: scoped `/api/w/<workspace-id>/execution-workspaces` routes exist, Browser create payload uses configured `repository_id`/selector/policy rather than raw `ExecutionWorkspaceRequest`, summaries avoid raw paths, UI explicitly creates/selects allocations, and cleanup is bounded to the allocation root。
|
- The implementation otherwise appears aligned with the pre-allocation design: scoped `/api/w/<workspace-id>/working-directories` routes exist, Browser create payload uses configured `repository_id`/selector/policy rather than raw `WorkingDirectoryRequest`, summaries avoid raw paths, UI explicitly creates/selects allocations, and cleanup is bounded to the allocation root。
|
||||||
|
|
||||||
Validation reviewed:
|
Validation reviewed:
|
||||||
- Reviewer read Ticket item/thread, inspected `git show --stat --check 684b19e8` / `git show --name-only 684b19e8`, and reviewed Runtime, Workspace API/host, and frontend launch files。
|
- Reviewer read Ticket item/thread, inspected `git show --stat --check 684b19e8` / `git show --name-only 684b19e8`, and reviewed Runtime, Workspace API/host, and frontend launch files。
|
||||||
|
|
@ -228,13 +228,13 @@ Validation reviewed:
|
||||||
|
|
||||||
Review-fix implementation report:
|
Review-fix implementation report:
|
||||||
|
|
||||||
- Coder Pod completed requested changes and committed follow-up `9adb0fae fix: preserve execution workspace cwd diagnostics` on branch `work/00001KWY8EHEJ-browser-execution-workspaces`.
|
- Coder Pod completed requested changes and committed follow-up `9adb0fae fix: preserve working directory cwd diagnostics` on branch `work/00001KWY8EHEJ-browser-working-directories`.
|
||||||
- Worktree status was clean after commit.
|
- Worktree status was clean after commit.
|
||||||
- Fix summary:
|
- Fix summary:
|
||||||
- Browser Worker spawn now prevalidates selected execution workspace allocation + `relative_cwd` through the materializer before spawn。
|
- Browser Worker spawn now prevalidates selected working directory allocation + `relative_cwd` through the materializer before spawn。
|
||||||
- `ExecutionWorkspaceDiagnostic` is mapped directly into Browser API error diagnostics, preserving typed codes such as `execution_workspace_relative_cwd_invalid`, `execution_workspace_relative_cwd_unavailable`, and `execution_workspace_relative_cwd_escape_rejected`。
|
- `WorkingDirectoryDiagnostic` is mapped directly into Browser API error diagnostics, preserving typed codes such as `working_directory_relative_cwd_invalid`, `working_directory_relative_cwd_unavailable`, and `working_directory_relative_cwd_escape_rejected`。
|
||||||
- Added a scoped Browser API regression test asserting invalid `relative_cwd: "../escape"` returns typed `execution_workspace_relative_cwd_invalid` diagnostic。
|
- Added a scoped Browser API regression test asserting invalid `relative_cwd: "../escape"` returns typed `working_directory_relative_cwd_invalid` diagnostic。
|
||||||
- Existing scoped execution workspace routes, safe summaries, explicit UI allocation flow, and bounded cleanup behavior remain unchanged。
|
- Existing scoped working directory routes, safe summaries, explicit UI allocation flow, and bounded cleanup behavior remain unchanged。
|
||||||
- Orchestrator reran validation in the implementation worktree:
|
- Orchestrator reran validation in the implementation worktree:
|
||||||
- `git diff --check`: pass
|
- `git diff --check`: pass
|
||||||
- `cd web/workspace && deno task check`: pass(0 errors / 0 warnings)
|
- `cd web/workspace && deno task check`: pass(0 errors / 0 warnings)
|
||||||
|
|
@ -259,13 +259,13 @@ Blockers: none.
|
||||||
Evidence reviewed in implementation worktree only:
|
Evidence reviewed in implementation worktree only:
|
||||||
- Ticket records: `.yoi/tickets/00001KWY8EHEJ/item.md`, `thread.md`, and orchestration artifact。
|
- Ticket records: `.yoi/tickets/00001KWY8EHEJ/item.md`, `thread.md`, and orchestration artifact。
|
||||||
- Fix diff: `9adb0fae` stat/check and `684b19e8..9adb0fae` focused diff for `crates/workspace-server/src/server.rs`。
|
- Fix diff: `9adb0fae` stat/check and `684b19e8..9adb0fae` focused diff for `crates/workspace-server/src/server.rs`。
|
||||||
- Relevant files: `workspace-server/src/server.rs`, `worker-runtime/src/execution_workspace.rs`, `worker_backend.rs`, scoped host/runtime API pieces, and worker launch UI/model/tests。
|
- Relevant files: `workspace-server/src/server.rs`, `worker-runtime/src/working_directory.rs`, `worker_backend.rs`, scoped host/runtime API pieces, and worker launch UI/model/tests。
|
||||||
|
|
||||||
Findings:
|
Findings:
|
||||||
- Prior blocker is resolved. Browser Worker spawn now pre-validates `execution_workspace.relative_cwd` through the execution workspace materializer before calling the runtime spawn path, and maps `ExecutionWorkspaceError` into API diagnostics via `execution_workspace_api_error`。
|
- Prior blocker is resolved. Browser Worker spawn now pre-validates `working_directory.relative_cwd` through the working directory materializer before calling the runtime spawn path, and maps `WorkingDirectoryError` into API diagnostics via `working_directory_api_error`。
|
||||||
- Invalid Browser spawn `relative_cwd` now returns typed diagnostics with the expected codes, covered by the added test asserting `execution_workspace_relative_cwd_invalid` appears both in diagnostics and message。
|
- Invalid Browser spawn `relative_cwd` now returns typed diagnostics with the expected codes, covered by the added test asserting `working_directory_relative_cwd_invalid` appears both in diagnostics and message。
|
||||||
- Browser-facing spawn payload remains allocation-based (`allocation_id` + optional `relative_cwd`) rather than exposing raw `ExecutionWorkspaceRequest` or host paths. UI request construction sends allocation id and relative cwd only。
|
- Browser-facing spawn payload remains allocation-based (`allocation_id` + optional `relative_cwd`) rather than exposing raw `WorkingDirectoryRequest` or host paths. UI request construction sends allocation id and relative cwd only。
|
||||||
- Scoped create/list/detail/cleanup routes remain under `/api/w/:workspace_id/execution-workspaces`; worker creation uses the scoped workspace API path from the Browser UI。
|
- Scoped create/list/detail/cleanup routes remain under `/api/w/:workspace_id/working-directories`; worker creation uses the scoped workspace API path from the Browser UI。
|
||||||
- Cleanup remains bounded to the allocation root: cleanup re-derives allocation root from the allocation id/root, canonicalizes workspace root, rejects escape, and removes only the recorded workspace root。
|
- Cleanup remains bounded to the allocation root: cleanup re-derives allocation root from the allocation id/root, canonicalizes workspace root, rejects escape, and removes only the recorded workspace root。
|
||||||
- Runtime/internal trusted path still performs canonical materialization/bind at Worker launch, while Browser gets a safer scoped allocation API。
|
- Runtime/internal trusted path still performs canonical materialization/bind at Worker launch, while Browser gets a safer scoped allocation API。
|
||||||
- Reported validation was comprehensive and green; reviewer did not rerun tests as this was a read-only follow-up review。
|
- Reported validation was comprehensive and green; reviewer did not rerun tests as this was a read-only follow-up review。
|
||||||
|
|
@ -283,7 +283,7 @@ No merge-blocking issues found.
|
||||||
Merge/validation acceptance:
|
Merge/validation acceptance:
|
||||||
|
|
||||||
- Follow-up external review approved the implementation after blocker fix。
|
- Follow-up external review approved the implementation after blocker fix。
|
||||||
- Implementation branch `work/00001KWY8EHEJ-browser-execution-workspaces` was merged into the orchestration branch with merge commit `b52986e5 merge: browser execution workspaces`。
|
- Implementation branch `work/00001KWY8EHEJ-browser-working-directories` was merged into the orchestration branch with merge commit `b52986e5 merge: browser working directories`。
|
||||||
- Final validation in Orchestrator worktree passed:
|
- Final validation in Orchestrator worktree passed:
|
||||||
- `git diff --check`: pass
|
- `git diff --check`: pass
|
||||||
- `cd web/workspace && deno task check`: pass(0 errors / 0 warnings)
|
- `cd web/workspace && deno task check`: pass(0 errors / 0 warnings)
|
||||||
|
|
@ -295,8 +295,8 @@ Merge/validation acceptance:
|
||||||
- `nix build .#yoi --no-link`: pass
|
- `nix build .#yoi --no-link`: pass
|
||||||
|
|
||||||
Implementation commits:
|
Implementation commits:
|
||||||
- `684b19e8 feat: add browser execution workspaces`
|
- `684b19e8 feat: add browser working directories`
|
||||||
- `9adb0fae fix: preserve execution workspace cwd diagnostics`
|
- `9adb0fae fix: preserve working directory cwd diagnostics`
|
||||||
|
|
||||||
No merge-blocking issues remain.
|
No merge-blocking issues remain.
|
||||||
|
|
||||||
|
|
@ -318,24 +318,24 @@ Ticket を closed にしました。
|
||||||
完了。
|
完了。
|
||||||
|
|
||||||
実装内容:
|
実装内容:
|
||||||
- Browser-managed Execution Workspace API を scoped Workspace API に追加した。
|
- Browser-managed working directory API を scoped Workspace API に追加した。
|
||||||
- `POST /api/w/<workspace-id>/execution-workspaces`
|
- `POST /api/w/<workspace-id>/working-directories`
|
||||||
- `GET /api/w/<workspace-id>/execution-workspaces`
|
- `GET /api/w/<workspace-id>/working-directories`
|
||||||
- detail / cleanup endpoint
|
- detail / cleanup endpoint
|
||||||
- Browser create payload は configured `repository_id` / selector / policy を使い、raw host path / raw internal `ExecutionWorkspaceRequest` / internal runtime path を受け取らないようにした。
|
- Browser create payload は configured `repository_id` / selector / policy を使い、raw host path / raw internal `WorkingDirectoryRequest` / internal runtime path を受け取らないようにした。
|
||||||
- Browser response は allocation id、repository id、selector/commit evidence、status、cleanup policy など safe summary のみを返すようにした。
|
- Browser response は allocation id、repository id、selector/commit evidence、status、cleanup policy など safe summary のみを返すようにした。
|
||||||
- Worker launch UI で Execution Workspace を create/select できるようにした。
|
- Worker launch UI で working directory を create/select できるようにした。
|
||||||
- Worker spawn は selected allocation id + optional `relative_cwd` を使い、Runtime materialized workspace に接続するようにした。
|
- Worker spawn は selected allocation id + optional `relative_cwd` を使い、Runtime materialized workspace に接続するようにした。
|
||||||
- `relative_cwd` は materialized root 相対として検証し、absolute path、`..` escape、symlink escape、nonexistent / non-directory を拒否するようにした。
|
- `relative_cwd` は materialized root 相対として検証し、absolute path、`..` escape、symlink escape、nonexistent / non-directory を拒否するようにした。
|
||||||
- invalid `relative_cwd` は Browser-facing API で typed `execution_workspace_relative_cwd_*` diagnostic として返るようにした。
|
- invalid `relative_cwd` は Browser-facing API で typed `working_directory_relative_cwd_*` diagnostic として返るようにした。
|
||||||
- Worker list/detail に execution workspace safe summary を含めた。
|
- Worker list/detail に working directory safe summary を含めた。
|
||||||
- cleanup は allocation root に bounded され、typed/sanitized diagnostic を返すようにした。
|
- cleanup は allocation root に bounded され、typed/sanitized diagnostic を返すようにした。
|
||||||
- Focused backend/runtime/web tests を追加した。
|
- Focused backend/runtime/web tests を追加した。
|
||||||
|
|
||||||
主な commit / merge:
|
主な commit / merge:
|
||||||
- implementation: `684b19e8 feat: add browser execution workspaces`
|
- implementation: `684b19e8 feat: add browser working directories`
|
||||||
- review fix: `9adb0fae fix: preserve execution workspace cwd diagnostics`
|
- review fix: `9adb0fae fix: preserve working directory cwd diagnostics`
|
||||||
- merge into orchestration: `b52986e5 merge: browser execution workspaces`
|
- merge into orchestration: `b52986e5 merge: browser working directories`
|
||||||
|
|
||||||
Review:
|
Review:
|
||||||
- 初回 external review は request_changes。
|
- 初回 external review は request_changes。
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ impl std::ops::Deref for RepositorySelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceRepository {
|
pub struct WorkingDirectoryRepository {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub provider: String,
|
pub provider: String,
|
||||||
pub uri: String,
|
pub uri: String,
|
||||||
|
|
@ -81,8 +81,8 @@ pub enum DirtyStatePolicy {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceRequest {
|
pub struct WorkingDirectoryRequest {
|
||||||
pub repository: ExecutionWorkspaceRepository,
|
pub repository: WorkingDirectoryRepository,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub materializer: MaterializerKind,
|
pub materializer: MaterializerKind,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -90,7 +90,7 @@ pub struct ExecutionWorkspaceRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceAllocationClaim {
|
pub struct WorkingDirectoryAllocationClaim {
|
||||||
pub allocation_id: String,
|
pub allocation_id: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub relative_cwd: Option<String>,
|
pub relative_cwd: Option<String>,
|
||||||
|
|
@ -98,21 +98,21 @@ pub struct ExecutionWorkspaceAllocationClaim {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ExecutionWorkspaceStatusKind {
|
pub enum WorkingDirectoryStatusKind {
|
||||||
Active,
|
Active,
|
||||||
Removed,
|
Removed,
|
||||||
CleanupPending,
|
CleanupPending,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceCleanupTarget {
|
pub struct WorkingDirectoryCleanupTarget {
|
||||||
pub kind: String,
|
pub kind: String,
|
||||||
pub allocation_id: String,
|
pub allocation_id: String,
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceSummary {
|
pub struct WorkingDirectorySummary {
|
||||||
pub allocation_id: String,
|
pub allocation_id: String,
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -124,15 +124,15 @@ pub struct ExecutionWorkspaceSummary {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub resolved_tree: Option<String>,
|
pub resolved_tree: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub cleanup_target: Option<ExecutionWorkspaceCleanupTarget>,
|
pub cleanup_target: Option<WorkingDirectoryCleanupTarget>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub cleanup_policy: Option<String>,
|
pub cleanup_policy: Option<String>,
|
||||||
pub status: ExecutionWorkspaceStatusKind,
|
pub status: WorkingDirectoryStatusKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceStatus {
|
pub struct WorkingDirectoryStatus {
|
||||||
pub summary: ExecutionWorkspaceSummary,
|
pub summary: WorkingDirectorySummary,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Canonical Runtime Worker creation request.
|
/// Canonical Runtime Worker creation request.
|
||||||
|
|
@ -141,9 +141,9 @@ pub struct ExecutionWorkspaceStatus {
|
||||||
/// request is built. The request contains only durable Runtime identity inputs:
|
/// request is built. The request contains only durable Runtime identity inputs:
|
||||||
/// a backend-decided profile selector, a previously synced ConfigBundle identity,
|
/// a backend-decided profile selector, a previously synced ConfigBundle identity,
|
||||||
/// optional initial user input that is committed in the same transaction as
|
/// optional initial user input that is committed in the same transaction as
|
||||||
/// Worker catalog/transcript persistence, and an optional execution workspace
|
/// Worker catalog/transcript persistence, and an optional working directory
|
||||||
/// request that preserves RepositoryPoint-style semantics for runtime-side
|
/// request that preserves RepositoryPoint-style semantics for runtime-side
|
||||||
/// materialization. Browser-facing status for materialized workspaces is
|
/// materialization. Browser-facing status for materialized working directories is
|
||||||
/// summarized without exposing raw host paths.
|
/// summarized without exposing raw host paths.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct CreateWorkerRequest {
|
pub struct CreateWorkerRequest {
|
||||||
|
|
@ -152,9 +152,9 @@ pub struct CreateWorkerRequest {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub initial_input: Option<WorkerInput>,
|
pub initial_input: Option<WorkerInput>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub execution_workspace: Option<ExecutionWorkspaceRequest>,
|
pub working_directory: Option<WorkingDirectoryRequest>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub execution_workspace_allocation: Option<ExecutionWorkspaceAllocationClaim>,
|
pub working_directory_allocation: Option<WorkingDirectoryAllocationClaim>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Worker lifecycle status for the in-memory embedded runtime.
|
/// Worker lifecycle status for the in-memory embedded runtime.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::catalog::{CreateWorkerRequest, ExecutionWorkspaceStatus};
|
use crate::catalog::{CreateWorkerRequest, WorkingDirectoryStatus};
|
||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
use crate::execution_workspace::ExecutionWorkspaceBinding;
|
|
||||||
use crate::identity::WorkerRef;
|
use crate::identity::WorkerRef;
|
||||||
use crate::interaction::WorkerInput;
|
use crate::interaction::WorkerInput;
|
||||||
#[cfg(feature = "ws-server")]
|
#[cfg(feature = "ws-server")]
|
||||||
use crate::observation::WorkerObservationEvent;
|
use crate::observation::WorkerObservationEvent;
|
||||||
|
use crate::working_directory::WorkingDirectoryBinding;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -154,7 +154,7 @@ pub struct WorkerExecutionStatus {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub binding: Option<WorkerExecutionBindingIdentity>,
|
pub binding: Option<WorkerExecutionBindingIdentity>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub execution_workspace: Option<ExecutionWorkspaceStatus>,
|
pub working_directory: Option<WorkingDirectoryStatus>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub last_result: Option<WorkerExecutionResult>,
|
pub last_result: Option<WorkerExecutionResult>,
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ impl WorkerExecutionStatus {
|
||||||
backend: WorkerExecutionBackendKind::Connected,
|
backend: WorkerExecutionBackendKind::Connected,
|
||||||
run_state,
|
run_state,
|
||||||
binding: None,
|
binding: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
last_result: None,
|
last_result: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -185,8 +185,8 @@ impl WorkerExecutionStatus {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_execution_workspace(mut self, status: ExecutionWorkspaceStatus) -> Self {
|
pub fn with_working_directory(mut self, status: WorkingDirectoryStatus) -> Self {
|
||||||
self.execution_workspace = Some(status);
|
self.working_directory = Some(status);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,7 +286,7 @@ pub struct WorkerExecutionSpawnRequest {
|
||||||
pub worker_ref: WorkerRef,
|
pub worker_ref: WorkerRef,
|
||||||
pub request: CreateWorkerRequest,
|
pub request: CreateWorkerRequest,
|
||||||
pub context: WorkerExecutionContext,
|
pub context: WorkerExecutionContext,
|
||||||
pub execution_workspace: Option<ExecutionWorkspaceBinding>,
|
pub working_directory: Option<WorkingDirectoryBinding>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result of backend Worker spawn/initialization.
|
/// Result of backend Worker spawn/initialization.
|
||||||
|
|
@ -295,7 +295,7 @@ pub enum WorkerExecutionSpawnResult {
|
||||||
Connected {
|
Connected {
|
||||||
handle: WorkerExecutionHandle,
|
handle: WorkerExecutionHandle,
|
||||||
run_state: WorkerExecutionRunState,
|
run_state: WorkerExecutionRunState,
|
||||||
execution_workspace: Option<ExecutionWorkspaceStatus>,
|
working_directory: Option<WorkingDirectoryStatus>,
|
||||||
},
|
},
|
||||||
Rejected(WorkerExecutionResult),
|
Rejected(WorkerExecutionResult),
|
||||||
Errored(WorkerExecutionResult),
|
Errored(WorkerExecutionResult),
|
||||||
|
|
|
||||||
|
|
@ -863,8 +863,8 @@ mod tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
execution_workspace_allocation: None,
|
working_directory_allocation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -879,8 +879,8 @@ mod tests {
|
||||||
WorkerExecutionSpawnResult::Connected {
|
WorkerExecutionSpawnResult::Connected {
|
||||||
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
||||||
run_state: WorkerExecutionRunState::Idle,
|
run_state: WorkerExecutionRunState::Idle,
|
||||||
execution_workspace: request
|
working_directory: request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.status()),
|
.map(|binding| binding.status()),
|
||||||
}
|
}
|
||||||
|
|
@ -1098,8 +1098,8 @@ mod ws_tests {
|
||||||
WorkerExecutionSpawnResult::Connected {
|
WorkerExecutionSpawnResult::Connected {
|
||||||
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
||||||
run_state: WorkerExecutionRunState::Idle,
|
run_state: WorkerExecutionRunState::Idle,
|
||||||
execution_workspace: request
|
working_directory: request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.status()),
|
.map(|binding| binding.status()),
|
||||||
}
|
}
|
||||||
|
|
@ -1148,8 +1148,8 @@ mod ws_tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
execution_workspace_allocation: None,
|
working_directory_allocation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ pub mod config_bundle;
|
||||||
pub mod diagnostics;
|
pub mod diagnostics;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod execution;
|
pub mod execution;
|
||||||
pub mod execution_workspace;
|
|
||||||
#[cfg(feature = "fs-store")]
|
#[cfg(feature = "fs-store")]
|
||||||
pub mod fs_store;
|
pub mod fs_store;
|
||||||
#[cfg(feature = "http-server")]
|
#[cfg(feature = "http-server")]
|
||||||
|
|
@ -22,6 +21,7 @@ pub mod management;
|
||||||
pub mod observation;
|
pub mod observation;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
pub mod worker_backend;
|
pub mod worker_backend;
|
||||||
|
pub mod working_directory;
|
||||||
|
|
||||||
#[cfg(feature = "fs-store")]
|
#[cfg(feature = "fs-store")]
|
||||||
pub use fs_store::{FsRuntimeStore, FsRuntimeStoreOptions};
|
pub use fs_store::{FsRuntimeStore, FsRuntimeStoreOptions};
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ use std::process::ExitCode;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use worker_runtime::error::RuntimeError;
|
use worker_runtime::error::RuntimeError;
|
||||||
use worker_runtime::execution_workspace::LocalGitWorktreeMaterializer;
|
|
||||||
use worker_runtime::fs_store::FsRuntimeStoreOptions;
|
use worker_runtime::fs_store::FsRuntimeStoreOptions;
|
||||||
use worker_runtime::http_server::{
|
use worker_runtime::http_server::{
|
||||||
RuntimeHttpServerConfig, RuntimeHttpServerError, RuntimeHttpStoreSelection,
|
RuntimeHttpServerConfig, RuntimeHttpServerError, RuntimeHttpStoreSelection,
|
||||||
};
|
};
|
||||||
use worker_runtime::identity::RuntimeId;
|
use worker_runtime::identity::RuntimeId;
|
||||||
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
|
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
|
||||||
|
use worker_runtime::working_directory::LocalGitWorktreeMaterializer;
|
||||||
use worker_runtime::{Runtime, RuntimeOptions};
|
use worker_runtime::{Runtime, RuntimeOptions};
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
|
|
@ -81,12 +81,12 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
|
||||||
if let Some(profile) = config.profile.clone() {
|
if let Some(profile) = config.profile.clone() {
|
||||||
factory = factory.with_profile(profile);
|
factory = factory.with_profile(profile);
|
||||||
}
|
}
|
||||||
let execution_workspace_root = execution_workspace_runtime_root(config);
|
let working_directory_root = working_directory_runtime_root(config);
|
||||||
let backend = Arc::new(
|
let backend = Arc::new(
|
||||||
WorkerRuntimeExecutionBackend::new(factory)
|
WorkerRuntimeExecutionBackend::new(factory)
|
||||||
.map_err(ProcessError::WorkerAdapter)?
|
.map_err(ProcessError::WorkerAdapter)?
|
||||||
.with_execution_workspace_materializer(LocalGitWorktreeMaterializer::new(
|
.with_working_directory_materializer(LocalGitWorktreeMaterializer::new(
|
||||||
execution_workspace_root,
|
working_directory_root,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execution_workspace_runtime_root(config: &ProcessConfig) -> PathBuf {
|
fn working_directory_runtime_root(config: &ProcessConfig) -> PathBuf {
|
||||||
match &config.http.store {
|
match &config.http.store {
|
||||||
RuntimeHttpStoreSelection::Fs { root } => root.clone(),
|
RuntimeHttpStoreSelection::Fs { root } => root.clone(),
|
||||||
_ => config
|
_ => config
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::catalog::{
|
use crate::catalog::{
|
||||||
ConfigBundleRef, CreateWorkerRequest,
|
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail, WorkerLifecycleAck,
|
||||||
ExecutionWorkspaceStatus as CatalogExecutionWorkspaceStatus, ProfileSelector, WorkerDetail,
|
WorkerStatus, WorkerSummary, WorkingDirectoryStatus as CatalogWorkingDirectoryStatus,
|
||||||
WorkerLifecycleAck, WorkerStatus, WorkerSummary,
|
|
||||||
};
|
};
|
||||||
use crate::config_bundle::{
|
use crate::config_bundle::{
|
||||||
ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary, validate_config_bundle,
|
ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary, validate_config_bundle,
|
||||||
|
|
@ -289,18 +288,18 @@ impl Runtime {
|
||||||
worker_ref: worker_ref.clone(),
|
worker_ref: worker_ref.clone(),
|
||||||
request,
|
request,
|
||||||
context: self.execution_context(worker_ref.clone()),
|
context: self.execution_context(worker_ref.clone()),
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
};
|
};
|
||||||
(backend, worker_ref, spawn_request)
|
(backend, worker_ref, spawn_request)
|
||||||
};
|
};
|
||||||
|
|
||||||
let spawn_result = backend.spawn_worker(spawn_request);
|
let spawn_result = backend.spawn_worker(spawn_request);
|
||||||
let (handle, run_state, execution_workspace) = match spawn_result {
|
let (handle, run_state, working_directory) = match spawn_result {
|
||||||
WorkerExecutionSpawnResult::Connected {
|
WorkerExecutionSpawnResult::Connected {
|
||||||
handle,
|
handle,
|
||||||
run_state,
|
run_state,
|
||||||
execution_workspace,
|
working_directory,
|
||||||
} => (handle, run_state, execution_workspace),
|
} => (handle, run_state, working_directory),
|
||||||
WorkerExecutionSpawnResult::Rejected(result)
|
WorkerExecutionSpawnResult::Rejected(result)
|
||||||
| WorkerExecutionSpawnResult::Errored(result) => {
|
| WorkerExecutionSpawnResult::Errored(result) => {
|
||||||
self.rollback_failed_create(&worker_ref)?;
|
self.rollback_failed_create(&worker_ref)?;
|
||||||
|
|
@ -334,7 +333,7 @@ impl Runtime {
|
||||||
&worker_ref,
|
&worker_ref,
|
||||||
handle,
|
handle,
|
||||||
WorkerExecutionRunState::Busy,
|
WorkerExecutionRunState::Busy,
|
||||||
execution_workspace,
|
working_directory,
|
||||||
WorkerExecutionResult::accepted(
|
WorkerExecutionResult::accepted(
|
||||||
WorkerExecutionOperation::Input,
|
WorkerExecutionOperation::Input,
|
||||||
WorkerExecutionRunState::Busy,
|
WorkerExecutionRunState::Busy,
|
||||||
|
|
@ -345,7 +344,7 @@ impl Runtime {
|
||||||
&worker_ref,
|
&worker_ref,
|
||||||
handle,
|
handle,
|
||||||
run_state,
|
run_state,
|
||||||
execution_workspace,
|
working_directory,
|
||||||
WorkerExecutionResult::accepted(WorkerExecutionOperation::Spawn, run_state),
|
WorkerExecutionResult::accepted(WorkerExecutionOperation::Spawn, run_state),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -441,7 +440,7 @@ impl Runtime {
|
||||||
backend: WorkerExecutionBackendKind::Connected,
|
backend: WorkerExecutionBackendKind::Connected,
|
||||||
run_state: dispatch_result.run_state,
|
run_state: dispatch_result.run_state,
|
||||||
binding: worker.execution.binding.clone(),
|
binding: worker.execution.binding.clone(),
|
||||||
execution_workspace: worker.execution.execution_workspace.clone(),
|
working_directory: worker.execution.working_directory.clone(),
|
||||||
last_result: Some(dispatch_result),
|
last_result: Some(dispatch_result),
|
||||||
};
|
};
|
||||||
worker.transcript.push(TranscriptEntry {
|
worker.transcript.push(TranscriptEntry {
|
||||||
|
|
@ -491,7 +490,7 @@ impl Runtime {
|
||||||
worker_ref: &WorkerRef,
|
worker_ref: &WorkerRef,
|
||||||
handle: WorkerExecutionHandle,
|
handle: WorkerExecutionHandle,
|
||||||
run_state: WorkerExecutionRunState,
|
run_state: WorkerExecutionRunState,
|
||||||
execution_workspace: Option<CatalogExecutionWorkspaceStatus>,
|
working_directory: Option<CatalogWorkingDirectoryStatus>,
|
||||||
result: WorkerExecutionResult,
|
result: WorkerExecutionResult,
|
||||||
) -> Result<WorkerDetail, RuntimeError> {
|
) -> Result<WorkerDetail, RuntimeError> {
|
||||||
let mut state = self.lock()?;
|
let mut state = self.lock()?;
|
||||||
|
|
@ -501,8 +500,8 @@ impl Runtime {
|
||||||
let worker = state.worker_mut(worker_ref)?;
|
let worker = state.worker_mut(worker_ref)?;
|
||||||
worker.execution_handle = Some(handle);
|
worker.execution_handle = Some(handle);
|
||||||
let mut execution = WorkerExecutionStatus::connected(run_state).with_binding(binding);
|
let mut execution = WorkerExecutionStatus::connected(run_state).with_binding(binding);
|
||||||
if let Some(status) = execution_workspace {
|
if let Some(status) = working_directory {
|
||||||
execution = execution.with_execution_workspace(status);
|
execution = execution.with_working_directory(status);
|
||||||
}
|
}
|
||||||
worker.execution = execution.with_result(result);
|
worker.execution = execution.with_result(result);
|
||||||
worker.detail(&runtime_id)
|
worker.detail(&runtime_id)
|
||||||
|
|
@ -538,7 +537,7 @@ impl Runtime {
|
||||||
backend: WorkerExecutionBackendKind::Connected,
|
backend: WorkerExecutionBackendKind::Connected,
|
||||||
run_state: result.run_state,
|
run_state: result.run_state,
|
||||||
binding: worker.execution.binding.clone(),
|
binding: worker.execution.binding.clone(),
|
||||||
execution_workspace: worker.execution.execution_workspace.clone(),
|
working_directory: worker.execution.working_directory.clone(),
|
||||||
last_result: Some(result),
|
last_result: Some(result),
|
||||||
};
|
};
|
||||||
state.persist_worker(&worker_ref.worker_id)?;
|
state.persist_worker(&worker_ref.worker_id)?;
|
||||||
|
|
@ -1593,8 +1592,8 @@ mod tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
execution_workspace_allocation: None,
|
working_directory_allocation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1660,8 +1659,8 @@ mod tests {
|
||||||
WorkerExecutionSpawnResult::Connected {
|
WorkerExecutionSpawnResult::Connected {
|
||||||
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
||||||
run_state: WorkerExecutionRunState::Idle,
|
run_state: WorkerExecutionRunState::Idle,
|
||||||
execution_workspace: request
|
working_directory: request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.status()),
|
.map(|binding| binding.status()),
|
||||||
}
|
}
|
||||||
|
|
@ -1935,8 +1934,8 @@ mod tests {
|
||||||
WorkerExecutionSpawnResult::Connected {
|
WorkerExecutionSpawnResult::Connected {
|
||||||
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
||||||
run_state: WorkerExecutionRunState::Idle,
|
run_state: WorkerExecutionRunState::Idle,
|
||||||
execution_workspace: request
|
working_directory: request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.status()),
|
.map(|binding| binding.status()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ use crate::execution::{
|
||||||
WorkerExecutionBackend, WorkerExecutionHandle, WorkerExecutionOperation, WorkerExecutionResult,
|
WorkerExecutionBackend, WorkerExecutionHandle, WorkerExecutionOperation, WorkerExecutionResult,
|
||||||
WorkerExecutionRunState, WorkerExecutionSpawnRequest, WorkerExecutionSpawnResult,
|
WorkerExecutionRunState, WorkerExecutionSpawnRequest, WorkerExecutionSpawnResult,
|
||||||
};
|
};
|
||||||
use crate::execution_workspace::{ExecutionWorkspaceBinding, ExecutionWorkspaceMaterializer};
|
|
||||||
use crate::interaction::{WorkerInput, WorkerInputKind};
|
use crate::interaction::{WorkerInput, WorkerInputKind};
|
||||||
|
use crate::working_directory::{WorkingDirectoryBinding, WorkingDirectoryMaterializer};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use manifest::paths;
|
use manifest::paths;
|
||||||
use protocol::{Method, Segment, WorkerStatus};
|
use protocol::{Method, Segment, WorkerStatus};
|
||||||
|
|
@ -162,12 +162,12 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||||
let worker_name = Self::runtime_worker_name(&request);
|
let worker_name = Self::runtime_worker_name(&request);
|
||||||
let profile = self.runtime_profile(&request);
|
let profile = self.runtime_profile(&request);
|
||||||
let workspace_root = request
|
let workspace_root = request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.workspace_root().to_path_buf())
|
.map(|binding| binding.workspace_root().to_path_buf())
|
||||||
.unwrap_or_else(|| self.workspace_root.clone());
|
.unwrap_or_else(|| self.workspace_root.clone());
|
||||||
let cwd = request
|
let cwd = request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.cwd().to_path_buf())
|
.map(|binding| binding.cwd().to_path_buf())
|
||||||
.unwrap_or_else(|| self.cwd.clone());
|
.unwrap_or_else(|| self.cwd.clone());
|
||||||
|
|
@ -210,14 +210,14 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||||
struct RuntimeWorkerExecution {
|
struct RuntimeWorkerExecution {
|
||||||
handle: WorkerHandle,
|
handle: WorkerHandle,
|
||||||
busy: Arc<AtomicBool>,
|
busy: Arc<AtomicBool>,
|
||||||
execution_workspace: Option<ExecutionWorkspaceBinding>,
|
working_directory: Option<WorkingDirectoryBinding>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `worker-runtime` execution backend backed by real `worker` crate Workers.
|
/// `worker-runtime` execution backend backed by real `worker` crate Workers.
|
||||||
pub struct WorkerRuntimeExecutionBackend<F = ProfileRuntimeWorkerFactory> {
|
pub struct WorkerRuntimeExecutionBackend<F = ProfileRuntimeWorkerFactory> {
|
||||||
backend_id: String,
|
backend_id: String,
|
||||||
factory: Arc<F>,
|
factory: Arc<F>,
|
||||||
execution_workspace_materializer: Option<Arc<dyn ExecutionWorkspaceMaterializer>>,
|
working_directory_materializer: Option<Arc<dyn WorkingDirectoryMaterializer>>,
|
||||||
runtime: Mutex<Option<Runtime>>,
|
runtime: Mutex<Option<Runtime>>,
|
||||||
workers: Mutex<HashMap<crate::identity::WorkerRef, RuntimeWorkerExecution>>,
|
workers: Mutex<HashMap<crate::identity::WorkerRef, RuntimeWorkerExecution>>,
|
||||||
}
|
}
|
||||||
|
|
@ -241,7 +241,7 @@ where
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
backend_id: DEFAULT_BACKEND_ID.to_string(),
|
backend_id: DEFAULT_BACKEND_ID.to_string(),
|
||||||
factory: Arc::new(factory),
|
factory: Arc::new(factory),
|
||||||
execution_workspace_materializer: None,
|
working_directory_materializer: None,
|
||||||
runtime: Mutex::new(Some(runtime)),
|
runtime: Mutex::new(Some(runtime)),
|
||||||
workers: Mutex::new(HashMap::new()),
|
workers: Mutex::new(HashMap::new()),
|
||||||
})
|
})
|
||||||
|
|
@ -252,11 +252,11 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_execution_workspace_materializer(
|
pub fn with_working_directory_materializer(
|
||||||
mut self,
|
mut self,
|
||||||
materializer: impl ExecutionWorkspaceMaterializer,
|
materializer: impl WorkingDirectoryMaterializer,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.execution_workspace_materializer = Some(Arc::new(materializer));
|
self.working_directory_materializer = Some(Arc::new(materializer));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -374,26 +374,26 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut request = request;
|
let mut request = request;
|
||||||
let execution_workspace = match (
|
let working_directory = match (
|
||||||
request.request.execution_workspace.as_ref(),
|
request.request.working_directory.as_ref(),
|
||||||
request.request.execution_workspace_allocation.as_ref(),
|
request.request.working_directory_allocation.as_ref(),
|
||||||
) {
|
) {
|
||||||
(Some(_), Some(_)) => {
|
(Some(_), Some(_)) => {
|
||||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||||
WorkerExecutionOperation::Spawn,
|
WorkerExecutionOperation::Spawn,
|
||||||
"worker spawn cannot specify both execution_workspace and execution_workspace_allocation",
|
"worker spawn cannot specify both working_directory and working_directory_allocation",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
(Some(workspace_request), None) => {
|
(Some(workspace_request), None) => {
|
||||||
let Some(materializer) = self.execution_workspace_materializer.as_ref() else {
|
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||||
WorkerExecutionOperation::Spawn,
|
WorkerExecutionOperation::Spawn,
|
||||||
"execution workspace materialization requested, but no materializer is configured for this runtime backend",
|
"working directory materialization requested, but no materializer is configured for this runtime backend",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
match materializer.materialize(&request.worker_ref, workspace_request) {
|
match materializer.materialize(&request.worker_ref, workspace_request) {
|
||||||
Ok(binding) => {
|
Ok(binding) => {
|
||||||
request.execution_workspace = Some(binding.clone());
|
request.working_directory = Some(binding.clone());
|
||||||
Some(binding)
|
Some(binding)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
@ -407,10 +407,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, Some(allocation)) => {
|
(None, Some(allocation)) => {
|
||||||
let Some(materializer) = self.execution_workspace_materializer.as_ref() else {
|
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||||
WorkerExecutionOperation::Spawn,
|
WorkerExecutionOperation::Spawn,
|
||||||
"execution workspace allocation requested, but no materializer is configured for this runtime backend",
|
"working directory allocation requested, but no materializer is configured for this runtime backend",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
match materializer.bind_allocation(
|
match materializer.bind_allocation(
|
||||||
|
|
@ -418,7 +418,7 @@ where
|
||||||
allocation.relative_cwd.as_deref(),
|
allocation.relative_cwd.as_deref(),
|
||||||
) {
|
) {
|
||||||
Ok(binding) => {
|
Ok(binding) => {
|
||||||
request.execution_workspace = Some(binding.clone());
|
request.working_directory = Some(binding.clone());
|
||||||
Some(binding)
|
Some(binding)
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
@ -444,8 +444,8 @@ where
|
||||||
Ok(handle) => handle,
|
Ok(handle) => handle,
|
||||||
Err(message) => {
|
Err(message) => {
|
||||||
if let (Some(materializer), Some(binding)) = (
|
if let (Some(materializer), Some(binding)) = (
|
||||||
self.execution_workspace_materializer.as_ref(),
|
self.working_directory_materializer.as_ref(),
|
||||||
execution_workspace.as_ref(),
|
working_directory.as_ref(),
|
||||||
) {
|
) {
|
||||||
let _ = materializer.cleanup(binding);
|
let _ = materializer.cleanup(binding);
|
||||||
}
|
}
|
||||||
|
|
@ -494,14 +494,14 @@ where
|
||||||
RuntimeWorkerExecution {
|
RuntimeWorkerExecution {
|
||||||
handle,
|
handle,
|
||||||
busy,
|
busy,
|
||||||
execution_workspace: execution_workspace.clone(),
|
working_directory: working_directory.clone(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
WorkerExecutionSpawnResult::Connected {
|
WorkerExecutionSpawnResult::Connected {
|
||||||
handle: WorkerExecutionHandle::new(worker_ref, self.backend_id()),
|
handle: WorkerExecutionHandle::new(worker_ref, self.backend_id()),
|
||||||
run_state: WorkerExecutionRunState::Idle,
|
run_state: WorkerExecutionRunState::Idle,
|
||||||
execution_workspace: execution_workspace.map(|binding| binding.status()),
|
working_directory: working_directory.map(|binding| binding.status()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -592,8 +592,8 @@ where
|
||||||
WorkerExecutionRunState::Stopped,
|
WorkerExecutionRunState::Stopped,
|
||||||
);
|
);
|
||||||
if let (Some(materializer), Some(binding)) = (
|
if let (Some(materializer), Some(binding)) = (
|
||||||
self.execution_workspace_materializer.as_ref(),
|
self.working_directory_materializer.as_ref(),
|
||||||
execution.execution_workspace.as_ref(),
|
execution.working_directory.as_ref(),
|
||||||
) {
|
) {
|
||||||
let _ = materializer.cleanup(binding);
|
let _ = materializer.cleanup(binding);
|
||||||
}
|
}
|
||||||
|
|
@ -627,13 +627,13 @@ mod tests {
|
||||||
|
|
||||||
use crate::Runtime as EmbeddedRuntime;
|
use crate::Runtime as EmbeddedRuntime;
|
||||||
use crate::catalog::{
|
use crate::catalog::{
|
||||||
ConfigBundleRef, CreateWorkerRequest, DirtyStatePolicy, ExecutionWorkspaceRepository,
|
ConfigBundleRef, CreateWorkerRequest, DirtyStatePolicy, MaterializerKind, ProfileSelector,
|
||||||
ExecutionWorkspaceRequest, MaterializerKind, ProfileSelector, RepositorySelector,
|
RepositorySelector, WorkingDirectoryRepository, WorkingDirectoryRequest,
|
||||||
};
|
};
|
||||||
use crate::execution_workspace::LocalGitWorktreeMaterializer;
|
|
||||||
use crate::identity::RuntimeId;
|
use crate::identity::RuntimeId;
|
||||||
use crate::management::RuntimeOptions;
|
use crate::management::RuntimeOptions;
|
||||||
use crate::observation::{TranscriptQuery, TranscriptRole};
|
use crate::observation::{TranscriptQuery, TranscriptRole};
|
||||||
|
use crate::working_directory::LocalGitWorktreeMaterializer;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use llm_engine::Engine;
|
use llm_engine::Engine;
|
||||||
|
|
@ -715,7 +715,7 @@ mod tests {
|
||||||
FsWorkerStore::new(&self.worker_metadata_dir).map_err(|err| err.to_string())?,
|
FsWorkerStore::new(&self.worker_metadata_dir).map_err(|err| err.to_string())?,
|
||||||
);
|
);
|
||||||
let cwd = request
|
let cwd = request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.cwd().to_path_buf())
|
.map(|binding| binding.cwd().to_path_buf())
|
||||||
.unwrap_or_else(|| self.cwd.clone());
|
.unwrap_or_else(|| self.cwd.clone());
|
||||||
|
|
@ -780,8 +780,8 @@ mod tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
execution_workspace_allocation: None,
|
working_directory_allocation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -809,9 +809,9 @@ mod tests {
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execution_workspace_request(repo: &std::path::Path) -> ExecutionWorkspaceRequest {
|
fn working_directory_request(repo: &std::path::Path) -> WorkingDirectoryRequest {
|
||||||
ExecutionWorkspaceRequest {
|
WorkingDirectoryRequest {
|
||||||
repository: ExecutionWorkspaceRepository {
|
repository: WorkingDirectoryRepository {
|
||||||
id: "repo-main".to_string(),
|
id: "repo-main".to_string(),
|
||||||
provider: "git".to_string(),
|
provider: "git".to_string(),
|
||||||
uri: ".".to_string(),
|
uri: ".".to_string(),
|
||||||
|
|
@ -920,7 +920,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
let backend = WorkerRuntimeExecutionBackend::new(factory)
|
let backend = WorkerRuntimeExecutionBackend::new(factory)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_execution_workspace_materializer(LocalGitWorktreeMaterializer::new(
|
.with_working_directory_materializer(LocalGitWorktreeMaterializer::new(
|
||||||
runtime_base.path(),
|
runtime_base.path(),
|
||||||
));
|
));
|
||||||
let runtime = EmbeddedRuntime::with_execution_backend(
|
let runtime = EmbeddedRuntime::with_execution_backend(
|
||||||
|
|
@ -933,11 +933,11 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
runtime.store_config_bundle(test_bundle()).unwrap();
|
runtime.store_config_bundle(test_bundle()).unwrap();
|
||||||
let mut request = create_request("chat");
|
let mut request = create_request("chat");
|
||||||
request.execution_workspace = Some(execution_workspace_request(repo.path()));
|
request.working_directory = Some(working_directory_request(repo.path()));
|
||||||
|
|
||||||
let detail = runtime.create_worker(request).unwrap();
|
let detail = runtime.create_worker(request).unwrap();
|
||||||
|
|
||||||
assert!(detail.execution.execution_workspace.is_some());
|
assert!(detail.execution.working_directory.is_some());
|
||||||
let cwds = observed_cwds.lock().unwrap();
|
let cwds = observed_cwds.lock().unwrap();
|
||||||
assert_eq!(cwds.len(), 1);
|
assert_eq!(cwds.len(), 1);
|
||||||
let cwd = &cwds[0];
|
let cwd = &cwds[0];
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::catalog::{
|
use crate::catalog::{
|
||||||
DirtyStatePolicy, ExecutionWorkspaceCleanupTarget, ExecutionWorkspaceRequest,
|
DirtyStatePolicy, MaterializerKind, WorkingDirectoryCleanupTarget, WorkingDirectoryRequest,
|
||||||
ExecutionWorkspaceStatus, ExecutionWorkspaceStatusKind, ExecutionWorkspaceSummary,
|
WorkingDirectoryStatus, WorkingDirectoryStatusKind, WorkingDirectorySummary,
|
||||||
MaterializerKind,
|
|
||||||
};
|
};
|
||||||
use crate::identity::WorkerRef;
|
use crate::identity::WorkerRef;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -11,12 +10,12 @@ use std::process::Command;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
const EXECUTION_WORKSPACES_DIR: &str = "execution-workspaces";
|
const WORKING_DIRECTORIES_DIR: &str = "working-directories";
|
||||||
const MATERIALIZATION_RECORD: &str = "materialization.json";
|
const MATERIALIZATION_RECORD: &str = "materialization.json";
|
||||||
static NEXT_ALLOCATION_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
static NEXT_ALLOCATION_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceEvidence {
|
pub struct WorkingDirectoryEvidence {
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub requested_selector: Option<String>,
|
pub requested_selector: Option<String>,
|
||||||
|
|
@ -28,20 +27,20 @@ pub struct ExecutionWorkspaceEvidence {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceAllocation {
|
pub struct WorkingDirectoryAllocation {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
pub materializer_kind: MaterializerKind,
|
pub materializer_kind: MaterializerKind,
|
||||||
pub dirty_state_policy: DirtyStatePolicy,
|
pub dirty_state_policy: DirtyStatePolicy,
|
||||||
pub evidence: ExecutionWorkspaceEvidence,
|
pub evidence: WorkingDirectoryEvidence,
|
||||||
pub cleanup_target: ExecutionWorkspaceCleanupTarget,
|
pub cleanup_target: WorkingDirectoryCleanupTarget,
|
||||||
pub cleanup_policy: String,
|
pub cleanup_policy: String,
|
||||||
pub status: ExecutionWorkspaceStatusKind,
|
pub status: WorkingDirectoryStatusKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionWorkspaceAllocation {
|
impl WorkingDirectoryAllocation {
|
||||||
pub fn status_summary(&self) -> ExecutionWorkspaceSummary {
|
pub fn status_summary(&self) -> WorkingDirectorySummary {
|
||||||
ExecutionWorkspaceSummary {
|
WorkingDirectorySummary {
|
||||||
allocation_id: self.id.clone(),
|
allocation_id: self.id.clone(),
|
||||||
repository_id: self.repository_id.clone(),
|
repository_id: self.repository_id.clone(),
|
||||||
requested_selector: self.evidence.requested_selector.clone(),
|
requested_selector: self.evidence.requested_selector.clone(),
|
||||||
|
|
@ -57,15 +56,15 @@ impl ExecutionWorkspaceAllocation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ExecutionWorkspaceBinding {
|
pub struct WorkingDirectoryBinding {
|
||||||
pub allocation: ExecutionWorkspaceAllocation,
|
pub allocation: WorkingDirectoryAllocation,
|
||||||
pub workspace_root: PathBuf,
|
pub workspace_root: PathBuf,
|
||||||
pub cwd: PathBuf,
|
pub cwd: PathBuf,
|
||||||
allocation_root: PathBuf,
|
allocation_root: PathBuf,
|
||||||
source_repository_path: PathBuf,
|
source_repository_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionWorkspaceBinding {
|
impl WorkingDirectoryBinding {
|
||||||
pub fn workspace_root(&self) -> &Path {
|
pub fn workspace_root(&self) -> &Path {
|
||||||
&self.workspace_root
|
&self.workspace_root
|
||||||
}
|
}
|
||||||
|
|
@ -82,20 +81,20 @@ impl ExecutionWorkspaceBinding {
|
||||||
&self.source_repository_path
|
&self.source_repository_path
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn status(&self) -> ExecutionWorkspaceStatus {
|
pub fn status(&self) -> WorkingDirectoryStatus {
|
||||||
ExecutionWorkspaceStatus {
|
WorkingDirectoryStatus {
|
||||||
summary: self.allocation.status_summary(),
|
summary: self.allocation.status_summary(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ExecutionWorkspaceDiagnostic {
|
pub struct WorkingDirectoryDiagnostic {
|
||||||
pub code: String,
|
pub code: String,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionWorkspaceDiagnostic {
|
impl WorkingDirectoryDiagnostic {
|
||||||
fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
|
fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
code: code.into(),
|
code: code.into(),
|
||||||
|
|
@ -104,50 +103,45 @@ impl ExecutionWorkspaceDiagnostic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ExecutionWorkspaceDiagnostic {
|
impl std::fmt::Display for WorkingDirectoryDiagnostic {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}: {}", self.code, self.message)
|
write!(f, "{}: {}", self.code, self.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for ExecutionWorkspaceDiagnostic {}
|
impl std::error::Error for WorkingDirectoryDiagnostic {}
|
||||||
|
|
||||||
pub trait ExecutionWorkspaceMaterializer: Send + Sync + 'static {
|
pub trait WorkingDirectoryMaterializer: Send + Sync + 'static {
|
||||||
fn materialize(
|
fn materialize(
|
||||||
&self,
|
&self,
|
||||||
worker_ref: &WorkerRef,
|
worker_ref: &WorkerRef,
|
||||||
request: &ExecutionWorkspaceRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic>;
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn preallocate(
|
fn preallocate(
|
||||||
&self,
|
&self,
|
||||||
request: &ExecutionWorkspaceRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic>;
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn bind_allocation(
|
fn bind_allocation(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
allocation_id: &str,
|
||||||
relative_cwd: Option<&str>,
|
relative_cwd: Option<&str>,
|
||||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic>;
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn list_allocations(
|
fn list_allocations(&self) -> Result<Vec<WorkingDirectoryStatus>, WorkingDirectoryDiagnostic>;
|
||||||
&self,
|
|
||||||
) -> Result<Vec<ExecutionWorkspaceStatus>, ExecutionWorkspaceDiagnostic>;
|
|
||||||
|
|
||||||
fn allocation_status(
|
fn allocation_status(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
allocation_id: &str,
|
||||||
) -> Result<ExecutionWorkspaceStatus, ExecutionWorkspaceDiagnostic>;
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn cleanup_allocation(
|
fn cleanup_allocation(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
allocation_id: &str,
|
||||||
) -> Result<ExecutionWorkspaceStatus, ExecutionWorkspaceDiagnostic>;
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn cleanup(
|
fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic>;
|
||||||
&self,
|
|
||||||
binding: &ExecutionWorkspaceBinding,
|
|
||||||
) -> Result<(), ExecutionWorkspaceDiagnostic>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -177,30 +171,30 @@ impl LocalGitWorktreeMaterializer {
|
||||||
|
|
||||||
fn allocation_root(&self, allocation_id: &str) -> PathBuf {
|
fn allocation_root(&self, allocation_id: &str) -> PathBuf {
|
||||||
self.runtime_root
|
self.runtime_root
|
||||||
.join(EXECUTION_WORKSPACES_DIR)
|
.join(WORKING_DIRECTORIES_DIR)
|
||||||
.join(allocation_id)
|
.join(allocation_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_record(
|
fn write_record(
|
||||||
&self,
|
&self,
|
||||||
binding: &ExecutionWorkspaceBinding,
|
binding: &WorkingDirectoryBinding,
|
||||||
) -> Result<(), ExecutionWorkspaceDiagnostic> {
|
) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
let record = ExecutionWorkspaceMaterializationRecord {
|
let record = WorkingDirectoryMaterializationRecord {
|
||||||
allocation: binding.allocation.clone(),
|
allocation: binding.allocation.clone(),
|
||||||
workspace_root: binding.workspace_root.clone(),
|
workspace_root: binding.workspace_root.clone(),
|
||||||
source_repository_path: binding.source_repository_path.clone(),
|
source_repository_path: binding.source_repository_path.clone(),
|
||||||
};
|
};
|
||||||
let path = binding.allocation_root.join(MATERIALIZATION_RECORD);
|
let path = binding.allocation_root.join(MATERIALIZATION_RECORD);
|
||||||
let raw = serde_json::to_vec_pretty(&record).map_err(|error| {
|
let raw = serde_json::to_vec_pretty(&record).map_err(|error| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_record_serialize_failed",
|
"working_directory_record_serialize_failed",
|
||||||
format!("failed to serialize execution workspace record: {error}"),
|
format!("failed to serialize working directory record: {error}"),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
fs::write(&path, raw).map_err(|_| {
|
fs::write(&path, raw).map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_record_write_failed",
|
"working_directory_record_write_failed",
|
||||||
"failed to write execution workspace record; backend-private path details were omitted",
|
"failed to write working directory record; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -208,22 +202,22 @@ impl LocalGitWorktreeMaterializer {
|
||||||
fn read_binding(
|
fn read_binding(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
allocation_id: &str,
|
||||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
let allocation_root = self.allocation_root(allocation_id);
|
let allocation_root = self.allocation_root(allocation_id);
|
||||||
let path = allocation_root.join(MATERIALIZATION_RECORD);
|
let path = allocation_root.join(MATERIALIZATION_RECORD);
|
||||||
let raw = fs::read(&path).map_err(|_| {
|
let raw = fs::read(&path).map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_allocation_not_found",
|
"working_directory_allocation_not_found",
|
||||||
"execution workspace allocation was not found",
|
"working directory allocation was not found",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let record: ExecutionWorkspaceMaterializationRecord = serde_json::from_slice(&raw).map_err(|_| {
|
let record: WorkingDirectoryMaterializationRecord = serde_json::from_slice(&raw).map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_record_invalid",
|
"working_directory_record_invalid",
|
||||||
"execution workspace allocation record is invalid; backend-private path details were omitted",
|
"working directory allocation record is invalid; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
Ok(ExecutionWorkspaceBinding {
|
Ok(WorkingDirectoryBinding {
|
||||||
allocation: record.allocation,
|
allocation: record.allocation,
|
||||||
workspace_root: record.workspace_root.clone(),
|
workspace_root: record.workspace_root.clone(),
|
||||||
cwd: record.workspace_root,
|
cwd: record.workspace_root,
|
||||||
|
|
@ -235,34 +229,34 @@ impl LocalGitWorktreeMaterializer {
|
||||||
fn materialize_with_allocation_id(
|
fn materialize_with_allocation_id(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: String,
|
allocation_id: String,
|
||||||
request: &ExecutionWorkspaceRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
cleanup_policy: &str,
|
cleanup_policy: &str,
|
||||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
validate_allocation_id(&allocation_id)?;
|
validate_allocation_id(&allocation_id)?;
|
||||||
if request.materializer != MaterializerKind::LocalGitWorktree {
|
if request.materializer != MaterializerKind::LocalGitWorktree {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_materializer_unsupported",
|
"working_directory_materializer_unsupported",
|
||||||
"only local_git_worktree execution workspace materialization is supported in v0",
|
"only local_git_worktree working directory materialization is supported in v0",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if request.repository.provider != "git" {
|
if request.repository.provider != "git" {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_repository_provider_unsupported",
|
"working_directory_repository_provider_unsupported",
|
||||||
format!(
|
format!(
|
||||||
"repository provider `{}` is not supported by the v0 execution workspace materializer",
|
"repository provider `{}` is not supported by the v0 working directory materializer",
|
||||||
request.repository.provider
|
request.repository.provider
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if request.dirty_state_policy != DirtyStatePolicy::CleanPointOnly {
|
if request.dirty_state_policy != DirtyStatePolicy::CleanPointOnly {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_dirty_policy_unsupported",
|
"working_directory_dirty_policy_unsupported",
|
||||||
"only clean_point_only dirty-state policy is supported in v0",
|
"only clean_point_only dirty-state policy is supported in v0",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if is_remote_uri(&request.repository.uri) {
|
if is_remote_uri(&request.repository.uri) {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_remote_repository_unsupported",
|
"working_directory_remote_repository_unsupported",
|
||||||
"remote repository URI materialization is not implemented in v0",
|
"remote repository URI materialization is not implemented in v0",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -275,17 +269,17 @@ impl LocalGitWorktreeMaterializer {
|
||||||
let source_root = git_stdout(&source_path, ["rev-parse", "--show-toplevel"])
|
let source_root = git_stdout(&source_path, ["rev-parse", "--show-toplevel"])
|
||||||
.map(|value| PathBuf::from(value.trim()))
|
.map(|value| PathBuf::from(value.trim()))
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_git_repository_unavailable",
|
"working_directory_git_repository_unavailable",
|
||||||
"configured local repository is not an available Git worktree; backend-private path details were omitted",
|
"configured local repository is not an available Git worktree; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let status = git_stdout(&source_root, ["status", "--porcelain"])?;
|
let status = git_stdout(&source_root, ["status", "--porcelain"])?;
|
||||||
if !status.trim().is_empty() {
|
if !status.trim().is_empty() {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_dirty_source_rejected",
|
"working_directory_dirty_source_rejected",
|
||||||
"clean_point_only execution workspace materialization rejects dirty source repository state",
|
"clean_point_only working directory materialization rejects dirty source repository state",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,21 +304,21 @@ impl LocalGitWorktreeMaterializer {
|
||||||
.join("root")
|
.join("root")
|
||||||
.join(sanitize_path_component(&request.repository.id));
|
.join(sanitize_path_component(&request.repository.id));
|
||||||
if workspace_root.exists() {
|
if workspace_root.exists() {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_allocation_exists",
|
"working_directory_allocation_exists",
|
||||||
"execution workspace allocation target already exists; cleanup or choose a new allocation",
|
"working directory allocation target already exists; cleanup or choose a new allocation",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
fs::create_dir_all(workspace_root.parent().ok_or_else(|| {
|
fs::create_dir_all(workspace_root.parent().ok_or_else(|| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_invalid_target",
|
"working_directory_invalid_target",
|
||||||
"execution workspace allocation target has no parent directory",
|
"working directory allocation target has no parent directory",
|
||||||
)
|
)
|
||||||
})?)
|
})?)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_create_failed",
|
"working_directory_create_failed",
|
||||||
"failed to create execution workspace allocation directory; backend-private path details were omitted",
|
"failed to create working directory allocation directory; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -340,12 +334,12 @@ impl LocalGitWorktreeMaterializer {
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let allocation = ExecutionWorkspaceAllocation {
|
let allocation = WorkingDirectoryAllocation {
|
||||||
id: allocation_id.clone(),
|
id: allocation_id.clone(),
|
||||||
repository_id: request.repository.id.clone(),
|
repository_id: request.repository.id.clone(),
|
||||||
materializer_kind: MaterializerKind::LocalGitWorktree,
|
materializer_kind: MaterializerKind::LocalGitWorktree,
|
||||||
dirty_state_policy: DirtyStatePolicy::CleanPointOnly,
|
dirty_state_policy: DirtyStatePolicy::CleanPointOnly,
|
||||||
evidence: ExecutionWorkspaceEvidence {
|
evidence: WorkingDirectoryEvidence {
|
||||||
repository_id: request.repository.id.clone(),
|
repository_id: request.repository.id.clone(),
|
||||||
requested_selector: request
|
requested_selector: request
|
||||||
.repository
|
.repository
|
||||||
|
|
@ -357,15 +351,15 @@ impl LocalGitWorktreeMaterializer {
|
||||||
materializer_kind: MaterializerKind::LocalGitWorktree,
|
materializer_kind: MaterializerKind::LocalGitWorktree,
|
||||||
dirty_state_policy: DirtyStatePolicy::CleanPointOnly,
|
dirty_state_policy: DirtyStatePolicy::CleanPointOnly,
|
||||||
},
|
},
|
||||||
cleanup_target: ExecutionWorkspaceCleanupTarget {
|
cleanup_target: WorkingDirectoryCleanupTarget {
|
||||||
kind: "git_worktree".to_string(),
|
kind: "git_worktree".to_string(),
|
||||||
allocation_id,
|
allocation_id,
|
||||||
repository_id: request.repository.id.clone(),
|
repository_id: request.repository.id.clone(),
|
||||||
},
|
},
|
||||||
cleanup_policy: cleanup_policy.to_string(),
|
cleanup_policy: cleanup_policy.to_string(),
|
||||||
status: ExecutionWorkspaceStatusKind::Active,
|
status: WorkingDirectoryStatusKind::Active,
|
||||||
};
|
};
|
||||||
let binding = ExecutionWorkspaceBinding {
|
let binding = WorkingDirectoryBinding {
|
||||||
allocation,
|
allocation,
|
||||||
workspace_root: workspace_root.clone(),
|
workspace_root: workspace_root.clone(),
|
||||||
cwd: workspace_root,
|
cwd: workspace_root,
|
||||||
|
|
@ -377,20 +371,20 @@ impl LocalGitWorktreeMaterializer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer {
|
||||||
fn materialize(
|
fn materialize(
|
||||||
&self,
|
&self,
|
||||||
worker_ref: &WorkerRef,
|
worker_ref: &WorkerRef,
|
||||||
request: &ExecutionWorkspaceRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
let allocation_id = Self::allocation_id(worker_ref, &request.repository.id);
|
let allocation_id = Self::allocation_id(worker_ref, &request.repository.id);
|
||||||
self.materialize_with_allocation_id(allocation_id, request, "remove_on_worker_stop")
|
self.materialize_with_allocation_id(allocation_id, request, "remove_on_worker_stop")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preallocate(
|
fn preallocate(
|
||||||
&self,
|
&self,
|
||||||
request: &ExecutionWorkspaceRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
let allocation_id = next_allocation_id(&request.repository.id);
|
let allocation_id = next_allocation_id(&request.repository.id);
|
||||||
self.materialize_with_allocation_id(allocation_id, request, "manual_or_worker_stop")
|
self.materialize_with_allocation_id(allocation_id, request, "manual_or_worker_stop")
|
||||||
}
|
}
|
||||||
|
|
@ -399,30 +393,28 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
allocation_id: &str,
|
||||||
relative_cwd: Option<&str>,
|
relative_cwd: Option<&str>,
|
||||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
validate_allocation_id(allocation_id)?;
|
validate_allocation_id(allocation_id)?;
|
||||||
let binding = self.read_binding(allocation_id)?;
|
let binding = self.read_binding(allocation_id)?;
|
||||||
if binding.allocation.status != ExecutionWorkspaceStatusKind::Active {
|
if binding.allocation.status != WorkingDirectoryStatusKind::Active {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_not_active",
|
"working_directory_not_active",
|
||||||
"execution workspace allocation is not active",
|
"working directory allocation is not active",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let cwd = validate_relative_cwd(binding.workspace_root(), relative_cwd)?;
|
let cwd = validate_relative_cwd(binding.workspace_root(), relative_cwd)?;
|
||||||
Ok(ExecutionWorkspaceBinding { cwd, ..binding })
|
Ok(WorkingDirectoryBinding { cwd, ..binding })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_allocations(
|
fn list_allocations(&self) -> Result<Vec<WorkingDirectoryStatus>, WorkingDirectoryDiagnostic> {
|
||||||
&self,
|
let root = self.runtime_root.join(WORKING_DIRECTORIES_DIR);
|
||||||
) -> Result<Vec<ExecutionWorkspaceStatus>, ExecutionWorkspaceDiagnostic> {
|
|
||||||
let root = self.runtime_root.join(EXECUTION_WORKSPACES_DIR);
|
|
||||||
let entries = match fs::read_dir(&root) {
|
let entries = match fs::read_dir(&root) {
|
||||||
Ok(entries) => entries,
|
Ok(entries) => entries,
|
||||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
|
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_list_failed",
|
"working_directory_list_failed",
|
||||||
"failed to list execution workspace allocations; backend-private path details were omitted",
|
"failed to list working directory allocations; backend-private path details were omitted",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -451,7 +443,7 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||||
fn allocation_status(
|
fn allocation_status(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
allocation_id: &str,
|
||||||
) -> Result<ExecutionWorkspaceStatus, ExecutionWorkspaceDiagnostic> {
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
validate_allocation_id(allocation_id)?;
|
validate_allocation_id(allocation_id)?;
|
||||||
Ok(self.read_binding(allocation_id)?.status())
|
Ok(self.read_binding(allocation_id)?.status())
|
||||||
}
|
}
|
||||||
|
|
@ -459,34 +451,31 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||||
fn cleanup_allocation(
|
fn cleanup_allocation(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
allocation_id: &str,
|
||||||
) -> Result<ExecutionWorkspaceStatus, ExecutionWorkspaceDiagnostic> {
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
validate_allocation_id(allocation_id)?;
|
validate_allocation_id(allocation_id)?;
|
||||||
let binding = self.read_binding(allocation_id)?;
|
let binding = self.read_binding(allocation_id)?;
|
||||||
self.cleanup(&binding)?;
|
self.cleanup(&binding)?;
|
||||||
self.allocation_status(allocation_id)
|
self.allocation_status(allocation_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup(
|
fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
&self,
|
|
||||||
binding: &ExecutionWorkspaceBinding,
|
|
||||||
) -> Result<(), ExecutionWorkspaceDiagnostic> {
|
|
||||||
let mut allocation = binding.allocation.clone();
|
let mut allocation = binding.allocation.clone();
|
||||||
let allocation_root = binding.allocation_root.canonicalize().map_err(|_| {
|
let allocation_root = binding.allocation_root.canonicalize().map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_cleanup_target_invalid",
|
"working_directory_cleanup_target_invalid",
|
||||||
"execution workspace allocation root is unavailable; backend-private path details were omitted",
|
"working directory allocation root is unavailable; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let workspace_root = binding.workspace_root.canonicalize().map_err(|_| {
|
let workspace_root = binding.workspace_root.canonicalize().map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_cleanup_target_invalid",
|
"working_directory_cleanup_target_invalid",
|
||||||
"execution workspace root is unavailable; backend-private path details were omitted",
|
"working directory root is unavailable; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if !workspace_root.starts_with(&allocation_root) {
|
if !workspace_root.starts_with(&allocation_root) {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_cleanup_escape_rejected",
|
"working_directory_cleanup_escape_rejected",
|
||||||
"execution workspace cleanup target is outside the allocation root",
|
"working directory cleanup target is outside the allocation root",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let workspace_root_arg = path_str(&workspace_root)?;
|
let workspace_root_arg = path_str(&workspace_root)?;
|
||||||
|
|
@ -497,9 +486,9 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||||
.or_else(|_| {
|
.or_else(|_| {
|
||||||
if workspace_root.exists() {
|
if workspace_root.exists() {
|
||||||
fs::remove_dir_all(&workspace_root).map_err(|_| {
|
fs::remove_dir_all(&workspace_root).map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_cleanup_failed",
|
"working_directory_cleanup_failed",
|
||||||
"failed to remove execution workspace; backend-private path details were omitted",
|
"failed to remove working directory; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -507,11 +496,11 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
allocation.status = if remove_result.is_ok() {
|
allocation.status = if remove_result.is_ok() {
|
||||||
ExecutionWorkspaceStatusKind::Removed
|
WorkingDirectoryStatusKind::Removed
|
||||||
} else {
|
} else {
|
||||||
ExecutionWorkspaceStatusKind::CleanupPending
|
WorkingDirectoryStatusKind::CleanupPending
|
||||||
};
|
};
|
||||||
let updated = ExecutionWorkspaceBinding {
|
let updated = WorkingDirectoryBinding {
|
||||||
allocation,
|
allocation,
|
||||||
workspace_root: binding.workspace_root.clone(),
|
workspace_root: binding.workspace_root.clone(),
|
||||||
cwd: binding.cwd.clone(),
|
cwd: binding.cwd.clone(),
|
||||||
|
|
@ -524,16 +513,13 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
struct ExecutionWorkspaceMaterializationRecord {
|
struct WorkingDirectoryMaterializationRecord {
|
||||||
allocation: ExecutionWorkspaceAllocation,
|
allocation: WorkingDirectoryAllocation,
|
||||||
workspace_root: PathBuf,
|
workspace_root: PathBuf,
|
||||||
source_repository_path: PathBuf,
|
source_repository_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn git_stdout<'a, I>(
|
fn git_stdout<'a, I>(repository_path: &Path, args: I) -> Result<String, WorkingDirectoryDiagnostic>
|
||||||
repository_path: &Path,
|
|
||||||
args: I,
|
|
||||||
) -> Result<String, ExecutionWorkspaceDiagnostic>
|
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = &'a str>,
|
I: IntoIterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
|
|
@ -543,32 +529,32 @@ where
|
||||||
.args(args)
|
.args(args)
|
||||||
.output()
|
.output()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_git_unavailable",
|
"working_directory_git_unavailable",
|
||||||
"Git command could not be executed; backend-private path details were omitted",
|
"Git command could not be executed; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_git_failed",
|
"working_directory_git_failed",
|
||||||
"Git command failed; backend-private path details were omitted",
|
"Git command failed; backend-private path details were omitted",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn git_status<'a, I>(repository_path: &Path, args: I) -> Result<(), ExecutionWorkspaceDiagnostic>
|
fn git_status<'a, I>(repository_path: &Path, args: I) -> Result<(), WorkingDirectoryDiagnostic>
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = &'a str>,
|
I: IntoIterator<Item = &'a str>,
|
||||||
{
|
{
|
||||||
git_stdout(repository_path, args).map(|_| ())
|
git_stdout(repository_path, args).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_str(path: &Path) -> Result<String, ExecutionWorkspaceDiagnostic> {
|
fn path_str(path: &Path) -> Result<String, WorkingDirectoryDiagnostic> {
|
||||||
path.to_str().map(ToString::to_string).ok_or_else(|| {
|
path.to_str().map(ToString::to_string).ok_or_else(|| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_non_utf8_path",
|
"working_directory_non_utf8_path",
|
||||||
"execution workspace path is not valid UTF-8; backend-private path details were omitted",
|
"working directory path is not valid UTF-8; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -612,7 +598,7 @@ fn next_allocation_id(repository_id: &str) -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_allocation_id(allocation_id: &str) -> Result<(), ExecutionWorkspaceDiagnostic> {
|
fn validate_allocation_id(allocation_id: &str) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
let sanitized = sanitize_path_component(allocation_id);
|
let sanitized = sanitize_path_component(allocation_id);
|
||||||
if allocation_id.is_empty()
|
if allocation_id.is_empty()
|
||||||
|| allocation_id != sanitized
|
|| allocation_id != sanitized
|
||||||
|
|
@ -620,9 +606,9 @@ fn validate_allocation_id(allocation_id: &str) -> Result<(), ExecutionWorkspaceD
|
||||||
|| allocation_id.contains('/')
|
|| allocation_id.contains('/')
|
||||||
|| allocation_id.contains('\\')
|
|| allocation_id.contains('\\')
|
||||||
{
|
{
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_allocation_id_invalid",
|
"working_directory_allocation_id_invalid",
|
||||||
"execution workspace allocation id is invalid",
|
"working directory allocation id is invalid",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -631,11 +617,11 @@ fn validate_allocation_id(allocation_id: &str) -> Result<(), ExecutionWorkspaceD
|
||||||
fn validate_relative_cwd(
|
fn validate_relative_cwd(
|
||||||
workspace_root: &Path,
|
workspace_root: &Path,
|
||||||
relative_cwd: Option<&str>,
|
relative_cwd: Option<&str>,
|
||||||
) -> Result<PathBuf, ExecutionWorkspaceDiagnostic> {
|
) -> Result<PathBuf, WorkingDirectoryDiagnostic> {
|
||||||
let workspace_root = workspace_root.canonicalize().map_err(|_| {
|
let workspace_root = workspace_root.canonicalize().map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_root_unavailable",
|
"working_directory_root_unavailable",
|
||||||
"execution workspace root is unavailable; backend-private path details were omitted",
|
"working directory root is unavailable; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let relative = relative_cwd.unwrap_or(".").trim();
|
let relative = relative_cwd.unwrap_or(".").trim();
|
||||||
|
|
@ -651,24 +637,24 @@ fn validate_relative_cwd(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_relative_cwd_invalid",
|
"working_directory_relative_cwd_invalid",
|
||||||
"execution workspace relative_cwd must be a relative path inside the allocation root",
|
"working directory relative_cwd must be a relative path inside the allocation root",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let target = workspace_root
|
let target = workspace_root
|
||||||
.join(relative_path)
|
.join(relative_path)
|
||||||
.canonicalize()
|
.canonicalize()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ExecutionWorkspaceDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_relative_cwd_unavailable",
|
"working_directory_relative_cwd_unavailable",
|
||||||
"execution workspace relative_cwd does not identify an existing directory",
|
"working directory relative_cwd does not identify an existing directory",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if !target.starts_with(&workspace_root) || !target.is_dir() {
|
if !target.starts_with(&workspace_root) || !target.is_dir() {
|
||||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"execution_workspace_relative_cwd_escape_rejected",
|
"working_directory_relative_cwd_escape_rejected",
|
||||||
"execution workspace relative_cwd must resolve to a directory inside the allocation root",
|
"working directory relative_cwd must resolve to a directory inside the allocation root",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(target)
|
Ok(target)
|
||||||
|
|
@ -677,7 +663,7 @@ fn validate_relative_cwd(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::catalog::{ExecutionWorkspaceRepository, RepositorySelector};
|
use crate::catalog::{RepositorySelector, WorkingDirectoryRepository};
|
||||||
use crate::identity::{RuntimeId, WorkerId};
|
use crate::identity::{RuntimeId, WorkerId};
|
||||||
|
|
||||||
fn git(path: &Path, args: &[&str]) {
|
fn git(path: &Path, args: &[&str]) {
|
||||||
|
|
@ -704,9 +690,9 @@ mod tests {
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request(repo: &Path) -> ExecutionWorkspaceRequest {
|
fn request(repo: &Path) -> WorkingDirectoryRequest {
|
||||||
ExecutionWorkspaceRequest {
|
WorkingDirectoryRequest {
|
||||||
repository: ExecutionWorkspaceRepository {
|
repository: WorkingDirectoryRepository {
|
||||||
id: "repo-main".to_string(),
|
id: "repo-main".to_string(),
|
||||||
provider: "git".to_string(),
|
provider: "git".to_string(),
|
||||||
uri: ".".to_string(),
|
uri: ".".to_string(),
|
||||||
|
|
@ -787,7 +773,7 @@ mod tests {
|
||||||
.materialize(&worker_ref(1), &request(repo.path()))
|
.materialize(&worker_ref(1), &request(repo.path()))
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(error.code, "execution_workspace_dirty_source_rejected");
|
assert_eq!(error.code, "working_directory_dirty_source_rejected");
|
||||||
assert!(error.message.contains("clean_point_only"));
|
assert!(error.message.contains("clean_point_only"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -803,7 +789,7 @@ mod tests {
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error.code,
|
error.code,
|
||||||
"execution_workspace_remote_repository_unsupported"
|
"working_directory_remote_repository_unsupported"
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut non_git = remote;
|
let mut non_git = remote;
|
||||||
|
|
@ -814,7 +800,7 @@ mod tests {
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
error.code,
|
error.code,
|
||||||
"execution_workspace_repository_provider_unsupported"
|
"working_directory_repository_provider_unsupported"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -858,28 +844,28 @@ mod tests {
|
||||||
.bind_allocation(&allocation.allocation.id, Some("/tmp"))
|
.bind_allocation(&allocation.allocation.id, Some("/tmp"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"execution_workspace_relative_cwd_invalid"
|
"working_directory_relative_cwd_invalid"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
materializer
|
materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("../outside"))
|
.bind_allocation(&allocation.allocation.id, Some("../outside"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"execution_workspace_relative_cwd_invalid"
|
"working_directory_relative_cwd_invalid"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
materializer
|
materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("missing"))
|
.bind_allocation(&allocation.allocation.id, Some("missing"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"execution_workspace_relative_cwd_unavailable"
|
"working_directory_relative_cwd_unavailable"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
materializer
|
materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("inside/file.txt"))
|
.bind_allocation(&allocation.allocation.id, Some("inside/file.txt"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"execution_workspace_relative_cwd_escape_rejected"
|
"working_directory_relative_cwd_escape_rejected"
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|
@ -890,7 +876,7 @@ mod tests {
|
||||||
.bind_allocation(&allocation.allocation.id, Some("escape"))
|
.bind_allocation(&allocation.allocation.id, Some("escape"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"execution_workspace_relative_cwd_escape_rejected"
|
"working_directory_relative_cwd_escape_rejected"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -387,9 +387,9 @@ fn spawn_companion_worker(runtime: &RuntimeRegistry) -> CompanionWorkerState {
|
||||||
},
|
},
|
||||||
profile: Some(selector),
|
profile: Some(selector),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
resolved_execution_workspace: None,
|
resolved_working_directory: None,
|
||||||
resolved_execution_workspace_allocation: None,
|
resolved_working_directory_allocation: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -578,8 +578,8 @@ mod tests {
|
||||||
"deterministic-companion-test",
|
"deterministic-companion-test",
|
||||||
),
|
),
|
||||||
run_state: WorkerExecutionRunState::Idle,
|
run_state: WorkerExecutionRunState::Idle,
|
||||||
execution_workspace: request
|
working_directory: request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.status()),
|
.map(|binding| binding.status()),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use worker_runtime::catalog::{
|
use worker_runtime::catalog::{
|
||||||
ConfigBundleRef, CreateWorkerRequest, ExecutionWorkspaceAllocationClaim,
|
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail as EmbeddedWorkerDetail,
|
||||||
ExecutionWorkspaceRequest, ExecutionWorkspaceSummary, ProfileSelector,
|
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryAllocationClaim, WorkingDirectoryRequest,
|
||||||
WorkerDetail as EmbeddedWorkerDetail, WorkerStatus as EmbeddedWorkerStatus,
|
WorkingDirectorySummary,
|
||||||
};
|
};
|
||||||
use worker_runtime::config_bundle::{
|
use worker_runtime::config_bundle::{
|
||||||
ConfigBundle, ConfigBundleAvailability, ConfigBundleMetadata, ConfigBundleProvenance,
|
ConfigBundle, ConfigBundleAvailability, ConfigBundleMetadata, ConfigBundleProvenance,
|
||||||
|
|
@ -240,7 +240,7 @@ pub struct WorkerSummary {
|
||||||
pub implementation: WorkerImplementationSummary,
|
pub implementation: WorkerImplementationSummary,
|
||||||
pub capabilities: WorkerCapabilitySummary,
|
pub capabilities: WorkerCapabilitySummary,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub execution_workspace: Option<ExecutionWorkspaceSummary>,
|
pub working_directory: Option<WorkingDirectorySummary>,
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,14 +268,14 @@ pub struct WorkerLookupResult {
|
||||||
/// The request carries Browser-facing launch semantics only: workspace intent,
|
/// The request carries Browser-facing launch semantics only: workspace intent,
|
||||||
/// optional display identity, acceptance policy, optional profile selector,
|
/// optional display identity, acceptance policy, optional profile selector,
|
||||||
/// optional initial input, and optional configured Repository selector for
|
/// optional initial input, and optional configured Repository selector for
|
||||||
/// execution-workspace materialization. Runtime execution authority is resolved by the host
|
/// working-directory materialization. Runtime execution authority is resolved by the host
|
||||||
/// into a synced ConfigBundle before the canonical Runtime create request is
|
/// into a synced ConfigBundle before the canonical Runtime create request is
|
||||||
/// built. Raw workspace roots, child cwd, executable paths, tool scope,
|
/// built. Raw workspace roots, child cwd, executable paths, tool scope,
|
||||||
/// credentials, raw config stores, sockets, sessions, and storage paths are not
|
/// credentials, raw config stores, sockets, sessions, and storage paths are not
|
||||||
/// accepted from Workspace API callers.
|
/// accepted from Workspace API callers.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct WorkerSpawnExecutionWorkspaceRequest {
|
pub struct WorkerSpawnWorkingDirectoryRequest {
|
||||||
/// Safe configured Repository id. The host resolves this id to repository
|
/// Safe configured Repository id. The host resolves this id to repository
|
||||||
/// authority from server-side config; browser callers cannot provide raw
|
/// authority from server-side config; browser callers cannot provide raw
|
||||||
/// source paths or runtime-internal storage paths.
|
/// source paths or runtime-internal storage paths.
|
||||||
|
|
@ -295,15 +295,15 @@ pub struct WorkerSpawnRequest {
|
||||||
pub profile: Option<ProfileSelector>,
|
pub profile: Option<ProfileSelector>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub initial_input: Option<EmbeddedWorkerInput>,
|
pub initial_input: Option<EmbeddedWorkerInput>,
|
||||||
/// Optional safe execution-workspace selector. The Workspace server resolves
|
/// Optional safe working-directory selector. The Workspace server resolves
|
||||||
/// this into a runtime-internal `ExecutionWorkspaceRequest` from configured
|
/// this into a runtime-internal `WorkingDirectoryRequest` from configured
|
||||||
/// repositories before calling a host.
|
/// repositories before calling a host.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub execution_workspace: Option<WorkerSpawnExecutionWorkspaceRequest>,
|
pub working_directory: Option<WorkerSpawnWorkingDirectoryRequest>,
|
||||||
#[serde(skip, default)]
|
#[serde(skip, default)]
|
||||||
pub resolved_execution_workspace: Option<ExecutionWorkspaceRequest>,
|
pub resolved_working_directory: Option<WorkingDirectoryRequest>,
|
||||||
#[serde(skip, default)]
|
#[serde(skip, default)]
|
||||||
pub resolved_execution_workspace_allocation: Option<ExecutionWorkspaceAllocationClaim>,
|
pub resolved_working_directory_allocation: Option<WorkingDirectoryAllocationClaim>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
@ -1143,9 +1143,9 @@ impl EmbeddedWorkerRuntime {
|
||||||
can_stop: self.can_stop_embedded_worker(summary.status, &summary.execution),
|
can_stop: self.can_stop_embedded_worker(summary.status, &summary.execution),
|
||||||
can_spawn_followup: false,
|
can_spawn_followup: false,
|
||||||
},
|
},
|
||||||
execution_workspace: summary
|
working_directory: summary
|
||||||
.execution
|
.execution
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.clone()
|
.clone()
|
||||||
.map(|status| status.summary),
|
.map(|status| status.summary),
|
||||||
diagnostics: embedded_worker_projection_diagnostics(&summary.execution),
|
diagnostics: embedded_worker_projection_diagnostics(&summary.execution),
|
||||||
|
|
@ -1177,9 +1177,9 @@ impl EmbeddedWorkerRuntime {
|
||||||
can_stop: self.can_stop_embedded_worker(detail.status, &detail.execution),
|
can_stop: self.can_stop_embedded_worker(detail.status, &detail.execution),
|
||||||
can_spawn_followup: false,
|
can_spawn_followup: false,
|
||||||
},
|
},
|
||||||
execution_workspace: detail
|
working_directory: detail
|
||||||
.execution
|
.execution
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.clone()
|
.clone()
|
||||||
.map(|status| status.summary),
|
.map(|status| status.summary),
|
||||||
diagnostics: embedded_worker_projection_diagnostics(&detail.execution),
|
diagnostics: embedded_worker_projection_diagnostics(&detail.execution),
|
||||||
|
|
@ -1356,8 +1356,8 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||||
profile,
|
profile,
|
||||||
config_bundle,
|
config_bundle,
|
||||||
initial_input: request.initial_input.clone(),
|
initial_input: request.initial_input.clone(),
|
||||||
execution_workspace: request.resolved_execution_workspace.clone(),
|
working_directory: request.resolved_working_directory.clone(),
|
||||||
execution_workspace_allocation: request.resolved_execution_workspace_allocation.clone(),
|
working_directory_allocation: request.resolved_working_directory_allocation.clone(),
|
||||||
};
|
};
|
||||||
match self.runtime.create_worker(create_request) {
|
match self.runtime.create_worker(create_request) {
|
||||||
Ok(detail) => {
|
Ok(detail) => {
|
||||||
|
|
@ -1833,7 +1833,7 @@ impl RemoteWorkerRuntime {
|
||||||
can_stop: runtime_worker_can_stop(true, summary.status, &summary.execution),
|
can_stop: runtime_worker_can_stop(true, summary.status, &summary.execution),
|
||||||
can_spawn_followup: false,
|
can_spawn_followup: false,
|
||||||
},
|
},
|
||||||
execution_workspace: summary.execution.execution_workspace.clone().map(|status| status.summary),
|
working_directory: summary.execution.working_directory.clone().map(|status| status.summary),
|
||||||
diagnostics: vec![diagnostic(
|
diagnostics: vec![diagnostic(
|
||||||
"remote_runtime_projection",
|
"remote_runtime_projection",
|
||||||
DiagnosticSeverity::Info,
|
DiagnosticSeverity::Info,
|
||||||
|
|
@ -1867,7 +1867,7 @@ impl RemoteWorkerRuntime {
|
||||||
can_stop: runtime_worker_can_stop(true, detail.status, &detail.execution),
|
can_stop: runtime_worker_can_stop(true, detail.status, &detail.execution),
|
||||||
can_spawn_followup: false,
|
can_spawn_followup: false,
|
||||||
},
|
},
|
||||||
execution_workspace: detail.execution.execution_workspace.clone().map(|status| status.summary),
|
working_directory: detail.execution.working_directory.clone().map(|status| status.summary),
|
||||||
diagnostics: vec![diagnostic(
|
diagnostics: vec![diagnostic(
|
||||||
"remote_runtime_projection",
|
"remote_runtime_projection",
|
||||||
DiagnosticSeverity::Info,
|
DiagnosticSeverity::Info,
|
||||||
|
|
@ -2032,8 +2032,8 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||||
profile,
|
profile,
|
||||||
config_bundle,
|
config_bundle,
|
||||||
initial_input: request.initial_input.clone(),
|
initial_input: request.initial_input.clone(),
|
||||||
execution_workspace: request.resolved_execution_workspace.clone(),
|
working_directory: request.resolved_working_directory.clone(),
|
||||||
execution_workspace_allocation: request.resolved_execution_workspace_allocation.clone(),
|
working_directory_allocation: request.resolved_working_directory_allocation.clone(),
|
||||||
};
|
};
|
||||||
match self.post_json::<_, RuntimeHttpWorkerResponse>("/v1/workers", &create) {
|
match self.post_json::<_, RuntimeHttpWorkerResponse>("/v1/workers", &create) {
|
||||||
Ok(response) => WorkerSpawnResult {
|
Ok(response) => WorkerSpawnResult {
|
||||||
|
|
@ -2844,7 +2844,7 @@ pub fn placeholder_worker(host_id: impl Into<String>) -> WorkerSummary {
|
||||||
can_stop: false,
|
can_stop: false,
|
||||||
can_spawn_followup: false,
|
can_spawn_followup: false,
|
||||||
},
|
},
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
diagnostics: vec![diagnostic(
|
diagnostics: vec![diagnostic(
|
||||||
"runtime_capability_unsupported",
|
"runtime_capability_unsupported",
|
||||||
DiagnosticSeverity::Info,
|
DiagnosticSeverity::Info,
|
||||||
|
|
@ -2958,8 +2958,8 @@ mod tests {
|
||||||
self.backend_id(),
|
self.backend_id(),
|
||||||
),
|
),
|
||||||
run_state: WorkerExecutionRunState::Idle,
|
run_state: WorkerExecutionRunState::Idle,
|
||||||
execution_workspace: request
|
working_directory: request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.status()),
|
.map(|binding| binding.status()),
|
||||||
}
|
}
|
||||||
|
|
@ -3040,7 +3040,7 @@ mod tests {
|
||||||
can_stop: false,
|
can_stop: false,
|
||||||
can_spawn_followup: false,
|
can_spawn_followup: false,
|
||||||
},
|
},
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
|
@ -3191,9 +3191,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: None,
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
resolved_execution_workspace: None,
|
resolved_working_directory: None,
|
||||||
resolved_execution_workspace_allocation: None,
|
resolved_working_directory_allocation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3323,9 +3323,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: None,
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
resolved_execution_workspace: None,
|
resolved_working_directory: None,
|
||||||
resolved_execution_workspace_allocation: None,
|
resolved_working_directory_allocation: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -3425,9 +3425,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
profile: Some(ProfileSelector::Builtin("builtin:coder".to_string())),
|
profile: Some(ProfileSelector::Builtin("builtin:coder".to_string())),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
resolved_execution_workspace: None,
|
resolved_working_directory: None,
|
||||||
resolved_execution_workspace_allocation: None,
|
resolved_working_directory_allocation: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -3456,9 +3456,9 @@ mod tests {
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
|
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
|
||||||
profile: None,
|
profile: None,
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
resolved_execution_workspace: None,
|
resolved_working_directory: None,
|
||||||
resolved_execution_workspace_allocation: None,
|
resolved_working_directory_allocation: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ use chrono::{SecondsFormat, Utc};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use worker_runtime::execution_workspace::{
|
|
||||||
ExecutionWorkspaceDiagnostic, ExecutionWorkspaceMaterializer, LocalGitWorktreeMaterializer,
|
|
||||||
};
|
|
||||||
use worker_runtime::worker_backend::WorkerRuntimeExecutionBackend;
|
use worker_runtime::worker_backend::WorkerRuntimeExecutionBackend;
|
||||||
|
use worker_runtime::working_directory::{
|
||||||
|
LocalGitWorktreeMaterializer, WorkingDirectoryDiagnostic, WorkingDirectoryMaterializer,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::companion::{
|
use crate::companion::{
|
||||||
CompanionCancelRequest, CompanionConsole, CompanionMessageRequest, CompanionMessageResponse,
|
CompanionCancelRequest, CompanionConsole, CompanionMessageRequest, CompanionMessageResponse,
|
||||||
|
|
@ -27,8 +27,8 @@ use crate::hosts::{
|
||||||
HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic, RuntimeRegistry,
|
HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic, RuntimeRegistry,
|
||||||
RuntimeRegistryUnregisterResult, RuntimeSummary, WorkerInputRequest, WorkerInputResult,
|
RuntimeRegistryUnregisterResult, RuntimeSummary, WorkerInputRequest, WorkerInputResult,
|
||||||
WorkerLifecycleRequest, WorkerLifecycleResult, WorkerOperationState,
|
WorkerLifecycleRequest, WorkerLifecycleResult, WorkerOperationState,
|
||||||
WorkerSpawnAcceptanceRequirement, WorkerSpawnExecutionWorkspaceRequest, WorkerSpawnIntent,
|
WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest, WorkerSpawnResult,
|
||||||
WorkerSpawnRequest, WorkerSpawnResult, WorkerSummary, WorkerTranscriptProjection,
|
WorkerSpawnWorkingDirectoryRequest, WorkerSummary, WorkerTranscriptProjection,
|
||||||
};
|
};
|
||||||
use crate::identity::WorkspaceIdentity;
|
use crate::identity::WorkspaceIdentity;
|
||||||
use crate::observation::{
|
use crate::observation::{
|
||||||
|
|
@ -45,9 +45,9 @@ use crate::repositories::{
|
||||||
use crate::store::{ControlPlaneStore, WorkspaceRecord};
|
use crate::store::{ControlPlaneStore, WorkspaceRecord};
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use worker_runtime::catalog::{
|
use worker_runtime::catalog::{
|
||||||
ConfigBundleRef, DirtyStatePolicy, ExecutionWorkspaceAllocationClaim,
|
ConfigBundleRef, DirtyStatePolicy, MaterializerKind, ProfileSelector,
|
||||||
ExecutionWorkspaceRepository, ExecutionWorkspaceRequest, ExecutionWorkspaceSummary,
|
RepositorySelector as RuntimeRepositorySelector, WorkingDirectoryAllocationClaim,
|
||||||
MaterializerKind, ProfileSelector, RepositorySelector as RuntimeRepositorySelector,
|
WorkingDirectoryRepository, WorkingDirectoryRequest, WorkingDirectorySummary,
|
||||||
};
|
};
|
||||||
use worker_runtime::config_bundle::ConfigBundle;
|
use worker_runtime::config_bundle::ConfigBundle;
|
||||||
use worker_runtime::http_server::{
|
use worker_runtime::http_server::{
|
||||||
|
|
@ -154,7 +154,7 @@ pub struct WorkspaceApi {
|
||||||
runtime: Arc<RuntimeRegistry>,
|
runtime: Arc<RuntimeRegistry>,
|
||||||
companion: Arc<CompanionConsole>,
|
companion: Arc<CompanionConsole>,
|
||||||
observation_proxy: BackendObservationProxy,
|
observation_proxy: BackendObservationProxy,
|
||||||
execution_workspace_materializer: Arc<LocalGitWorktreeMaterializer>,
|
working_directory_materializer: Arc<LocalGitWorktreeMaterializer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkspaceApi {
|
impl WorkspaceApi {
|
||||||
|
|
@ -169,7 +169,7 @@ impl WorkspaceApi {
|
||||||
"failed to initialize embedded Worker backend: {err}"
|
"failed to initialize embedded Worker backend: {err}"
|
||||||
))
|
))
|
||||||
})?
|
})?
|
||||||
.with_execution_workspace_materializer((*materializer).clone());
|
.with_working_directory_materializer((*materializer).clone());
|
||||||
Self::new_with_execution_backend(config, store, Arc::new(execution_backend)).await
|
Self::new_with_execution_backend(config, store, Arc::new(execution_backend)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,7 +204,7 @@ impl WorkspaceApi {
|
||||||
let runtime = Arc::new(runtime);
|
let runtime = Arc::new(runtime);
|
||||||
let companion = Arc::new(CompanionConsole::new(runtime.clone()));
|
let companion = Arc::new(CompanionConsole::new(runtime.clone()));
|
||||||
let observation_proxy = BackendObservationProxy::new(config.runtime_event_sources.clone());
|
let observation_proxy = BackendObservationProxy::new(config.runtime_event_sources.clone());
|
||||||
let execution_workspace_materializer = Arc::new(LocalGitWorktreeMaterializer::new(
|
let working_directory_materializer = Arc::new(LocalGitWorktreeMaterializer::new(
|
||||||
config.embedded_runtime_store_root.clone(),
|
config.embedded_runtime_store_root.clone(),
|
||||||
));
|
));
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
@ -214,7 +214,7 @@ impl WorkspaceApi {
|
||||||
runtime,
|
runtime,
|
||||||
companion,
|
companion,
|
||||||
observation_proxy,
|
observation_proxy,
|
||||||
execution_workspace_materializer,
|
working_directory_materializer,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,12 +271,12 @@ pub fn build_router(api: WorkspaceApi) -> Router {
|
||||||
.route("/api/hosts", get(list_hosts))
|
.route("/api/hosts", get(list_hosts))
|
||||||
.route("/api/w/{workspace_id}/hosts", get(scoped_list_hosts))
|
.route("/api/w/{workspace_id}/hosts", get(scoped_list_hosts))
|
||||||
.route(
|
.route(
|
||||||
"/api/w/{workspace_id}/execution-workspaces",
|
"/api/w/{workspace_id}/working-directories",
|
||||||
get(scoped_list_execution_workspaces).post(scoped_create_execution_workspace),
|
get(scoped_list_working_directories).post(scoped_create_working_directory),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/w/{workspace_id}/execution-workspaces/{allocation_id}",
|
"/api/w/{workspace_id}/working-directories/{allocation_id}",
|
||||||
get(scoped_execution_workspace_detail).delete(scoped_cleanup_execution_workspace),
|
get(scoped_working_directory_detail).delete(scoped_cleanup_working_directory),
|
||||||
)
|
)
|
||||||
.route("/api/runtimes", get(list_runtimes))
|
.route("/api/runtimes", get(list_runtimes))
|
||||||
.route("/api/w/{workspace_id}/runtimes", get(scoped_list_runtimes))
|
.route("/api/w/{workspace_id}/runtimes", get(scoped_list_runtimes))
|
||||||
|
|
@ -547,8 +547,8 @@ pub struct WorkerLaunchOptionsResponse {
|
||||||
pub workspace_id: String,
|
pub workspace_id: String,
|
||||||
pub runtimes: Vec<WorkerLaunchRuntimeOption>,
|
pub runtimes: Vec<WorkerLaunchRuntimeOption>,
|
||||||
pub profiles: Vec<WorkerLaunchProfileCandidate>,
|
pub profiles: Vec<WorkerLaunchProfileCandidate>,
|
||||||
pub repositories: Vec<ExecutionWorkspaceRepositoryOption>,
|
pub repositories: Vec<WorkingDirectoryRepositoryOption>,
|
||||||
pub execution_workspaces: Vec<ExecutionWorkspaceSummary>,
|
pub working_directories: Vec<WorkingDirectorySummary>,
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -570,7 +570,7 @@ pub struct WorkerLaunchProfileCandidate {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ExecutionWorkspaceRepositoryOption {
|
pub struct WorkingDirectoryRepositoryOption {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -578,54 +578,54 @@ pub struct ExecutionWorkspaceRepositoryOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct BrowserExecutionWorkspaceCreatePolicy {
|
pub struct BrowserWorkingDirectoryCreatePolicy {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub dirty_state: BrowserExecutionWorkspaceDirtyStatePolicy,
|
pub dirty_state: BrowserWorkingDirectoryDirtyStatePolicy,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub cleanup: BrowserExecutionWorkspaceCleanupPolicy,
|
pub cleanup: BrowserWorkingDirectoryCleanupPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum BrowserExecutionWorkspaceDirtyStatePolicy {
|
pub enum BrowserWorkingDirectoryDirtyStatePolicy {
|
||||||
#[default]
|
#[default]
|
||||||
CleanPointOnly,
|
CleanPointOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum BrowserExecutionWorkspaceCleanupPolicy {
|
pub enum BrowserWorkingDirectoryCleanupPolicy {
|
||||||
#[default]
|
#[default]
|
||||||
ManualOrWorkerStop,
|
ManualOrWorkerStop,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct BrowserExecutionWorkspaceCreateRequest {
|
pub struct BrowserWorkingDirectoryCreateRequest {
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selector: Option<String>,
|
pub selector: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub policy: BrowserExecutionWorkspaceCreatePolicy,
|
pub policy: BrowserWorkingDirectoryCreatePolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct BrowserExecutionWorkspaceListResponse {
|
pub struct BrowserWorkingDirectoryListResponse {
|
||||||
pub workspace_id: String,
|
pub workspace_id: String,
|
||||||
pub items: Vec<ExecutionWorkspaceSummary>,
|
pub items: Vec<WorkingDirectorySummary>,
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct BrowserExecutionWorkspaceDetailResponse {
|
pub struct BrowserWorkingDirectoryDetailResponse {
|
||||||
pub workspace_id: String,
|
pub workspace_id: String,
|
||||||
pub item: ExecutionWorkspaceSummary,
|
pub item: WorkingDirectorySummary,
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct BrowserWorkerExecutionWorkspaceSelection {
|
pub struct BrowserWorkerWorkingDirectorySelection {
|
||||||
pub allocation_id: String,
|
pub allocation_id: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub relative_cwd: Option<String>,
|
pub relative_cwd: Option<String>,
|
||||||
|
|
@ -639,7 +639,7 @@ pub struct BrowserCreateWorkerRequest {
|
||||||
pub profile: String,
|
pub profile: String,
|
||||||
pub initial_text: String,
|
pub initial_text: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub execution_workspace: Option<BrowserWorkerExecutionWorkspaceSelection>,
|
pub working_directory: Option<BrowserWorkerWorkingDirectorySelection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
|
@ -742,7 +742,7 @@ struct ScopedRuntimePath {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ScopedExecutionWorkspacePath {
|
struct ScopedWorkingDirectoryPath {
|
||||||
workspace_id: String,
|
workspace_id: String,
|
||||||
allocation_id: String,
|
allocation_id: String,
|
||||||
}
|
}
|
||||||
|
|
@ -884,28 +884,28 @@ async fn scoped_get_worker_launch_options(
|
||||||
get_worker_launch_options(State(api)).await
|
get_worker_launch_options(State(api)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scoped_list_execution_workspaces(
|
async fn scoped_list_working_directories(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||||
) -> ApiResult<Json<BrowserExecutionWorkspaceListResponse>> {
|
) -> ApiResult<Json<BrowserWorkingDirectoryListResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let items = execution_workspace_summaries(&api)?;
|
let items = working_directory_summaries(&api)?;
|
||||||
Ok(Json(BrowserExecutionWorkspaceListResponse {
|
Ok(Json(BrowserWorkingDirectoryListResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
items,
|
items,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scoped_create_execution_workspace(
|
async fn scoped_create_working_directory(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||||
Json(request): Json<BrowserExecutionWorkspaceCreateRequest>,
|
Json(request): Json<BrowserWorkingDirectoryCreateRequest>,
|
||||||
) -> ApiResult<Json<BrowserExecutionWorkspaceDetailResponse>> {
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let workspace_request = execution_workspace_request_for_browser(&api, request)?;
|
let workspace_request = working_directory_request_for_browser(&api, request)?;
|
||||||
let binding = api
|
let binding = api
|
||||||
.execution_workspace_materializer
|
.working_directory_materializer
|
||||||
.preallocate(&workspace_request)
|
.preallocate(&workspace_request)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
ApiError::from(Error::RuntimeOperationFailed {
|
||||||
|
|
@ -914,20 +914,20 @@ async fn scoped_create_execution_workspace(
|
||||||
message: diagnostic.message,
|
message: diagnostic.message,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
Ok(Json(BrowserExecutionWorkspaceDetailResponse {
|
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
item: binding.status().summary,
|
item: binding.status().summary,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scoped_execution_workspace_detail(
|
async fn scoped_working_directory_detail(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedExecutionWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkingDirectoryPath>,
|
||||||
) -> ApiResult<Json<BrowserExecutionWorkspaceDetailResponse>> {
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let status = api
|
let status = api
|
||||||
.execution_workspace_materializer
|
.working_directory_materializer
|
||||||
.allocation_status(&path.allocation_id)
|
.allocation_status(&path.allocation_id)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
ApiError::from(Error::RuntimeOperationFailed {
|
||||||
|
|
@ -936,20 +936,20 @@ async fn scoped_execution_workspace_detail(
|
||||||
message: diagnostic.message,
|
message: diagnostic.message,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
Ok(Json(BrowserExecutionWorkspaceDetailResponse {
|
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
item: status.summary,
|
item: status.summary,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scoped_cleanup_execution_workspace(
|
async fn scoped_cleanup_working_directory(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedExecutionWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkingDirectoryPath>,
|
||||||
) -> ApiResult<Json<BrowserExecutionWorkspaceDetailResponse>> {
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let status = api
|
let status = api
|
||||||
.execution_workspace_materializer
|
.working_directory_materializer
|
||||||
.cleanup_allocation(&path.allocation_id)
|
.cleanup_allocation(&path.allocation_id)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
ApiError::from(Error::RuntimeOperationFailed {
|
||||||
|
|
@ -958,7 +958,7 @@ async fn scoped_cleanup_execution_workspace(
|
||||||
message: diagnostic.message,
|
message: diagnostic.message,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
Ok(Json(BrowserExecutionWorkspaceDetailResponse {
|
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
item: status.summary,
|
item: status.summary,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
|
|
@ -1551,12 +1551,12 @@ async fn get_worker_launch_options(
|
||||||
Ok(Json(worker_launch_options_response(&api)))
|
Ok(Json(worker_launch_options_response(&api)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execution_workspace_request_from_repository(
|
fn working_directory_request_from_repository(
|
||||||
repository: &ConfiguredRepository,
|
repository: &ConfiguredRepository,
|
||||||
selector: Option<&str>,
|
selector: Option<&str>,
|
||||||
) -> ExecutionWorkspaceRequest {
|
) -> WorkingDirectoryRequest {
|
||||||
ExecutionWorkspaceRequest {
|
WorkingDirectoryRequest {
|
||||||
repository: ExecutionWorkspaceRepository {
|
repository: WorkingDirectoryRepository {
|
||||||
id: repository.id.clone(),
|
id: repository.id.clone(),
|
||||||
provider: repository.provider.clone(),
|
provider: repository.provider.clone(),
|
||||||
uri: repository.uri.clone(),
|
uri: repository.uri.clone(),
|
||||||
|
|
@ -1576,21 +1576,21 @@ fn execution_workspace_request_from_repository(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configured_execution_workspace_request(
|
fn configured_working_directory_request(
|
||||||
config: &ServerConfig,
|
config: &ServerConfig,
|
||||||
request: &WorkerSpawnExecutionWorkspaceRequest,
|
request: &WorkerSpawnWorkingDirectoryRequest,
|
||||||
) -> Result<ExecutionWorkspaceRequest> {
|
) -> Result<WorkingDirectoryRequest> {
|
||||||
let repository = config
|
let repository = config
|
||||||
.repositories
|
.repositories
|
||||||
.iter()
|
.iter()
|
||||||
.find(|repository| repository.id == request.repository_id)
|
.find(|repository| repository.id == request.repository_id)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::Config(format!(
|
Error::Config(format!(
|
||||||
"unknown repository id `{}` for Worker execution workspace",
|
"unknown repository id `{}` for Worker working directory",
|
||||||
request.repository_id
|
request.repository_id
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
Ok(execution_workspace_request_from_repository(
|
Ok(working_directory_request_from_repository(
|
||||||
repository,
|
repository,
|
||||||
request.selector.as_deref(),
|
request.selector.as_deref(),
|
||||||
))
|
))
|
||||||
|
|
@ -1621,31 +1621,31 @@ async fn create_workspace_worker(
|
||||||
content: initial_text,
|
content: initial_text,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let resolved_execution_workspace_allocation =
|
let resolved_working_directory_allocation =
|
||||||
request
|
request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.map(|selection| ExecutionWorkspaceAllocationClaim {
|
.map(|selection| WorkingDirectoryAllocationClaim {
|
||||||
allocation_id: selection.allocation_id,
|
allocation_id: selection.allocation_id,
|
||||||
relative_cwd: selection.relative_cwd,
|
relative_cwd: selection.relative_cwd,
|
||||||
});
|
});
|
||||||
if resolved_execution_workspace_allocation.is_some()
|
if resolved_working_directory_allocation.is_some()
|
||||||
&& request.runtime_id != EMBEDDED_WORKER_RUNTIME_ID
|
&& request.runtime_id != EMBEDDED_WORKER_RUNTIME_ID
|
||||||
{
|
{
|
||||||
return Err(Error::RuntimeOperationFailed {
|
return Err(Error::RuntimeOperationFailed {
|
||||||
runtime_id: request.runtime_id.clone(),
|
runtime_id: request.runtime_id.clone(),
|
||||||
code: "execution_workspace_runtime_unsupported".to_string(),
|
code: "working_directory_runtime_unsupported".to_string(),
|
||||||
message: "preallocated execution workspaces are only supported by the embedded local runtime in v0".to_string(),
|
message: "preallocated working directories are only supported by the embedded local runtime in v0".to_string(),
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
if let Some(allocation) = resolved_execution_workspace_allocation.as_ref() {
|
if let Some(allocation) = resolved_working_directory_allocation.as_ref() {
|
||||||
api.execution_workspace_materializer
|
api.working_directory_materializer
|
||||||
.bind_allocation(
|
.bind_allocation(
|
||||||
&allocation.allocation_id,
|
&allocation.allocation_id,
|
||||||
allocation.relative_cwd.as_deref(),
|
allocation.relative_cwd.as_deref(),
|
||||||
)
|
)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
execution_workspace_api_error(request.runtime_id.clone(), diagnostic)
|
working_directory_api_error(request.runtime_id.clone(), diagnostic)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
let result = api
|
let result = api
|
||||||
|
|
@ -1660,9 +1660,9 @@ async fn create_workspace_worker(
|
||||||
},
|
},
|
||||||
profile: Some(profile_selector),
|
profile: Some(profile_selector),
|
||||||
initial_input,
|
initial_input,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
resolved_execution_workspace: None,
|
resolved_working_directory: None,
|
||||||
resolved_execution_workspace_allocation,
|
resolved_working_directory_allocation,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map_err(|err| err.into_error())?;
|
.map_err(|err| err.into_error())?;
|
||||||
|
|
@ -1751,11 +1751,11 @@ async fn create_runtime_worker(
|
||||||
AxumPath(runtime_id): AxumPath<String>,
|
AxumPath(runtime_id): AxumPath<String>,
|
||||||
Json(mut request): Json<WorkerSpawnRequest>,
|
Json(mut request): Json<WorkerSpawnRequest>,
|
||||||
) -> ApiResult<Json<WorkerSpawnResult>> {
|
) -> ApiResult<Json<WorkerSpawnResult>> {
|
||||||
request.resolved_execution_workspace = request
|
request.resolved_working_directory = request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|execution_workspace| {
|
.map(|working_directory| {
|
||||||
configured_execution_workspace_request(&api.config, execution_workspace)
|
configured_working_directory_request(&api.config, working_directory)
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result = api
|
let result = api
|
||||||
|
|
@ -2688,19 +2688,19 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> WorkerLaunchOptionsResp
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
runtimes,
|
runtimes,
|
||||||
profiles: worker_profile_candidates(),
|
profiles: worker_profile_candidates(),
|
||||||
repositories: execution_workspace_repository_options(api),
|
repositories: working_directory_repository_options(api),
|
||||||
execution_workspaces: execution_workspace_summaries(api).unwrap_or_default(),
|
working_directories: working_directory_summaries(api).unwrap_or_default(),
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execution_workspace_repository_options(
|
fn working_directory_repository_options(
|
||||||
api: &WorkspaceApi,
|
api: &WorkspaceApi,
|
||||||
) -> Vec<ExecutionWorkspaceRepositoryOption> {
|
) -> Vec<WorkingDirectoryRepositoryOption> {
|
||||||
api.config
|
api.config
|
||||||
.repositories
|
.repositories
|
||||||
.iter()
|
.iter()
|
||||||
.map(|repository| ExecutionWorkspaceRepositoryOption {
|
.map(|repository| WorkingDirectoryRepositoryOption {
|
||||||
id: repository.id.clone(),
|
id: repository.id.clone(),
|
||||||
display_name: repository
|
display_name: repository
|
||||||
.display_name
|
.display_name
|
||||||
|
|
@ -2711,8 +2711,8 @@ fn execution_workspace_repository_options(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execution_workspace_summaries(api: &WorkspaceApi) -> ApiResult<Vec<ExecutionWorkspaceSummary>> {
|
fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult<Vec<WorkingDirectorySummary>> {
|
||||||
api.execution_workspace_materializer
|
api.working_directory_materializer
|
||||||
.list_allocations()
|
.list_allocations()
|
||||||
.map(|items| items.into_iter().map(|status| status.summary).collect())
|
.map(|items| items.into_iter().map(|status| status.summary).collect())
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
|
|
@ -2724,10 +2724,10 @@ fn execution_workspace_summaries(api: &WorkspaceApi) -> ApiResult<Vec<ExecutionW
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execution_workspace_request_for_browser(
|
fn working_directory_request_for_browser(
|
||||||
api: &WorkspaceApi,
|
api: &WorkspaceApi,
|
||||||
request: BrowserExecutionWorkspaceCreateRequest,
|
request: BrowserWorkingDirectoryCreateRequest,
|
||||||
) -> ApiResult<ExecutionWorkspaceRequest> {
|
) -> ApiResult<WorkingDirectoryRequest> {
|
||||||
let repository = api
|
let repository = api
|
||||||
.config
|
.config
|
||||||
.repositories
|
.repositories
|
||||||
|
|
@ -2739,13 +2739,13 @@ fn execution_workspace_request_for_browser(
|
||||||
.or_else(|| repository.default_selector.clone())
|
.or_else(|| repository.default_selector.clone())
|
||||||
.filter(|selector| !selector.trim().is_empty());
|
.filter(|selector| !selector.trim().is_empty());
|
||||||
match request.policy.dirty_state {
|
match request.policy.dirty_state {
|
||||||
BrowserExecutionWorkspaceDirtyStatePolicy::CleanPointOnly => {}
|
BrowserWorkingDirectoryDirtyStatePolicy::CleanPointOnly => {}
|
||||||
}
|
}
|
||||||
match request.policy.cleanup {
|
match request.policy.cleanup {
|
||||||
BrowserExecutionWorkspaceCleanupPolicy::ManualOrWorkerStop => {}
|
BrowserWorkingDirectoryCleanupPolicy::ManualOrWorkerStop => {}
|
||||||
}
|
}
|
||||||
Ok(ExecutionWorkspaceRequest {
|
Ok(WorkingDirectoryRequest {
|
||||||
repository: ExecutionWorkspaceRepository {
|
repository: WorkingDirectoryRepository {
|
||||||
id: repository.id.clone(),
|
id: repository.id.clone(),
|
||||||
provider: "git".to_string(),
|
provider: "git".to_string(),
|
||||||
uri: repository.path.to_string_lossy().to_string(),
|
uri: repository.path.to_string_lossy().to_string(),
|
||||||
|
|
@ -2822,9 +2822,9 @@ fn worker_create_not_accepted_error(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execution_workspace_api_error(
|
fn working_directory_api_error(
|
||||||
runtime_id: String,
|
runtime_id: String,
|
||||||
diagnostic: ExecutionWorkspaceDiagnostic,
|
diagnostic: WorkingDirectoryDiagnostic,
|
||||||
) -> ApiError {
|
) -> ApiError {
|
||||||
ApiError::with_diagnostics(
|
ApiError::with_diagnostics(
|
||||||
Error::RuntimeOperationFailed {
|
Error::RuntimeOperationFailed {
|
||||||
|
|
@ -3168,7 +3168,7 @@ impl IntoResponse for ApiError {
|
||||||
if code.starts_with("workspace_settings_")
|
if code.starts_with("workspace_settings_")
|
||||||
|| code.starts_with("invalid_")
|
|| code.starts_with("invalid_")
|
||||||
|| code.starts_with("unsupported_worker_profile")
|
|| code.starts_with("unsupported_worker_profile")
|
||||||
|| code.starts_with("execution_workspace_")
|
|| code.starts_with("working_directory_")
|
||||||
|| code.ends_with("_already_exists")
|
|| code.ends_with("_already_exists")
|
||||||
|| code.ends_with("_not_config_managed")
|
|| code.ends_with("_not_config_managed")
|
||||||
|| code.ends_with("_unsupported") =>
|
|| code.ends_with("_unsupported") =>
|
||||||
|
|
@ -3284,8 +3284,8 @@ mod tests {
|
||||||
self.backend_id(),
|
self.backend_id(),
|
||||||
),
|
),
|
||||||
run_state: worker_runtime::execution::WorkerExecutionRunState::Idle,
|
run_state: worker_runtime::execution::WorkerExecutionRunState::Idle,
|
||||||
execution_workspace: request
|
working_directory: request
|
||||||
.execution_workspace
|
.working_directory
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|binding| binding.status()),
|
.map(|binding| binding.status()),
|
||||||
}
|
}
|
||||||
|
|
@ -3418,8 +3418,8 @@ mod tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
execution_workspace_allocation: None,
|
working_directory_allocation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3435,11 +3435,11 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn browser_execution_workspace_preallocate_list_detail_and_cleanup_are_path_safe() {
|
async fn browser_working_directory_preallocate_list_detail_and_cleanup_are_path_safe() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
init_clean_git_workspace(dir.path());
|
init_clean_git_workspace(dir.path());
|
||||||
let app = test_app(dir.path()).await;
|
let app = test_app(dir.path()).await;
|
||||||
let workspace_path = format!("/api/w/{TEST_WORKSPACE_ID}/execution-workspaces");
|
let workspace_path = format!("/api/w/{TEST_WORKSPACE_ID}/working-directories");
|
||||||
|
|
||||||
let created = post_json(
|
let created = post_json(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
|
|
@ -3478,10 +3478,10 @@ mod tests {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
init_clean_git_workspace(dir.path());
|
init_clean_git_workspace(dir.path());
|
||||||
let app = test_app(dir.path()).await;
|
let app = test_app(dir.path()).await;
|
||||||
let workspaces_path = format!("/api/w/{TEST_WORKSPACE_ID}/execution-workspaces");
|
let working_directories_path = format!("/api/w/{TEST_WORKSPACE_ID}/working-directories");
|
||||||
let created = post_json(
|
let created = post_json(
|
||||||
app.clone(),
|
app.clone(),
|
||||||
&workspaces_path,
|
&working_directories_path,
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"repository_id": TEST_REPOSITORY_ID,
|
"repository_id": TEST_REPOSITORY_ID,
|
||||||
"selector": "HEAD",
|
"selector": "HEAD",
|
||||||
|
|
@ -3500,7 +3500,7 @@ mod tests {
|
||||||
"display_name": "Coding Worker",
|
"display_name": "Coding Worker",
|
||||||
"profile": "builtin:coder",
|
"profile": "builtin:coder",
|
||||||
"initial_text": "",
|
"initial_text": "",
|
||||||
"execution_workspace": {
|
"working_directory": {
|
||||||
"allocation_id": allocation_id,
|
"allocation_id": allocation_id,
|
||||||
"relative_cwd": "../escape"
|
"relative_cwd": "../escape"
|
||||||
}
|
}
|
||||||
|
|
@ -3513,14 +3513,14 @@ mod tests {
|
||||||
.as_array()
|
.as_array()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|diagnostic| diagnostic["code"] == "execution_workspace_relative_cwd_invalid"),
|
.any(|diagnostic| diagnostic["code"] == "working_directory_relative_cwd_invalid"),
|
||||||
"expected typed relative_cwd diagnostic, got {response}"
|
"expected typed relative_cwd diagnostic, got {response}"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
response["message"]
|
response["message"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.contains("execution_workspace_relative_cwd_invalid")
|
.contains("working_directory_relative_cwd_invalid")
|
||||||
);
|
);
|
||||||
let projected = serde_json::to_string(&response).unwrap();
|
let projected = serde_json::to_string(&response).unwrap();
|
||||||
assert!(!projected.contains(dir.path().to_string_lossy().as_ref()));
|
assert!(!projected.contains(dir.path().to_string_lossy().as_ref()));
|
||||||
|
|
@ -3840,7 +3840,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn runtime_worker_spawn_rejects_raw_execution_workspace_fields() {
|
async fn runtime_worker_spawn_rejects_raw_working_directory_fields() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
let app = test_app(dir.path()).await;
|
let app = test_app(dir.path()).await;
|
||||||
let response = request_json(
|
let response = request_json(
|
||||||
|
|
@ -3857,7 +3857,7 @@ mod tests {
|
||||||
"kind": "run_accepted",
|
"kind": "run_accepted",
|
||||||
"expected_segments": 0
|
"expected_segments": 0
|
||||||
},
|
},
|
||||||
"execution_workspace": {
|
"working_directory": {
|
||||||
"repository_id": TEST_REPOSITORY_ID,
|
"repository_id": TEST_REPOSITORY_ID,
|
||||||
"local_path": dir.path().display().to_string()
|
"local_path": dir.path().display().to_string()
|
||||||
}
|
}
|
||||||
|
|
@ -3870,7 +3870,7 @@ mod tests {
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.contains("unknown field"),
|
.contains("unknown field"),
|
||||||
"raw execution workspace field should be rejected: {response}"
|
"raw working directory field should be rejected: {response}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3892,7 +3892,7 @@ mod tests {
|
||||||
"kind": "run_accepted",
|
"kind": "run_accepted",
|
||||||
"expected_segments": 0
|
"expected_segments": 0
|
||||||
},
|
},
|
||||||
"execution_workspace": {
|
"working_directory": {
|
||||||
"repository_id": TEST_REPOSITORY_ID,
|
"repository_id": TEST_REPOSITORY_ID,
|
||||||
"selector": "HEAD"
|
"selector": "HEAD"
|
||||||
}
|
}
|
||||||
|
|
@ -4379,9 +4379,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: None,
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
execution_workspace: None,
|
working_directory: None,
|
||||||
resolved_execution_workspace: None,
|
resolved_working_directory: None,
|
||||||
resolved_execution_workspace_allocation: None,
|
resolved_working_directory_allocation: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("spawn worker");
|
.expect("spawn worker");
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import { buildBrowserCreateWorkerRequest, defaultWorkerLaunchForm } from './worker-launch';
|
import { buildBrowserCreateWorkerRequest, defaultWorkerLaunchForm } from './worker-launch';
|
||||||
import type {
|
import type {
|
||||||
BrowserCreateWorkerResponse,
|
BrowserCreateWorkerResponse,
|
||||||
BrowserExecutionWorkspaceCreateResponse,
|
BrowserWorkingDirectoryCreateResponse,
|
||||||
ListResponse,
|
ListResponse,
|
||||||
Worker,
|
Worker,
|
||||||
WorkerLaunchOptionsResponse,
|
WorkerLaunchOptionsResponse,
|
||||||
|
|
@ -36,11 +36,11 @@
|
||||||
let runtimeId = $state('');
|
let runtimeId = $state('');
|
||||||
let profile = $state('builtin:coder');
|
let profile = $state('builtin:coder');
|
||||||
let initialText = $state('');
|
let initialText = $state('');
|
||||||
let executionWorkspaceAllocationId = $state('');
|
let workingDirectoryAllocationId = $state('');
|
||||||
let executionWorkspaceRepositoryId = $state('');
|
let workingDirectoryRepositoryId = $state('');
|
||||||
let executionWorkspaceSelector = $state('HEAD');
|
let workingDirectorySelector = $state('HEAD');
|
||||||
let relativeCwd = $state('');
|
let relativeCwd = $state('');
|
||||||
let creatingWorkspace = $state(false);
|
let creatingWorkingDirectory = $state(false);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!workspaceId) {
|
if (!workspaceId) {
|
||||||
|
|
@ -102,17 +102,17 @@
|
||||||
display_name: displayName,
|
display_name: displayName,
|
||||||
profile,
|
profile,
|
||||||
initial_text: initialText,
|
initial_text: initialText,
|
||||||
execution_workspace_allocation_id: executionWorkspaceAllocationId,
|
working_directory_allocation_id: workingDirectoryAllocationId,
|
||||||
execution_workspace_repository_id: executionWorkspaceRepositoryId,
|
working_directory_repository_id: workingDirectoryRepositoryId,
|
||||||
execution_workspace_selector: executionWorkspaceSelector,
|
working_directory_selector: workingDirectorySelector,
|
||||||
relative_cwd: relativeCwd,
|
relative_cwd: relativeCwd,
|
||||||
});
|
});
|
||||||
runtimeId = form.runtime_id;
|
runtimeId = form.runtime_id;
|
||||||
displayName = form.display_name;
|
displayName = form.display_name;
|
||||||
profile = form.profile;
|
profile = form.profile;
|
||||||
executionWorkspaceAllocationId = form.execution_workspace_allocation_id;
|
workingDirectoryAllocationId = form.working_directory_allocation_id;
|
||||||
executionWorkspaceRepositoryId = form.execution_workspace_repository_id;
|
workingDirectoryRepositoryId = form.working_directory_repository_id;
|
||||||
executionWorkspaceSelector = form.execution_workspace_selector;
|
workingDirectorySelector = form.working_directory_selector;
|
||||||
relativeCwd = form.relative_cwd;
|
relativeCwd = form.relative_cwd;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof DOMException && err.name === 'AbortError') {
|
if (err instanceof DOMException && err.name === 'AbortError') {
|
||||||
|
|
@ -122,36 +122,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createExecutionWorkspace() {
|
async function createWorkingDirectory() {
|
||||||
if (!executionWorkspaceRepositoryId) {
|
if (!workingDirectoryRepositoryId) {
|
||||||
submitError = 'select a repository before creating an execution workspace';
|
submitError = 'select a repository before creating a working directory';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
creatingWorkspace = true;
|
creatingWorkingDirectory = true;
|
||||||
submitError = null;
|
submitError = null;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(workerApiPath('/execution-workspaces'), {
|
const response = await fetch(workerApiPath('/working-directories'), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'content-type': 'application/json' },
|
headers: { 'content-type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
repository_id: executionWorkspaceRepositoryId,
|
repository_id: workingDirectoryRepositoryId,
|
||||||
selector: executionWorkspaceSelector || null,
|
selector: workingDirectorySelector || null,
|
||||||
policy: { dirty_state: 'clean_point_only', cleanup: 'manual_or_worker_stop' },
|
policy: { dirty_state: 'clean_point_only', cleanup: 'manual_or_worker_stop' },
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(await responseErrorMessage(response, 'execution workspace create failed'));
|
throw new Error(await responseErrorMessage(response, 'working directory create failed'));
|
||||||
}
|
}
|
||||||
const payload = (await response.json()) as BrowserExecutionWorkspaceCreateResponse;
|
const payload = (await response.json()) as BrowserWorkingDirectoryCreateResponse;
|
||||||
const items = options?.execution_workspaces ?? [];
|
const items = options?.working_directories ?? [];
|
||||||
options = options
|
options = options
|
||||||
? { ...options, execution_workspaces: [...items.filter((item) => item.allocation_id !== payload.item.allocation_id), payload.item] }
|
? { ...options, working_directories: [...items.filter((item) => item.allocation_id !== payload.item.allocation_id), payload.item] }
|
||||||
: options;
|
: options;
|
||||||
executionWorkspaceAllocationId = payload.item.allocation_id;
|
workingDirectoryAllocationId = payload.item.allocation_id;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
submitError = err instanceof Error ? err.message : 'execution workspace create failed';
|
submitError = err instanceof Error ? err.message : 'working directory create failed';
|
||||||
} finally {
|
} finally {
|
||||||
creatingWorkspace = false;
|
creatingWorkingDirectory = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,9 +172,9 @@
|
||||||
display_name: displayName,
|
display_name: displayName,
|
||||||
profile,
|
profile,
|
||||||
initial_text: initialText,
|
initial_text: initialText,
|
||||||
execution_workspace_allocation_id: executionWorkspaceAllocationId,
|
working_directory_allocation_id: workingDirectoryAllocationId,
|
||||||
execution_workspace_repository_id: executionWorkspaceRepositoryId,
|
working_directory_repository_id: workingDirectoryRepositoryId,
|
||||||
execution_workspace_selector: executionWorkspaceSelector,
|
working_directory_selector: workingDirectorySelector,
|
||||||
relative_cwd: relativeCwd,
|
relative_cwd: relativeCwd,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
@ -251,22 +251,22 @@
|
||||||
{/if}
|
{/if}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<fieldset class="worker-execution-workspace">
|
<fieldset class="worker-working-directory">
|
||||||
<legend>Execution workspace</legend>
|
<legend>Working directory</legend>
|
||||||
<label>
|
<label>
|
||||||
<span>Allocation</span>
|
<span>Allocation</span>
|
||||||
<select bind:value={executionWorkspaceAllocationId}>
|
<select bind:value={workingDirectoryAllocationId}>
|
||||||
<option value="">No allocation selected</option>
|
<option value="">No allocation selected</option>
|
||||||
{#each options?.execution_workspaces ?? [] as workspace}
|
{#each options?.working_directories ?? [] as directory}
|
||||||
<option value={workspace.allocation_id} disabled={workspace.status !== 'active'}>
|
<option value={directory.allocation_id} disabled={directory.status !== 'active'}>
|
||||||
{workspace.repository_id} · {workspace.requested_selector ?? 'HEAD'} · {workspace.resolved_commit.slice(0, 12)} · {workspace.status}
|
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'} · {directory.resolved_commit.slice(0, 12)} · {directory.status}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Repository for new allocation</span>
|
<span>Repository for new allocation</span>
|
||||||
<select bind:value={executionWorkspaceRepositoryId}>
|
<select bind:value={workingDirectoryRepositoryId}>
|
||||||
{#if options?.repositories.length}
|
{#if options?.repositories.length}
|
||||||
{#each options.repositories as repository}
|
{#each options.repositories as repository}
|
||||||
<option value={repository.id}>{repository.display_name}</option>
|
<option value={repository.id}>{repository.display_name}</option>
|
||||||
|
|
@ -278,10 +278,10 @@
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Selector</span>
|
<span>Selector</span>
|
||||||
<input bind:value={executionWorkspaceSelector} autocomplete="off" placeholder="HEAD" />
|
<input bind:value={workingDirectorySelector} autocomplete="off" placeholder="HEAD" />
|
||||||
</label>
|
</label>
|
||||||
<button type="button" disabled={creatingWorkspace || !executionWorkspaceRepositoryId} onclick={() => void createExecutionWorkspace()}>
|
<button type="button" disabled={creatingWorkingDirectory || !workingDirectoryRepositoryId} onclick={() => void createWorkingDirectory()}>
|
||||||
{creatingWorkspace ? 'Allocating…' : 'Create execution workspace'}
|
{creatingWorkingDirectory ? 'Allocating…' : 'Create working directory'}
|
||||||
</button>
|
</button>
|
||||||
<label>
|
<label>
|
||||||
<span>Relative cwd</span>
|
<span>Relative cwd</span>
|
||||||
|
|
@ -298,7 +298,7 @@
|
||||||
{#if submitError}
|
{#if submitError}
|
||||||
<p class="section-state error">{submitError}</p>
|
<p class="section-state error">{submitError}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<button type="submit" disabled={submitting || !runtimeId || !profile || !executionWorkspaceAllocationId}>
|
<button type="submit" disabled={submitting || !runtimeId || !profile || !workingDirectoryAllocationId}>
|
||||||
{submitting ? 'Starting…' : 'Start Coding Worker'}
|
{submitting ? 'Starting…' : 'Start Coding Worker'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -322,7 +322,7 @@
|
||||||
</span>
|
</span>
|
||||||
<span class="item-meta">
|
<span class="item-meta">
|
||||||
{worker.role ? `${worker.role} · ` : ''}{worker.state} · {worker.status} · 🖥 {worker.host_id}
|
{worker.role ? `${worker.role} · ` : ''}{worker.state} · {worker.status} · 🖥 {worker.host_id}
|
||||||
{worker.execution_workspace ? ` · ws:${worker.execution_workspace.repository_id}@${worker.execution_workspace.resolved_commit.slice(0, 8)}` : ''}
|
{worker.working_directory ? ` · wd:${worker.working_directory.repository_id}@${worker.working_directory.resolved_commit.slice(0, 8)}` : ''}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import type {
|
import type {
|
||||||
Event as PodProtocolEvent,
|
Event as PodProtocolEvent,
|
||||||
Method as PodProtocolMethod,
|
Method as PodProtocolMethod,
|
||||||
Segment as PodProtocolSegment
|
Segment as PodProtocolSegment,
|
||||||
} from '$lib/generated/protocol';
|
} from "$lib/generated/protocol";
|
||||||
|
|
||||||
export type { PodProtocolEvent, PodProtocolMethod, PodProtocolSegment };
|
export type { PodProtocolEvent, PodProtocolMethod, PodProtocolSegment };
|
||||||
|
|
||||||
|
|
@ -88,11 +88,11 @@ export type Worker = {
|
||||||
last_seen_at?: string | null;
|
last_seen_at?: string | null;
|
||||||
implementation: { kind: string; display_hint: string };
|
implementation: { kind: string; display_hint: string };
|
||||||
capabilities: WorkerCapabilities;
|
capabilities: WorkerCapabilities;
|
||||||
execution_workspace?: ExecutionWorkspaceSummary | null;
|
working_directory?: WorkingDirectorySummary | null;
|
||||||
diagnostics: Diagnostic[];
|
diagnostics: Diagnostic[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkerOperationState = 'accepted' | 'unsupported' | 'rejected';
|
export type WorkerOperationState = "accepted" | "unsupported" | "rejected";
|
||||||
|
|
||||||
export type WorkerLaunchRuntimeOption = {
|
export type WorkerLaunchRuntimeOption = {
|
||||||
runtime_id: string;
|
runtime_id: string;
|
||||||
|
|
@ -109,13 +109,13 @@ export type WorkerLaunchProfileCandidate = {
|
||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ExecutionWorkspaceRepositoryOption = {
|
export type WorkingDirectoryRepositoryOption = {
|
||||||
id: string;
|
id: string;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
default_selector?: string | null;
|
default_selector?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ExecutionWorkspaceSummary = {
|
export type WorkingDirectorySummary = {
|
||||||
allocation_id: string;
|
allocation_id: string;
|
||||||
repository_id: string;
|
repository_id: string;
|
||||||
requested_selector?: string | null;
|
requested_selector?: string | null;
|
||||||
|
|
@ -132,29 +132,29 @@ export type ExecutionWorkspaceSummary = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserExecutionWorkspaceCreateResponse = {
|
export type BrowserWorkingDirectoryCreateResponse = {
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
item: ExecutionWorkspaceSummary;
|
item: WorkingDirectorySummary;
|
||||||
diagnostics: Diagnostic[];
|
diagnostics: Diagnostic[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserExecutionWorkspaceListResponse = {
|
export type BrowserWorkingDirectoryListResponse = {
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
items: ExecutionWorkspaceSummary[];
|
items: WorkingDirectorySummary[];
|
||||||
diagnostics: Diagnostic[];
|
diagnostics: Diagnostic[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserWorkerExecutionWorkspaceSelection = {
|
export type BrowserWorkerWorkingDirectorySelection = {
|
||||||
allocation_id: string;
|
allocation_id: string;
|
||||||
relative_cwd?: string | null;
|
relative_cwd?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserExecutionWorkspaceCreateRequest = {
|
export type BrowserWorkingDirectoryCreateRequest = {
|
||||||
repository_id: string;
|
repository_id: string;
|
||||||
selector?: string | null;
|
selector?: string | null;
|
||||||
policy?: {
|
policy?: {
|
||||||
dirty_state?: 'clean_point_only';
|
dirty_state?: "clean_point_only";
|
||||||
cleanup?: 'manual_or_worker_stop';
|
cleanup?: "manual_or_worker_stop";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -162,8 +162,8 @@ export type WorkerLaunchOptionsResponse = {
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
runtimes: WorkerLaunchRuntimeOption[];
|
runtimes: WorkerLaunchRuntimeOption[];
|
||||||
profiles: WorkerLaunchProfileCandidate[];
|
profiles: WorkerLaunchProfileCandidate[];
|
||||||
repositories: ExecutionWorkspaceRepositoryOption[];
|
repositories: WorkingDirectoryRepositoryOption[];
|
||||||
execution_workspaces: ExecutionWorkspaceSummary[];
|
working_directories: WorkingDirectorySummary[];
|
||||||
diagnostics: Diagnostic[];
|
diagnostics: Diagnostic[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -187,7 +187,7 @@ export type WorkerInputResult = {
|
||||||
|
|
||||||
export type WorkerTranscriptItem = {
|
export type WorkerTranscriptItem = {
|
||||||
sequence: number;
|
sequence: number;
|
||||||
role: 'user' | 'assistant' | 'system' | string;
|
role: "user" | "assistant" | "system" | string;
|
||||||
content: string;
|
content: string;
|
||||||
event_id: number;
|
event_id: number;
|
||||||
};
|
};
|
||||||
|
|
@ -218,8 +218,8 @@ export type ClientWorkerEventWsDiagnostic = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ClientWorkerEventWsFrame =
|
export type ClientWorkerEventWsFrame =
|
||||||
| { kind: 'event'; envelope: ClientWorkerEventWsEnvelope }
|
| { kind: "event"; envelope: ClientWorkerEventWsEnvelope }
|
||||||
| { kind: 'diagnostic'; diagnostic: ClientWorkerEventWsDiagnostic };
|
| { kind: "diagnostic"; diagnostic: ClientWorkerEventWsDiagnostic };
|
||||||
|
|
||||||
export type ListResponse<T> = {
|
export type ListResponse<T> = {
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
|
|
@ -349,13 +349,13 @@ export type ObjectiveListResponse = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CompanionState =
|
export type CompanionState =
|
||||||
| 'ready'
|
| "ready"
|
||||||
| 'busy'
|
| "busy"
|
||||||
| 'error'
|
| "error"
|
||||||
| 'timeout'
|
| "timeout"
|
||||||
| 'cancelled'
|
| "cancelled"
|
||||||
| 'accepted'
|
| "accepted"
|
||||||
| 'rejected';
|
| "rejected";
|
||||||
|
|
||||||
export type CompanionTransportSummary = {
|
export type CompanionTransportSummary = {
|
||||||
kind: string;
|
kind: string;
|
||||||
|
|
@ -372,7 +372,7 @@ export type CompanionStatusResponse = {
|
||||||
|
|
||||||
export type CompanionTranscriptItem = {
|
export type CompanionTranscriptItem = {
|
||||||
sequence: number;
|
sequence: number;
|
||||||
role: 'user' | 'assistant' | 'system' | string;
|
role: "user" | "assistant" | "system" | string;
|
||||||
content: string;
|
content: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
source: string;
|
source: string;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import { buildBrowserCreateWorkerRequest, defaultWorkerLaunchForm } from './worker-launch.ts';
|
import {
|
||||||
import type { WorkerLaunchOptionsResponse } from './types.ts';
|
buildBrowserCreateWorkerRequest,
|
||||||
|
defaultWorkerLaunchForm,
|
||||||
|
} from "./worker-launch.ts";
|
||||||
|
import type { WorkerLaunchOptionsResponse } from "./types.ts";
|
||||||
|
|
||||||
declare const Deno: {
|
declare const Deno: {
|
||||||
test(name: string, fn: () => Promise<void> | void): void;
|
test(name: string, fn: () => Promise<void> | void): void;
|
||||||
|
|
@ -14,89 +17,93 @@ function assertEquals<T>(actual: T, expected: T): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: WorkerLaunchOptionsResponse = {
|
const options: WorkerLaunchOptionsResponse = {
|
||||||
workspace_id: 'workspace',
|
workspace_id: "workspace",
|
||||||
runtimes: [
|
runtimes: [
|
||||||
{
|
{
|
||||||
runtime_id: 'remote',
|
runtime_id: "remote",
|
||||||
display_name: 'Remote',
|
display_name: "Remote",
|
||||||
status: 'active',
|
status: "active",
|
||||||
can_spawn_worker: true,
|
can_spawn_worker: true,
|
||||||
built_in: false,
|
built_in: false,
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
runtime_id: 'embedded',
|
runtime_id: "embedded",
|
||||||
display_name: 'Embedded',
|
display_name: "Embedded",
|
||||||
status: 'active',
|
status: "active",
|
||||||
can_spawn_worker: true,
|
can_spawn_worker: true,
|
||||||
built_in: false,
|
built_in: false,
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
profiles: [
|
profiles: [
|
||||||
{ id: 'builtin:companion', label: 'Companion', description: 'chat' },
|
{ id: "builtin:companion", label: "Companion", description: "chat" },
|
||||||
{ id: 'builtin:coder', label: 'Coder', description: 'code' },
|
{ id: "builtin:coder", label: "Coder", description: "code" },
|
||||||
],
|
],
|
||||||
repositories: [
|
repositories: [
|
||||||
{ id: 'repo', display_name: 'Repo', default_selector: 'HEAD' },
|
{ id: "repo", display_name: "Repo", default_selector: "HEAD" },
|
||||||
],
|
],
|
||||||
execution_workspaces: [
|
working_directories: [
|
||||||
{
|
{
|
||||||
allocation_id: 'alloc-1-repo',
|
allocation_id: "alloc-1-repo",
|
||||||
repository_id: 'repo',
|
repository_id: "repo",
|
||||||
requested_selector: 'HEAD',
|
requested_selector: "HEAD",
|
||||||
materializer_kind: 'local_git_worktree',
|
materializer_kind: "local_git_worktree",
|
||||||
dirty_state_policy: 'clean_point_only',
|
dirty_state_policy: "clean_point_only",
|
||||||
resolved_commit: '0123456789abcdef',
|
resolved_commit: "0123456789abcdef",
|
||||||
status: 'active',
|
status: "active",
|
||||||
cleanup_policy: 'manual_or_worker_stop',
|
cleanup_policy: "manual_or_worker_stop",
|
||||||
cleanup_target: { kind: 'git_worktree', allocation_id: 'alloc-1-repo', repository_id: 'repo' },
|
cleanup_target: {
|
||||||
|
kind: "git_worktree",
|
||||||
|
allocation_id: "alloc-1-repo",
|
||||||
|
repository_id: "repo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
Deno.test('defaultWorkerLaunchForm chooses active runtime, coder profile, repository, and workspace', () => {
|
Deno.test("defaultWorkerLaunchForm chooses active runtime, coder profile, repository, and working directory", () => {
|
||||||
const form = defaultWorkerLaunchForm(options, {
|
const form = defaultWorkerLaunchForm(options, {
|
||||||
runtime_id: '',
|
runtime_id: "",
|
||||||
display_name: '',
|
display_name: "",
|
||||||
profile: '',
|
profile: "",
|
||||||
initial_text: 'hello',
|
initial_text: "hello",
|
||||||
execution_workspace_allocation_id: '',
|
working_directory_allocation_id: "",
|
||||||
execution_workspace_repository_id: '',
|
working_directory_repository_id: "",
|
||||||
execution_workspace_selector: '',
|
working_directory_selector: "",
|
||||||
relative_cwd: '',
|
relative_cwd: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(form.runtime_id, 'remote');
|
assertEquals(form.runtime_id, "remote");
|
||||||
assertEquals(form.display_name, 'Coding Worker');
|
assertEquals(form.display_name, "Coding Worker");
|
||||||
assertEquals(form.profile, 'builtin:coder');
|
assertEquals(form.profile, "builtin:coder");
|
||||||
assertEquals(form.initial_text, 'hello');
|
assertEquals(form.initial_text, "hello");
|
||||||
assertEquals(form.execution_workspace_allocation_id, 'alloc-1-repo');
|
assertEquals(form.working_directory_allocation_id, "alloc-1-repo");
|
||||||
assertEquals(form.execution_workspace_repository_id, 'repo');
|
assertEquals(form.working_directory_repository_id, "repo");
|
||||||
assertEquals(form.execution_workspace_selector, 'HEAD');
|
assertEquals(form.working_directory_selector, "HEAD");
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test('buildBrowserCreateWorkerRequest sends allocation id and relative cwd only', () => {
|
Deno.test("buildBrowserCreateWorkerRequest sends allocation id and relative cwd only", () => {
|
||||||
const request = buildBrowserCreateWorkerRequest({
|
const request = buildBrowserCreateWorkerRequest({
|
||||||
runtime_id: 'embedded',
|
runtime_id: "embedded",
|
||||||
display_name: 'Worker',
|
display_name: "Worker",
|
||||||
profile: 'builtin:coder',
|
profile: "builtin:coder",
|
||||||
initial_text: 'go',
|
initial_text: "go",
|
||||||
execution_workspace_allocation_id: 'alloc-1-repo',
|
working_directory_allocation_id: "alloc-1-repo",
|
||||||
execution_workspace_repository_id: 'repo',
|
working_directory_repository_id: "repo",
|
||||||
execution_workspace_selector: 'main',
|
working_directory_selector: "main",
|
||||||
relative_cwd: 'crates/yoi',
|
relative_cwd: "crates/yoi",
|
||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(request, {
|
assertEquals(request, {
|
||||||
runtime_id: 'embedded',
|
runtime_id: "embedded",
|
||||||
display_name: 'Worker',
|
display_name: "Worker",
|
||||||
profile: 'builtin:coder',
|
profile: "builtin:coder",
|
||||||
initial_text: 'go',
|
initial_text: "go",
|
||||||
execution_workspace: {
|
working_directory: {
|
||||||
allocation_id: 'alloc-1-repo',
|
allocation_id: "alloc-1-repo",
|
||||||
relative_cwd: 'crates/yoi',
|
relative_cwd: "crates/yoi",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import type { BrowserWorkerExecutionWorkspaceSelection, WorkerLaunchOptionsResponse } from './types';
|
import type {
|
||||||
|
BrowserWorkerWorkingDirectorySelection,
|
||||||
|
WorkerLaunchOptionsResponse,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export type WorkerLaunchFormState = {
|
export type WorkerLaunchFormState = {
|
||||||
runtime_id: string;
|
runtime_id: string;
|
||||||
display_name: string;
|
display_name: string;
|
||||||
profile: string;
|
profile: string;
|
||||||
initial_text: string;
|
initial_text: string;
|
||||||
execution_workspace_allocation_id: string;
|
working_directory_allocation_id: string;
|
||||||
execution_workspace_repository_id: string;
|
working_directory_repository_id: string;
|
||||||
execution_workspace_selector: string;
|
working_directory_selector: string;
|
||||||
relative_cwd: string;
|
relative_cwd: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -16,37 +19,51 @@ export type BrowserCreateWorkerRequest = {
|
||||||
display_name: string;
|
display_name: string;
|
||||||
profile: string;
|
profile: string;
|
||||||
initial_text: string;
|
initial_text: string;
|
||||||
execution_workspace?: BrowserWorkerExecutionWorkspaceSelection;
|
working_directory?: BrowserWorkerWorkingDirectorySelection;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function defaultWorkerLaunchForm(
|
export function defaultWorkerLaunchForm(
|
||||||
options: WorkerLaunchOptionsResponse | null,
|
options: WorkerLaunchOptionsResponse | null,
|
||||||
current: WorkerLaunchFormState,
|
current: WorkerLaunchFormState,
|
||||||
): WorkerLaunchFormState {
|
): WorkerLaunchFormState {
|
||||||
const preferredRuntime = options?.runtimes.find((runtime) => runtime.can_spawn_worker && runtime.status === 'active')
|
const preferredRuntime =
|
||||||
?? options?.runtimes.find((runtime) => runtime.can_spawn_worker)
|
options?.runtimes.find((runtime) =>
|
||||||
?? options?.runtimes[0];
|
runtime.can_spawn_worker && runtime.status === "active"
|
||||||
const preferredProfile = options?.profiles.find((candidate) => candidate.id === 'builtin:coder')
|
) ??
|
||||||
?? options?.profiles[0];
|
options?.runtimes.find((runtime) => runtime.can_spawn_worker) ??
|
||||||
const preferredExecutionWorkspace = options?.execution_workspaces.find((workspace) => workspace.status === 'active')
|
options?.runtimes[0];
|
||||||
?? options?.execution_workspaces[0];
|
const preferredProfile =
|
||||||
const preferredRepository = options?.repositories.find((repository) => repository.id === current.execution_workspace_repository_id)
|
options?.profiles.find((candidate) => candidate.id === "builtin:coder") ??
|
||||||
?? options?.repositories[0];
|
options?.profiles[0];
|
||||||
|
const preferredWorkingDirectory =
|
||||||
|
options?.working_directories.find((directory) =>
|
||||||
|
directory.status === "active"
|
||||||
|
) ??
|
||||||
|
options?.working_directories[0];
|
||||||
|
const preferredRepository =
|
||||||
|
options?.repositories.find((repository) =>
|
||||||
|
repository.id === current.working_directory_repository_id
|
||||||
|
) ??
|
||||||
|
options?.repositories[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
runtime_id: current.runtime_id || preferredRuntime?.runtime_id || '',
|
runtime_id: current.runtime_id || preferredRuntime?.runtime_id || "",
|
||||||
display_name: current.display_name || 'Coding Worker',
|
display_name: current.display_name || "Coding Worker",
|
||||||
profile: options?.profiles.some((candidate) => candidate.id === current.profile)
|
profile:
|
||||||
? current.profile
|
options?.profiles.some((candidate) => candidate.id === current.profile)
|
||||||
: preferredProfile?.id || '',
|
? current.profile
|
||||||
|
: preferredProfile?.id || "",
|
||||||
initial_text: current.initial_text,
|
initial_text: current.initial_text,
|
||||||
execution_workspace_allocation_id: options?.execution_workspaces.some(
|
working_directory_allocation_id: options?.working_directories.some(
|
||||||
(workspace) => workspace.allocation_id === current.execution_workspace_allocation_id,
|
(directory) =>
|
||||||
)
|
directory.allocation_id === current.working_directory_allocation_id,
|
||||||
? current.execution_workspace_allocation_id
|
)
|
||||||
: preferredExecutionWorkspace?.allocation_id || '',
|
? current.working_directory_allocation_id
|
||||||
execution_workspace_repository_id: current.execution_workspace_repository_id || preferredRepository?.id || '',
|
: preferredWorkingDirectory?.allocation_id || "",
|
||||||
execution_workspace_selector: current.execution_workspace_selector || preferredRepository?.default_selector || 'HEAD',
|
working_directory_repository_id: current.working_directory_repository_id ||
|
||||||
|
preferredRepository?.id || "",
|
||||||
|
working_directory_selector: current.working_directory_selector ||
|
||||||
|
preferredRepository?.default_selector || "HEAD",
|
||||||
relative_cwd: current.relative_cwd,
|
relative_cwd: current.relative_cwd,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -60,13 +77,13 @@ export function buildBrowserCreateWorkerRequest(
|
||||||
profile: form.profile,
|
profile: form.profile,
|
||||||
initial_text: form.initial_text,
|
initial_text: form.initial_text,
|
||||||
};
|
};
|
||||||
if (form.execution_workspace_allocation_id) {
|
if (form.working_directory_allocation_id) {
|
||||||
request.execution_workspace = {
|
request.working_directory = {
|
||||||
allocation_id: form.execution_workspace_allocation_id,
|
allocation_id: form.working_directory_allocation_id,
|
||||||
};
|
};
|
||||||
const relativeCwd = form.relative_cwd.trim();
|
const relativeCwd = form.relative_cwd.trim();
|
||||||
if (relativeCwd) {
|
if (relativeCwd) {
|
||||||
request.execution_workspace.relative_cwd = relativeCwd;
|
request.working_directory.relative_cwd = relativeCwd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return request;
|
return request;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user