fix: tuiからのPod作成の挙動を修正・開発時にcargo runでpodを起動する経路を実装

This commit is contained in:
Keisuke Hirata 2026-05-02 02:13:30 +09:00
parent 288e2239d4
commit d8d802d120
No known key found for this signature in database
5 changed files with 74 additions and 21 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.direnv .direnv
*.local* *.local*
.env .env
.worktree

View File

@ -2,6 +2,7 @@
- [ ] 内部 Worker / 内部 Pod の Workflow 化 → [tickets/internal-worker-workflow.md](tickets/internal-worker-workflow.md) - [ ] 内部 Worker / 内部 Pod の Workflow 化 → [tickets/internal-worker-workflow.md](tickets/internal-worker-workflow.md)
- [ ] Agent Skills を Workflow として ingest → [tickets/agent-skills.md](tickets/agent-skills.md) - [ ] Agent Skills を Workflow として ingest → [tickets/agent-skills.md](tickets/agent-skills.md)
- [ ] パーミッション: パターンベースのツール実行制御 → [tickets/permission-extension-point.md](tickets/permission-extension-point.md) - [ ] パーミッション: パターンベースのツール実行制御 → [tickets/permission-extension-point.md](tickets/permission-extension-point.md)
- [ ] Resume 時の Scope claim の改善 → [tickets/resume-scope-claim.md](tickets/resume-scope-claim.md)
- [ ] Pod CLI: マニフェスト関連フラグの整理 → [tickets/pod-cli-manifest-flags.md](tickets/pod-cli-manifest-flags.md) - [ ] Pod CLI: マニフェスト関連フラグの整理 → [tickets/pod-cli-manifest-flags.md](tickets/pod-cli-manifest-flags.md)
- [ ] OpenAI Responses: sampling パラメータの取り扱い → [tickets/responses-sampling-params.md](tickets/responses-sampling-params.md) - [ ] OpenAI Responses: sampling パラメータの取り扱い → [tickets/responses-sampling-params.md](tickets/responses-sampling-params.md)
- [ ] llm-worker のエラー耐性 - [ ] llm-worker のエラー耐性

View File

