不要なforkの削除
This commit is contained in:
parent
3c90729156
commit
e98a596235
|
|
@ -1581,45 +1581,40 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
|
|||
/// Restore a Pod from an existing session log.
|
||||
///
|
||||
/// Resolves the manifest cascade exactly like [`Self::from_manifest`]
|
||||
/// (pwd / scope / scope-lock / client / prompt catalog), then forks
|
||||
/// the source session at its current head and seeds a fresh Worker
|
||||
/// from the resulting `RestoredState`. The Pod writes to the new
|
||||
/// fork session's jsonl; the source session's log is left intact.
|
||||
/// (pwd / scope / scope-lock / client / prompt catalog), seeds a
|
||||
/// fresh Worker from the source session's `RestoredState`, and
|
||||
/// reuses the same `session_id` so subsequent turns append to the
|
||||
/// source jsonl as a continuation of the same conversation.
|
||||
///
|
||||
/// Refuses to resume if another live Pod is currently writing to
|
||||
/// `source_session_id` (detected via `scope.lock`).
|
||||
/// Concurrent writers are prevented by the `scope.lock` registry:
|
||||
/// the registration carries `session_id`, and this constructor
|
||||
/// refuses to start when `scope_lock::lookup_session` already finds
|
||||
/// a live Pod writing to `session_id`. So there is no need to fork —
|
||||
/// resume is "the same session, a different process owning it".
|
||||
///
|
||||
/// `system_prompt` is replayed verbatim from the session log —
|
||||
/// templates are not re-rendered on restore so a long-running
|
||||
/// session keeps a stable cache prefix even when the manifest's
|
||||
/// instruction template would render differently today.
|
||||
pub async fn restore_from_manifest(
|
||||
source_session_id: SessionId,
|
||||
session_id: SessionId,
|
||||
manifest: PodManifest,
|
||||
store: St,
|
||||
loader: PromptLoader,
|
||||
) -> Result<Self, PodError> {
|
||||
// Refuse to resume into a session that's already being written.
|
||||
if let Some(info) = scope_lock::lookup_session(source_session_id)? {
|
||||
if let Some(info) = scope_lock::lookup_session(session_id)? {
|
||||
return Err(PodError::SessionInUse {
|
||||
session_id: source_session_id,
|
||||
session_id,
|
||||
pod_name: info.pod_name,
|
||||
socket: info.socket,
|
||||
});
|
||||
}
|
||||
|
||||
// Read the source state, then fork it into a fresh session id.
|
||||
// The fork's SessionStart captures the full history with
|
||||
// `forked_from` provenance pointing back to the source, so the
|
||||
// source jsonl stays untouched and double-write races are
|
||||
// impossible by construction.
|
||||
let state = session_store::restore(&store, source_session_id).await?;
|
||||
let Some(source_head) = state.head_hash.clone() else {
|
||||
return Err(PodError::SessionEmpty {
|
||||
session_id: source_session_id,
|
||||
});
|
||||
};
|
||||
let session_id = session_store::fork_at(&store, source_session_id, &source_head).await?;
|
||||
let state = session_store::restore(&store, session_id).await?;
|
||||
if state.head_hash.is_none() {
|
||||
return Err(PodError::SessionEmpty { session_id });
|
||||
}
|
||||
|
||||
let common = prepare_pod_common(&manifest, &loader, /* parse_template */ false)?;
|
||||
|
||||
|
|
@ -1663,26 +1658,12 @@ impl<St: Store> Pod<Box<dyn LlmClient>, St> {
|
|||
|
||||
let extract_pointer = memory::extract::fold_pointer(&state.extensions);
|
||||
|
||||
// The fork's SessionStart hash is the new head. We could
|
||||
// recompute it by reading the new session log, but
|
||||
// `session_store::fork_at` already returns the new session_id
|
||||
// and we know the chain starts fresh. The next `save_delta`
|
||||
// call will read head from store before appending, so leaving
|
||||
// `head_hash = None` here is safe but less efficient — we
|
||||
// refresh from the store to avoid a chain refresh on first
|
||||
// append.
|
||||
let head_hash = store
|
||||
.read_head_hash(session_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let mut pod = Self {
|
||||
manifest,
|
||||
worker: Some(worker),
|
||||
store,
|
||||
session_id,
|
||||
head_hash,
|
||||
head_hash: state.head_hash,
|
||||
pwd: common.pwd,
|
||||
scope: common.scope,
|
||||
hook_builder: HookRegistryBuilder::new(),
|
||||
|
|
|
|||
|
|
@ -102,17 +102,41 @@ pub async fn run() -> Result<PickerOutcome, PickerError> {
|
|||
}
|
||||
}
|
||||
Some(Action::Submit) => {
|
||||
drop(terminal);
|
||||
close_viewport(&mut terminal)?;
|
||||
return Ok(PickerOutcome::Picked(rows[selected].id));
|
||||
}
|
||||
Some(Action::Cancel) => {
|
||||
drop(terminal);
|
||||
close_viewport(&mut terminal)?;
|
||||
return Ok(PickerOutcome::Cancelled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Park the cursor at the very bottom of the picker's inline viewport
|
||||
/// and emit one newline before dropping the terminal. Without this the
|
||||
/// inline area is left with the cursor still inside it, so the next
|
||||
/// `Terminal::with_options(Inline(_))` call (the resume name dialog)
|
||||
/// computes its own area starting from inside the picker — drawing the
|
||||
/// new dialog on top of the lower picker rows.
|
||||
///
|
||||
/// Setting the cursor to `area.bottom() - 1` and writing `\r\n`
|
||||
/// scrolls the terminal up exactly one row, so the next inline
|
||||
/// viewport opens immediately below the picker rather than on top of
|
||||
/// it.
|
||||
fn close_viewport(
|
||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
) -> io::Result<()> {
|
||||
let area = terminal.get_frame().area();
|
||||
let last_row = area.bottom().saturating_sub(1);
|
||||
terminal.set_cursor_position((0, last_row))?;
|
||||
use std::io::Write;
|
||||
let mut out = io::stdout();
|
||||
out.write_all(b"\r\n")?;
|
||||
out.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn open_default_store() -> Result<FsStore, PickerError> {
|
||||
let dir = manifest::paths::sessions_dir().ok_or_else(|| {
|
||||
PickerError::Io(io::Error::new(
|
||||
|
|
|
|||
|
|
@ -36,13 +36,12 @@ TUI には既に新規 Pod 起動用の spawn UI があるため、同じよう
|
|||
|
||||
### 復元 Pod の構築
|
||||
|
||||
復元時はソース session を直接書き継がず、**fork** して新しい session を起こす。
|
||||
復元 Pod はソース session の **同じ session_id** を引き継ぎ、以降の turn を同じ jsonl に追記する。fork は行わない。
|
||||
|
||||
- `session_store::fork_at(source_session_id, source_head_hash)` で新しい session を作成する。新 session の `SessionStart` には全履歴と `forked_from = SessionOrigin { source_session_id, source_head_hash }` を載せる。
|
||||
- Pod は新 session_id 上で動作し、ソース session の jsonl は不変のまま残る。
|
||||
- これにより同一 session への同時書き込みは構造的に発生しない。
|
||||
- 同一 session への同時書き込みは `scope.lock` の `session_id` で構造的に防止する(次節)ので、resume 時にあえて新しい session を起こす必要はない。fork すると `SessionStart` だけが入った空に近い session が picker に積み上がるため不適切。
|
||||
- manifest / scope / tool 登録 / prompt loader は、通常の新規 Pod 起動と同じ現在の cascade 解決結果を使う。
|
||||
- Worker の会話履歴・system prompt・request config・turn count・usage history 等は `session_store::restore` で得た `RestoredState` を使う。`system_prompt` は session に保存された値をそのまま使い、`SystemPromptTemplate` の再レンダリングはしない。
|
||||
- `head_hash` は `RestoredState.head_hash` をそのまま採用し、次回の append が正しい hash chain で繋がる。
|
||||
- 復元起動時、runtime の `history.json` / `status.json` / `Event::History` で TUI が初期履歴を正しく再構築できる。
|
||||
- 復元された session が interrupted / paused 相当の状態を持つ場合、起動直後に `Resume` 可能な状態として扱う。通常終了済みなら `Idle` として新しい入力を受け付ける。
|
||||
|
||||
|
|
@ -82,12 +81,12 @@ TUI には既に新規 Pod 起動用の spawn UI があるため、同じよう
|
|||
|
||||
## 完了条件
|
||||
|
||||
- `pod --session <UUID>` で既存 session から Pod を起動でき、ソース session jsonl は不変のまま新しい fork session が作られる
|
||||
- `pod --session <UUID>` で既存 session から Pod を起動でき、以降の turn は同じ jsonl に追記される(fork 由来の空 session は生成されない)
|
||||
- `tui -r` / `tui --resume` で直近 10 件の既存 session 一覧を表示し、選択した session を復元対象にできる
|
||||
- `tui --session <UUID>` で session picker を経由せず、指定 session の復帰 name 入力へ進める
|
||||
- 復帰フローでは session 選択後または `--session` 指定後に name 入力ダイアログが表示され、その name の Pod として起動・attach できる
|
||||
- 復元直後の TUI に過去履歴が表示される
|
||||
- 復元後に新しい入力を送ると、既存履歴に続く turn として動作し、新しい fork session の jsonl に追記される
|
||||
- 復元後に新しい入力を送ると、既存履歴に続く turn として動作し、ソース session の jsonl にそのまま追記される
|
||||
- interrupted / paused 状態の session では、復元直後に Resume 導線が動作する
|
||||
- 同一 source session に対する live Pod が存在する場合、復元起動はエラーで終了し、既存 Pod の `pod_name` / socket がメッセージに表示される
|
||||
- `scope.lock` には各 Pod の `session_id` が記録される
|
||||
|
|
@ -95,8 +94,9 @@ TUI には既に新規 Pod 起動用の spawn UI があるため、同じよう
|
|||
## 範囲外
|
||||
|
||||
- session log の全文検索 UI
|
||||
- compact 前後の session chain / fork チェーンを 1 つの論理スレッドとして束ねる UI
|
||||
- compact 前後の session chain を 1 つの論理スレッドとして束ねる UI
|
||||
- 過去 session の編集・削除・名前付け
|
||||
- spawn された子 Pod / scope delegation ツリー全体の復元
|
||||
- 別マシンから転送された session store の import UI
|
||||
- `tui` での picker 復帰や自動 attach 切替(live セッション選択時はエラー終了)
|
||||
- 任意位置からの fork 起動(`fork_at` を resume 経路に組み込まない。将来別フローとして扱う)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user