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

This commit is contained in:
Keisuke Hirata 2026-05-02 02:13:30 +09:00
parent 14862fbc37
commit f1d8f42fd5
5 changed files with 74 additions and 21 deletions

1
.gitignore vendored
View File

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

View File

@ -2,6 +2,7 @@
- [ ] 内部 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)
- [ ] パーミッション: パターンベースのツール実行制御 → [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)
- [ ] OpenAI Responses: sampling パラメータの取り扱い → [tickets/responses-sampling-params.md](tickets/responses-sampling-params.md)
- [ ] llm-worker のエラー耐性

View File

@ -268,12 +268,11 @@ async fn wait_for_ready(
form: &mut Form,
overlay_toml: &str,
) -> 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 mut command = Command::new(&pod_bin);
command
.args(&pod_args)
.arg("--overlay")
.arg(overlay_toml)
.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")
}
/// 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
/// at e.g. `cargo run -p pod --quiet --`; the first token is the program
/// and the rest are prepended before `--overlay` and friends.
fn resolve_pod_command() -> (PathBuf, Vec<String>) {
/// `INSOMNIA_POD_COMMAND` overrides the lookup (used by tests to inject
/// a mock binary). Otherwise we defer to `PATH` — missing binary
/// surfaces as the spawn `io::Error`.
fn resolve_pod_command() -> PathBuf {
if let Ok(cmd) = std::env::var("INSOMNIA_POD_COMMAND") {
let mut tokens = cmd.split_whitespace();
if let Some(program) = tokens.next() {
let args = tokens.map(str::to_owned).collect();
return (PathBuf::from(program), args);
if !cmd.is_empty() {
return PathBuf::from(cmd);
}
}
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
let candidate = dir.join("pod");
if candidate.is_file() {
return (candidate, Vec::new());
}
}
}
(PathBuf::from("pod"), Vec::new())
PathBuf::from("pod")
}
struct StderrTail {

View File

@ -1,4 +1,23 @@
{ 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 {
packages = with pkgs; [
nixfmt
@ -6,12 +25,12 @@ pkgs.mkShell {
git
rustc
cargo
pod-dev
];
buildInputs = with pkgs; [
pkg-config
openssl
];
INSOMNIA_POD_COMMAND = "cargo run -p pod --quiet --";
shellHook = ''
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 情報を含めるための最小限の拡張のみで足りる想定)