@ -268,12 +268,11 @@ async fn wait_for_ready(
form: &mut Form, form: &mut Form,
overlay_toml: &str, overlay_toml: &str,
) -> Result<SpawnReady, SpawnError> { ) -> Result<SpawnReady, SpawnError> {
let (pod_bin, pod_args) = resolve_pod_command(); let pod_bin = resolve_pod_command();
let cwd = std::env::current_dir().map_err(SpawnError::Io)?; let cwd = std::env::current_dir().map_err(SpawnError::Io)?;
let mut command = Command::new(&pod_bin); let mut command = Command::new(&pod_bin);
command command
.args(&pod_args)
.arg("--overlay") .arg("--overlay")
.arg(overlay_toml) .arg(overlay_toml)
.current_dir(&cwd) .current_dir(&cwd)
@ -375,28 +374,21 @@ fn build_overlay_toml(form: &Form) -> String {
toml::to_string(&toml::Value::Table(root)).expect("overlay serialisation cannot fail") toml::to_string(&toml::Value::Table(root)).expect("overlay serialisation cannot fail")
} }
/// Resolves the program (and any leading args) used to launch a child Pod. /// Resolves the binary used to launch a child Pod. Must point at a
/// `pod`-compatible executable — the parent reads the child's stderr
/// directly looking for `INSOMNIA-READY`, so any wrapper that emits
/// extra lines on stderr will pollute that handshake.
/// ///
/// `INSOMNIA_POD_COMMAND` is split on whitespace so devshells can point it /// `INSOMNIA_POD_COMMAND` overrides the lookup (used by tests to inject
/// at e.g. `cargo run -p pod --quiet --`; the first token is the program /// a mock binary). Otherwise we defer to `PATH` — missing binary
/// and the rest are prepended before `--overlay` and friends. /// surfaces as the spawn `io::Error`.
fn resolve_pod_command() -> (PathBuf, Vec<String>) { fn resolve_pod_command() -> PathBuf {
if let Ok(cmd) = std::env::var("INSOMNIA_POD_COMMAND") { if let Ok(cmd) = std::env::var("INSOMNIA_POD_COMMAND") {
let mut tokens = cmd.split_whitespace(); if !cmd.is_empty() {
if let Some(program) = tokens.next() { return PathBuf::from(cmd);
let args = tokens.map(str::to_owned).collect();
return (PathBuf::from(program), args);
} }
} }
if let Ok(exe) = std::env::current_exe() { PathBuf::from("pod")
if let Some(dir) = exe.parent() {
let candidate = dir.join("pod");
if candidate.is_file() {
return (candidate, Vec::new());
}
}
}
(PathBuf::from("pod"), Vec::new())
} }
struct StderrTail { struct StderrTail {

View File

@ -1,4 +1,23 @@
{ pkgs }: { pkgs }:
let
# Dev-only wrapper. tui の spawn 経路は `pod` バイナリを直に exec し、
# stderr の `INSOMNIA-READY` 行で握手するので、cargo の進捗や rustc の
# warning が混ざると tail に余計な行が積もり本当のエラーが押し出される。
# ここで一度ビルドを切り離し、成功時はビルド出力を一切捨てて素のバイナリ
# を exec、失敗時のみ build log を stderr に流して exit する。
pod-dev = pkgs.writeShellScriptBin "pod" ''
set -u
buildlog=$(mktemp)
trap 'rm -f "$buildlog"' EXIT
if ! cargo build --quiet -p pod 2>"$buildlog"; then
cat "$buildlog" >&2
exit 1
fi
manifest=$(cargo locate-project --workspace --message-format plain 2>/dev/null)
target_dir=''${CARGO_TARGET_DIR:-$(dirname "$manifest")/target}
exec "$target_dir/debug/pod" "$@"
'';
in
pkgs.mkShell { pkgs.mkShell {
packages = with pkgs; [ packages = with pkgs; [
nixfmt nixfmt
@ -6,12 +25,12 @@ pkgs.mkShell {
git git
rustc rustc
cargo cargo
pod-dev
]; ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
pkg-config pkg-config
openssl openssl
]; ];
INSOMNIA_POD_COMMAND = "cargo run -p pod --quiet --";
shellHook = '' shellHook = ''
echo "dev-shell-loaded" echo "dev-shell-loaded"
''; '';

View File

@ -0,0 +1,40 @@
# Resume 時の Scope Claim の改善
## 背景
`tickets/dynamic-scope.md` で in-process Scope の縮小SpawnPod による委譲時の Write revokeと pod-registry 上の delegation 記録が揃った。これにより「セッション中に scope が縮む」状態を Pod / registry の双方が一貫して表現できる。
一方で `tui -r` 経由の resume は、`crates/tui/src/spawn.rs` の `build_overlay_toml` を通じて fresh spawn と同じロジックで overlay を合成する。manifest cascade に scope 宣言が無い場合、cwd 直下に `write` 再帰の rule を毎回付ける挙動。
このため次のような衝突が起きる:
- セッション S が稼働中に SpawnPod で子 C を作り、cwd 配下のサブパスを委譲した
- 親が exit、子 C は registry 上にエントリが残存(あるいはまだ稼働中)
- ユーザーが S を resume しようとすると、新しい Pod が cwd 全体に `write` を claim → 委譲された部分と overlap して registry が拒否
resume の意図は「過去のセッションの続きを取る」であって「過去の effective scope より広い範囲を新たに掴み直す」ではない。現状は後者になっており、過去に手放した scope を resume が勝手に取り戻そうとする形になっている。
## ゴール
セッション resume 時に claim する scope が、当該セッションが最後に持っていた effective scope に揃う。委譲済み・他 Pod が保持中の部分は claim 対象から外れ、resume された Pod は当時と同じ範囲だけで動作する。
## 要件
- resume 時の overlay 合成は cwd 盲信ではなく、当該セッションが過去に持っていた scope を反映する。情報源は session log / registry / その他のいずれでも良いが、何らかの永続情報から復元できること
- 過去の scope 情報が取得できないセッション(旧形式 / 破損)は、明示的なエラーで止めるか、ユーザーに確認させてから fresh claim にフォールバックする(黙って広げない)
- claim 試行が registry の既存 allocation と衝突した場合、エラーメッセージで衝突相手の Pod 名 と target rule の双方が伝わる(現状は Pod 名のみ)
- 委譲済みエントリ(`delegated_from` を持つ allocationが同じセッションの委譲チェーンに属する場合、resume はその範囲を claim せずに進行する
## 完了条件
- 「親 Pod がセッション中に SpawnPod を実行 → 子に委譲 → 親 exit → 親セッションを resume」のフローが、既存子 allocation を残したまま衝突なしで成功する
- 既存の無関係な Pod と衝突するケースは、衝突 rule と相手 Pod 名を含む明確なエラーで失敗する
- 単体テスト or 統合テストで上記 2 ケースが検証される
- 既存の fresh spawn (resume なし) の挙動には変化なし
## 範囲外
- 過去スコープの永続化スキーマを新規導入するかの判断は実装時に決めるsession log の既存フィールドで足りるなら追加しない)
- 自動的に既存 Pod を kill / reclaim して claim を通す挙動
- protocol 経由の外部からの GrantScope / RevokeScope`tickets/dynamic-scope.md` の範囲外宣言を継承)
- registry 側のエラー型の全面再設計rule 情報を含めるための最小限の拡張のみで足りる想定)