rename: adopt yoi identity
This commit is contained in:
parent
6e133a7229
commit
e6c458021c
0
.insomnia/.gitignore → .yoi/.gitignore
vendored
0
.insomnia/.gitignore → .yoi/.gitignore
vendored
|
|
@ -23,7 +23,7 @@ Podの状態から純粋に再現可能で、且つ揮発性の無い操作で
|
|||
|
||||
## 実際のセッションを読んでデバッグする
|
||||
|
||||
`~/.insomnia/sessions`にすべてのセッションがある。jsonlなので、いい感じにBashで読むこと。
|
||||
`~/.yoi/sessions`にすべてのセッションがある。jsonlなので、いい感じにBashで読むこと。
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -74,4 +74,4 @@ worktree と併用して作業を進める場合、必ずブランチを切る
|
|||
|
||||
---
|
||||
|
||||
insomniaでinsomniaを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。
|
||||
YoiでYoiを開発している際、AI自身のフィードバックを元に改善を回すために `docs/report/`ディレクトリに感じた障壁や改善案等を書き残す形にした。 明確に力不足な点/ツールの問題があった場合や、ユーザーからの指示があった際に作ること。
|
||||
|
|
|
|||
31
Cargo.lock
generated
31
Cargo.lock
generated
|
|
@ -1479,21 +1479,6 @@ dependencies = [
|
|||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insomnia"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client",
|
||||
"memory",
|
||||
"pod",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"session-store",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.12"
|
||||
|
|
@ -4761,6 +4746,22 @@ dependencies = [
|
|||
"markup5ever",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"client",
|
||||
"manifest",
|
||||
"memory",
|
||||
"pod",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"session-store",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.2"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ members = [
|
|||
"crates/secrets",
|
||||
"crates/manifest",
|
||||
"crates/pod",
|
||||
"crates/insomnia",
|
||||
"crates/yoi",
|
||||
"crates/pod-store",
|
||||
"crates/protocol",
|
||||
"crates/provider",
|
||||
|
|
@ -35,7 +35,7 @@ manifest = { path = "crates/manifest" }
|
|||
lint-common = { path = "crates/lint-common" }
|
||||
memory = { path = "crates/memory" }
|
||||
pod = { path = "crates/pod" }
|
||||
insomnia = { path = "crates/insomnia" }
|
||||
yoi = { path = "crates/yoi" }
|
||||
pod-registry = { path = "crates/pod-registry" }
|
||||
pod-store = { path = "crates/pod-store" }
|
||||
protocol = { path = "crates/protocol" }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# INSOMNIA
|
||||
# 夜居 | Yoi agent
|
||||
|
||||
insomnia(i6a)は不休のエージェントループを回すためのエージェントプラットフォーム。
|
||||
夜居(Yoi)は不休のエージェントループを回すためのエージェントプラットフォーム。
|
||||
|
||||
ワークフローを統括し、四六時中電力を消費し、イテレーションします。
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! - [`PodClient`]: 既存 pod の Unix ソケットへ接続して `Method` を送り、
|
||||
//! `Event` を受け取る低レベル接続。
|
||||
//! - [`spawn`]: pod バイナリをサブプロセスとして起動し、`INSOMNIA-READY`
|
||||
//! - [`spawn`]: pod バイナリをサブプロセスとして起動し、`YOI-READY`
|
||||
//! ハンドシェイクが終わるまで待つフロー。subprocess を立ち上げる必要が
|
||||
//! ない呼び出し側 (=既存 pod に attach する場合) は使わなくてよい。
|
||||
//!
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::fmt;
|
|||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const POD_RUNTIME_COMMAND_ENV: &str = "INSOMNIA_POD_RUNTIME_COMMAND";
|
||||
const POD_RUNTIME_COMMAND_ENV: &str = "YOI_POD_RUNTIME_COMMAND";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PodRuntimeCommand {
|
||||
|
|
@ -29,9 +29,9 @@ impl PodRuntimeCommand {
|
|||
|
||||
/// Resolve the Pod runtime command used for subprocess launches.
|
||||
///
|
||||
/// The default launch path is always the current `insomnia` executable plus
|
||||
/// The default launch path is always the current `yoi` executable plus
|
||||
/// the unified `pod` prefix argument. During development, a non-empty
|
||||
/// `INSOMNIA_POD_RUNTIME_COMMAND` value replaces only the executable path;
|
||||
/// `YOI_POD_RUNTIME_COMMAND` value replaces only the executable path;
|
||||
/// the `pod` prefix is still added here and the env value is not parsed as a
|
||||
/// shell command.
|
||||
pub fn resolve() -> io::Result<Self> {
|
||||
|
|
@ -89,10 +89,10 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn insomnia_binary_defaults_to_pod_prefix() {
|
||||
let command = PodRuntimeCommand::for_executable("/opt/insomnia/bin/insomnia");
|
||||
fn yoi_binary_defaults_to_pod_prefix() {
|
||||
let command = PodRuntimeCommand::for_executable("/opt/yoi/bin/yoi");
|
||||
|
||||
assert_eq!(command.program(), Path::new("/opt/insomnia/bin/insomnia"));
|
||||
assert_eq!(command.program(), Path::new("/opt/yoi/bin/yoi"));
|
||||
assert_eq!(command.prefix_args(), [OsString::from("pod")]);
|
||||
assert_eq!(
|
||||
command.argv_with(["--pod", "agent"]),
|
||||
|
|
@ -105,12 +105,9 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn any_runtime_executable_gets_pod_prefix() {
|
||||
let command = PodRuntimeCommand::for_executable("/opt/insomnia/bin/custom-runtime");
|
||||
let command = PodRuntimeCommand::for_executable("/opt/yoi/bin/custom-runtime");
|
||||
|
||||
assert_eq!(
|
||||
command.program(),
|
||||
Path::new("/opt/insomnia/bin/custom-runtime")
|
||||
);
|
||||
assert_eq!(command.program(), Path::new("/opt/yoi/bin/custom-runtime"));
|
||||
assert_eq!(command.prefix_args(), [OsString::from("pod")]);
|
||||
assert_eq!(
|
||||
command.argv_with(["--pod", "agent"]),
|
||||
|
|
@ -124,38 +121,38 @@ mod tests {
|
|||
#[test]
|
||||
fn resolve_uses_current_exe_when_override_is_unset() {
|
||||
let command = PodRuntimeCommand::resolve_from_env_value(None, || {
|
||||
Ok(PathBuf::from("/opt/insomnia/bin/insomnia"))
|
||||
Ok(PathBuf::from("/opt/yoi/bin/yoi"))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
command,
|
||||
PodRuntimeCommand::for_executable("/opt/insomnia/bin/insomnia")
|
||||
PodRuntimeCommand::for_executable("/opt/yoi/bin/yoi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_uses_current_exe_when_override_is_empty() {
|
||||
let command = PodRuntimeCommand::resolve_from_env_value(Some(OsString::new()), || {
|
||||
Ok(PathBuf::from("/opt/insomnia/bin/insomnia"))
|
||||
Ok(PathBuf::from("/opt/yoi/bin/yoi"))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
command,
|
||||
PodRuntimeCommand::for_executable("/opt/insomnia/bin/insomnia")
|
||||
PodRuntimeCommand::for_executable("/opt/yoi/bin/yoi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_override_replaces_only_program_and_keeps_pod_prefix() {
|
||||
let command = PodRuntimeCommand::resolve_from_env_value(
|
||||
Some(OsString::from("/tmp/rebuilt insomnia")),
|
||||
Some(OsString::from("/tmp/rebuilt yoi")),
|
||||
|| panic!("override must not inspect current_exe"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(command.program(), Path::new("/tmp/rebuilt insomnia"));
|
||||
assert_eq!(command.program(), Path::new("/tmp/rebuilt yoi"));
|
||||
assert_eq!(command.prefix_args(), [OsString::from("pod")]);
|
||||
assert_eq!(
|
||||
command.argv_with(["--pod", "agent"]),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Pod runtime command をサブプロセスとして立ち上げ、`INSOMNIA-READY` を待つ
|
||||
//! Pod runtime command をサブプロセスとして立ち上げ、`YOI-READY` を待つ
|
||||
//! ハンドシェイク。
|
||||
//!
|
||||
//! - 親プロセス (TUI / GUI / E2E) は profile/default/typed restore flags を
|
||||
//! 指定してこの関数に渡す。pod はそれを受けて socket を bind し、stderr に
|
||||
//! `INSOMNIA-READY\t<name>\t<socket>` を吐く。
|
||||
//! `YOI-READY\t<name>\t<socket>` を吐く。
|
||||
//! - 待機中の stderr 行は `progress` コールバック越しに呼び出し側へ流す。
|
||||
//! UI の進捗表示や E2E のログ収集はここで賄う。
|
||||
//! - `kill_on_drop = false` + `process_group(0)` により、親プロセス
|
||||
|
|
@ -19,7 +19,7 @@ use crate::PodRuntimeCommand;
|
|||
use tokio::process::Command;
|
||||
use uuid::Uuid;
|
||||
|
||||
const READY_PREFIX: &str = "INSOMNIA-READY\t";
|
||||
const READY_PREFIX: &str = "YOI-READY\t";
|
||||
const READY_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
|
||||
/// `spawn_pod` の入力。
|
||||
|
|
@ -69,7 +69,7 @@ impl std::fmt::Display for SpawnError {
|
|||
Self::Io(e) => write!(f, "io error: {e}"),
|
||||
Self::RuntimeDirUnavailable => write!(
|
||||
f,
|
||||
"could not resolve runtime directory (set INSOMNIA_HOME, INSOMNIA_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)"
|
||||
"could not resolve runtime directory (set YOI_HOME, YOI_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)"
|
||||
),
|
||||
Self::PodLaunchFailed { command, source } => write!(
|
||||
f,
|
||||
|
|
@ -106,7 +106,7 @@ impl From<io::Error> for SpawnError {
|
|||
}
|
||||
}
|
||||
|
||||
/// pod を spawn し、`INSOMNIA-READY` ハンドシェイクが終わるまで待つ。
|
||||
/// pod を spawn し、`YOI-READY` ハンドシェイクが終わるまで待つ。
|
||||
///
|
||||
/// `progress` は ready 行を見つけるまでに観測した stderr の各行で呼ばれる
|
||||
/// (ready 行自体は除外される)。UI の表示更新や E2E ログ取得に使う。
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! `response.*` 名前空間の SSE を共通の [`Event`](crate::llm_client::event::Event)
|
||||
//! に変換する。Responses の (output_index, content_index) 2 次元座標と
|
||||
//! insomnia 側 1 次元 `BlockStart/Delta/Stop::index` のマッピングは
|
||||
//! yoi 側 1 次元 `BlockStart/Delta/Stop::index` のマッピングは
|
||||
//! [`OpenAIResponsesState`] が保持する。
|
||||
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! LLM Client Common Types
|
||||
//!
|
||||
//! Core conversation types for insomnia's LLM interaction model.
|
||||
//! Core conversation types for yoi's LLM interaction model.
|
||||
//! The core abstraction is `Item` which represents different types of conversation elements:
|
||||
//! - Message items (user/assistant messages with content parts)
|
||||
//! - ToolCall items (tool invocations)
|
||||
|
|
|
|||
|
|
@ -688,7 +688,7 @@ mod tests {
|
|||
use crate::{Permission, ReasoningEffort, ScopeRule};
|
||||
|
||||
fn abs(path: &str) -> PathBuf {
|
||||
PathBuf::from(format!("/tmp/insomnia-test{path}"))
|
||||
PathBuf::from(format!("/tmp/yoi-test{path}"))
|
||||
}
|
||||
|
||||
fn api_key_file_auth(path: PathBuf) -> AuthRef {
|
||||
|
|
@ -791,14 +791,14 @@ mod tests {
|
|||
fn resolve_paths_joins_relative_auth_file() {
|
||||
let mut cfg = minimal_valid();
|
||||
cfg.model.auth = Some(api_key_file_auth(PathBuf::from("keys/anthropic")));
|
||||
let resolved = cfg.resolve_paths(Path::new("/home/user/.config/insomnia"));
|
||||
let resolved = cfg.resolve_paths(Path::new("/home/user/.config/yoi"));
|
||||
let file = match resolved.model.auth {
|
||||
Some(AuthRef::ApiKey { file, .. }) => file,
|
||||
_ => panic!("expected ApiKey"),
|
||||
};
|
||||
assert_eq!(
|
||||
file.as_deref(),
|
||||
Some(Path::new("/home/user/.config/insomnia/keys/anthropic"))
|
||||
Some(Path::new("/home/user/.config/yoi/keys/anthropic"))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ pub const COMPACT_OVERVIEW_DEADLINE_TOKENS: u64 = 40_000;
|
|||
|
||||
/// Default instruction asset reference used when `worker.instruction`
|
||||
/// is omitted. See the `PromptLoader` prefix addressing scheme for the
|
||||
/// `$insomnia/` / `$user/` / `$workspace/` namespaces.
|
||||
pub const DEFAULT_INSTRUCTION: &str = "$insomnia/default";
|
||||
/// `$yoi/` / `$user/` / `$workspace/` namespaces.
|
||||
pub const DEFAULT_INSTRUCTION: &str = "$yoi/default";
|
||||
|
||||
/// Default language policy used by the main worker for normal prose
|
||||
/// responses. See [`crate::WorkerManifest::language`].
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ pub struct PodMeta {
|
|||
pub struct WorkerManifest {
|
||||
/// Reference to the instruction prompt asset used as the body of
|
||||
/// the worker's system prompt. Uses the `PromptLoader` prefix
|
||||
/// addressing scheme (`$insomnia/...`, `$user/...`,
|
||||
/// addressing scheme (`$yoi/...`, `$user/...`,
|
||||
/// `$workspace/...`) and is always populated after resolution —
|
||||
/// unset manifests fall through to [`defaults::DEFAULT_INSTRUCTION`].
|
||||
#[serde(default = "default_instruction")]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Insomnia のホームディレクトリ配下のパス解決を一元化するモジュール。
|
||||
//! Yoi のホームディレクトリ配下のパス解決を一元化するモジュール。
|
||||
//!
|
||||
//! 用途別に三つの base directory を持つ:
|
||||
//!
|
||||
|
|
@ -10,13 +10,13 @@
|
|||
//!
|
||||
//! ## 解決順 (優先順位高 → 低)
|
||||
//!
|
||||
//! | base | 1. `INSOMNIA_<KIND>_DIR` | 2. `INSOMNIA_HOME` | 3. `XDG_*` | 4. 既定 |
|
||||
//! | base | 1. `YOI_<KIND>_DIR` | 2. `YOI_HOME` | 3. `XDG_*` | 4. 既定 |
|
||||
//! |---|---|---|---|---|
|
||||
//! | config | `INSOMNIA_CONFIG_DIR` | `$INSOMNIA_HOME/config` | `$XDG_CONFIG_HOME/insomnia` | `$HOME/.config/insomnia` |
|
||||
//! | data | `INSOMNIA_DATA_DIR` | `$INSOMNIA_HOME` | — | `$HOME/.insomnia` |
|
||||
//! | runtime | `INSOMNIA_RUNTIME_DIR` | `$INSOMNIA_HOME/run` | `$XDG_RUNTIME_DIR/insomnia` | `$HOME/.insomnia/run` |
|
||||
//! | config | `YOI_CONFIG_DIR` | `$YOI_HOME/config` | `$XDG_CONFIG_HOME/yoi` | `$HOME/.config/yoi` |
|
||||
//! | data | `YOI_DATA_DIR` | `$YOI_HOME` | — | `$HOME/.yoi` |
|
||||
//! | runtime | `YOI_RUNTIME_DIR` | `$YOI_HOME/run` | `$XDG_RUNTIME_DIR/yoi` | `$HOME/.yoi/run` |
|
||||
//!
|
||||
//! `INSOMNIA_HOME=$X` のとき config は `$X/config`、data は `$X` 直下、
|
||||
//! `YOI_HOME=$X` のとき config は `$X/config`、data は `$X` 直下、
|
||||
//! runtime は `$X/run` に集約される。テストや sandbox 利用ではこれ一本
|
||||
//! で全部 tempdir に向けられる。
|
||||
//!
|
||||
|
|
@ -29,8 +29,8 @@ use std::path::PathBuf;
|
|||
/// `prompts/` などが置かれる。
|
||||
pub fn config_dir() -> Option<PathBuf> {
|
||||
resolve_config_dir_from_parts(
|
||||
env_path("INSOMNIA_CONFIG_DIR"),
|
||||
env_path("INSOMNIA_HOME"),
|
||||
env_path("YOI_CONFIG_DIR"),
|
||||
env_path("YOI_HOME"),
|
||||
env_path("XDG_CONFIG_HOME"),
|
||||
env_path("HOME"),
|
||||
)
|
||||
|
|
@ -40,8 +40,8 @@ pub fn config_dir() -> Option<PathBuf> {
|
|||
/// 置き場。
|
||||
pub fn data_dir() -> Option<PathBuf> {
|
||||
resolve_data_dir_from_parts(
|
||||
env_path("INSOMNIA_DATA_DIR"),
|
||||
env_path("INSOMNIA_HOME"),
|
||||
env_path("YOI_DATA_DIR"),
|
||||
env_path("YOI_HOME"),
|
||||
env_path("HOME"),
|
||||
)
|
||||
}
|
||||
|
|
@ -50,8 +50,8 @@ pub fn data_dir() -> Option<PathBuf> {
|
|||
/// `status.json` 等が置かれる。再起動で消えて構わない。
|
||||
pub fn runtime_dir() -> Option<PathBuf> {
|
||||
resolve_runtime_dir_from_parts(
|
||||
env_path("INSOMNIA_RUNTIME_DIR"),
|
||||
env_path("INSOMNIA_HOME"),
|
||||
env_path("YOI_RUNTIME_DIR"),
|
||||
env_path("YOI_HOME"),
|
||||
env_path("XDG_RUNTIME_DIR"),
|
||||
env_path("HOME"),
|
||||
)
|
||||
|
|
@ -111,53 +111,53 @@ pub fn pod_socket_path(pod_name: &str) -> Option<PathBuf> {
|
|||
// ---- internals --------------------------------------------------------------
|
||||
|
||||
fn resolve_config_dir_from_parts(
|
||||
insomnia_config_dir: Option<PathBuf>,
|
||||
insomnia_home: Option<PathBuf>,
|
||||
yoi_config_dir: Option<PathBuf>,
|
||||
yoi_home: Option<PathBuf>,
|
||||
xdg_config_home: Option<PathBuf>,
|
||||
home: Option<PathBuf>,
|
||||
) -> Option<PathBuf> {
|
||||
if let Some(p) = insomnia_config_dir {
|
||||
if let Some(p) = yoi_config_dir {
|
||||
return Some(p);
|
||||
}
|
||||
if let Some(p) = insomnia_home {
|
||||
if let Some(p) = yoi_home {
|
||||
return Some(p.join("config"));
|
||||
}
|
||||
if let Some(p) = xdg_config_home {
|
||||
return Some(p.join("insomnia"));
|
||||
return Some(p.join("yoi"));
|
||||
}
|
||||
Some(home?.join(".config").join("insomnia"))
|
||||
Some(home?.join(".config").join("yoi"))
|
||||
}
|
||||
|
||||
fn resolve_data_dir_from_parts(
|
||||
insomnia_data_dir: Option<PathBuf>,
|
||||
insomnia_home: Option<PathBuf>,
|
||||
yoi_data_dir: Option<PathBuf>,
|
||||
yoi_home: Option<PathBuf>,
|
||||
home: Option<PathBuf>,
|
||||
) -> Option<PathBuf> {
|
||||
if let Some(p) = insomnia_data_dir {
|
||||
if let Some(p) = yoi_data_dir {
|
||||
return Some(p);
|
||||
}
|
||||
if let Some(p) = insomnia_home {
|
||||
if let Some(p) = yoi_home {
|
||||
return Some(p);
|
||||
}
|
||||
Some(home?.join(".insomnia"))
|
||||
Some(home?.join(".yoi"))
|
||||
}
|
||||
|
||||
fn resolve_runtime_dir_from_parts(
|
||||
insomnia_runtime_dir: Option<PathBuf>,
|
||||
insomnia_home: Option<PathBuf>,
|
||||
yoi_runtime_dir: Option<PathBuf>,
|
||||
yoi_home: Option<PathBuf>,
|
||||
xdg_runtime_dir: Option<PathBuf>,
|
||||
home: Option<PathBuf>,
|
||||
) -> Option<PathBuf> {
|
||||
if let Some(p) = insomnia_runtime_dir {
|
||||
if let Some(p) = yoi_runtime_dir {
|
||||
return Some(p);
|
||||
}
|
||||
if let Some(p) = insomnia_home {
|
||||
if let Some(p) = yoi_home {
|
||||
return Some(p.join("run"));
|
||||
}
|
||||
if let Some(p) = xdg_runtime_dir {
|
||||
return Some(p.join("insomnia"));
|
||||
return Some(p.join("yoi"));
|
||||
}
|
||||
Some(home?.join(".insomnia").join("run"))
|
||||
Some(home?.join(".yoi").join("run"))
|
||||
}
|
||||
|
||||
fn user_profiles_path_from_config_dir(config_dir: Option<PathBuf>) -> Option<PathBuf> {
|
||||
|
|
@ -221,7 +221,7 @@ mod tests {
|
|||
fn config_dir_falls_back_to_home_dot_config() {
|
||||
assert_eq!(
|
||||
resolve_config_dir_from_parts(None, None, None, Some(PathBuf::from("/h"))).unwrap(),
|
||||
PathBuf::from("/h/.config/insomnia")
|
||||
PathBuf::from("/h/.config/yoi")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -235,12 +235,12 @@ mod tests {
|
|||
Some(PathBuf::from("/h")),
|
||||
)
|
||||
.unwrap(),
|
||||
PathBuf::from("/x/insomnia")
|
||||
PathBuf::from("/x/yoi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_dir_insomnia_home_outranks_xdg() {
|
||||
fn config_dir_yoi_home_outranks_xdg() {
|
||||
assert_eq!(
|
||||
resolve_config_dir_from_parts(
|
||||
None,
|
||||
|
|
@ -254,7 +254,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn config_dir_explicit_wins_over_insomnia_home() {
|
||||
fn config_dir_explicit_wins_over_yoi_home() {
|
||||
assert_eq!(
|
||||
resolve_config_dir_from_parts(
|
||||
Some(PathBuf::from("/explicit-cfg")),
|
||||
|
|
@ -268,15 +268,15 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn data_dir_default_is_dot_insomnia() {
|
||||
fn data_dir_default_is_dot_yoi() {
|
||||
assert_eq!(
|
||||
resolve_data_dir_from_parts(None, None, Some(PathBuf::from("/h"))).unwrap(),
|
||||
PathBuf::from("/h/.insomnia")
|
||||
PathBuf::from("/h/.yoi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_dir_insomnia_home_is_data_dir_itself() {
|
||||
fn data_dir_yoi_home_is_data_dir_itself() {
|
||||
assert_eq!(
|
||||
resolve_data_dir_from_parts(
|
||||
None,
|
||||
|
|
@ -289,7 +289,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn data_dir_explicit_wins_over_insomnia_home() {
|
||||
fn data_dir_explicit_wins_over_yoi_home() {
|
||||
assert_eq!(
|
||||
resolve_data_dir_from_parts(
|
||||
Some(PathBuf::from("/explicit-data")),
|
||||
|
|
@ -311,20 +311,20 @@ mod tests {
|
|||
Some(PathBuf::from("/h")),
|
||||
)
|
||||
.unwrap(),
|
||||
PathBuf::from("/xdg-runtime/insomnia")
|
||||
PathBuf::from("/xdg-runtime/yoi")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_dir_falls_back_to_dot_insomnia_run() {
|
||||
fn runtime_dir_falls_back_to_dot_yoi_run() {
|
||||
assert_eq!(
|
||||
resolve_runtime_dir_from_parts(None, None, None, Some(PathBuf::from("/h"))).unwrap(),
|
||||
PathBuf::from("/h/.insomnia/run")
|
||||
PathBuf::from("/h/.yoi/run")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_dir_insomnia_home_is_run_subdir() {
|
||||
fn runtime_dir_yoi_home_is_run_subdir() {
|
||||
assert_eq!(
|
||||
resolve_runtime_dir_from_parts(
|
||||
None,
|
||||
|
|
@ -338,7 +338,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_dir_explicit_wins_over_insomnia_home() {
|
||||
fn runtime_dir_explicit_wins_over_yoi_home() {
|
||||
assert_eq!(
|
||||
resolve_runtime_dir_from_parts(
|
||||
Some(PathBuf::from("/explicit-run")),
|
||||
|
|
@ -358,7 +358,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
resolve_config_dir_from_parts(None, None, xdg_config_home, Some(PathBuf::from("/h")))
|
||||
.unwrap(),
|
||||
PathBuf::from("/h/.config/insomnia")
|
||||
PathBuf::from("/h/.config/yoi")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ use crate::{
|
|||
ScopeConfig, ScopeRule, SkillsConfig, WebConfig, WorkerManifestConfig, paths,
|
||||
};
|
||||
|
||||
const PROFILE_FORMAT_V1: &str = "insomnia.lua-profile.v1";
|
||||
const PROFILE_FORMAT_V1: &str = "yoi.lua-profile.v1";
|
||||
const BUILTIN_DEFAULT_PROFILE_NAME: &str = "default";
|
||||
const BUILTIN_DEFAULT_PROFILE: &str = include_str!("../../../resources/profiles/default.lua");
|
||||
const BUILTIN_MODEL_CATALOG: &str = include_str!("../../../resources/models/builtin.toml");
|
||||
const DEFAULT_POD_NAME: &str = "insomnia";
|
||||
const DEFAULT_POD_NAME: &str = "yoi";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
|
@ -694,7 +694,7 @@ fn find_project_profiles_from(start: &Path) -> Option<PathBuf> {
|
|||
.unwrap_or_else(|| start.to_path_buf());
|
||||
let mut cur: Option<&Path> = Some(start.as_path());
|
||||
while let Some(dir) = cur {
|
||||
let candidate = dir.join(".insomnia").join("profiles.toml");
|
||||
let candidate = dir.join(".yoi").join("profiles.toml");
|
||||
if candidate.is_file() {
|
||||
return Some(candidate);
|
||||
}
|
||||
|
|
@ -709,7 +709,7 @@ fn add_builtin_profiles(registry: &mut ProfileRegistry) {
|
|||
BUILTIN_DEFAULT_PROFILE_NAME,
|
||||
"builtin:default",
|
||||
BUILTIN_DEFAULT_PROFILE,
|
||||
Some("Bundled default Insomnia coding profile".into()),
|
||||
Some("Bundled default Yoi coding profile".into()),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -827,7 +827,7 @@ fn require_module(
|
|||
if let Some(value) = host_module(lua, name)? {
|
||||
return Ok(value);
|
||||
}
|
||||
if name.starts_with("insomnia.") || name == "insomnia" {
|
||||
if name.starts_with("yoi.") || name == "yoi" {
|
||||
return Err(mlua::Error::RuntimeError(format!(
|
||||
"unknown host module `{name}`"
|
||||
)));
|
||||
|
|
@ -876,7 +876,7 @@ fn require_module(
|
|||
|
||||
fn host_module(lua: &Lua, name: &str) -> mlua::Result<Option<LuaValue>> {
|
||||
match name {
|
||||
"insomnia" => {
|
||||
"yoi" => {
|
||||
let t = lua.create_table()?;
|
||||
t.set("profile", profile_function(lua)?)?;
|
||||
t.set("models", models_module(lua)?)?;
|
||||
|
|
@ -884,10 +884,10 @@ fn host_module(lua: &Lua, name: &str) -> mlua::Result<Option<LuaValue>> {
|
|||
t.set("scope", scope_module(lua)?)?;
|
||||
Ok(Some(LuaValue::Table(t)))
|
||||
}
|
||||
"insomnia.profile" => Ok(Some(LuaValue::Function(profile_function(lua)?))),
|
||||
"insomnia.models" => Ok(Some(LuaValue::Table(models_module(lua)?))),
|
||||
"insomnia.compact" => Ok(Some(LuaValue::Table(compact_module(lua)?))),
|
||||
"insomnia.scope" => Ok(Some(LuaValue::Table(scope_module(lua)?))),
|
||||
"yoi.profile" => Ok(Some(LuaValue::Function(profile_function(lua)?))),
|
||||
"yoi.models" => Ok(Some(LuaValue::Table(models_module(lua)?))),
|
||||
"yoi.compact" => Ok(Some(LuaValue::Table(compact_module(lua)?))),
|
||||
"yoi.scope" => Ok(Some(LuaValue::Table(scope_module(lua)?))),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
|
@ -997,7 +997,7 @@ fn reject_manifest_shaped_profile(value: &serde_json::Value) -> Result<(), Profi
|
|||
for key in ["allow", "deny"] {
|
||||
if scope.contains_key(key) {
|
||||
return Err(ProfileError::InvalidProfile(format!(
|
||||
"field `scope.{key}` grants concrete authority and is not allowed in reusable Profiles; use require(\"insomnia.scope\") intent helpers"
|
||||
"field `scope.{key}` grants concrete authority and is not allowed in reusable Profiles; use require(\"yoi.scope\") intent helpers"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
@ -1312,8 +1312,8 @@ mod tests {
|
|||
tmp.path(),
|
||||
"coder.lua",
|
||||
r#"
|
||||
local profile = require("insomnia.profile")
|
||||
local scope = require("insomnia.scope")
|
||||
local profile = require("yoi.profile")
|
||||
local scope = require("yoi.scope")
|
||||
return profile {
|
||||
slug = "coder",
|
||||
model = { scheme = "anthropic", model_id = "claude-sonnet-4-20250514" },
|
||||
|
|
@ -1352,19 +1352,19 @@ return profile {
|
|||
let tmp = TempDir::new().unwrap();
|
||||
std::fs::write(
|
||||
tmp.path().join("shared.lua"),
|
||||
r#"return { model = require("insomnia.models").catalog("codex-oauth/gpt-5.5") }"#,
|
||||
r#"return { model = require("yoi.models").catalog("codex-oauth/gpt-5.5") }"#,
|
||||
)
|
||||
.unwrap();
|
||||
let profile = write_profile(
|
||||
tmp.path(),
|
||||
"main.lua",
|
||||
r#"
|
||||
local insomnia = require("insomnia")
|
||||
local yoi = require("yoi")
|
||||
local shared = require("shared")
|
||||
return insomnia.profile {
|
||||
return yoi.profile {
|
||||
slug = "main",
|
||||
model = shared.model,
|
||||
scope = insomnia.scope.workspace_write(),
|
||||
scope = yoi.scope.workspace_write(),
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
|
@ -1445,9 +1445,9 @@ return insomnia.profile {
|
|||
tmp.path(),
|
||||
"ratio.lua",
|
||||
r#"
|
||||
local profile = require("insomnia.profile")
|
||||
local models = require("insomnia.models")
|
||||
local compact = require("insomnia.compact")
|
||||
local profile = require("yoi.profile")
|
||||
local models = require("yoi.models")
|
||||
local compact = require("yoi.compact")
|
||||
return profile {
|
||||
model = models.catalog("codex-oauth/gpt-5.5"),
|
||||
compaction = compact.ratio { threshold = 0.5, request = 0.75, worker = 0.25 },
|
||||
|
|
@ -1473,7 +1473,7 @@ return profile {
|
|||
.with_workspace_base(tmp.path())
|
||||
.resolve(&ProfileSelector::Default, ProfileResolveOptions::default())
|
||||
.unwrap();
|
||||
assert_eq!(resolved.manifest.pod.name, "insomnia");
|
||||
assert_eq!(resolved.manifest.pod.name, "yoi");
|
||||
assert_eq!(
|
||||
resolved.manifest.model.ref_.as_deref(),
|
||||
Some("codex-oauth/gpt-5.5")
|
||||
|
|
@ -1516,7 +1516,7 @@ return profile {
|
|||
fn discovery_reads_user_and_project_registry_and_project_default_wins() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let user_config = tmp.path().join("profiles.toml");
|
||||
let project_dir = tmp.path().join("project/.insomnia");
|
||||
let project_dir = tmp.path().join("project/.yoi");
|
||||
std::fs::create_dir_all(&project_dir).unwrap();
|
||||
let project_config = project_dir.join("profiles.toml");
|
||||
std::fs::write(
|
||||
|
|
@ -1542,7 +1542,7 @@ return profile {
|
|||
#[test]
|
||||
fn default_marks_direct_profile_entry() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let project_dir = tmp.path().join("project/.insomnia");
|
||||
let project_dir = tmp.path().join("project/.yoi");
|
||||
std::fs::create_dir_all(&project_dir).unwrap();
|
||||
let project_config = project_dir.join("profiles.toml");
|
||||
std::fs::write(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! Append-only JSONL audit log for memory workers and tools.
|
||||
//!
|
||||
//! The log is evidence-only observability data under
|
||||
//! `.insomnia/memory/_logs/current.log`. It is intentionally separate from
|
||||
//! `.yoi/memory/_logs/current.log`. It is intentionally separate from
|
||||
//! `_staging` and `_usage`, and consolidation never consumes it. Operators can
|
||||
//! follow the latest stream with:
|
||||
//!
|
||||
//! ```text
|
||||
//! tail -f .insomnia/memory/_logs/current.log
|
||||
//! tail -f .yoi/memory/_logs/current.log
|
||||
//! ```
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
|
@ -260,7 +260,7 @@ pub struct RecordSnapshot {
|
|||
pub hash: String,
|
||||
}
|
||||
|
||||
/// Append one audit event to `.insomnia/memory/_logs/current.log`.
|
||||
/// Append one audit event to `.yoi/memory/_logs/current.log`.
|
||||
pub fn append_audit_event(layout: &WorkspaceLayout, event: &AuditEvent) -> io::Result<()> {
|
||||
let path = layout.audit_current_log_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
|
|
@ -425,7 +425,7 @@ mod tests {
|
|||
#[test]
|
||||
fn counts_created_edited_deleted_records() {
|
||||
let (dir, layout) = setup();
|
||||
let decision_dir = dir.path().join(".insomnia/memory/decisions");
|
||||
let decision_dir = dir.path().join(".yoi/memory/decisions");
|
||||
fs::create_dir_all(&decision_dir).unwrap();
|
||||
fs::write(decision_dir.join("a.md"), "old").unwrap();
|
||||
fs::write(decision_dir.join("gone.md"), "old").unwrap();
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ pub fn render_staging_records(entries: &[StagingEntry]) -> String {
|
|||
out
|
||||
}
|
||||
|
||||
/// `<workspace>/.insomnia/memory/{summary.md,decisions/*,requests/*}` を
|
||||
/// `<workspace>/.yoi/memory/{summary.md,decisions/*,requests/*}` を
|
||||
/// 「`### <kind>:<slug>` ヘッダ + raw markdown ブロック」で全文渡す。
|
||||
pub fn render_existing_memory_records(layout: &WorkspaceLayout) -> String {
|
||||
let mut out = String::new();
|
||||
|
|
@ -230,11 +230,11 @@ mod tests {
|
|||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
|
||||
write(
|
||||
&dir.path().join(".insomnia/memory/summary.md"),
|
||||
&dir.path().join(".yoi/memory/summary.md"),
|
||||
&format!("---\nupdated_at: {n}\n---\nstate of the world\n", n = now()),
|
||||
);
|
||||
write(
|
||||
&dir.path().join(".insomnia/memory/decisions/dec.md"),
|
||||
&dir.path().join(".yoi/memory/decisions/dec.md"),
|
||||
&format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody\n",
|
||||
n = now()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
//! が追加した分は残す
|
||||
//!
|
||||
//! 占有判定は Linux/macOS の `kill(pid, 0)` 経由で行う(`ESRCH` で死亡判定)。
|
||||
//! Windows は対象外: INSOMNIA は POSIX 環境を前提にしている。
|
||||
//! Windows は対象外: Yoi は POSIX 環境を前提にしている。
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
|
|||
|
|
@ -133,8 +133,8 @@ pub fn collect_tidy_hints(layout: &WorkspaceLayout) -> TidyHints {
|
|||
hints
|
||||
}
|
||||
|
||||
/// `<root>/.insomnia/memory/<kind>/*.md` (Knowledge は
|
||||
/// `<root>/.insomnia/knowledge/*.md`) を slug ごとに `(slug, full content)`
|
||||
/// `<root>/.yoi/memory/<kind>/*.md` (Knowledge は
|
||||
/// `<root>/.yoi/knowledge/*.md`) を slug ごとに `(slug, full content)`
|
||||
/// 化して返す。
|
||||
fn read_kind_records(layout: &WorkspaceLayout, kind: RecordKind) -> BTreeMap<String, String> {
|
||||
let dir = match kind {
|
||||
|
|
@ -280,14 +280,14 @@ mod tests {
|
|||
fn collects_replaced_chain() {
|
||||
let (dir, layout) = workspace();
|
||||
write(
|
||||
&dir.path().join(".insomnia/memory/decisions/replaced.md"),
|
||||
&dir.path().join(".yoi/memory/decisions/replaced.md"),
|
||||
&format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: replaced\nreplaced_by: winner\n---\n",
|
||||
n = now()
|
||||
),
|
||||
);
|
||||
write(
|
||||
&dir.path().join(".insomnia/memory/decisions/winner.md"),
|
||||
&dir.path().join(".yoi/memory/decisions/winner.md"),
|
||||
&format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
|
||||
n = now()
|
||||
|
|
@ -308,7 +308,7 @@ mod tests {
|
|||
.map(|i| format!(" - segment_id: s{i}\n range: [{i}, {i}]\n"))
|
||||
.collect();
|
||||
write(
|
||||
&dir.path().join(".insomnia/memory/decisions/big.md"),
|
||||
&dir.path().join(".yoi/memory/decisions/big.md"),
|
||||
&format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nstatus: open\nsources:\n{m}---\n",
|
||||
n = now(),
|
||||
|
|
@ -327,8 +327,7 @@ mod tests {
|
|||
let (dir, layout) = workspace();
|
||||
for slug in ["db-pool", "db-pol", "db-pools", "alpha"] {
|
||||
write(
|
||||
&dir.path()
|
||||
.join(format!(".insomnia/memory/decisions/{slug}.md")),
|
||||
&dir.path().join(format!(".yoi/memory/decisions/{slug}.md")),
|
||||
&format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
|
||||
n = now()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! extract: 活動抽出。
|
||||
//!
|
||||
//! 通常 Pod の post-run hook で発火する disposable Worker と、その
|
||||
//! 出力を `<workspace>/.insomnia/memory/_staging/<id>.json` に書き出す
|
||||
//! 出力を `<workspace>/.yoi/memory/_staging/<id>.json` に書き出す
|
||||
//! ヘルパーを提供する。Pod 側はこのモジュールから:
|
||||
//!
|
||||
//! - [`build_extract_input`] を sub-Worker の最初の user 入力に
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! `<workspace>/.insomnia/memory/_staging/<id>.json` への書き出しヘルパー。
|
||||
//! `<workspace>/.yoi/memory/_staging/<id>.json` への書き出しヘルパー。
|
||||
//!
|
||||
//! 1 件 1 ファイル、UUIDv7 命名(短命なので衝突回避と順序を兼ねる)。
|
||||
//! `source` を機械付与した [`StagingRecord`] 形式で保存する。
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ mod tests {
|
|||
#[test]
|
||||
fn decision_with_unknown_replaced_by_errors() {
|
||||
let (dir, linter) = workspace();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
let content = format!(
|
||||
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: ghost\n---\nbody\n",
|
||||
now = iso_now()
|
||||
|
|
@ -308,7 +308,7 @@ mod tests {
|
|||
#[test]
|
||||
fn decision_replaced_by_self_errors() {
|
||||
let (dir, linter) = workspace();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
let content = format!(
|
||||
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: foo\n---\nbody\n",
|
||||
now = iso_now()
|
||||
|
|
@ -326,7 +326,7 @@ mod tests {
|
|||
fn decision_replaced_by_existing_ok() {
|
||||
let (dir, linter) = workspace();
|
||||
// Pre-create the target.
|
||||
let target = dir.path().join(".insomnia/memory/decisions/bar.md");
|
||||
let target = dir.path().join(".yoi/memory/decisions/bar.md");
|
||||
write(
|
||||
&target,
|
||||
&format!(
|
||||
|
|
@ -334,7 +334,7 @@ mod tests {
|
|||
now = iso_now()
|
||||
),
|
||||
);
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
let content = format!(
|
||||
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: replaced\nreplaced_by: bar\n---\nbody\n",
|
||||
now = iso_now()
|
||||
|
|
@ -346,7 +346,7 @@ mod tests {
|
|||
#[test]
|
||||
fn missing_required_field_errors() {
|
||||
let (dir, linter) = workspace();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
// Missing `status`.
|
||||
let content = format!(
|
||||
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\n---\nbody\n",
|
||||
|
|
@ -363,7 +363,7 @@ mod tests {
|
|||
#[test]
|
||||
fn knowledge_long_description_with_model_invokation_errors() {
|
||||
let (dir, linter) = workspace();
|
||||
let path = dir.path().join(".insomnia/knowledge/foo.md");
|
||||
let path = dir.path().join(".yoi/knowledge/foo.md");
|
||||
let big_desc = "x".repeat(2000);
|
||||
let content = format!(
|
||||
"---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: true\nuser_invocable: true\nlast_sources: []\n---\nbody\n",
|
||||
|
|
@ -381,7 +381,7 @@ mod tests {
|
|||
#[test]
|
||||
fn knowledge_long_description_without_model_invokation_ok() {
|
||||
let (dir, linter) = workspace();
|
||||
let path = dir.path().join(".insomnia/knowledge/foo.md");
|
||||
let path = dir.path().join(".yoi/knowledge/foo.md");
|
||||
let big_desc = "x".repeat(2000);
|
||||
let content = format!(
|
||||
"---\ncreated_at: {now}\nupdated_at: {now}\nkind: rule\ndescription: {big_desc}\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\nbody\n",
|
||||
|
|
@ -394,7 +394,7 @@ mod tests {
|
|||
#[test]
|
||||
fn summary_path_accepted() {
|
||||
let (dir, linter) = workspace();
|
||||
let path = dir.path().join(".insomnia/memory/summary.md");
|
||||
let path = dir.path().join(".yoi/memory/summary.md");
|
||||
let content = format!(
|
||||
"---\nupdated_at: {now}\n---\nsummary body\n",
|
||||
now = iso_now()
|
||||
|
|
@ -406,7 +406,7 @@ mod tests {
|
|||
#[test]
|
||||
fn create_when_existing_errors() {
|
||||
let (dir, linter) = workspace();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
write(
|
||||
&path,
|
||||
&format!(
|
||||
|
|
@ -434,15 +434,14 @@ mod tests {
|
|||
// `db-pol` (1 deletion), `db-pools` (1 insertion).
|
||||
for slug in ["db-pol", "db-pools"] {
|
||||
write(
|
||||
&dir.path()
|
||||
.join(format!(".insomnia/memory/decisions/{slug}.md")),
|
||||
&dir.path().join(format!(".yoi/memory/decisions/{slug}.md")),
|
||||
&format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
|
||||
n = iso_now()
|
||||
),
|
||||
);
|
||||
}
|
||||
let path = dir.path().join(".insomnia/memory/decisions/db-pool.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/db-pool.md");
|
||||
let content = format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody\n",
|
||||
n = iso_now()
|
||||
|
|
@ -464,15 +463,14 @@ mod tests {
|
|||
let (dir, linter) = workspace();
|
||||
for slug in ["alpha", "bravo"] {
|
||||
write(
|
||||
&dir.path()
|
||||
.join(format!(".insomnia/memory/decisions/{slug}.md")),
|
||||
&dir.path().join(format!(".yoi/memory/decisions/{slug}.md")),
|
||||
&format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
|
||||
n = iso_now()
|
||||
),
|
||||
);
|
||||
}
|
||||
let path = dir.path().join(".insomnia/memory/decisions/charlie.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/charlie.md");
|
||||
let content = format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n",
|
||||
n = iso_now()
|
||||
|
|
@ -491,7 +489,7 @@ mod tests {
|
|||
#[test]
|
||||
fn body_size_limit_errors() {
|
||||
let (dir, linter) = workspace();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
let big_body = "x".repeat(8001);
|
||||
let content = format!(
|
||||
"---\ncreated_at: {now}\nupdated_at: {now}\nsources: []\nstatus: open\n---\n{body}",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
//! - [`collect_resident_knowledge`] — resident-injection candidates
|
||||
//! (`model_invokation: true`) returned as `(slug, description)` pairs.
|
||||
//! - [`collect_resident_summary`] — the body of
|
||||
//! `<workspace>/.insomnia/memory/summary.md` when it parses as a summary
|
||||
//! `<workspace>/.yoi/memory/summary.md` when it parses as a summary
|
||||
//! record and has non-empty body.
|
||||
//! - [`list_knowledge_slugs`] — every slug whose file parses, regardless
|
||||
//! of `model_invokation`. Used by the Pod IPC layer to answer TUI `#`
|
||||
|
|
@ -25,7 +25,7 @@ pub struct ResidentKnowledgeEntry {
|
|||
pub description: String,
|
||||
}
|
||||
|
||||
/// Walk `<workspace>/.insomnia/knowledge/*.md` and return entries whose
|
||||
/// Walk `<workspace>/.yoi/knowledge/*.md` and return entries whose
|
||||
/// frontmatter has `model_invokation: true`, sorted by slug. A missing
|
||||
/// directory yields an empty vec.
|
||||
pub fn collect_resident_knowledge(layout: &WorkspaceLayout) -> Vec<ResidentKnowledgeEntry> {
|
||||
|
|
@ -42,7 +42,7 @@ pub fn collect_resident_knowledge(layout: &WorkspaceLayout) -> Vec<ResidentKnowl
|
|||
out
|
||||
}
|
||||
|
||||
/// Read `<workspace>/.insomnia/memory/summary.md` for resident prompt
|
||||
/// Read `<workspace>/.yoi/memory/summary.md` for resident prompt
|
||||
/// injection. Returns only the markdown body (frontmatter stripped), and
|
||||
/// degrades to `None` for missing, unreadable, malformed, or empty records.
|
||||
pub fn collect_resident_summary(layout: &WorkspaceLayout) -> Option<String> {
|
||||
|
|
@ -115,7 +115,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn write_summary(dir: &Path, body: &str) {
|
||||
let path = dir.join(".insomnia/memory/summary.md");
|
||||
let path = dir.join(".yoi/memory/summary.md");
|
||||
let content = format!("---\nupdated_at: {n}\n---\n{body}", n = now());
|
||||
std::fs::write(path, content).unwrap();
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ mod tests {
|
|||
model_invokation: bool,
|
||||
body: &str,
|
||||
) {
|
||||
let path = dir.join(".insomnia/knowledge").join(format!("{slug}.md"));
|
||||
let path = dir.join(".yoi/knowledge").join(format!("{slug}.md"));
|
||||
let content = format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nkind: policy\ndescription: \"{description}\"\nmodel_invokation: {flag}\nuser_invocable: true\nlast_sources: []\n---\n{body}",
|
||||
n = now(),
|
||||
|
|
@ -138,8 +138,8 @@ mod tests {
|
|||
|
||||
fn setup() -> (TempDir, WorkspaceLayout) {
|
||||
let dir = TempDir::new().unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".insomnia/knowledge")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".insomnia/memory")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".yoi/knowledge")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".yoi/memory")).unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
(dir, layout)
|
||||
}
|
||||
|
|
@ -166,7 +166,7 @@ mod tests {
|
|||
fn malformed_summary_returns_none() {
|
||||
let (dir, layout) = setup();
|
||||
std::fs::write(
|
||||
dir.path().join(".insomnia/memory/summary.md"),
|
||||
dir.path().join(".yoi/memory/summary.md"),
|
||||
"---\nthis is not yaml: : :\n---\nbody\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -222,7 +222,7 @@ mod tests {
|
|||
write_knowledge(dir.path(), "good", "ok", true, "");
|
||||
// Garbage in frontmatter — must be skipped, not panic.
|
||||
std::fs::write(
|
||||
dir.path().join(".insomnia/knowledge/bad.md"),
|
||||
dir.path().join(".yoi/knowledge/bad.md"),
|
||||
"---\nthis is not yaml: : :\n---\nbody\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -236,11 +236,7 @@ mod tests {
|
|||
fn non_md_files_ignored() {
|
||||
let (dir, layout) = setup();
|
||||
write_knowledge(dir.path(), "good", "ok", true, "");
|
||||
std::fs::write(
|
||||
dir.path().join(".insomnia/knowledge/note.txt"),
|
||||
"not markdown\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(dir.path().join(".yoi/knowledge/note.txt"), "not markdown\n").unwrap();
|
||||
|
||||
let got = collect_resident_knowledge(&layout);
|
||||
assert_eq!(got.len(), 1);
|
||||
|
|
@ -269,15 +265,11 @@ mod tests {
|
|||
let (dir, layout) = setup();
|
||||
write_knowledge(dir.path(), "good", "ok", true, "");
|
||||
std::fs::write(
|
||||
dir.path().join(".insomnia/knowledge/bad.md"),
|
||||
dir.path().join(".yoi/knowledge/bad.md"),
|
||||
"---\nthis is not yaml: : :\n---\nbody\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
dir.path().join(".insomnia/knowledge/note.txt"),
|
||||
"not markdown\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(dir.path().join(".yoi/knowledge/note.txt"), "not markdown\n").unwrap();
|
||||
|
||||
let got = list_knowledge_slugs(&layout);
|
||||
assert_eq!(got, vec!["good"]);
|
||||
|
|
|
|||
|
|
@ -41,9 +41,9 @@ mod tests {
|
|||
let layout = WorkspaceLayout::new(PathBuf::from("/ws"));
|
||||
let rules = deny_write_rules(&layout);
|
||||
assert_eq!(rules.len(), 2);
|
||||
assert_eq!(rules[0].target, PathBuf::from("/ws/.insomnia/memory"));
|
||||
assert_eq!(rules[0].target, PathBuf::from("/ws/.yoi/memory"));
|
||||
assert_eq!(rules[0].permission, Permission::Write);
|
||||
assert!(rules[0].recursive);
|
||||
assert_eq!(rules[1].target, PathBuf::from("/ws/.insomnia/knowledge"));
|
||||
assert_eq!(rules[1].target, PathBuf::from("/ws/.yoi/knowledge"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ mod tests {
|
|||
fn setup() -> (TempDir, WorkspaceLayout, PathBuf) {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
let initial = format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nbody body\n",
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
//! omitted, returns one entry per file (no excerpt) so the agent can
|
||||
//! enumerate what records exist without knowing what's inside them.
|
||||
//!
|
||||
//! - `MemoryQuery` walks `.insomnia/memory/{summary.md,decisions/,
|
||||
//! requests/}`. `.insomnia/workflow/`, `.insomnia/memory/_staging/`,
|
||||
//! `.insomnia/memory/_usage/`, and `.insomnia/memory/_logs/` are excluded
|
||||
//! - `MemoryQuery` walks `.yoi/memory/{summary.md,decisions/,
|
||||
//! requests/}`. `.yoi/workflow/`, `.yoi/memory/_staging/`,
|
||||
//! `.yoi/memory/_usage/`, and `.yoi/memory/_logs/` are excluded
|
||||
//! by construction.
|
||||
//! - `KnowledgeQuery` walks `.insomnia/knowledge/*.md` and supports a
|
||||
//! - `KnowledgeQuery` walks `.yoi/knowledge/*.md` and supports a
|
||||
//! `kind` filter against the Knowledge frontmatter's `kind` field.
|
||||
//!
|
||||
//! No derived index — the file tree is the source of truth and is
|
||||
|
|
@ -513,18 +513,16 @@ mod tests {
|
|||
fn setup() -> (TempDir, WorkspaceLayout) {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
std::fs::create_dir_all(dir.path().join(".insomnia/memory/decisions")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".insomnia/memory/requests")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".insomnia/memory/_staging")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".insomnia/workflow")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".insomnia/knowledge")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".yoi/memory/decisions")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".yoi/memory/requests")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".yoi/memory/_staging")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".yoi/workflow")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".yoi/knowledge")).unwrap();
|
||||
(dir, layout)
|
||||
}
|
||||
|
||||
fn write_decision(dir: &Path, slug: &str, body: &str) {
|
||||
let path = dir
|
||||
.join(".insomnia/memory/decisions")
|
||||
.join(format!("{slug}.md"));
|
||||
let path = dir.join(".yoi/memory/decisions").join(format!("{slug}.md"));
|
||||
let content = format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\n{body}",
|
||||
n = now()
|
||||
|
|
@ -533,7 +531,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn write_knowledge(dir: &Path, slug: &str, kind: &str, description: &str, body: &str) {
|
||||
let path = dir.join(".insomnia/knowledge").join(format!("{slug}.md"));
|
||||
let path = dir.join(".yoi/knowledge").join(format!("{slug}.md"));
|
||||
let content = format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nkind: {kind}\ndescription: \"{description}\"\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\n{body}",
|
||||
n = now()
|
||||
|
|
@ -590,7 +588,7 @@ mod tests {
|
|||
let (dir, layout) = setup();
|
||||
write_decision(dir.path(), "alpha", "body\n");
|
||||
write_decision(dir.path(), "beta", "body\n");
|
||||
let summary_path = dir.path().join(".insomnia/memory/summary.md");
|
||||
let summary_path = dir.path().join(".yoi/memory/summary.md");
|
||||
std::fs::write(
|
||||
&summary_path,
|
||||
format!("---\nupdated_at: {n}\n---\nhello\n", n = now()),
|
||||
|
|
@ -610,7 +608,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn memory_query_finds_summary() {
|
||||
let (dir, layout) = setup();
|
||||
let summary_path = dir.path().join(".insomnia/memory/summary.md");
|
||||
let summary_path = dir.path().join(".yoi/memory/summary.md");
|
||||
std::fs::write(
|
||||
&summary_path,
|
||||
format!("---\nupdated_at: {n}\n---\nthe needle is here\n", n = now()),
|
||||
|
|
@ -628,9 +626,9 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn memory_query_excludes_workflow_and_staging() {
|
||||
let (dir, layout) = setup();
|
||||
let wf = dir.path().join(".insomnia/workflow/wf.md");
|
||||
let wf = dir.path().join(".yoi/workflow/wf.md");
|
||||
std::fs::write(&wf, "needle in workflow\n").unwrap();
|
||||
let stg = dir.path().join(".insomnia/memory/_staging/abc.json");
|
||||
let stg = dir.path().join(".yoi/memory/_staging/abc.json");
|
||||
std::fs::write(&stg, "needle in staging\n").unwrap();
|
||||
|
||||
let (_, tool) = memory_query_tool(layout, QueryConfig::default())();
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn read_decision_by_slug() {
|
||||
let (dir, layout) = setup();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
std::fs::write(&path, "alpha\nbeta\n").unwrap();
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn read_summary_without_slug() {
|
||||
let (dir, layout) = setup();
|
||||
let path = dir.path().join(".insomnia/memory/summary.md");
|
||||
let path = dir.path().join(".yoi/memory/summary.md");
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
std::fs::write(&path, "summary body\n").unwrap();
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn knowledge_path_resolution() {
|
||||
let (dir, layout) = setup();
|
||||
let path = dir.path().join(".insomnia/knowledge/policy.md");
|
||||
let path = dir.path().join(".yoi/knowledge/policy.md");
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
std::fs::write(&path, "k\n").unwrap();
|
||||
|
||||
|
|
@ -287,7 +287,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn read_logs_explicit_use_when_usage_session_is_set() {
|
||||
let (dir, layout) = setup();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
std::fs::write(&path, "alpha\n").unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn write_creates_summary() {
|
||||
let (dir, layout) = setup();
|
||||
let path = dir.path().join(".insomnia/memory/summary.md");
|
||||
let path = dir.path().join(".yoi/memory/summary.md");
|
||||
let content = format!("---\nupdated_at: {n}\n---\nbody\n", n = now());
|
||||
|
||||
let (meta, tool) = write_tool(layout)();
|
||||
|
|
@ -257,7 +257,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn write_update_existing() {
|
||||
let (dir, layout) = setup();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
let initial = format!(
|
||||
"---\ncreated_at: {n}\nupdated_at: {n}\nsources: []\nstatus: open\n---\nold\n",
|
||||
|
|
@ -290,7 +290,7 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn write_does_not_persist_on_lint_failure() {
|
||||
let (dir, layout) = setup();
|
||||
let path = dir.path().join(".insomnia/memory/decisions/foo.md");
|
||||
let path = dir.path().join(".yoi/memory/decisions/foo.md");
|
||||
let bad = "no frontmatter at all";
|
||||
let (_, tool) = write_tool(layout)();
|
||||
let inp = serde_json::json!({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Workspace-local usage event log for memory / knowledge / workflow records.
|
||||
//!
|
||||
//! The log is append-only JSONL under the workspace's `.insomnia/` tree. It is
|
||||
//! The log is append-only JSONL under the workspace's `.yoi/` tree. It is
|
||||
//! intentionally evidence-only: aggregation reports explicit context reads and
|
||||
//! resident exposure cost telemetry, but it does not classify records as
|
||||
//! Knowledge candidates or tidy-protected records.
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
//! Workspace-level path layout for the memory subsystem.
|
||||
//!
|
||||
//! `WorkspaceLayout` carries the workspace root (typically the Pod's
|
||||
//! pwd). All insomnia-managed content lives under the conventional
|
||||
//! `<root>/.insomnia/` subdirectory — the same place that holds
|
||||
//! pwd). All yoi-managed content lives under the conventional
|
||||
//! `<root>/.yoi/` subdirectory — the same place that holds
|
||||
//! `profiles.toml`, `prompts/`, workflow, knowledge, and generated
|
||||
//! memory. The trees inside it:
|
||||
//!
|
||||
//! - `<root>/.insomnia/workflow/<slug>.md`
|
||||
//! - `<root>/.insomnia/knowledge/<slug>.md`
|
||||
//! - `<root>/.insomnia/memory/summary.md`
|
||||
//! - `<root>/.insomnia/memory/decisions/<slug>.md`
|
||||
//! - `<root>/.insomnia/memory/requests/<slug>.md`
|
||||
//! - `<root>/.insomnia/memory/_staging/<id>.json`
|
||||
//! - `<root>/.insomnia/memory/_logs/current.log` (append-only audit log)
|
||||
//! - `<root>/.yoi/workflow/<slug>.md`
|
||||
//! - `<root>/.yoi/knowledge/<slug>.md`
|
||||
//! - `<root>/.yoi/memory/summary.md`
|
||||
//! - `<root>/.yoi/memory/decisions/<slug>.md`
|
||||
//! - `<root>/.yoi/memory/requests/<slug>.md`
|
||||
//! - `<root>/.yoi/memory/_staging/<id>.json`
|
||||
//! - `<root>/.yoi/memory/_logs/current.log` (append-only audit log)
|
||||
//!
|
||||
//! `memory/` is reserved for session-derived / generated state;
|
||||
//! Workflows are human-managed and live one level up under
|
||||
//! `.insomnia/workflow/`.
|
||||
//! `.yoi/workflow/`.
|
||||
//!
|
||||
//! Configuring `[memory]` with an empty body is therefore sufficient
|
||||
//! for any workspace that already uses the `.insomnia/` convention; no
|
||||
//! for any workspace that already uses the `.yoi/` convention; no
|
||||
//! `workspace_root` override is needed.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -29,7 +29,7 @@ use crate::error::LintError;
|
|||
#[cfg(test)]
|
||||
use lint_common::RecordLintError;
|
||||
|
||||
const INSOMNIA_DIR: &str = ".insomnia";
|
||||
const YOI_DIR: &str = ".yoi";
|
||||
const MEMORY_DIR: &str = "memory";
|
||||
const KNOWLEDGE_DIR: &str = "knowledge";
|
||||
const WORKFLOW_DIR: &str = "workflow";
|
||||
|
|
@ -100,17 +100,17 @@ impl WorkspaceLayout {
|
|||
&self.root
|
||||
}
|
||||
|
||||
/// `<root>/.insomnia/`. The base of every other memory path.
|
||||
pub fn insomnia_dir(&self) -> PathBuf {
|
||||
self.root.join(INSOMNIA_DIR)
|
||||
/// `<root>/.yoi/`. The base of every other memory path.
|
||||
pub fn yoi_dir(&self) -> PathBuf {
|
||||
self.root.join(YOI_DIR)
|
||||
}
|
||||
|
||||
pub fn memory_dir(&self) -> PathBuf {
|
||||
self.insomnia_dir().join(MEMORY_DIR)
|
||||
self.yoi_dir().join(MEMORY_DIR)
|
||||
}
|
||||
|
||||
pub fn knowledge_dir(&self) -> PathBuf {
|
||||
self.insomnia_dir().join(KNOWLEDGE_DIR)
|
||||
self.yoi_dir().join(KNOWLEDGE_DIR)
|
||||
}
|
||||
|
||||
pub fn summary_path(&self) -> PathBuf {
|
||||
|
|
@ -125,9 +125,9 @@ impl WorkspaceLayout {
|
|||
self.memory_dir().join(REQUESTS_DIR)
|
||||
}
|
||||
|
||||
/// Workflow directory: `<root>/.insomnia/workflow/`.
|
||||
/// Workflow directory: `<root>/.yoi/workflow/`.
|
||||
pub fn workflow_dir(&self) -> PathBuf {
|
||||
self.insomnia_dir().join(WORKFLOW_DIR)
|
||||
self.yoi_dir().join(WORKFLOW_DIR)
|
||||
}
|
||||
|
||||
pub fn staging_dir(&self) -> PathBuf {
|
||||
|
|
@ -149,7 +149,7 @@ impl WorkspaceLayout {
|
|||
/// Tail-friendly latest memory audit log path.
|
||||
///
|
||||
/// Operators can inspect live memory worker and tool events with:
|
||||
/// `tail -f .insomnia/memory/_logs/current.log`.
|
||||
/// `tail -f .yoi/memory/_logs/current.log`.
|
||||
pub fn audit_current_log_path(&self) -> PathBuf {
|
||||
self.audit_logs_dir().join(AUDIT_CURRENT_LOG_FILE)
|
||||
}
|
||||
|
|
@ -171,12 +171,12 @@ impl WorkspaceLayout {
|
|||
}
|
||||
|
||||
/// Classify a path under the memory tree. Returns `None` if the
|
||||
/// path is not under `.insomnia/memory/` or `.insomnia/knowledge/`
|
||||
/// path is not under `.yoi/memory/` or `.yoi/knowledge/`
|
||||
/// of this workspace, or if it lives in
|
||||
/// `_staging/` / `_usage/` / `_logs/` (opaque subsystem-owned trees).
|
||||
///
|
||||
/// On a conventional path that's *almost* a record but malformed
|
||||
/// (e.g. `.insomnia/memory/decisions/Foo.md` with an invalid slug),
|
||||
/// (e.g. `.yoi/memory/decisions/Foo.md` with an invalid slug),
|
||||
/// returns `Err(LintError::Record(InvalidSlug) | InvalidPath)` so the caller
|
||||
/// can surface it as a write violation.
|
||||
pub fn classify(&self, path: &Path) -> Result<Option<ClassifiedPath>, LintError> {
|
||||
|
|
@ -265,7 +265,7 @@ mod tests {
|
|||
#[test]
|
||||
fn classifies_summary() {
|
||||
let cp = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/summary.md"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/summary.md"))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(cp.kind, RecordKind::Summary);
|
||||
|
|
@ -275,7 +275,7 @@ mod tests {
|
|||
#[test]
|
||||
fn classifies_decision_with_slug() {
|
||||
let cp = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/decisions/foo-bar.md"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/decisions/foo-bar.md"))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(cp.kind, RecordKind::Decision);
|
||||
|
|
@ -285,7 +285,7 @@ mod tests {
|
|||
#[test]
|
||||
fn classifies_knowledge() {
|
||||
let cp = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/knowledge/x.md"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/knowledge/x.md"))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(cp.kind, RecordKind::Knowledge);
|
||||
|
|
@ -294,7 +294,7 @@ mod tests {
|
|||
#[test]
|
||||
fn workflow_under_memory_is_invalid_path() {
|
||||
let err = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/workflow/wf.md"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/workflow/wf.md"))
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, LintError::InvalidPath(_)));
|
||||
}
|
||||
|
|
@ -303,7 +303,7 @@ mod tests {
|
|||
fn staging_returns_none() {
|
||||
assert!(
|
||||
layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/_staging/abc.json"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/_staging/abc.json"))
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
|
|
@ -312,7 +312,7 @@ mod tests {
|
|||
#[test]
|
||||
fn usage_tree_is_opaque_to_classifier() {
|
||||
let cp = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/_usage/events.jsonl"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/_usage/events.jsonl"))
|
||||
.unwrap();
|
||||
assert!(cp.is_none());
|
||||
}
|
||||
|
|
@ -320,7 +320,7 @@ mod tests {
|
|||
#[test]
|
||||
fn logs_tree_is_opaque_to_classifier() {
|
||||
let cp = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/_logs/current.log"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/_logs/current.log"))
|
||||
.unwrap();
|
||||
assert!(cp.is_none());
|
||||
}
|
||||
|
|
@ -344,7 +344,7 @@ mod tests {
|
|||
#[test]
|
||||
fn invalid_slug_rejected() {
|
||||
let err = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/decisions/Foo.md"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/decisions/Foo.md"))
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
|
|
@ -355,7 +355,7 @@ mod tests {
|
|||
#[test]
|
||||
fn nested_under_record_dir_rejected() {
|
||||
let err = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/decisions/sub/foo.md"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/decisions/sub/foo.md"))
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, LintError::InvalidPath(_)));
|
||||
}
|
||||
|
|
@ -363,7 +363,7 @@ mod tests {
|
|||
#[test]
|
||||
fn unknown_top_level_dir_rejected() {
|
||||
let err = layout()
|
||||
.classify(&PathBuf::from("/ws/.insomnia/memory/something/foo.md"))
|
||||
.classify(&PathBuf::from("/ws/.yoi/memory/something/foo.md"))
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, LintError::InvalidPath(_)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,14 +71,14 @@ impl LockFile {
|
|||
|
||||
/// Default on-disk path: `<runtime_dir>/pods.json` resolved via
|
||||
/// [`manifest::paths::pod_registry_path`]. Tests should point this
|
||||
/// elsewhere by setting `INSOMNIA_HOME` or `INSOMNIA_RUNTIME_DIR` to a
|
||||
/// elsewhere by setting `YOI_HOME` or `YOI_RUNTIME_DIR` to a
|
||||
/// tempdir.
|
||||
pub fn default_registry_path() -> io::Result<PathBuf> {
|
||||
paths::pod_registry_path().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"could not resolve pods.json path (no INSOMNIA_HOME / \
|
||||
INSOMNIA_RUNTIME_DIR / XDG_RUNTIME_DIR / HOME)",
|
||||
"could not resolve pods.json path (no YOI_HOME / \
|
||||
YOI_RUNTIME_DIR / XDG_RUNTIME_DIR / HOME)",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
@ -190,7 +190,7 @@ mod tests {
|
|||
fn open_creates_file_with_owner_only_permissions() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let parent = dir.path().join("insomnia");
|
||||
let parent = dir.path().join("yoi");
|
||||
let path = parent.join("pods.json");
|
||||
let _guard = LockFileGuard::open(&path).unwrap();
|
||||
let file_mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ pub(crate) fn sid() -> SegmentId {
|
|||
/// parallel test's `default_registry_path()` lookup.
|
||||
pub(crate) static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
|
||||
|
||||
/// Sandbox `INSOMNIA_RUNTIME_DIR` to a tempdir for the duration of
|
||||
/// a test; restore the previous value (and any `INSOMNIA_HOME` /
|
||||
/// Sandbox `YOI_RUNTIME_DIR` to a tempdir for the duration of
|
||||
/// a test; restore the previous value (and any `YOI_HOME` /
|
||||
/// `XDG_RUNTIME_DIR` that would otherwise outrank it) on drop.
|
||||
pub(crate) struct RuntimeDirSandbox {
|
||||
prev_runtime: Option<String>,
|
||||
|
|
@ -33,16 +33,16 @@ pub(crate) struct RuntimeDirSandbox {
|
|||
impl RuntimeDirSandbox {
|
||||
pub(crate) fn new(dir: &Path) -> Self {
|
||||
let guard = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let prev_runtime = std::env::var("INSOMNIA_RUNTIME_DIR").ok();
|
||||
let prev_home = std::env::var("INSOMNIA_HOME").ok();
|
||||
let prev_runtime = std::env::var("YOI_RUNTIME_DIR").ok();
|
||||
let prev_home = std::env::var("YOI_HOME").ok();
|
||||
let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok();
|
||||
// SAFETY: ENV_LOCK serialises env writes across this test
|
||||
// module; other modules that touch env vars rely on their
|
||||
// own lock or `serial_test`.
|
||||
unsafe {
|
||||
std::env::remove_var("INSOMNIA_HOME");
|
||||
std::env::remove_var("YOI_HOME");
|
||||
std::env::remove_var("XDG_RUNTIME_DIR");
|
||||
std::env::set_var("INSOMNIA_RUNTIME_DIR", dir);
|
||||
std::env::set_var("YOI_RUNTIME_DIR", dir);
|
||||
}
|
||||
Self {
|
||||
prev_runtime,
|
||||
|
|
@ -57,12 +57,12 @@ impl Drop for RuntimeDirSandbox {
|
|||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match &self.prev_runtime {
|
||||
Some(v) => std::env::set_var("INSOMNIA_RUNTIME_DIR", v),
|
||||
None => std::env::remove_var("INSOMNIA_RUNTIME_DIR"),
|
||||
Some(v) => std::env::set_var("YOI_RUNTIME_DIR", v),
|
||||
None => std::env::remove_var("YOI_RUNTIME_DIR"),
|
||||
}
|
||||
match &self.prev_home {
|
||||
Some(v) => std::env::set_var("INSOMNIA_HOME", v),
|
||||
None => std::env::remove_var("INSOMNIA_HOME"),
|
||||
Some(v) => std::env::set_var("YOI_HOME", v),
|
||||
None => std::env::remove_var("YOI_HOME"),
|
||||
}
|
||||
match &self.prev_xdg {
|
||||
Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v),
|
||||
|
|
|
|||
|
|
@ -18,5 +18,5 @@
|
|||
|
||||
### ランタイム
|
||||
|
||||
- `RuntimeDir` — `$XDG_RUNTIME_DIR/insomnia/{pod_name}/` 配下のランタイムディレクトリ管理(ステータス・履歴のアトミック書き込み)
|
||||
- `RuntimeDir` — `$XDG_RUNTIME_DIR/yoi/{pod_name}/` 配下のランタイムディレクトリ管理(ステータス・履歴のアトミック書き込み)
|
||||
- `SocketServer` — Pod Protocol 用 Unix ソケットサーバー
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Minimal example: Pod running a single prompt with persistence.
|
||||
//!
|
||||
//! Demonstrates the core insomnia abstraction — a TOML manifest drives
|
||||
//! Demonstrates the core yoi abstraction — a TOML manifest drives
|
||||
//! provider selection, model config, and system prompt, while FsStore
|
||||
//! persists the session to disk automatically.
|
||||
//!
|
||||
|
|
|
|||
|
|
@ -822,7 +822,7 @@ mod tests {
|
|||
let runtime_base = root.path().join("runtime");
|
||||
std::fs::create_dir_all(&runtime_base).unwrap();
|
||||
unsafe {
|
||||
std::env::set_var("INSOMNIA_RUNTIME_DIR", &runtime_base);
|
||||
std::env::set_var("YOI_RUNTIME_DIR", &runtime_base);
|
||||
}
|
||||
|
||||
let store = FsPodStore::new(&store_dir).unwrap();
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ fn load_single_manifest(
|
|||
}
|
||||
|
||||
pub async fn run_cli() -> ExitCode {
|
||||
run_cli_from("insomnia pod", std::env::args_os().skip(1)).await
|
||||
run_cli_from("yoi pod", std::env::args_os().skip(1)).await
|
||||
}
|
||||
|
||||
pub async fn run_cli_from<I, T>(bin_name: &'static str, args: I) -> ExitCode
|
||||
|
|
@ -246,7 +246,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode {
|
|||
};
|
||||
|
||||
// Initialize persistent store. `paths::sessions_dir()` only
|
||||
// returns None when none of INSOMNIA_HOME / INSOMNIA_DATA_DIR /
|
||||
// returns None when none of YOI_HOME / YOI_DATA_DIR /
|
||||
// HOME is set — surface that as a hard error to match the
|
||||
// runtime-dir resolution below, rather than silently writing to a
|
||||
// relative path under cwd.
|
||||
|
|
@ -257,7 +257,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode {
|
|||
None => {
|
||||
eprintln!(
|
||||
"error: could not resolve sessions directory \
|
||||
(set --store, INSOMNIA_HOME, INSOMNIA_DATA_DIR, or HOME)"
|
||||
(set --store, YOI_HOME, YOI_DATA_DIR, or HOME)"
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
|
@ -375,7 +375,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode {
|
|||
None => {
|
||||
eprintln!(
|
||||
"error: could not resolve runtime directory \
|
||||
(set INSOMNIA_HOME, INSOMNIA_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)"
|
||||
(set YOI_HOME, YOI_RUNTIME_DIR, XDG_RUNTIME_DIR, or HOME)"
|
||||
);
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
|
@ -393,7 +393,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode {
|
|||
// (e.g. the TUI's interactive `spawn` flow). Tab-separated so a
|
||||
// pod name with spaces still parses cleanly. Emit before the
|
||||
// human line so a stderr-watching parent sees it first.
|
||||
eprintln!("INSOMNIA-READY\t{pod_name}\t{}", socket_path.display());
|
||||
eprintln!("YOI-READY\t{pod_name}\t{}", socket_path.display());
|
||||
eprintln!("pod: {pod_name} listening on {:?}", socket_path);
|
||||
|
||||
tokio::select! {
|
||||
|
|
@ -443,36 +443,30 @@ permission = "write"
|
|||
|
||||
#[test]
|
||||
fn user_manifest_flag_is_not_accepted() {
|
||||
let err =
|
||||
Cli::try_parse_from(["insomnia pod", "--user-manifest", "manifest.toml"]).unwrap_err();
|
||||
let err = Cli::try_parse_from(["yoi pod", "--user-manifest", "manifest.toml"]).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subcommand_help_uses_insomnia_pod_invocation() {
|
||||
let err = parse_cli_from("insomnia pod", ["--help"]).unwrap_err();
|
||||
fn subcommand_help_uses_yoi_pod_invocation() {
|
||||
let err = parse_cli_from("yoi pod", ["--help"]).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
|
||||
let help = err.to_string();
|
||||
assert!(help.contains("Usage: insomnia pod"), "{help}");
|
||||
assert!(help.contains("Usage: yoi pod"), "{help}");
|
||||
assert!(help.contains("--pod <NAME>"), "{help}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_conflicts_with_project() {
|
||||
let project_err = Cli::try_parse_from([
|
||||
"insomnia pod",
|
||||
"--manifest",
|
||||
"manifest.toml",
|
||||
"--project",
|
||||
".",
|
||||
])
|
||||
.unwrap_err();
|
||||
let project_err =
|
||||
Cli::try_parse_from(["yoi pod", "--manifest", "manifest.toml", "--project", "."])
|
||||
.unwrap_err();
|
||||
assert_eq!(project_err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlay_flag_is_not_accepted() {
|
||||
let err = Cli::try_parse_from(["insomnia pod", "--overlay", "pod.name = 'x'"]).unwrap_err();
|
||||
let err = Cli::try_parse_from(["yoi pod", "--overlay", "pod.name = 'x'"]).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument);
|
||||
}
|
||||
|
||||
|
|
@ -481,8 +475,8 @@ permission = "write"
|
|||
let tmp = TempDir::new().unwrap();
|
||||
let manifest = tmp.path().join("manifest.toml");
|
||||
write(&manifest, &manifest_toml("single", tmp.path()));
|
||||
let cli = Cli::try_parse_from(["insomnia pod", "--manifest", manifest.to_str().unwrap()])
|
||||
.unwrap();
|
||||
let cli =
|
||||
Cli::try_parse_from(["yoi pod", "--manifest", manifest.to_str().unwrap()]).unwrap();
|
||||
|
||||
let (manifest, loader) = resolve_manifest(&cli).unwrap();
|
||||
|
||||
|
|
@ -496,7 +490,7 @@ permission = "write"
|
|||
let tmp = TempDir::new().unwrap();
|
||||
let profile = tmp.path().join("profile.lua");
|
||||
let cli = Cli::try_parse_from([
|
||||
"insomnia pod",
|
||||
"yoi pod",
|
||||
"--profile",
|
||||
profile.to_str().unwrap(),
|
||||
"--profile-pod-name",
|
||||
|
|
@ -529,7 +523,7 @@ permission = "write"
|
|||
fn profile_accepts_source_qualified_discovered_name() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cli = Cli::try_parse_from([
|
||||
"insomnia pod",
|
||||
"yoi pod",
|
||||
"--profile",
|
||||
"project:coder",
|
||||
"--profile-pod-name",
|
||||
|
|
@ -564,7 +558,7 @@ permission = "write"
|
|||
#[test]
|
||||
fn normal_startup_uses_default_profile() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cli = Cli::try_parse_from(["insomnia pod"]).unwrap();
|
||||
let cli = Cli::try_parse_from(["yoi pod"]).unwrap();
|
||||
let mut called = false;
|
||||
|
||||
let (manifest, _loader) =
|
||||
|
|
@ -585,7 +579,7 @@ permission = "write"
|
|||
|
||||
#[test]
|
||||
fn project_flag_no_longer_enables_ambient_manifest_cascade() {
|
||||
let cli = Cli::try_parse_from(["insomnia pod", "--project", "."]).unwrap();
|
||||
let cli = Cli::try_parse_from(["yoi pod", "--project", "."]).unwrap();
|
||||
let err = resolve_manifest_with_profile_loader(&cli, |_, _| {
|
||||
panic!("default profile loader must not run when deprecated --project is present")
|
||||
})
|
||||
|
|
@ -597,7 +591,7 @@ permission = "write"
|
|||
fn pod_flag_conflicts_with_session() {
|
||||
let segment_id = session_store::new_segment_id();
|
||||
let segment_id = segment_id.to_string();
|
||||
let err = Cli::try_parse_from(["insomnia pod", "--pod", "agent", "--session", &segment_id])
|
||||
let err = Cli::try_parse_from(["yoi pod", "--pod", "agent", "--session", &segment_id])
|
||||
.unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
|
@ -608,7 +602,7 @@ permission = "write"
|
|||
let manifest = tmp.path().join("manifest.toml");
|
||||
write(&manifest, &manifest_toml("from-file", tmp.path()));
|
||||
let cli = Cli::try_parse_from([
|
||||
"insomnia pod",
|
||||
"yoi pod",
|
||||
"--manifest",
|
||||
manifest.to_str().unwrap(),
|
||||
"--pod",
|
||||
|
|
@ -640,7 +634,7 @@ permission = "write"
|
|||
"#,
|
||||
);
|
||||
let cli = Cli::try_parse_from([
|
||||
"insomnia pod",
|
||||
"yoi pod",
|
||||
"--manifest",
|
||||
manifest.to_str().unwrap(),
|
||||
"--pod",
|
||||
|
|
@ -657,7 +651,7 @@ permission = "write"
|
|||
#[test]
|
||||
fn pod_flag_with_no_manifest_creates_from_default_profile_with_typed_name() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cli = Cli::try_parse_from(["insomnia pod", "--pod", "agent"]).unwrap();
|
||||
let cli = Cli::try_parse_from(["yoi pod", "--pod", "agent"]).unwrap();
|
||||
let mut called = false;
|
||||
|
||||
let (manifest, _loader) =
|
||||
|
|
@ -683,15 +677,9 @@ permission = "write"
|
|||
fn profile_conflicts_with_manifest_and_restore_modes() {
|
||||
let segment_id = session_store::new_segment_id().to_string();
|
||||
for args in [
|
||||
vec!["insomnia pod", "--profile", "p.lua", "--manifest", "m.toml"],
|
||||
vec!["insomnia pod", "--profile", "p.lua", "--pod", "agent"],
|
||||
vec![
|
||||
"insomnia pod",
|
||||
"--profile",
|
||||
"p.lua",
|
||||
"--session",
|
||||
&segment_id,
|
||||
],
|
||||
vec!["yoi pod", "--profile", "p.lua", "--manifest", "m.toml"],
|
||||
vec!["yoi pod", "--profile", "p.lua", "--pod", "agent"],
|
||||
vec!["yoi pod", "--profile", "p.lua", "--session", &segment_id],
|
||||
] {
|
||||
let err = Cli::try_parse_from(args).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
|
|
@ -700,14 +688,14 @@ permission = "write"
|
|||
|
||||
#[test]
|
||||
fn profile_pod_name_requires_profile() {
|
||||
let err = Cli::try_parse_from(["insomnia pod", "--profile-pod-name", "agent"]).unwrap_err();
|
||||
let err = Cli::try_parse_from(["yoi pod", "--profile-pod-name", "agent"]).unwrap_err();
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_pod_name_is_not_restore_pod_flag() {
|
||||
let cli = Cli::try_parse_from([
|
||||
"insomnia pod",
|
||||
"yoi pod",
|
||||
"--profile",
|
||||
"p.lua",
|
||||
"--profile-pod-name",
|
||||
|
|
@ -724,13 +712,9 @@ permission = "write"
|
|||
let single_manifest = tmp.path().join("single.toml");
|
||||
write(&single_manifest, &manifest_toml("single-file", tmp.path()));
|
||||
std::fs::create_dir_all(tmp.path().join("prompts")).unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join(".insomnia").join("prompts")).unwrap();
|
||||
let cli = Cli::try_parse_from([
|
||||
"insomnia pod",
|
||||
"--manifest",
|
||||
single_manifest.to_str().unwrap(),
|
||||
])
|
||||
.unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join(".yoi").join("prompts")).unwrap();
|
||||
let cli = Cli::try_parse_from(["yoi pod", "--manifest", single_manifest.to_str().unwrap()])
|
||||
.unwrap();
|
||||
|
||||
let (manifest, loader) = resolve_manifest(&cli).unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ pub struct Pod<C: LlmClient, St: Store> {
|
|||
/// [`Self::from_manifest`], or defaults to the builtin pack when a
|
||||
/// Pod is constructed through lower-level paths that have no loader.
|
||||
prompts: Arc<PromptCatalog>,
|
||||
/// Registry loaded from `<workspace>/.insomnia/workflow/*.md` when
|
||||
/// Registry loaded from `<workspace>/.yoi/workflow/*.md` when
|
||||
/// memory is enabled. Missing memory config keeps this empty.
|
||||
workflow_registry: workflow_crate::WorkflowRegistry,
|
||||
/// Memory workspace layout used by the workflow resolver to load required
|
||||
|
|
@ -5337,21 +5337,21 @@ mod build_summary_prompt_tests {
|
|||
let pwd = dir.path().join("workspace");
|
||||
std::fs::create_dir_all(&pwd).unwrap();
|
||||
if let Some(doc) = summary_doc {
|
||||
std::fs::create_dir_all(pwd.join(".insomnia/memory")).unwrap();
|
||||
std::fs::write(pwd.join(".insomnia/memory/summary.md"), doc).unwrap();
|
||||
std::fs::create_dir_all(pwd.join(".yoi/memory")).unwrap();
|
||||
std::fs::write(pwd.join(".yoi/memory/summary.md"), doc).unwrap();
|
||||
}
|
||||
if include_knowledge {
|
||||
std::fs::create_dir_all(pwd.join(".insomnia/knowledge")).unwrap();
|
||||
std::fs::create_dir_all(pwd.join(".yoi/knowledge")).unwrap();
|
||||
std::fs::write(
|
||||
pwd.join(".insomnia/knowledge/resident-policy.md"),
|
||||
pwd.join(".yoi/knowledge/resident-policy.md"),
|
||||
knowledge_doc("knowledge resident desc"),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
if include_workflow {
|
||||
std::fs::create_dir_all(pwd.join(".insomnia/workflow")).unwrap();
|
||||
std::fs::create_dir_all(pwd.join(".yoi/workflow")).unwrap();
|
||||
std::fs::write(
|
||||
pwd.join(".insomnia/workflow/resident-flow.md"),
|
||||
pwd.join(".yoi/workflow/resident-flow.md"),
|
||||
workflow_doc("workflow resident desc"),
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -5379,7 +5379,7 @@ mod build_summary_prompt_tests {
|
|||
pod.set_resident_workflow_injection(gates.workflows);
|
||||
}
|
||||
let template = SystemPromptTemplate::parse(
|
||||
"$insomnia/default",
|
||||
"$yoi/default",
|
||||
crate::prompt::loader::PromptLoader::builtins_only(),
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
//! binary. Must cover every [`PodPrompt`] variant (build-time check).
|
||||
//! 2. **user** — `<config_dir>/prompts.toml`, when a caller supplies it.
|
||||
//! Optional.
|
||||
//! 3. **workspace** — `<project>/.insomnia/prompts.toml`, when a caller
|
||||
//! 3. **workspace** — `<project>/.yoi/prompts.toml`, when a caller
|
||||
//! supplies it. Optional.
|
||||
//! 4. **manifest pack** — `manifest.pod.prompt_pack`, an explicit path
|
||||
//! per-Pod. Optional.
|
||||
|
|
@ -258,7 +258,7 @@ struct PackFile {
|
|||
/// Owns a `minijinja::Environment` with one template registered per
|
||||
/// [`PodPrompt`] key (after the 4-layer merge). Includes inside templates
|
||||
/// are resolved via a provided [`PromptLoader`], so values can pull from
|
||||
/// `$insomnia` / `$user` / `$workspace`.
|
||||
/// `$yoi` / `$user` / `$workspace`.
|
||||
pub struct PromptCatalog {
|
||||
env: Environment<'static>,
|
||||
}
|
||||
|
|
@ -271,7 +271,7 @@ impl std::fmt::Debug for PromptCatalog {
|
|||
|
||||
impl PromptCatalog {
|
||||
/// Builtin-only catalog. All `{% include %}` references must resolve
|
||||
/// through `$insomnia` (user/workspace prefixes are unavailable).
|
||||
/// through `$yoi` (user/workspace prefixes are unavailable).
|
||||
pub fn builtins_only() -> Result<Arc<Self>, CatalogError> {
|
||||
Self::load(&PromptLoader::builtins_only(), None)
|
||||
}
|
||||
|
|
@ -707,7 +707,7 @@ interrupt_system_note = "[FROM-MANIFEST-PACK]"
|
|||
#[test]
|
||||
fn value_can_pull_long_text_via_include() {
|
||||
// A runtime pack that overrides `compact_system` with an
|
||||
// `{% include %}` into the same `$insomnia` namespace — exercises
|
||||
// `{% include %}` into the same `$yoi` namespace — exercises
|
||||
// the template resolver path through all four layers.
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let pack = tmp.path().join("p.toml");
|
||||
|
|
@ -715,7 +715,7 @@ interrupt_system_note = "[FROM-MANIFEST-PACK]"
|
|||
&pack,
|
||||
r#"
|
||||
[prompt]
|
||||
compact_system = "PREFIX\n{% include \"$insomnia/internal/compact_system\" %}"
|
||||
compact_system = "PREFIX\n{% include \"$yoi/internal/compact_system\" %}"
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
//!
|
||||
//! | prefix | location |
|
||||
//! |--------------|---------------------------------------------------------|
|
||||
//! | `$insomnia` | builtin, baked into the binary via `include_dir!` |
|
||||
//! | `$yoi` | builtin, baked into the binary via `include_dir!` |
|
||||
//! | `$user` | `<config_dir>/prompts/` (resolved by `manifest::paths`) |
|
||||
//! | `$workspace` | `<project>/.insomnia/prompts/` |
|
||||
//! | `$workspace` | `<project>/.yoi/prompts/` |
|
||||
//!
|
||||
//! A reference is `$<prefix>/<path>` where `<path>` is a `/`-separated
|
||||
//! name without the `.md` extension (e.g. `$insomnia/common/header`).
|
||||
//! name without the `.md` extension (e.g. `$yoi/common/header`).
|
||||
//! Unqualified names (no `$prefix/` at the front) are resolved relative
|
||||
//! to an optional current reference — typically the file that issued
|
||||
//! the `{% include %}` — so a prompt library can be authored as a
|
||||
|
|
@ -25,13 +25,13 @@ use thiserror::Error;
|
|||
|
||||
static BUILTIN_PROMPTS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/../../resources/prompts");
|
||||
|
||||
const PREFIX_INSOMNIA: &str = "$insomnia";
|
||||
const PREFIX_YOI: &str = "$yoi";
|
||||
const PREFIX_USER: &str = "$user";
|
||||
const PREFIX_WORKSPACE: &str = "$workspace";
|
||||
|
||||
/// Prefix-resolved reference to a prompt asset. Produced by
|
||||
/// [`PromptLoader::parse_ref`] from a user-supplied string such as
|
||||
/// `"$insomnia/default"`.
|
||||
/// `"$yoi/default"`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PromptRef {
|
||||
prefix: Prefix,
|
||||
|
|
@ -42,7 +42,7 @@ pub struct PromptRef {
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Prefix {
|
||||
Insomnia,
|
||||
Yoi,
|
||||
User,
|
||||
Workspace,
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ enum Prefix {
|
|||
impl Prefix {
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Insomnia => PREFIX_INSOMNIA,
|
||||
Self::Yoi => PREFIX_YOI,
|
||||
Self::User => PREFIX_USER,
|
||||
Self::Workspace => PREFIX_WORKSPACE,
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ pub struct PromptLoader {
|
|||
}
|
||||
|
||||
impl PromptLoader {
|
||||
/// Loader with only the builtin `$insomnia` library available.
|
||||
/// Loader with only the builtin `$yoi` library available.
|
||||
/// `$user` / `$workspace` references fail with
|
||||
/// [`LoaderError::PrefixNotConfigured`].
|
||||
pub fn builtins_only() -> Self {
|
||||
|
|
@ -221,7 +221,7 @@ impl PromptLoader {
|
|||
/// when the prefix is not configured or the file does not exist.
|
||||
pub fn load(&self, reference: &PromptRef) -> Result<String, LoaderError> {
|
||||
match reference.prefix {
|
||||
Prefix::Insomnia => load_from_include_dir(&BUILTIN_PROMPTS, reference),
|
||||
Prefix::Yoi => load_from_include_dir(&BUILTIN_PROMPTS, reference),
|
||||
Prefix::User => match self.user_dir.as_deref() {
|
||||
Some(dir) => load_from_dir(dir, reference),
|
||||
None => Err(LoaderError::PrefixNotConfigured {
|
||||
|
|
@ -252,7 +252,7 @@ impl PromptLoader {
|
|||
|
||||
fn parse_prefix(raw: &str, prefix_name: &str) -> Result<Prefix, LoaderError> {
|
||||
match prefix_name {
|
||||
"insomnia" => Ok(Prefix::Insomnia),
|
||||
"yoi" => Ok(Prefix::Yoi),
|
||||
"user" => Ok(Prefix::User),
|
||||
"workspace" => Ok(Prefix::Workspace),
|
||||
_ => Err(LoaderError::UnknownPrefix {
|
||||
|
|
@ -311,15 +311,15 @@ mod tests {
|
|||
#[test]
|
||||
fn builtin_default_resolves() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let (r, source) = loader.resolve("$insomnia/default", None).unwrap();
|
||||
assert_eq!(r.to_qualified_string(), "$insomnia/default");
|
||||
let (r, source) = loader.resolve("$yoi/default", None).unwrap();
|
||||
assert_eq!(r.to_qualified_string(), "$yoi/default");
|
||||
assert!(!source.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_subdirectory_lookup() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let (_, source) = loader.resolve("$insomnia/common/tool-usage", None).unwrap();
|
||||
let (_, source) = loader.resolve("$yoi/common/tool-usage", None).unwrap();
|
||||
assert!(source.contains("tool"));
|
||||
}
|
||||
|
||||
|
|
@ -346,9 +346,7 @@ mod tests {
|
|||
#[test]
|
||||
fn missing_file_is_hard_error() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let err = loader
|
||||
.resolve("$insomnia/definitely-missing", None)
|
||||
.unwrap_err();
|
||||
let err = loader.resolve("$yoi/definitely-missing", None).unwrap_err();
|
||||
assert!(matches!(err, LoaderError::NotFound { .. }));
|
||||
}
|
||||
|
||||
|
|
@ -379,20 +377,18 @@ mod tests {
|
|||
#[test]
|
||||
fn unqualified_ref_resolves_relative_to_current() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let current = loader
|
||||
.parse_ref("$insomnia/common/tool-usage", None)
|
||||
.unwrap();
|
||||
let current = loader.parse_ref("$yoi/common/tool-usage", None).unwrap();
|
||||
// Sibling lookup under the same prefix and directory.
|
||||
let sibling = loader.parse_ref("workspace", Some(¤t)).unwrap();
|
||||
assert_eq!(sibling.to_qualified_string(), "$insomnia/common/workspace");
|
||||
assert_eq!(sibling.to_qualified_string(), "$yoi/common/workspace");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unqualified_ref_from_root_file_has_empty_dir() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let current = loader.parse_ref("$insomnia/default", None).unwrap();
|
||||
let current = loader.parse_ref("$yoi/default", None).unwrap();
|
||||
let sibling = loader.parse_ref("other", Some(¤t)).unwrap();
|
||||
assert_eq!(sibling.to_qualified_string(), "$insomnia/other");
|
||||
assert_eq!(sibling.to_qualified_string(), "$yoi/other");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -402,8 +398,8 @@ mod tests {
|
|||
std::fs::write(user_dir.join("custom.md"), "user-body").unwrap();
|
||||
let loader = PromptLoader::new(Some(user_dir), None);
|
||||
|
||||
let current = loader.parse_ref("$insomnia/default", None).unwrap();
|
||||
// Even with an $insomnia-rooted current, an explicit $user
|
||||
let current = loader.parse_ref("$yoi/default", None).unwrap();
|
||||
// Even with an $yoi-rooted current, an explicit $user
|
||||
// prefix must win.
|
||||
let (reference, source) = loader.resolve("$user/custom", Some(¤t)).unwrap();
|
||||
assert_eq!(reference.to_qualified_string(), "$user/custom");
|
||||
|
|
@ -413,7 +409,7 @@ mod tests {
|
|||
#[test]
|
||||
fn traversal_segments_rejected() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let err = loader.resolve("$insomnia/../etc/passwd", None).unwrap_err();
|
||||
let err = loader.resolve("$yoi/../etc/passwd", None).unwrap_err();
|
||||
assert!(matches!(err, LoaderError::InvalidRef { .. }));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ pub struct SystemPromptContext<'a> {
|
|||
/// Not visible from the template; consumed by the trailing-section
|
||||
/// formatter in [`SystemPromptTemplate::render`].
|
||||
pub agents_md: Option<String>,
|
||||
/// The body of `<workspace>/.insomnia/memory/summary.md`, with
|
||||
/// The body of `<workspace>/.yoi/memory/summary.md`, with
|
||||
/// frontmatter stripped. `None` disables the resident summary section;
|
||||
/// empty strings are ignored by the trailing-section formatter.
|
||||
pub resident_summary: Option<&'a str>,
|
||||
|
|
@ -164,7 +164,7 @@ pub struct SystemPromptContext<'a> {
|
|||
/// section entirely (memory disabled, or a consolidation worker that opts
|
||||
/// out); `Some(&[])` also yields no section.
|
||||
pub resident_knowledge: Option<&'a [ResidentKnowledgeEntry]>,
|
||||
/// Resident workflow descriptions from `<workspace>/.insomnia/workflow/*`
|
||||
/// Resident workflow descriptions from `<workspace>/.yoi/workflow/*`
|
||||
/// whose frontmatter has `model_invokation: true`. `None` disables the
|
||||
/// section; consolidation workers opt out together with resident Knowledge.
|
||||
pub resident_workflows: Option<&'a [ResidentWorkflowEntry]>,
|
||||
|
|
@ -557,9 +557,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn instruction_default_resolves_to_insomnia_default() {
|
||||
fn instruction_default_resolves_to_yoi_default() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap();
|
||||
let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let scope = build_scope(dir.path());
|
||||
let rendered = tmpl
|
||||
|
|
@ -582,7 +582,7 @@ mod tests {
|
|||
#[test]
|
||||
fn instruction_default_omits_memory_guidance_without_memory_tools() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap();
|
||||
let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let scope = build_scope(dir.path());
|
||||
let rendered = tmpl
|
||||
|
|
@ -608,7 +608,7 @@ mod tests {
|
|||
#[test]
|
||||
fn memory_guidance_names_only_available_memory_tools() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap();
|
||||
let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let scope = build_scope(dir.path());
|
||||
let rendered = tmpl
|
||||
|
|
@ -632,7 +632,7 @@ mod tests {
|
|||
#[test]
|
||||
fn pod_orchestration_guidance_is_included_for_pod_management_tools() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap();
|
||||
let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let scope = build_scope(dir.path());
|
||||
let rendered = tmpl
|
||||
|
|
@ -651,7 +651,7 @@ mod tests {
|
|||
#[test]
|
||||
fn pod_orchestration_guidance_is_omitted_without_pod_management_tools() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let tmpl = SystemPromptTemplate::parse("$insomnia/default", loader).unwrap();
|
||||
let tmpl = SystemPromptTemplate::parse("$yoi/default", loader).unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let scope = build_scope(dir.path());
|
||||
let rendered = tmpl
|
||||
|
|
@ -735,7 +735,7 @@ mod tests {
|
|||
let tmp = TempDir::new().unwrap();
|
||||
std::fs::write(
|
||||
tmp.path().join("root.md"),
|
||||
"U-ROOT\n{% include \"$insomnia/common/tool-usage\" %}",
|
||||
"U-ROOT\n{% include \"$yoi/common/tool-usage\" %}",
|
||||
)
|
||||
.unwrap();
|
||||
let loader = PromptLoader::new(Some(tmp.path().to_path_buf()), None);
|
||||
|
|
@ -758,7 +758,7 @@ mod tests {
|
|||
#[test]
|
||||
fn prefix_with_missing_file_is_hard_error() {
|
||||
let loader = PromptLoader::builtins_only();
|
||||
let err = SystemPromptTemplate::parse("$insomnia/definitely-missing", loader).unwrap_err();
|
||||
let err = SystemPromptTemplate::parse("$yoi/definitely-missing", loader).unwrap_err();
|
||||
assert!(matches!(err, SystemPromptError::LoaderResolve(_)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,8 +116,8 @@ pub fn default_base() -> Result<PathBuf, io::Error> {
|
|||
paths::runtime_dir().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"could not resolve runtime directory (no INSOMNIA_HOME / \
|
||||
INSOMNIA_RUNTIME_DIR / XDG_RUNTIME_DIR / HOME)",
|
||||
"could not resolve runtime directory (no YOI_HOME / \
|
||||
YOI_RUNTIME_DIR / XDG_RUNTIME_DIR / HOME)",
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
@ -203,13 +203,13 @@ mod tests {
|
|||
|
||||
let records = vec![SpawnedPodRecord {
|
||||
pod_name: "child".into(),
|
||||
socket_path: "/run/insomnia/child/sock".into(),
|
||||
socket_path: "/run/yoi/child/sock".into(),
|
||||
scope_delegated: vec![ScopeRule {
|
||||
target: "/tmp/work".into(),
|
||||
permission: Permission::Write,
|
||||
recursive: true,
|
||||
}],
|
||||
callback_address: "/run/insomnia/my-pod/sock".into(),
|
||||
callback_address: "/run/yoi/my-pod/sock".into(),
|
||||
}];
|
||||
rt.write_spawned_pods(&records).await.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ struct SpawnPodInput {
|
|||
/// unambiguous profile slug. Raw/path selectors are rejected.
|
||||
#[serde(default)]
|
||||
profile: Option<String>,
|
||||
/// Instruction-file reference (e.g. `$insomnia/default`, `$user/my-agent`).
|
||||
/// Instruction-file reference (e.g. `$yoi/default`, `$user/my-agent`).
|
||||
#[serde(default)]
|
||||
instruction: Option<String>,
|
||||
/// First message sent to the spawned Pod via `Method::Run`.
|
||||
|
|
@ -214,7 +214,7 @@ pub struct SpawnPodTool {
|
|||
/// Path to the spawner's Unix socket. Handed to the child via
|
||||
/// `--callback` so its `PodEvent` callbacks have somewhere to land.
|
||||
callback_socket: PathBuf,
|
||||
/// Root of the `$XDG_RUNTIME_DIR/insomnia/` tree, used to predict
|
||||
/// Root of the `$XDG_RUNTIME_DIR/yoi/` tree, used to predict
|
||||
/// the spawned Pod's socket path before the child has bound it.
|
||||
runtime_base: PathBuf,
|
||||
/// Directory the spawned Pod should run in when the LLM did not
|
||||
|
|
@ -890,7 +890,7 @@ mod tests {
|
|||
..Default::default()
|
||||
},
|
||||
worker: WorkerManifestConfig {
|
||||
instruction: Some("$insomnia/parent".into()),
|
||||
instruction: Some("$yoi/parent".into()),
|
||||
language: Some("Parentish".into()),
|
||||
max_tokens: Some(1234),
|
||||
stop_sequences: Some(vec!["STOP".into()]),
|
||||
|
|
@ -916,8 +916,8 @@ mod tests {
|
|||
default: Option<&str>,
|
||||
profiles: &[(&str, &str, &str)],
|
||||
) -> AvailableProfiles {
|
||||
let insomnia = project.join(".insomnia");
|
||||
let profile_dir = insomnia.join("profiles");
|
||||
let yoi = project.join(".yoi");
|
||||
let profile_dir = yoi.join("profiles");
|
||||
std::fs::create_dir_all(&profile_dir).unwrap();
|
||||
let mut registry_toml = String::new();
|
||||
if let Some(default) = default {
|
||||
|
|
@ -928,7 +928,7 @@ mod tests {
|
|||
std::fs::write(profile_dir.join(file), body).unwrap();
|
||||
registry_toml.push_str(&format!("{name} = \"profiles/{file}\"\n"));
|
||||
}
|
||||
let registry_path = insomnia.join("profiles.toml");
|
||||
let registry_path = yoi.join("profiles.toml");
|
||||
std::fs::write(®istry_path, registry_toml).unwrap();
|
||||
AvailableProfiles {
|
||||
registry: Some(
|
||||
|
|
@ -963,23 +963,23 @@ mod tests {
|
|||
}
|
||||
|
||||
const CODER_PROFILE: &str = r#"
|
||||
local profile = require("insomnia.profile")
|
||||
local scope = require("insomnia.scope")
|
||||
local profile = require("yoi.profile")
|
||||
local scope = require("yoi.scope")
|
||||
return profile {
|
||||
slug = "coder",
|
||||
model = { scheme = "anthropic", model_id = "coder-model" },
|
||||
worker = { instruction = "$insomnia/coder", language = "Coderish", max_tokens = 2222 },
|
||||
worker = { instruction = "$yoi/coder", language = "Coderish", max_tokens = 2222 },
|
||||
scope = scope.workspace_write(),
|
||||
}
|
||||
"#;
|
||||
|
||||
const REVIEWER_PROFILE: &str = r#"
|
||||
local profile = require("insomnia.profile")
|
||||
local scope = require("insomnia.scope")
|
||||
local profile = require("yoi.profile")
|
||||
local scope = require("yoi.scope")
|
||||
return profile {
|
||||
slug = "reviewer",
|
||||
model = { scheme = "anthropic", model_id = "reviewer-model" },
|
||||
worker = { instruction = "$insomnia/reviewer", language = "Reviewerish", max_tokens = 3333 },
|
||||
worker = { instruction = "$yoi/reviewer", language = "Reviewerish", max_tokens = 3333 },
|
||||
scope = scope.workspace_write(),
|
||||
}
|
||||
"#;
|
||||
|
|
@ -997,7 +997,7 @@ return profile {
|
|||
};
|
||||
|
||||
let config_json =
|
||||
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap();
|
||||
build_spawn_config_json("child", "$yoi/default", &[], &model, false).unwrap();
|
||||
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||
|
||||
assert_eq!(parsed.model.scheme, Some(SchemeKind::Anthropic));
|
||||
|
|
@ -1020,7 +1020,7 @@ return profile {
|
|||
..Default::default()
|
||||
};
|
||||
let config_json =
|
||||
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap();
|
||||
build_spawn_config_json("child", "$yoi/default", &[], &model, false).unwrap();
|
||||
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||
assert_eq!(
|
||||
parsed.model.ref_.as_deref(),
|
||||
|
|
@ -1041,7 +1041,7 @@ return profile {
|
|||
}];
|
||||
|
||||
let config_json =
|
||||
build_spawn_config_json("child", "$insomnia/default", &scope, &model, true).unwrap();
|
||||
build_spawn_config_json("child", "$yoi/default", &scope, &model, true).unwrap();
|
||||
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||
assert_eq!(
|
||||
parsed.session.as_ref().and_then(|s| s.record_event_trace),
|
||||
|
|
@ -1062,7 +1062,7 @@ return profile {
|
|||
..Default::default()
|
||||
};
|
||||
let config_json =
|
||||
build_spawn_config_json("child", "$insomnia/default", &[], &model, false).unwrap();
|
||||
build_spawn_config_json("child", "$yoi/default", &[], &model, false).unwrap();
|
||||
let parsed: PodManifestConfig = serde_json::from_str(&config_json).unwrap();
|
||||
|
||||
assert!(parsed.session.is_none());
|
||||
|
|
@ -1098,10 +1098,7 @@ return profile {
|
|||
|
||||
assert_eq!(config.pod.name.as_deref(), Some("child-default"));
|
||||
assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model"));
|
||||
assert_eq!(
|
||||
config.worker.instruction.as_deref(),
|
||||
Some("$insomnia/reviewer")
|
||||
);
|
||||
assert_eq!(config.worker.instruction.as_deref(), Some("$yoi/reviewer"));
|
||||
assert_eq!(config.worker.language.as_deref(), Some("Reviewerish"));
|
||||
assert_eq!(config.scope.allow, scope);
|
||||
assert!(config.scope.deny.is_empty());
|
||||
|
|
@ -1140,10 +1137,7 @@ return profile {
|
|||
|
||||
assert_eq!(config.pod.name.as_deref(), Some("review-child"));
|
||||
assert_eq!(config.model.model_id.as_deref(), Some("reviewer-model"));
|
||||
assert_eq!(
|
||||
config.worker.instruction.as_deref(),
|
||||
Some("$insomnia/reviewer")
|
||||
);
|
||||
assert_eq!(config.worker.instruction.as_deref(), Some("$yoi/reviewer"));
|
||||
assert_eq!(config.worker.language.as_deref(), Some("Reviewerish"));
|
||||
assert_eq!(config.worker.max_tokens, Some(3333));
|
||||
assert_eq!(config.scope.allow, scope);
|
||||
|
|
@ -1177,10 +1171,7 @@ return profile {
|
|||
|
||||
assert_eq!(config.pod.name.as_deref(), Some("inherited-child"));
|
||||
assert_eq!(config.model.model_id.as_deref(), Some("parent-model"));
|
||||
assert_eq!(
|
||||
config.worker.instruction.as_deref(),
|
||||
Some("$insomnia/parent")
|
||||
);
|
||||
assert_eq!(config.worker.instruction.as_deref(), Some("$yoi/parent"));
|
||||
assert_eq!(config.worker.language.as_deref(), Some("Parentish"));
|
||||
assert_eq!(config.worker.max_tokens, Some(1234));
|
||||
assert_eq!(
|
||||
|
|
@ -1313,7 +1304,7 @@ return profile {
|
|||
|
||||
let user_config = tmp.path().join("user-profiles.toml");
|
||||
std::fs::write(&user_config, "[profile]\ncoder = \"user-coder.lua\"\n").unwrap();
|
||||
let project_config = project.join(".insomnia/profiles.toml");
|
||||
let project_config = project.join(".yoi/profiles.toml");
|
||||
let ambiguous = AvailableProfiles {
|
||||
registry: Some(
|
||||
ProfileDiscovery::with_sources(Some(user_config), Some(project_config))
|
||||
|
|
|
|||
|
|
@ -145,11 +145,11 @@ mod tests {
|
|||
let dir = TempDir::new().unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
write(
|
||||
&dir.path().join(".insomnia/knowledge/policy.md"),
|
||||
&dir.path().join(".yoi/knowledge/policy.md"),
|
||||
"---\ncreated_at: 2026-01-01T00:00:00Z\nupdated_at: 2026-01-01T00:00:00Z\nkind: policy\ndescription: p\nmodel_invokation: false\nuser_invocable: true\nlast_sources: []\n---\npolicy body\n",
|
||||
);
|
||||
write(
|
||||
&dir.path().join(".insomnia/workflow/run-it.md"),
|
||||
&dir.path().join(".yoi/workflow/run-it.md"),
|
||||
"---\ndescription: run\nrequires: [policy]\n---\nworkflow body\n",
|
||||
);
|
||||
let registry = workflow_crate::load_workflows(&layout).unwrap();
|
||||
|
|
@ -173,7 +173,7 @@ mod tests {
|
|||
fn user_invocable_false_errors() {
|
||||
let (dir, layout, _registry) = setup();
|
||||
write(
|
||||
&dir.path().join(".insomnia/workflow/hidden.md"),
|
||||
&dir.path().join(".yoi/workflow/hidden.md"),
|
||||
"---\ndescription: hidden\nuser_invocable: false\n---\nbody\n",
|
||||
);
|
||||
let registry = workflow_crate::load_workflows(&layout).unwrap();
|
||||
|
|
@ -186,7 +186,7 @@ mod tests {
|
|||
let dir = TempDir::new().unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
write(
|
||||
&dir.path().join(".insomnia/workflow/bad.md"),
|
||||
&dir.path().join(".yoi/workflow/bad.md"),
|
||||
"---\ndescription: bad\nrequires: [ghost]\n---\nbody\n",
|
||||
);
|
||||
let registry = workflow_crate::load_workflows(&layout).unwrap();
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ use tokio::sync::mpsc;
|
|||
use tokio::task::JoinHandle;
|
||||
|
||||
/// Serialises env-mutating tests. The test harness runs tasks across
|
||||
/// threads, and `INSOMNIA_RUNTIME_DIR` is a process-wide resource.
|
||||
/// threads, and `YOI_RUNTIME_DIR` is a process-wide resource.
|
||||
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
|
||||
|
||||
/// Take `ENV_LOCK` and clear any env vars that would outrank
|
||||
/// `INSOMNIA_RUNTIME_DIR` in `paths::runtime_dir` resolution; restore
|
||||
/// `YOI_RUNTIME_DIR` in `paths::runtime_dir` resolution; restore
|
||||
/// previous values on drop.
|
||||
struct EnvGuard {
|
||||
prev_home: Option<String>,
|
||||
|
|
@ -44,10 +44,10 @@ struct EnvGuard {
|
|||
impl EnvGuard {
|
||||
fn acquire() -> Self {
|
||||
let lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let prev_home = std::env::var("INSOMNIA_HOME").ok();
|
||||
let prev_home = std::env::var("YOI_HOME").ok();
|
||||
let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok();
|
||||
unsafe {
|
||||
std::env::remove_var("INSOMNIA_HOME");
|
||||
std::env::remove_var("YOI_HOME");
|
||||
std::env::remove_var("XDG_RUNTIME_DIR");
|
||||
}
|
||||
Self {
|
||||
|
|
@ -62,14 +62,14 @@ impl Drop for EnvGuard {
|
|||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match &self.prev_home {
|
||||
Some(v) => std::env::set_var("INSOMNIA_HOME", v),
|
||||
None => std::env::remove_var("INSOMNIA_HOME"),
|
||||
Some(v) => std::env::set_var("YOI_HOME", v),
|
||||
None => std::env::remove_var("YOI_HOME"),
|
||||
}
|
||||
match &self.prev_xdg {
|
||||
Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v),
|
||||
None => std::env::remove_var("XDG_RUNTIME_DIR"),
|
||||
}
|
||||
std::env::remove_var("INSOMNIA_RUNTIME_DIR");
|
||||
std::env::remove_var("YOI_RUNTIME_DIR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -400,7 +400,7 @@ async fn stop_pod_sends_shutdown_and_releases_scope() {
|
|||
.unwrap(),
|
||||
);
|
||||
unsafe {
|
||||
std::env::set_var("INSOMNIA_RUNTIME_DIR", tmp.path());
|
||||
std::env::set_var("YOI_RUNTIME_DIR", tmp.path());
|
||||
}
|
||||
let lock_path = tmp.path().join("pods.json");
|
||||
|
||||
|
|
@ -486,7 +486,7 @@ async fn stop_pod_succeeds_even_when_child_unreachable() {
|
|||
let _env = EnvGuard::acquire();
|
||||
let (tmp, registry, _rd) = setup_registry().await;
|
||||
unsafe {
|
||||
std::env::set_var("INSOMNIA_RUNTIME_DIR", tmp.path());
|
||||
std::env::set_var("YOI_RUNTIME_DIR", tmp.path());
|
||||
}
|
||||
|
||||
// No live listener — socket never bound. Registered record points
|
||||
|
|
@ -518,7 +518,7 @@ async fn restored_registry_uses_pod_state_without_runtime_file() {
|
|||
FsPodStore::new(store_tmp.path().join("pods")).unwrap(),
|
||||
);
|
||||
unsafe {
|
||||
std::env::set_var("INSOMNIA_RUNTIME_DIR", runtime_tmp.path());
|
||||
std::env::set_var("YOI_RUNTIME_DIR", runtime_tmp.path());
|
||||
}
|
||||
|
||||
let rd = Arc::new(
|
||||
|
|
@ -633,7 +633,7 @@ async fn load_from_pod_state_reclaims_missing_child_scope_and_records_history()
|
|||
FsPodStore::new(store_tmp.path().join("pods")).unwrap(),
|
||||
);
|
||||
unsafe {
|
||||
std::env::set_var("INSOMNIA_RUNTIME_DIR", runtime_tmp.path());
|
||||
std::env::set_var("YOI_RUNTIME_DIR", runtime_tmp.path());
|
||||
}
|
||||
let rd = Arc::new(
|
||||
RuntimeDir::create(runtime_tmp.path(), "spawner")
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ use protocol::{Event, Greeting, Method, Permission, PodEvent, PodStatus, ScopeRu
|
|||
use tempfile::TempDir;
|
||||
use tokio::net::UnixListener;
|
||||
|
||||
/// Serialises tests that mutate `INSOMNIA_RUNTIME_DIR`.
|
||||
/// Serialises tests that mutate `YOI_RUNTIME_DIR`.
|
||||
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
|
||||
|
||||
/// Take `ENV_LOCK` and clear any env vars that would outrank
|
||||
/// `INSOMNIA_RUNTIME_DIR`; restore previous values on drop.
|
||||
/// `YOI_RUNTIME_DIR`; restore previous values on drop.
|
||||
struct EnvGuard {
|
||||
prev_home: Option<String>,
|
||||
prev_xdg: Option<String>,
|
||||
|
|
@ -32,10 +32,10 @@ struct EnvGuard {
|
|||
impl EnvGuard {
|
||||
fn acquire() -> Self {
|
||||
let lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let prev_home = std::env::var("INSOMNIA_HOME").ok();
|
||||
let prev_home = std::env::var("YOI_HOME").ok();
|
||||
let prev_xdg = std::env::var("XDG_RUNTIME_DIR").ok();
|
||||
unsafe {
|
||||
std::env::remove_var("INSOMNIA_HOME");
|
||||
std::env::remove_var("YOI_HOME");
|
||||
std::env::remove_var("XDG_RUNTIME_DIR");
|
||||
}
|
||||
Self {
|
||||
|
|
@ -50,29 +50,29 @@ impl Drop for EnvGuard {
|
|||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match &self.prev_home {
|
||||
Some(v) => std::env::set_var("INSOMNIA_HOME", v),
|
||||
None => std::env::remove_var("INSOMNIA_HOME"),
|
||||
Some(v) => std::env::set_var("YOI_HOME", v),
|
||||
None => std::env::remove_var("YOI_HOME"),
|
||||
}
|
||||
match &self.prev_xdg {
|
||||
Some(v) => std::env::set_var("XDG_RUNTIME_DIR", v),
|
||||
None => std::env::remove_var("XDG_RUNTIME_DIR"),
|
||||
}
|
||||
std::env::remove_var("INSOMNIA_RUNTIME_DIR");
|
||||
std::env::remove_var("YOI_RUNTIME_DIR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Point `INSOMNIA_RUNTIME_DIR` at `dir`. The pod-registry then lives at
|
||||
/// Point `YOI_RUNTIME_DIR` at `dir`. The pod-registry then lives at
|
||||
/// `<dir>/pods.json` and Pod runtime sub-dirs at `<dir>/{pod_name}/`.
|
||||
fn set_runtime_dir(dir: &std::path::Path) {
|
||||
unsafe {
|
||||
std::env::set_var("INSOMNIA_RUNTIME_DIR", dir);
|
||||
std::env::set_var("YOI_RUNTIME_DIR", dir);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_runtime_dir() {
|
||||
unsafe {
|
||||
std::env::remove_var("INSOMNIA_RUNTIME_DIR");
|
||||
std::env::remove_var("YOI_RUNTIME_DIR");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use std::sync::Arc;
|
|||
use tempfile::TempDir;
|
||||
use tokio::net::UnixListener;
|
||||
|
||||
/// Serialises tests that mutate `INSOMNIA_RUNTIME_DIR` across the
|
||||
/// Serialises tests that mutate `YOI_RUNTIME_DIR` across the
|
||||
/// thread-pooled test harness.
|
||||
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ impl EnvGuard {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set up a tempdir, point `INSOMNIA_RUNTIME_DIR` at it (so
|
||||
/// Set up a tempdir, point `YOI_RUNTIME_DIR` at it (so
|
||||
/// `pods.json` and per-Pod runtime subdirs both land in the
|
||||
/// sandbox), and install a live top-level "spawner" allocation so the
|
||||
/// tool has something to delegate from. Returns the tempdir (keeps it
|
||||
|
|
@ -57,9 +57,9 @@ async fn setup_spawner(
|
|||
unsafe {
|
||||
// Outranking env vars must be cleared so `paths::runtime_dir`
|
||||
// resolves to our sandbox instead of the developer's real one.
|
||||
std::env::remove_var("INSOMNIA_HOME");
|
||||
std::env::remove_var("YOI_HOME");
|
||||
std::env::remove_var("XDG_RUNTIME_DIR");
|
||||
std::env::set_var("INSOMNIA_RUNTIME_DIR", &runtime_base);
|
||||
std::env::set_var("YOI_RUNTIME_DIR", &runtime_base);
|
||||
}
|
||||
|
||||
let spawner_rd = RuntimeDir::create(&runtime_base, spawner_name)
|
||||
|
|
@ -209,7 +209,7 @@ fn shared_scope_for(allow_root: &Path) -> SharedScope {
|
|||
|
||||
fn clear_env() {
|
||||
unsafe {
|
||||
std::env::remove_var("INSOMNIA_RUNTIME_DIR");
|
||||
std::env::remove_var("YOI_RUNTIME_DIR");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1258,7 +1258,7 @@ mod tests {
|
|||
let method = Method::PodEvent(PodEvent::ScopeSubDelegated {
|
||||
parent_pod: "child".into(),
|
||||
sub_pod: "grandchild".into(),
|
||||
sub_socket: "/run/insomnia/grandchild/sock".into(),
|
||||
sub_socket: "/run/yoi/grandchild/sock".into(),
|
||||
scope: vec![ScopeRule {
|
||||
target: "/tmp/work".into(),
|
||||
permission: Permission::Write,
|
||||
|
|
@ -1276,7 +1276,7 @@ mod tests {
|
|||
}) => {
|
||||
assert_eq!(parent_pod, "child");
|
||||
assert_eq!(sub_pod, "grandchild");
|
||||
assert_eq!(sub_socket, PathBuf::from("/run/insomnia/grandchild/sock"));
|
||||
assert_eq!(sub_socket, PathBuf::from("/run/yoi/grandchild/sock"));
|
||||
assert_eq!(scope.len(), 1);
|
||||
assert_eq!(scope[0].target, PathBuf::from("/tmp/work"));
|
||||
assert_eq!(scope[0].permission, Permission::Write);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
## 責務
|
||||
|
||||
- プロバイダ / モデルカタログの builtin (`resources/{providers,models}/builtin.toml`) と user override (`$XDG_CONFIG_HOME/insomnia/{providers,models}.toml`) の解決
|
||||
- プロバイダ / モデルカタログの builtin (`resources/{providers,models}/builtin.toml`) と user override (`$XDG_CONFIG_HOME/yoi/{providers,models}.toml`) の解決
|
||||
- `ModelManifest` の ref 形を `(provider, model_id)` に split し、`ModelConfig` へ展開
|
||||
- `AuthRef::SecretRef` / `AuthRef::ApiKey` を `ResolvedAuth::ApiKey` に解決(通常は local secret store、低レベル manifest では明示ファイルも可)
|
||||
- `AuthRef::None` / `AuthRef::CodexOAuth` の解決
|
||||
|
|
|
|||
|
|
@ -636,9 +636,9 @@ auth_hint = { kind = "none" }
|
|||
assert!(matches!(err, CatalogError::Parse { .. }));
|
||||
}
|
||||
|
||||
/// `INSOMNIA_CONFIG_DIR` を tempdir に向けるテストガード。
|
||||
/// `paths::config_dir` は他の env (INSOMNIA_HOME / XDG_CONFIG_HOME)
|
||||
/// より高優先で `INSOMNIA_CONFIG_DIR` を尊重するため、これだけで
|
||||
/// `YOI_CONFIG_DIR` を tempdir に向けるテストガード。
|
||||
/// `paths::config_dir` は他の env (YOI_HOME / XDG_CONFIG_HOME)
|
||||
/// より高優先で `YOI_CONFIG_DIR` を尊重するため、これだけで
|
||||
/// 開発機の env 設定に左右されないテストになる。
|
||||
struct ConfigDirGuard {
|
||||
prev: Option<String>,
|
||||
|
|
@ -646,10 +646,10 @@ auth_hint = { kind = "none" }
|
|||
|
||||
impl ConfigDirGuard {
|
||||
fn new(path: &Path) -> Self {
|
||||
let prev = std::env::var("INSOMNIA_CONFIG_DIR").ok();
|
||||
let prev = std::env::var("YOI_CONFIG_DIR").ok();
|
||||
// SAFETY: serial_test の `#[serial]` 属性で env を弄るテスト
|
||||
// 同士は直列化される。
|
||||
unsafe { std::env::set_var("INSOMNIA_CONFIG_DIR", path) };
|
||||
unsafe { std::env::set_var("YOI_CONFIG_DIR", path) };
|
||||
Self { prev }
|
||||
}
|
||||
}
|
||||
|
|
@ -658,8 +658,8 @@ auth_hint = { kind = "none" }
|
|||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
match &self.prev {
|
||||
Some(v) => std::env::set_var("INSOMNIA_CONFIG_DIR", v),
|
||||
None => std::env::remove_var("INSOMNIA_CONFIG_DIR"),
|
||||
Some(v) => std::env::set_var("YOI_CONFIG_DIR", v),
|
||||
None => std::env::remove_var("YOI_CONFIG_DIR"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! `~/.codex/auth.json` の読み書き。
|
||||
//!
|
||||
//! Codex CLI と schema を共有するが、insomnia は知らないフィールドを
|
||||
//! Codex CLI と schema を共有するが、yoi は知らないフィールドを
|
||||
//! 失わないようファイル全体を `serde_json::Value` で保持し、必要箇所
|
||||
//! のみアクセスする。書込は `mode 0o600` を再設定(Codex CLI 同様)、
|
||||
//! ファイルロックは取らない(manager 側で guarded reload)。
|
||||
|
|
@ -106,7 +106,7 @@ pub async fn load(path: &Path) -> Result<AuthSnapshot, CodexAuthError> {
|
|||
/// 既存ファイルを再読込し、`tokens.{id_token,access_token,refresh_token}` と
|
||||
/// `last_refresh` を更新して書き戻す。Codex CLI の `persist_tokens` 相当。
|
||||
///
|
||||
/// 並行する Codex CLI / 別 insomnia プロセスが先に refresh していた場合の
|
||||
/// 並行する Codex CLI / 別 yoi プロセスが先に refresh していた場合の
|
||||
/// fields を保護するため、書込前に再 load して merge する。
|
||||
pub async fn persist_refreshed(
|
||||
path: &Path,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
//! [`CodexAuthProvider`] はこのクレートに置く(feedback_llm_worker_scope)
|
||||
//! - access_token JWT の `exp` を読み、`now` 以下で proactive refresh
|
||||
//! (Codex CLI と同じバッファなし)
|
||||
//! - 並行する Codex CLI / 別 insomnia の refresh と取り違えないよう、
|
||||
//! - 並行する Codex CLI / 別 yoi の refresh と取り違えないよう、
|
||||
//! refresh 直前に再 load して account_id 一致を確認(guarded reload)
|
||||
//! - ファイルロックは取らず、書込前に再 load + diff merge で吸収
|
||||
//! - Codex の Keyring storage は対象外。auth.json 不在ならエラーで案内
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl SecretResolver for DefaultSecretResolver {
|
|||
path: std::path::PathBuf::from("<data_dir>"),
|
||||
source: std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"could not determine insomnia data directory",
|
||||
"could not determine yoi data directory",
|
||||
),
|
||||
})?;
|
||||
SecretStore::new(data_dir).get(id)
|
||||
|
|
|
|||
|
|
@ -305,7 +305,7 @@ pub fn validate_id(id: &str) -> Result<()> {
|
|||
|
||||
fn derive_key(data_dir: &Path) -> [u8; KEY_LEN] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"insomnia local secret store obfuscation key v1");
|
||||
hasher.update(b"yoi local secret store obfuscation key v1");
|
||||
hasher.update(data_dir.as_os_str().as_encoded_bytes());
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
|
@ -339,7 +339,7 @@ fn make_nonce(id: &str, plaintext: &[u8]) -> Vec<u8> {
|
|||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos();
|
||||
hasher.update(b"insomnia nonce v1");
|
||||
hasher.update(b"yoi nonce v1");
|
||||
hasher.update(now.to_le_bytes());
|
||||
hasher.update(std::process::id().to_le_bytes());
|
||||
hasher.update(NONCE_COUNTER.fetch_add(1, Ordering::Relaxed).to_le_bytes());
|
||||
|
|
@ -353,7 +353,7 @@ fn xor_stream(key: &[u8; KEY_LEN], nonce: &[u8], input: &[u8]) -> Vec<u8> {
|
|||
let mut counter = 0u64;
|
||||
for chunk in input.chunks(KEY_LEN) {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"insomnia secret keystream v1");
|
||||
hasher.update(b"yoi secret keystream v1");
|
||||
hasher.update(key);
|
||||
hasher.update(nonce);
|
||||
hasher.update(counter.to_le_bytes());
|
||||
|
|
@ -368,7 +368,7 @@ fn xor_stream(key: &[u8; KEY_LEN], nonce: &[u8], input: &[u8]) -> Vec<u8> {
|
|||
|
||||
fn tag(key: &[u8; KEY_LEN], id: &str, nonce: &[u8], ciphertext: &[u8]) -> [u8; TAG_LEN] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"insomnia secret tag v1");
|
||||
hasher.update(b"yoi secret tag v1");
|
||||
hasher.update(key);
|
||||
hasher.update(id.as_bytes());
|
||||
hasher.update(nonce);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
//!
|
||||
//! Migration: this layout is incompatible with the pre-`session-grouping`
|
||||
//! flat `{root}/{segment_id}.jsonl` form. Project policy is no
|
||||
//! backward compatibility — discard `~/.insomnia/sessions/` (or whatever
|
||||
//! backward compatibility — discard `~/.yoi/sessions/` (or whatever
|
||||
//! `root` resolved to) before running the new code. `list_sessions`
|
||||
//! ignores top-level files outside session directories, so leftover
|
||||
//! flat files do not corrupt new sessions, but they are no longer
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ impl Tool for BashTool {
|
|||
// close before bash itself exits.
|
||||
// exit $__exit propagate the user's exit
|
||||
let wrapped = format!(
|
||||
"exec >{out} 2>&1\n{{ {user_cmd}\n}}\n__insomnia_exit=$?\nwait 2>/dev/null\nexit $__insomnia_exit\n",
|
||||
"exec >{out} 2>&1\n{{ {user_cmd}\n}}\n__yoi_exit=$?\nwait 2>/dev/null\nexit $__yoi_exit\n",
|
||||
out = shell_single_quote(output_path_str),
|
||||
user_cmd = params.command,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Built-in tools for the Insomnia LLM agent.
|
||||
//! Built-in tools for the Yoi LLM agent.
|
||||
//!
|
||||
//! Implements Read / Write / Edit / Glob / Grep / Bash on top of the
|
||||
//! `llm-worker` `Tool` infrastructure. Filesystem access is mediated by
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
//! let scope = Scope::writable("/workspace").unwrap();
|
||||
//! let fs = ScopedFs::new(scope, PathBuf::from("/workspace")); // pod lifetime
|
||||
//! let tracker = Tracker::new(); // session lifetime
|
||||
//! let bash_outputs = PathBuf::from("/run/insomnia/bash-output");
|
||||
//! let bash_outputs = PathBuf::from("/run/yoi/bash-output");
|
||||
//! let task_store = tools::TaskStore::new();
|
||||
//! let defs = builtin_tools(fs, tracker, task_store, bash_outputs, None);
|
||||
//! ```
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ impl WebTools {
|
|||
pub fn new(config: Option<WebConfig>) -> Self {
|
||||
let client = Client::builder()
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.user_agent("insomnia-web-tools/0.1")
|
||||
.user_agent("yoi-web-tools/0.1")
|
||||
.build()
|
||||
.expect("static reqwest client configuration is valid");
|
||||
let secret_store = manifest::paths::data_dir().map(SecretStore::new);
|
||||
|
|
@ -249,7 +249,7 @@ async fn brave_search(
|
|||
let api_key_secret = cfg.api_key_secret.as_ref().ok_or_else(|| {
|
||||
disabled_error(
|
||||
"WebSearch",
|
||||
"set web.search.api_key_secret to the insomnia keys secret id for the Brave API key",
|
||||
"set web.search.api_key_secret to the yoi keys secret id for the Brave API key",
|
||||
)
|
||||
})?;
|
||||
let store = secret_store.ok_or_else(|| {
|
||||
|
|
@ -1783,7 +1783,7 @@ mod tests {
|
|||
);
|
||||
let search_err = tools
|
||||
.run_search(WebSearchInput {
|
||||
query: "insomnia".into(),
|
||||
query: "yoi".into(),
|
||||
limit: None,
|
||||
offset: None,
|
||||
})
|
||||
|
|
@ -2099,7 +2099,7 @@ mod tests {
|
|||
);
|
||||
let result = tools
|
||||
.run_search(WebSearchInput {
|
||||
query: "insomnia".into(),
|
||||
query: "yoi".into(),
|
||||
limit: Some(1),
|
||||
offset: Some(0),
|
||||
})
|
||||
|
|
@ -2126,12 +2126,12 @@ mod tests {
|
|||
fetch: None,
|
||||
}));
|
||||
let cfg = brave_search_config(format!("http://{addr}/search"));
|
||||
let result = brave_search_with_api_key(&tools.client, &cfg, "test-key", "insomnia", 1, 0)
|
||||
let result = brave_search_with_api_key(&tools.client, &cfg, "test-key", "yoi", 1, 0)
|
||||
.await
|
||||
.unwrap();
|
||||
let value: Value = serde_json::from_str(result.content.as_deref().unwrap()).unwrap();
|
||||
let request = captured.lock().await.clone().unwrap();
|
||||
assert!(request.starts_with("GET /search?q=insomnia&count=1&offset=0 "));
|
||||
assert!(request.starts_with("GET /search?q=yoi&count=1&offset=0 "));
|
||||
assert!(
|
||||
request
|
||||
.to_ascii_lowercase()
|
||||
|
|
@ -2158,7 +2158,7 @@ mod tests {
|
|||
fetch: None,
|
||||
}));
|
||||
let cfg = brave_search_config(format!("http://{addr}/search"));
|
||||
let err = brave_search_with_api_key(&tools.client, &cfg, "test-key", "insomnia", 1, 0)
|
||||
let err = brave_search_with_api_key(&tools.client, &cfg, "test-key", "yoi", 1, 0)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("Content-Length"));
|
||||
|
|
|
|||
|
|
@ -221,14 +221,14 @@ pub async fn launch() -> ExitCode {
|
|||
let data_dir = match manifest::paths::data_dir() {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
eprintln!("insomnia keys: could not determine insomnia data directory");
|
||||
eprintln!("yoi keys: could not determine yoi data directory");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
match run(SecretStore::new(data_dir)) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
eprintln!("insomnia keys: {err}");
|
||||
eprintln!("yoi keys: {err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
|
@ -391,7 +391,7 @@ fn draw(frame: &mut Frame<'_>, app: &KeysApp) {
|
|||
fn title_line(app: &KeysApp) -> Line<'_> {
|
||||
Line::from(vec![
|
||||
Span::styled(
|
||||
"insomnia keys local secrets",
|
||||
"yoi keys local secrets",
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(" "),
|
||||
|
|
|
|||
|
|
@ -38,20 +38,20 @@ pub enum LaunchMode {
|
|||
Spawn {
|
||||
profile: Option<String>,
|
||||
},
|
||||
/// `insomnia <name>` / `insomnia --pod <name>`: attach to a live Pod by name if
|
||||
/// `yoi <name>` / `yoi --pod <name>`: attach to a live Pod by name if
|
||||
/// possible; otherwise launch the Pod runtime command with `--pod <name>` so it
|
||||
/// resumes from name-keyed state or creates a fresh same-name Pod.
|
||||
PodName {
|
||||
pod_name: String,
|
||||
socket_override: Option<PathBuf>,
|
||||
},
|
||||
/// `insomnia -r` / `insomnia --resume`: open the Pod picker, then attach to the
|
||||
/// `yoi -r` / `yoi --resume`: open the Pod picker, then attach to the
|
||||
/// selected live Pod or restore the selected stopped Pod by name.
|
||||
Resume,
|
||||
/// `insomnia --session <UUID>`: skip the picker, go straight to the
|
||||
/// `yoi --session <UUID>`: skip the picker, go straight to the
|
||||
/// resume name dialog with `id` baked in.
|
||||
ResumeWithSession(SegmentId),
|
||||
/// `insomnia --multi`: open the multi-Pod dashboard. This is intentionally
|
||||
/// `yoi --multi`: open the multi-Pod dashboard. This is intentionally
|
||||
/// separate from `-r`/`--resume`, which keeps its single-Pod picker
|
||||
/// meaning.
|
||||
Multi,
|
||||
|
|
@ -64,12 +64,12 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
|
|||
} = options;
|
||||
|
||||
if let Err(e) = enable_raw_mode() {
|
||||
eprintln!("insomnia: failed to enter raw mode: {e}");
|
||||
eprintln!("yoi: failed to enter raw mode: {e}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
if let Err(e) = execute!(io::stdout(), EnableBracketedPaste) {
|
||||
let _ = disable_raw_mode();
|
||||
eprintln!("insomnia: {e}");
|
||||
eprintln!("yoi: {e}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
|
|||
// duplicate. Other errors (pod-name failures, terminal setup
|
||||
// hiccups, etc.) need surfacing here.
|
||||
if e.downcast_ref::<spawn::SpawnError>().is_none() {
|
||||
eprintln!("insomnia: {e}");
|
||||
eprintln!("yoi: {e}");
|
||||
}
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ impl std::fmt::Display for MultiPodError {
|
|||
Self::Store(e) => write!(f, "session store error: {e}"),
|
||||
Self::NoPods => write!(
|
||||
f,
|
||||
"no pods found — start a fresh pod with `insomnia` or restore one with `insomnia -r`"
|
||||
"no pods found — start a fresh pod with `yoi` or restore one with `yoi -r`"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ impl std::fmt::Display for PickerError {
|
|||
Self::Store(e) => write!(f, "session store error: {e}"),
|
||||
Self::NoPods => write!(
|
||||
f,
|
||||
"no pods found — start a fresh pod with `insomnia` and try again"
|
||||
"no pods found — start a fresh pod with `yoi` and try again"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +169,7 @@ fn default_store_dir() -> Result<PathBuf, PickerError> {
|
|||
PickerError::Io(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"could not resolve sessions directory \
|
||||
(set INSOMNIA_HOME, INSOMNIA_DATA_DIR, or HOME)",
|
||||
(set YOI_HOME, YOI_DATA_DIR, or HOME)",
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ fn default_pod_store_dir() -> Result<PathBuf, PickerError> {
|
|||
PickerError::Io(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"could not resolve pod state directory \
|
||||
(set INSOMNIA_HOME, INSOMNIA_DATA_DIR, or HOME)",
|
||||
(set YOI_HOME, YOI_DATA_DIR, or HOME)",
|
||||
))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ fn resolve_socket(pod_name: &str, override_path: Option<PathBuf>) -> PathBuf {
|
|||
}
|
||||
manifest::paths::pod_socket_path(pod_name).unwrap_or_else(|| {
|
||||
PathBuf::from("/tmp")
|
||||
.join("insomnia")
|
||||
.join("yoi")
|
||||
.join(pod_name)
|
||||
.join("sock")
|
||||
})
|
||||
|
|
@ -307,7 +307,7 @@ impl TerminalEventReader {
|
|||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let thread_stop = Arc::clone(&stop);
|
||||
let thread = thread::Builder::new()
|
||||
.name("insomnia-tui-terminal-reader".to_string())
|
||||
.name("yoi-tui-terminal-reader".to_string())
|
||||
.spawn(move || read_terminal_events(thread_stop, tx))?;
|
||||
|
||||
Ok((
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
//! Inline-viewport "spawn Pod and attach" UX.
|
||||
//!
|
||||
//! Rendered at the user's current cursor position when `insomnia` is invoked
|
||||
//! with no positional argument. Discovers `.insomnia/profiles.toml` profile
|
||||
//! Rendered at the user's current cursor position when `yoi` is invoked
|
||||
//! with no positional argument. Discovers `.yoi/profiles.toml` profile
|
||||
//! choices plus bundled profiles, defaults to the builtin profile, prompts for
|
||||
//! the Pod's name, and on confirmation launches the Pod runtime command as an
|
||||
//! independent process. Once the process reports its socket via the
|
||||
//! `INSOMNIA-READY` stderr line, the dialog hands control back so main can
|
||||
//! `YOI-READY` stderr line, the dialog hands control back so main can
|
||||
//! switch the terminal to alternate-screen mode.
|
||||
//!
|
||||
//! The viewport's last frame stays in the terminal's scrollback so the
|
||||
|
|
@ -654,10 +654,10 @@ mod tests {
|
|||
fn profile_choices_use_project_registry_default() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let project = temp.path().join("project");
|
||||
let insomnia = project.join(".insomnia");
|
||||
std::fs::create_dir_all(&insomnia).unwrap();
|
||||
let yoi = project.join(".yoi");
|
||||
std::fs::create_dir_all(&yoi).unwrap();
|
||||
std::fs::write(
|
||||
insomnia.join("profiles.toml"),
|
||||
yoi.join("profiles.toml"),
|
||||
r#"
|
||||
default = "coder"
|
||||
[profile]
|
||||
|
|
@ -678,10 +678,10 @@ coder = "profiles/coder.lua"
|
|||
fn profile_choices_include_builtin_and_project_default_marker() {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let project = temp.path().join("project");
|
||||
let insomnia = project.join(".insomnia");
|
||||
std::fs::create_dir_all(&insomnia).unwrap();
|
||||
let yoi = project.join(".yoi");
|
||||
std::fs::create_dir_all(&yoi).unwrap();
|
||||
std::fs::write(
|
||||
insomnia.join("profiles.toml"),
|
||||
yoi.join("profiles.toml"),
|
||||
r#"
|
||||
default = "coder"
|
||||
[profile.coder]
|
||||
|
|
@ -695,7 +695,7 @@ description = "Project coder"
|
|||
assert_eq!(choices[0].selector.as_deref(), Some("builtin:default"));
|
||||
assert_eq!(
|
||||
choices[0].label,
|
||||
"builtin:default — Bundled default Insomnia coding profile"
|
||||
"builtin:default — Bundled default Yoi coding profile"
|
||||
);
|
||||
assert_eq!(default_index, 1);
|
||||
assert_eq!(choices[1].selector.as_deref(), Some("project:coder"));
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ mod tests {
|
|||
fn workflow_lint_accepts_valid_file() {
|
||||
let (dir, linter) = workspace();
|
||||
write(
|
||||
&dir.path().join(".insomnia/knowledge/policy.md"),
|
||||
&dir.path().join(".yoi/knowledge/policy.md"),
|
||||
"---\ndescription: p\n---\nbody",
|
||||
);
|
||||
let wf = "---\ndescription: run\nrequires: [policy]\n---\nbody";
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use manifest::{Permission, ScopeRule};
|
|||
use memory::WorkspaceLayout;
|
||||
|
||||
/// Build deny rules that strip Write permission from
|
||||
/// `<workspace>/.insomnia/workflow/` for generic CRUD tools.
|
||||
/// `<workspace>/.yoi/workflow/` for generic CRUD tools.
|
||||
pub fn deny_write_rules(layout: &WorkspaceLayout) -> Vec<ScopeRule> {
|
||||
vec![deny_write(layout.workflow_dir().as_path())]
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ mod tests {
|
|||
let layout = WorkspaceLayout::new(PathBuf::from("/ws"));
|
||||
let rules = deny_write_rules(&layout);
|
||||
assert_eq!(rules.len(), 1);
|
||||
assert_eq!(rules[0].target, PathBuf::from("/ws/.insomnia/workflow"));
|
||||
assert_eq!(rules[0].target, PathBuf::from("/ws/.yoi/workflow"));
|
||||
assert_eq!(rules[0].permission, Permission::Write);
|
||||
assert!(rules[0].recursive);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
//! Skills follow the [agentskills.io](https://agentskills.io/specification)
|
||||
//! spec: a directory `<root>/<name>/` containing `SKILL.md` (YAML frontmatter
|
||||
//! + Markdown body) and optional `scripts/` / `references/` / `assets/`
|
||||
//! subdirectories. The body is procedural agent guidance; insomnia ingests
|
||||
//! subdirectories. The body is procedural agent guidance; yoi ingests
|
||||
//! it as a Workflow so `/<name>` resolves to it just like an internal
|
||||
//! Workflow.
|
||||
//!
|
||||
//! Parsing is intentionally lenient at the directory-scan level — one
|
||||
//! malformed SKILL.md emits `tracing::warn!` and is skipped, leaving sibling
|
||||
//! skills loadable. Internal Workflows (`.insomnia/workflow/<slug>.md`) keep
|
||||
//! skills loadable. Internal Workflows (`.yoi/workflow/<slug>.md`) keep
|
||||
//! their hard-error semantics.
|
||||
|
||||
use std::io;
|
||||
|
|
@ -30,7 +30,7 @@ pub const SKILL_FILENAME: &str = "SKILL.md";
|
|||
/// SKILL.md frontmatter as defined by the agent-skills spec.
|
||||
///
|
||||
/// Fields beyond `name` / `description` are accepted to be spec-compatible
|
||||
/// but not used by insomnia today: `license`, `compatibility`, and
|
||||
/// but not used by yoi today: `license`, `compatibility`, and
|
||||
/// `metadata` are documentary, while `allowed-tools` is recognised and
|
||||
/// emits a warning until [`permission-extension-point.md`] lands.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Workflow loader and registry.
|
||||
//!
|
||||
//! Workflows live under `<workspace>/.insomnia/workflow/<slug>.md`. They are
|
||||
//! Workflows live under `<workspace>/.yoi/workflow/<slug>.md`. They are
|
||||
//! human-authored Markdown documents with YAML frontmatter. The loader is
|
||||
//! intentionally strict about malformed records because Pod startup should
|
||||
//! fail rather than silently ignoring a broken procedural instruction.
|
||||
|
|
@ -27,7 +27,7 @@ pub const WORKFLOW_DESCRIPTION_HARD_CAP: usize = 1024;
|
|||
/// win over external skills.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum WorkflowSource {
|
||||
/// `<workspace>/.insomnia/workflow/<slug>.md`. Authored in-tree by
|
||||
/// `<workspace>/.yoi/workflow/<slug>.md`. Authored in-tree by
|
||||
/// the project.
|
||||
WorkspaceWorkflow,
|
||||
/// SKILL.md ingested from a `[skills] directories` entry in the
|
||||
|
|
@ -316,13 +316,13 @@ mod tests {
|
|||
|
||||
fn setup() -> (TempDir, WorkspaceLayout) {
|
||||
let dir = TempDir::new().unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".insomnia/workflow")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".yoi/workflow")).unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
(dir, layout)
|
||||
}
|
||||
|
||||
fn write_workflow(root: &Path, slug: &str, frontmatter: &str, body: &str) {
|
||||
let path = root.join(".insomnia/workflow").join(format!("{slug}.md"));
|
||||
let path = root.join(".yoi/workflow").join(format!("{slug}.md"));
|
||||
std::fs::write(path, format!("---\n{frontmatter}\n---\n{body}")).unwrap();
|
||||
}
|
||||
|
||||
|
|
@ -380,12 +380,12 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn workflow_under_memory_is_ignored() {
|
||||
// The legacy `.insomnia/memory/workflow/` location is no longer
|
||||
// The legacy `.yoi/memory/workflow/` location is no longer
|
||||
// a Workflow source. Files placed there must be ignored (the
|
||||
// loader is rooted at `.insomnia/workflow/` only).
|
||||
// loader is rooted at `.yoi/workflow/` only).
|
||||
let dir = TempDir::new().unwrap();
|
||||
let layout = WorkspaceLayout::new(dir.path().to_path_buf());
|
||||
let legacy = dir.path().join(".insomnia/memory/workflow");
|
||||
let legacy = dir.path().join(".yoi/memory/workflow");
|
||||
std::fs::create_dir_all(&legacy).unwrap();
|
||||
std::fs::write(
|
||||
legacy.join("ghost.md"),
|
||||
|
|
@ -509,7 +509,7 @@ mod tests {
|
|||
let s = ShadowedSkill {
|
||||
slug: Slug::parse("x").unwrap(),
|
||||
kept_source: WorkflowSource::WorkspaceWorkflow,
|
||||
kept_path: std::path::PathBuf::from("/ws/.insomnia/workflow/x.md"),
|
||||
kept_path: std::path::PathBuf::from("/ws/.yoi/workflow/x.md"),
|
||||
shadowed_source: WorkflowSource::Skill {
|
||||
dir: std::path::PathBuf::from("/skills"),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "insomnia"
|
||||
name = "yoi"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
|
@ -7,6 +7,7 @@ license.workspace = true
|
|||
[dependencies]
|
||||
client = { workspace = true }
|
||||
memory = { workspace = true }
|
||||
manifest = { workspace = true }
|
||||
pod = { workspace = true }
|
||||
session-store = { workspace = true }
|
||||
tui = { workspace = true }
|
||||
|
|
@ -35,8 +35,8 @@ async fn main() -> ExitCode {
|
|||
let mode = match parse_args() {
|
||||
Ok(mode) => mode,
|
||||
Err(e) => {
|
||||
eprintln!("insomnia: {e}");
|
||||
eprintln!("try `insomnia --help` for usage.");
|
||||
eprintln!("yoi: {e}");
|
||||
eprintln!("try `yoi --help` for usage.");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
|
@ -54,17 +54,17 @@ async fn main() -> ExitCode {
|
|||
Ok(LintStatus::Clean) => ExitCode::SUCCESS,
|
||||
Ok(LintStatus::Failed) => ExitCode::FAILURE,
|
||||
Err(e) => {
|
||||
eprintln!("insomnia memory lint: {e}");
|
||||
eprintln!("yoi memory lint: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
},
|
||||
Mode::PodRuntime(args) => pod::entrypoint::run_cli_from("insomnia pod", args).await,
|
||||
Mode::PodRuntime(args) => pod::entrypoint::run_cli_from("yoi pod", args).await,
|
||||
Mode::Keys => tui::keys::launch().await,
|
||||
Mode::Tui(mode) => {
|
||||
let runtime_command = match PodRuntimeCommand::resolve() {
|
||||
Ok(command) => command,
|
||||
Err(e) => {
|
||||
eprintln!("insomnia: failed to resolve Pod runtime command: {e}");
|
||||
eprintln!("yoi: failed to resolve Pod runtime command: {e}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
|
|
@ -100,7 +100,7 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
|
|||
"pod" => return Ok(Mode::PodRuntime(args[1..].to_vec())),
|
||||
"keys" => {
|
||||
if args.len() != 1 {
|
||||
return Err(ParseError("insomnia keys does not accept arguments".into()));
|
||||
return Err(ParseError("yoi keys does not accept arguments".into()));
|
||||
}
|
||||
return Ok(Mode::Keys);
|
||||
}
|
||||
|
|
@ -322,13 +322,13 @@ fn parse_session_id(value: &str) -> Result<SegmentId, ParseError> {
|
|||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"insomnia\n\nUsage:\n insomnia [OPTIONS] [POD_NAME]\n insomnia keys\n insomnia pod [POD_OPTIONS]\n insomnia memory lint [OPTIONS]\n\nOptions:\n -r, --resume Open the Pod picker and resume/attach a Pod\n --multi Open the multi-Pod dashboard\n --pod <NAME> Attach/restore/create a Pod by name\n --socket <PATH> Attach to a specific Pod socket with --pod\n --session <UUID> Resume a specific session segment\n --profile <REF> Start a fresh Pod from a profile\n -h, --help Print help\n"
|
||||
"yoi\n\nUsage:\n yoi [OPTIONS] [POD_NAME]\n yoi keys\n yoi pod [POD_OPTIONS]\n yoi memory lint [OPTIONS]\n\nOptions:\n -r, --resume Open the Pod picker and resume/attach a Pod\n --multi Open the multi-Pod dashboard\n --pod <NAME> Attach/restore/create a Pod by name\n --socket <PATH> Attach to a specific Pod socket with --pod\n --session <UUID> Resume a specific session segment\n --profile <REF> Start a fresh Pod from a profile\n -h, --help Print help\n"
|
||||
);
|
||||
}
|
||||
|
||||
fn print_memory_lint_help() {
|
||||
println!(
|
||||
"insomnia memory lint\n\nUsage:\n insomnia memory lint [OPTIONS]\n\nOptions:\n --workspace <PATH> Workspace root to lint (defaults to cwd)\n --json Emit a JSON report\n --warnings-as-errors Return failure when warnings are present\n -h, --help Print help\n"
|
||||
"yoi memory lint\n\nUsage:\n yoi memory lint [OPTIONS]\n\nOptions:\n --workspace <PATH> Workspace root to lint (defaults to cwd)\n --json Emit a JSON report\n --warnings-as-errors Return failure when warnings are present\n -h, --help Print help\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -524,7 +524,7 @@ mod tests {
|
|||
"--profile".to_string(),
|
||||
"p.lua".to_string(),
|
||||
"--socket".to_string(),
|
||||
"/tmp/insomnia/sock".to_string(),
|
||||
"/tmp/yoi/sock".to_string(),
|
||||
],
|
||||
"--profile can only be used for fresh spawn",
|
||||
),
|
||||
|
|
@ -416,19 +416,16 @@ mod tests {
|
|||
fn lints_only_workspace_memory_and_knowledge_records() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path();
|
||||
write(&root.join(".insomnia/memory/summary.md"), valid_summary());
|
||||
write(&root.join(".yoi/memory/summary.md"), valid_summary());
|
||||
write(
|
||||
&root.join(".insomnia/memory/requests/request-one.md"),
|
||||
&root.join(".yoi/memory/requests/request-one.md"),
|
||||
valid_request(),
|
||||
);
|
||||
write(
|
||||
&root.join(".insomnia/memory/_logs/ignored.md"),
|
||||
"not frontmatter",
|
||||
);
|
||||
write(
|
||||
&root.join(".insomnia/workflow/ignored.md"),
|
||||
&root.join(".yoi/memory/_logs/ignored.md"),
|
||||
"not frontmatter",
|
||||
);
|
||||
write(&root.join(".yoi/workflow/ignored.md"), "not frontmatter");
|
||||
|
||||
let report = lint_workspace(root).unwrap();
|
||||
assert_eq!(
|
||||
|
|
@ -438,8 +435,8 @@ mod tests {
|
|||
.map(|file| file.path.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
".insomnia/memory/requests/request-one.md",
|
||||
".insomnia/memory/summary.md",
|
||||
".yoi/memory/requests/request-one.md",
|
||||
".yoi/memory/summary.md",
|
||||
]
|
||||
);
|
||||
assert_eq!(report.counts.files, 2);
|
||||
|
|
@ -450,10 +447,7 @@ mod tests {
|
|||
fn invalid_records_count_as_lint_failures() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path();
|
||||
write(
|
||||
&root.join(".insomnia/memory/summary.md"),
|
||||
"missing frontmatter",
|
||||
);
|
||||
write(&root.join(".yoi/memory/summary.md"), "missing frontmatter");
|
||||
|
||||
let report = lint_workspace(root).unwrap();
|
||||
assert_eq!(report.counts.files, 1);
|
||||
|
|
@ -467,7 +461,7 @@ mod tests {
|
|||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path();
|
||||
write(
|
||||
&root.join(".insomnia/memory/requests/large-record.md"),
|
||||
&root.join(".yoi/memory/requests/large-record.md"),
|
||||
&warning_request(),
|
||||
);
|
||||
|
||||
|
|
@ -492,7 +486,7 @@ mod tests {
|
|||
fn json_output_is_machine_readable() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path();
|
||||
write(&root.join(".insomnia/memory/summary.md"), valid_summary());
|
||||
write(&root.join(".yoi/memory/summary.md"), valid_summary());
|
||||
|
||||
let mut output = Vec::new();
|
||||
let status = run_with_writer(
|
||||
|
|
@ -509,7 +503,7 @@ mod tests {
|
|||
let parsed: Value = serde_json::from_slice(&output).unwrap();
|
||||
assert_eq!(parsed["workspace"], root.display().to_string());
|
||||
assert_eq!(parsed["counts"]["files"], 1);
|
||||
assert_eq!(parsed["files"][0]["path"], ".insomnia/memory/summary.md");
|
||||
assert_eq!(parsed["files"][0]["path"], ".yoi/memory/summary.md");
|
||||
assert!(parsed["files"][0]["errors"].as_array().unwrap().is_empty());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Insomnia アーキテクチャ
|
||||
# Yoi アーキテクチャ
|
||||
|
||||
## プロジェクトの目的
|
||||
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
### 宣言した層が解決する
|
||||
|
||||
ある層が構成を宣言として受け取ったなら、その解決もその層の責務。マニフェストに `[model] ref = "anthropic/claude-sonnet-4-6"`(あるいは `scheme = "anthropic"` + `model_id = ...` の inline 形式)と書いた以上、`ModelManifest` → `LlmClient` の変換は insomnia 側(`crates/provider`)が行う。逆に llm-worker が `LlmClient` trait だけを受け取るのは正しい — llm-worker はプロバイダの選択を宣言として受け取っていないから。
|
||||
ある層が構成を宣言として受け取ったなら、その解決もその層の責務。マニフェストに `[model] ref = "anthropic/claude-sonnet-4-6"`(あるいは `scheme = "anthropic"` + `model_id = ...` の inline 形式)と書いた以上、`ModelManifest` → `LlmClient` の変換は yoi 側(`crates/provider`)が行う。逆に llm-worker が `LlmClient` trait だけを受け取るのは正しい — llm-worker はプロバイダの選択を宣言として受け取っていないから。
|
||||
|
||||
### 概念の追加は不在が問題になってから
|
||||
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
### 最小の構造化で最大の自由度
|
||||
|
||||
insomnia は環境再現・コンテナ管理・VCS 統合などを自身の責務としない。Pod が動くホストの fs 上で活動する主体を提供し、それにコンテキストを与えてスポーンさせられる仕組みを付与する。構築する環境はユーザー次第。
|
||||
yoi は環境再現・コンテナ管理・VCS 統合などを自身の責務としない。Pod が動くホストの fs 上で活動する主体を提供し、それにコンテキストを与えてスポーンさせられる仕組みを付与する。構築する環境はユーザー次第。
|
||||
|
||||
## Pod
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ name = "agent"
|
|||
ref = "anthropic/claude-sonnet-4-6"
|
||||
|
||||
[worker]
|
||||
instruction = "$insomnia/default"
|
||||
instruction = "$yoi/default"
|
||||
max_tokens = 4096
|
||||
temperature = 0.3
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ permission = "write"
|
|||
|
||||
通常の Pod 起動は Lua profile discovery/default から `PodManifest` を生成する。bundled `builtin:default` が fallback default で、user/project `profiles.toml` は profile registry と default selection だけを担う。user/project `manifest.toml` の ambient cascade は通常起動では使わない。
|
||||
|
||||
`insomnia pod --manifest <PATH>` は explicit one-file compatibility/debug input で、指定 TOML 1 枚だけに builtin defaults を merge し、`PodManifestConfig -> PodManifest` の required validation を通す。
|
||||
`yoi pod --manifest <PATH>` は explicit one-file compatibility/debug input で、指定 TOML 1 枚だけに builtin defaults を merge し、`PodManifestConfig -> PodManifest` の required validation を通す。
|
||||
|
||||
`PodFactory` の user/project/overlay API は低レベル構成部品として残るが、CLI の通常起動 path では generic TOML overlay を公開しない。
|
||||
|
||||
|
|
@ -111,11 +111,11 @@ permission = "write"
|
|||
|
||||
`worker.instruction` はファイル参照。3 層の prefix addressing でプロンプト資産を解決:
|
||||
|
||||
- `$insomnia/...` — バイナリ同梱(`resources/prompts/`、`include_dir!` で埋め込み)
|
||||
- `$yoi/...` — バイナリ同梱(`resources/prompts/`、`include_dir!` で埋め込み)
|
||||
- `$user/...` — `<config_dir>/prompts/`(`manifest::paths` で解決)
|
||||
- `$workspace/...` — `<project>/.insomnia/prompts/`
|
||||
- `$workspace/...` — `<project>/.yoi/prompts/`
|
||||
|
||||
テンプレートは minijinja で評価。`{% include "$insomnia/common/tool-usage" %}` のようにプロンプト間で参照可能(prefix なしの include は現在のファイルからの相対解決)。
|
||||
テンプレートは minijinja で評価。`{% include "$yoi/common/tool-usage" %}` のようにプロンプト間で参照可能(prefix なしの include は現在のファイルからの相対解決)。
|
||||
|
||||
レンダリング結果の末尾に scope summary と AGENTS.md(あれば)がコード側で固定付加される。ユーザーテンプレートからはこれらに触れない。
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ If `Yoi` is adopted, the rename should cover at least:
|
|||
- docs, reports, AGENTS instructions, tickets, and release material;
|
||||
- socket/runtime path labels and diagnostics where user-visible.
|
||||
|
||||
Compatibility aliases should be considered separately, but the public identity should not preserve `insomnia` longer than necessary.
|
||||
The public identity should move cleanly to Yoi; do not preserve `insomnia` through compatibility aliases.
|
||||
|
||||
## Adoption checks
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# 環境変数ポリシー
|
||||
|
||||
INSOMNIA では、プロセス境界で本当に必要な場合を除き、環境変数の利用を避ける。新しい ambient な入力を増やすより、明示的な profile / manifest / config file / typed secret reference / CLI argument を優先する。
|
||||
Yoi では、プロセス境界で本当に必要な場合を除き、環境変数の利用を避ける。新しい ambient な入力を増やすより、明示的な profile / manifest / config file / typed secret reference / CLI argument を優先する。
|
||||
|
||||
それでも、path discovery、runtime directory、外部 provider の credential 慣習との移行互換のために、一部の環境変数はまだサポートしている。この文書に載せた通常 runtime 用の環境変数は公開 surface として扱う。ただし、fallback 変数は独立した設定項目ではなく、対応する main key の解決順の一部として扱う。開発・テスト都合だけの環境変数は、通常ユーザー向け configuration として扱わない明確な escape hatch に限る。
|
||||
それでも、path discovery、runtime directory、外部 provider の credential 慣習のために、一部の環境変数はまだサポートしている。この文書に載せた通常 runtime 用の環境変数は公開 surface として扱う。ただし、fallback 変数は独立した設定項目ではなく、対応する main key の解決順の一部として扱う。開発・テスト都合だけの環境変数は、通常ユーザー向け configuration として扱わない明確な escape hatch に限る。
|
||||
|
||||
## 原則
|
||||
|
||||
|
|
@ -19,10 +19,10 @@ Path 系の環境変数は論理的な key ごとに立項する。`XDG_*` や `
|
|||
|
||||
| 論理 key | Main env | Fallback / 解決順 | 用途と位置付け |
|
||||
| --- | --- | --- | --- |
|
||||
| `home` | `INSOMNIA_HOME` | なし | config / data / runtime をまとめて sandbox する root override。設定を細かく分けるより、test や isolated run ではまずこれを使う。 |
|
||||
| `config_dir` | `INSOMNIA_CONFIG_DIR` | `$INSOMNIA_HOME/config` → `$XDG_CONFIG_HOME/insomnia` → `$HOME/.config/insomnia` | 人が書く設定・override の置き場。`profiles.toml`、prompt override、model/provider override など。 |
|
||||
| `data_dir` | `INSOMNIA_DATA_DIR` | `$INSOMNIA_HOME` → `$HOME/.insomnia` | プログラムが書く永続データの置き場。session log、Pod metadata など、再起動後も restore / replay の根拠になるもの。通常ユーザー向けの primary knob ではなく、migration、test、isolated data store 用の advanced override。 |
|
||||
| `runtime_dir` | `INSOMNIA_RUNTIME_DIR` | `$INSOMNIA_HOME/run` → `$XDG_RUNTIME_DIR/insomnia` → `$HOME/.insomnia/run` | socket、pid/status file、live registry mirror など、再起動で捨ててよい runtime state の置き場。 |
|
||||
| `home` | `YOI_HOME` | なし | config / data / runtime をまとめて sandbox する root override。設定を細かく分けるより、test や isolated run ではまずこれを使う。 |
|
||||
| `config_dir` | `YOI_CONFIG_DIR` | `$YOI_HOME/config` → `$XDG_CONFIG_HOME/yoi` → `$HOME/.config/yoi` | 人が書く設定・override の置き場。`profiles.toml`、prompt override、model/provider override など。 |
|
||||
| `data_dir` | `YOI_DATA_DIR` | `$YOI_HOME` → `$HOME/.yoi` | プログラムが書く永続データの置き場。session log、Pod metadata など、再起動後も restore / replay の根拠になるもの。通常ユーザー向けの primary knob ではなく、test や isolated data store 用の advanced override。 |
|
||||
| `runtime_dir` | `YOI_RUNTIME_DIR` | `$YOI_HOME/run` → `$XDG_RUNTIME_DIR/yoi` → `$HOME/.yoi/run` | socket、pid/status file、live registry mirror など、再起動で捨ててよい runtime state の置き場。 |
|
||||
|
||||
空の path 環境変数は、`manifest::paths` では原則として unset 相当に扱う。
|
||||
|
||||
|
|
@ -36,11 +36,11 @@ Path 系の環境変数は論理的な key ごとに立項する。`XDG_*` や `
|
|||
|
||||
### Builtin assets と `config_dir`
|
||||
|
||||
Builtin profiles and catalogs are embedded in the binary at build time. User/project-owned overrides remain under `config_dir` and project `.insomnia/` files such as `profiles.toml`; package runtime resource lookup is not a supported configuration surface.
|
||||
Builtin profiles and catalogs are embedded in the binary at build time. User/project-owned overrides remain under `config_dir` and project `.yoi/` files such as `profiles.toml`; package runtime resource lookup is not a supported configuration surface.
|
||||
|
||||
## Credential と外部 auth
|
||||
|
||||
Provider API key と WebSearch credential は、通常の runtime では環境変数から読まない。`insomnia keys` で local secret store に論理 id を追加し、profile / manifest / provider catalog / web config がその id を明示的に参照する。
|
||||
Provider API key と WebSearch credential は、通常の runtime では環境変数から読まない。`yoi keys` で local secret store に論理 id を追加し、profile / manifest / provider catalog / web config がその id を明示的に参照する。
|
||||
|
||||
```toml
|
||||
[model]
|
||||
|
|
@ -71,7 +71,7 @@ On-disk store は `<data_dir>/secrets/store.json`。secret value は軽量な ob
|
|||
|
||||
| 変数 | Context | 備考 |
|
||||
| --- | --- | --- |
|
||||
| `INSOMNIA_POD_RUNTIME_COMMAND` | 開発中に起動中の `insomnia` binary が rebuild され、`std::env::current_exe()` が `target/debug/insomnia (deleted)` のような stale path を返す場合の Pod runtime executable override。 | Unset または empty の場合は既定どおり current executable に `pod` prefix argument を付けて起動する。Non-empty の場合は値を executable path としてそのまま使い、`pod` prefix argument は常に自動追加する。shell parsing や argument splitting は行わないため、値に flags や `pod` を含めない。 |
|
||||
| `YOI_POD_RUNTIME_COMMAND` | 開発中に起動中の `yoi` binary が rebuild され、`std::env::current_exe()` が `target/debug/yoi (deleted)` のような stale path を返す場合の Pod runtime executable override。 | Unset または empty の場合は既定どおり current executable に `pod` prefix argument を付けて起動する。Non-empty の場合は値を executable path としてそのまま使い、`pod` prefix argument は常に自動追加する。shell parsing や argument splitting は行わないため、値に flags や `pod` を含めない。 |
|
||||
|
||||
## Build / example variables
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ On-disk store は `<data_dir>/secrets/store.json`。secret value は軽量な ob
|
|||
| `PATH` | test / dev command lookup。 | helper executable を探す場合だけ使う。 |
|
||||
| `TMPDIR` | shell script / test。 | `tickets.sh` が temporary file に使う。 |
|
||||
| `RUST_LOG` | example / dev diagnostics。 | example CLI が tracing setup 経由で読む場合がある。 |
|
||||
| `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` などの provider example vars | `llm-worker` や Pod example / fixture recorder。 | example code が `dotenv::dotenv().ok()` を呼ぶことがある。通常の `insomnia` runtime startup には適用されない。 |
|
||||
| `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` などの provider example vars | `llm-worker` や Pod example / fixture recorder。 | example code が `dotenv::dotenv().ok()` を呼ぶことがある。通常の `yoi` runtime startup には適用されない。 |
|
||||
|
||||
## 整理方針
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
# Manifest profiles
|
||||
|
||||
Profiles are reusable Lua-authored recipes for generating an Insomnia runtime manifest. The Rust resolver evaluates a selected `.lua` profile in-process, validates that it is Profile-shaped rather than a complete Manifest, then binds runtime values such as Pod name and concrete scope to produce the persisted `PodManifest` snapshot.
|
||||
Profiles are reusable Lua-authored recipes for generating a Yoi runtime manifest. The Rust resolver evaluates a selected `.lua` profile in-process, validates that it is Profile-shaped rather than a complete Manifest, then binds runtime values such as Pod name and concrete scope to produce the persisted `PodManifest` snapshot.
|
||||
|
||||
Profiles are intentionally not authority-bearing manifests. `pod.name`, concrete `scope.allow` / `scope.deny`, runtime directories, sockets, active session state, and raw secret material do not belong in reusable profiles. Use `insomnia keys` to store provider/WebSearch credentials, then reference explicit secret ids such as `auth = { kind = "secret_ref", ref = "providers/anthropic/default" }` or `web.search.api_key_secret = "web/brave/default"`. Use `--manifest` when you need the explicit low-level complete Manifest escape hatch.
|
||||
Profiles are intentionally not authority-bearing manifests. `pod.name`, concrete `scope.allow` / `scope.deny`, runtime directories, sockets, active session state, and raw secret material do not belong in reusable profiles. Use `yoi keys` to store provider/WebSearch credentials, then reference explicit secret ids such as `auth = { kind = "secret_ref", ref = "providers/anthropic/default" }` or `web.search.api_key_secret = "web/brave/default"`. Use `--manifest` when you need the explicit low-level complete Manifest escape hatch.
|
||||
|
||||
## Minimal profile
|
||||
|
||||
```lua
|
||||
local profile = require("insomnia.profile")
|
||||
local models = require("insomnia.models")
|
||||
local scope = require("insomnia.scope")
|
||||
local profile = require("yoi.profile")
|
||||
local models = require("yoi.models")
|
||||
local scope = require("yoi.scope")
|
||||
|
||||
return profile {
|
||||
slug = "coder",
|
||||
|
|
@ -26,24 +26,24 @@ return profile {
|
|||
Run an explicit path with:
|
||||
|
||||
```sh
|
||||
insomnia pod --profile ./coder.lua
|
||||
yoi pod --profile ./coder.lua
|
||||
# or through the TUI fresh-spawn dialog
|
||||
insomnia --profile ./coder.lua
|
||||
yoi --profile ./coder.lua
|
||||
```
|
||||
|
||||
`--profile` accepts an explicit path, `path:<path>`, a discovered profile name, `default`, or a source-qualified name such as `project:coder`, `user:coder`, or `builtin:coder`. Path-like values containing `/`, starting with `.`, or ending in `.lua` are explicit paths. ``.nix` paths are no longer supported as profiles and fail with a diagnostic that points users at Lua profiles or `--manifest`.
|
||||
|
||||
`--profile` conflicts with `insomnia pod --manifest` and with restore/session/adopt modes. Use `--profile-pod-name <name>` when a launcher needs a creation-time Pod name override without invoking `--pod` restore semantics. Profile evaluation is a creation-time path; Pod resume restores saved Pod state/resolved snapshots rather than re-evaluating the profile source.
|
||||
`--profile` conflicts with `yoi pod --manifest` and with restore/session/adopt modes. Use `--profile-pod-name <name>` when a launcher needs a creation-time Pod name override without invoking `--pod` restore semantics. Profile evaluation is a creation-time path; Pod resume restores saved Pod state/resolved snapshots rather than re-evaluating the profile source.
|
||||
|
||||
## Controlled Lua environment
|
||||
|
||||
Profiles run in a restricted Lua VM. Host virtual modules are available through controlled `require`:
|
||||
|
||||
- `require("insomnia")`
|
||||
- `require("insomnia.profile")`
|
||||
- `require("insomnia.models")`
|
||||
- `require("insomnia.compact")`
|
||||
- `require("insomnia.scope")`
|
||||
- `require("yoi")`
|
||||
- `require("yoi.profile")`
|
||||
- `require("yoi.models")`
|
||||
- `require("yoi.compact")`
|
||||
- `require("yoi.scope")`
|
||||
|
||||
Profile-local modules may be required by dotted names such as `require("shared")` or `require("shared.models")`; those resolve only under the selected profile file's directory. Unsafe/unrestricted Lua facilities such as `os`, `io`, `debug`, `package`, `dofile`, and `loadfile` are unavailable by default.
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ Profile-local modules may be required by dotted names such as `require("shared")
|
|||
|
||||
Profile discovery is separate from runtime manifest merging. User/project `profiles.toml` files may declare profile registry metadata, but those files are application/project UX configuration and are not merged into the selected profile artifact.
|
||||
|
||||
Example project config at `.insomnia/profiles.toml`:
|
||||
Example project config at `.yoi/profiles.toml`:
|
||||
|
||||
```toml
|
||||
default = "coder"
|
||||
|
|
@ -69,19 +69,19 @@ path = "profiles/coder.lua"
|
|||
description = "Project coding assistant"
|
||||
```
|
||||
|
||||
Relative registry paths are resolved against the `profiles.toml` file that declares them. Discovery checks bundled builtin profiles, then the user registry at `<config_dir>/profiles.toml`, then the nearest project registry at `.insomnia/profiles.toml`. The bundled `builtin:default` profile is the fallback default when no user/project registry declares another default. Later defaults override earlier defaults, so a project default wins over a user default, and either wins over the builtin default. Unqualified defaults resolve within the declaring source by default. Unqualified ambiguous names fail closed:
|
||||
Relative registry paths are resolved against the `profiles.toml` file that declares them. Discovery checks bundled builtin profiles, then the user registry at `<config_dir>/profiles.toml`, then the nearest project registry at `.yoi/profiles.toml`. The bundled `builtin:default` profile is the fallback default when no user/project registry declares another default. Later defaults override earlier defaults, so a project default wins over a user default, and either wins over the builtin default. Unqualified defaults resolve within the declaring source by default. Unqualified ambiguous names fail closed:
|
||||
|
||||
```sh
|
||||
insomnia --profile coder # fails if both user:coder and project:coder exist
|
||||
insomnia --profile project:coder # source-qualified selection
|
||||
insomnia --profile default # selected registry default
|
||||
yoi --profile coder # fails if both user:coder and project:coder exist
|
||||
yoi --profile project:coder # source-qualified selection
|
||||
yoi --profile default # selected registry default
|
||||
```
|
||||
|
||||
The fresh-spawn TUI also uses discovery. The new Pod dialog defaults to the selected registry default, normally `builtin:default` unless a user/project registry overrides it. `Tab`/`Down` cycles forward through discovered profiles and `Shift-Tab`/`Up` cycles backward; there is no ambient manifest-cascade opt-out. Passing `insomnia --profile <selector>` opens the same new Pod dialog with that selector selected and leaves Pod-name editing unchanged.
|
||||
The fresh-spawn TUI also uses discovery. The new Pod dialog defaults to the selected registry default, normally `builtin:default` unless a user/project registry overrides it. `Tab`/`Down` cycles forward through discovered profiles and `Shift-Tab`/`Up` cycles backward; there is no ambient manifest-cascade opt-out. Passing `yoi --profile <selector>` opens the same new Pod dialog with that selector selected and leaves Pod-name editing unchanged.
|
||||
|
||||
## One-file manifests
|
||||
|
||||
`insomnia pod --manifest <PATH>` remains as an explicit compatibility/debug path. It reads exactly that TOML file, resolves relative paths against the file's parent directory, merges builtin defaults, and validates through the same `PodManifestConfig -> PodManifest` boundary as profile artifacts. It does not load user or project `manifest.toml` files and conflicts with `--profile`.
|
||||
`yoi pod --manifest <PATH>` remains as an explicit compatibility/debug path. It reads exactly that TOML file, resolves relative paths against the file's parent directory, merges builtin defaults, and validates through the same `PodManifestConfig -> PodManifest` boundary as profile artifacts. It does not load user or project `manifest.toml` files and conflicts with `--profile`.
|
||||
|
||||
Ambient user/project `manifest.toml` cascade startup has been removed. Normal fresh spawns use profile discovery/default selection, with `profiles.toml` acting only as a profile registry/default selector.
|
||||
|
||||
|
|
@ -89,4 +89,4 @@ Ambient user/project `manifest.toml` cascade startup has been removed. Normal fr
|
|||
|
||||
A Lua profile should return either `profile { ... }` or a plain table containing Profile fields. The resolver converts reusable fields such as `model`, `worker`, `compaction`, `memory`, `web`, `permissions`, `session`, and scope intent into a concrete Manifest. Runtime Pod name and concrete scope authority are supplied by launch context, then the resolved Manifest snapshot is persisted for restore.
|
||||
|
||||
Profile and one-file manifest CLI paths currently use builtin prompt assets only. `$insomnia/...` instruction refs work; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery.
|
||||
Profile and one-file manifest CLI paths currently use builtin prompt assets only. `$yoi/...` instruction refs work; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
# このファイル形式は低レベル runtime manifest。通常起動は profile discovery/default
|
||||
# (`profiles.toml` と bundled builtin profile) から manifest を生成する。
|
||||
# `insomnia pod --manifest <path>` の one-file compatibility/debug mode では、
|
||||
# `yoi pod --manifest <path>` の one-file compatibility/debug mode では、
|
||||
# 指定した TOML 1 枚に builtin defaults を merge し、required validation を行う。
|
||||
# user/project `manifest.toml` を暗黙に merge する通常起動 cascade は使わない。
|
||||
#
|
||||
|
|
@ -65,7 +65,7 @@ ref = "anthropic/claude-sonnet-4-6"
|
|||
# 任意 (ref 未指定時は実質必須)。デフォルト: なし。
|
||||
# kind の値: "none" | "secret_ref" | "api_key" | "codex_oauth"
|
||||
# - "none" … 認証不要 (ローカル Ollama 等)
|
||||
# - "secret_ref" … `insomnia keys` の local secret store から key を読む。
|
||||
# - "secret_ref" … `yoi keys` の local secret store から key を読む。
|
||||
# `ref` はユーザー設定が明示的に選ぶ論理 secret id。
|
||||
# store は id -> value のみを持ち、provider 種別を解釈しない。
|
||||
# - "api_key" … 明示ファイルから key を読む低レベル形式。通常は
|
||||
|
|
@ -95,10 +95,10 @@ ref = "anthropic/claude-sonnet-4-6"
|
|||
# ワーカーの生成パラメータ等。セクション自体省略可 (全フィールド任意)。
|
||||
[worker]
|
||||
|
||||
# 任意。デフォルト: "$insomnia/default" (`defaults::DEFAULT_INSTRUCTION`)。
|
||||
# 任意。デフォルト: "$yoi/default" (`defaults::DEFAULT_INSTRUCTION`)。
|
||||
# システムプロンプト本体の `PromptLoader` 参照。
|
||||
# プレフィクス: "$insomnia/..." | "$user/..." | "$workspace/..."
|
||||
# instruction = "$insomnia/default"
|
||||
# プレフィクス: "$yoi/..." | "$user/..." | "$workspace/..."
|
||||
# instruction = "$yoi/default"
|
||||
|
||||
# 任意。デフォルト: なし (プロバイダ任せ)。
|
||||
# 1 レスポンスあたりの出力 token 上限。
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## 目的
|
||||
|
||||
AI maintainer は、insomnia リポジトリの開発を継続的に進めるための orchestration role である。単発の `/auto-maintain` より広く、設計相談、work item 整理、実装委譲、レビュー、運用課題の記録、改善提案を一つの maintainer loop として扱う。
|
||||
AI maintainer は、yoi リポジトリの開発を継続的に進めるための orchestration role である。単発の `/auto-maintain` より広く、設計相談、work item 整理、実装委譲、レビュー、運用課題の記録、改善提案を一つの maintainer loop として扱う。
|
||||
|
||||
`/auto-maintain` はこの設計の限定実行形であり、`tickets.sh` / `work-items/` から小さな実装作業を選んで実装・レビューを orchestration する Workflow に留まる。
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ AI maintainer は、insomnia リポジトリの開発を継続的に進めるた
|
|||
- `resolution.md` は close 時の完了記録
|
||||
- 時系列と状態遷移の最終根拠は git history
|
||||
- `docs/report/` は観測・所感・改善候補の記録であり、最新仕様の authority ではない
|
||||
- `.insomnia/memory` は個人/生成 state であり、project record の正本ではない
|
||||
- `.yoi/memory` は個人/生成 state であり、project record の正本ではない
|
||||
|
||||
AI maintainer は project record を勝手に膨らませない。明確な実装単位は work item 化し、小粒な所見は `KNOWN_ISSUES.md`、ドッグフーディング上の障壁やツール問題は `docs/report/` に記録する。
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ Maintainer Pod は「便利だから」project record を書き換えない。
|
|||
制約:
|
||||
|
||||
- 指定 scope 外を編集しない
|
||||
- `.insomnia` や main workspace の control-plane record を勝手に編集しない
|
||||
- `.yoi` や main workspace の control-plane record を勝手に編集しない
|
||||
- work item / review / close は maintainer の責務として扱う
|
||||
- 実装報告には変更点、検証、未解決点を含める
|
||||
|
||||
|
|
@ -185,8 +185,8 @@ AI maintainer は以下で人間に戻す。
|
|||
## Reports / Knowledge / Memory
|
||||
|
||||
- `docs/report/`: ドッグフーディングで感じた障壁、改善案、ツール問題の記録。明確な作業単位になったら work item 化する
|
||||
- `.insomnia/knowledge`: curated project knowledge。正本ではなく補助 context
|
||||
- `.insomnia/memory`: generated/personal state。project record の代替にしない
|
||||
- `.yoi/knowledge`: curated project knowledge。正本ではなく補助 context
|
||||
- `.yoi/memory`: generated/personal state。project record の代替にしない
|
||||
- `KNOWN_ISSUES.md`: ticket 化するほどではないが、次に近所を触る時に拾いたい小粒所見
|
||||
|
||||
## Future extension points
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Context
|
||||
|
||||
INSOMNIA が利用する LLM プロバイダとその認証方式を決める。従量 API 課金の心理的負担を避け、定額サブスク枠(ChatGPT Codex / Ollama Cloud)を活かせる構成にする。
|
||||
Yoi が利用する LLM プロバイダとその認証方式を決める。従量 API 課金の心理的負担を避け、定額サブスク枠(ChatGPT Codex / Ollama Cloud)を活かせる構成にする。
|
||||
|
||||
詳細な現状調査は `docs/ref/llm-provider-landscape.md` と `docs/ref/llm-pricing-2026-04.md` を参照。
|
||||
|
||||
|
|
@ -46,13 +46,13 @@ Ollama は独自 scheme を作らず `scheme/anthropic` を base_url 差し替
|
|||
- 認証ストアを読むアダプタ(`~/.codex/auth.json` 等)は **llm-worker 直下に置かず上位層に配置**。llm-worker は低レベル基盤に留める方針(`feedback_llm_worker_scope.md`)と整合
|
||||
- モデル列挙は **auto_discover と宣言型の両輪**。Ollama は `/api/tags` で自動、OpenAI 互換枠はモデルカタログ(`resources/models/builtin.toml` + `<config_dir>/models.toml` の user override、`<config_dir>` は `manifest::paths` で解決)で宣言
|
||||
- UI のプロバイダ選択肢も第一級 → 二次の優先順位で並べる
|
||||
- **`ollama launch insomnia` 対応を視野に**、env 注入(`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等)で起動設定を受け入れる作り
|
||||
- **`ollama launch yoi` 対応を視野に**、env 注入(`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等)で起動設定を受け入れる作り
|
||||
|
||||
## 機能方針
|
||||
|
||||
### プロバイダ側の高次ツールは使用しない
|
||||
|
||||
`web_search` / `code_interpreter` / `computer_use` / Live Search 等はプロバイダ依存を避けるため不採用。insomnia の自前 Tool 層で統一。fallback や routing もワーカー側で管理するため OpenRouter の `transforms` / `provider routing` も使わない。
|
||||
`web_search` / `code_interpreter` / `computer_use` / Live Search 等はプロバイダ依存を避けるため不採用。yoi の自前 Tool 層で統一。fallback や routing もワーカー側で管理するため OpenRouter の `transforms` / `provider routing` も使わない。
|
||||
|
||||
### 必須 capability
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
- ID: timestamp + slug 形式
|
||||
- Doctor: `./tickets.sh doctor`
|
||||
|
||||
`work-items/` は repo-managed な project coordination record であり、`.insomnia/memory` はその代替ではない。
|
||||
`work-items/` は repo-managed な project coordination record であり、`.yoi/memory` はその代替ではない。
|
||||
|
||||
## 現在も残る設計余地
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
- `memory/summary.md` は「Always-on サマリ」と設計されているが、通常 Pod の system prompt へ常駐注入されていない
|
||||
- consolidation は `KnowledgeCandidateReport::empty()` を受け取り、prompt 上も「候補レポートが空なら新規 Knowledge を作るな」としているため、Knowledge の cold-start が起きない
|
||||
- `decisions/*` と `requests/*` は記録としては残るが、description / resident injection を持たず、後続 turn で自然に読まれにくい
|
||||
- bundled prompt に INSOMNIA 開発固有の ticket / TODO 運用を前提にした shadow 禁止が入り、一般ユーザー workspace の管理文脈を過剰に落とす可能性がある
|
||||
- bundled prompt に Yoi 開発固有の ticket / TODO 運用を前提にした shadow 禁止が入り、一般ユーザー workspace の管理文脈を過剰に落とす可能性がある
|
||||
|
||||
この状態で usage metrics を先に実装しても、「使われていない / 発見されない memory」を測るだけになり、Knowledge 化候補や保護閾値の信号が十分に育たない。先に memory が読まれ、Knowledge が最低限生まれる経路を作る。
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ Knowledge が空のまま固定される問題を避ける。metrics 実装前
|
|||
|
||||
### Problem
|
||||
|
||||
bundled memory prompts は、INSOMNIA 自身の ticket / TODO / worktree 運用を一般ユーザーへ押し付けている。ticket はこのプロジェクトの管理手法であり、ユーザー workspace の正本や作業管理の形は project ごとに異なる。
|
||||
bundled memory prompts は、Yoi 自身の ticket / TODO / worktree 運用を一般ユーザーへ押し付けている。ticket はこのプロジェクトの管理手法であり、ユーザー workspace の正本や作業管理の形は project ごとに異なる。
|
||||
|
||||
### Direction
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ Default prompt では特定の管理手法名を禁止対象として列挙し
|
|||
- 既存の authoritative record を逐語的に mirror しない
|
||||
- ファイル操作ログや VCS 履歴そのものを memory に再保存しない
|
||||
- ただし、将来の作業判断に効く project-management 上の制約・優先順位理由・プロセス決定・ recurring pattern は抽象化して保存してよい
|
||||
- INSOMNIA 自身の ticket shadow 回避は bundled default ではなく workspace / user prompt override で表現する
|
||||
- Yoi 自身の ticket shadow 回避は bundled default ではなく workspace / user prompt override で表現する
|
||||
|
||||
### Expected effect
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ cold-start gate を緩めると Knowledge が増えすぎる可能性がある
|
|||
|
||||
### Prompt override boundary
|
||||
|
||||
INSOMNIA 自身の ticket shadow 回避を完全に消すと、この repository の memory は作業ログ寄りに戻る可能性がある。これは bundled default ではなく workspace prompt override で解くべきで、default prompt の責務ではない。
|
||||
Yoi 自身の ticket shadow 回避を完全に消すと、この repository の memory は作業ログ寄りに戻る可能性がある。これは bundled default ではなく workspace prompt override で解くべきで、default prompt の責務ではない。
|
||||
|
||||
### `#<slug>` history representation
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Context
|
||||
|
||||
INSOMNIA がユーザーのプロジェクトに対して提供するメモリ機構。プロジェクトの暗黙知蓄積と同じ失敗を繰り返さないための記憶が目的。エージェントに連続するアイデンティティや自己意識を持たせる方向は対象外。
|
||||
Yoi がユーザーのプロジェクトに対して提供するメモリ機構。プロジェクトの暗黙知蓄積と同じ失敗を繰り返さないための記憶が目的。エージェントに連続するアイデンティティや自己意識を持たせる方向は対象外。
|
||||
|
||||
リサーチは `docs/ref/memory-systems.md`。前提として、**レポジトリがファイルシステム上にある**ケースで設計する(越境・バックエンド抽象は Scope 外)。
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ Workflow(`/<slug>` で呼び出される制約付き作業フロー)は別 p
|
|||
|
||||
### 記録対象の 4 種
|
||||
|
||||
本ドキュメント以下のパスはすべて **`<workspace_root>/.insomnia/`** からの相対表記。`.insomnia/` は manifest / prompts と同じく workspace に紐付く insomnia コンテンツのルートで、memory もこの規約に従う。`workspace_root` 既定は Pod の pwd。
|
||||
本ドキュメント以下のパスはすべて **`<workspace_root>/.yoi/`** からの相対表記。`.yoi/` は manifest / prompts と同じく workspace に紐付く yoi コンテンツのルートで、memory もこの規約に従う。`workspace_root` 既定は Pod の pwd。
|
||||
|
||||
| 種別 | パス | 備考 |
|
||||
| ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
|
|
@ -98,7 +98,7 @@ Linter ルールは 2 系統:
|
|||
- Decisions / Requests: `created_at`, `updated_at`, `sources`
|
||||
- Knowledge: `kind`, `description`, `model_invokation`, `user_invocable`, `last_sources`, `created_at`, `updated_at`
|
||||
- Summary: `updated_at`(optional: `last_rewritten_from_range`)
|
||||
- Workflow パス(`.insomnia/workflow/`)への書き込み禁止(sub-Worker context のみ、人間編集は除外)
|
||||
- Workflow パス(`.yoi/workflow/`)への書き込み禁止(sub-Worker context のみ、人間編集は除外)
|
||||
- 同 slug での新規作成禁止(既存があれば update に切り替えるサイン)
|
||||
- `#<slug>` 参照が実在ファイルを指す
|
||||
- `replaced_by: <slug>` が実在 record を指す
|
||||
|
|
|
|||
|
|
@ -23,26 +23,26 @@ Pod が連携する等)では、マシン間のメッセージングが必要
|
|||
|
||||
## アドレッシング
|
||||
|
||||
Pod のネットワークアドレスは `insomnia.pod-name@host` の形式を**論理的な
|
||||
Pod のネットワークアドレスは `yoi.pod-name@host` の形式を**論理的な
|
||||
宛先表記**として使う。実際の SSH 接続がこの文字列そのままで行えるかは
|
||||
transport 方式に依存する(後述)。
|
||||
|
||||
- `insomnia` = SSH ユーザー名(固定)
|
||||
- `yoi` = SSH ユーザー名(固定)
|
||||
- `host` = 相手マシンのホスト名 or IP
|
||||
- `pod-name` = 送信先の Pod 名(相手マシン上のローカル workspace 内で一意)
|
||||
|
||||
推奨構文は **`insomnia@host:pod-name`**(git 方式)。
|
||||
推奨構文は **`yoi@host:pod-name`**(git 方式)。
|
||||
詳細は後述の「アドレッシング構文」を参照。
|
||||
|
||||
## アドレッシング構文
|
||||
|
||||
論理的な宛先表記として **`insomnia@host:pod-name`** を推奨する。
|
||||
論理的な宛先表記として **`yoi@host:pod-name`** を推奨する。
|
||||
git の `git@github.com:user/repo` と同じ構文で:
|
||||
|
||||
- SSH ユーザーは `insomnia` 固定(動的ユーザー名が不要)
|
||||
- SSH ユーザーは `yoi` 固定(動的ユーザー名が不要)
|
||||
- `:` 以降がルーティング情報(Pod 名)
|
||||
- クライアント側が `insomnia@host:pod-name` をパースし、
|
||||
`ssh insomnia@host "insomnia-route pod-name"` に変換する
|
||||
- クライアント側が `yoi@host:pod-name` をパースし、
|
||||
`ssh yoi@host "yoi-route pod-name"` に変換する
|
||||
|
||||
git がこの方式で `git-upload-pack user/repo` にルーティングしている
|
||||
のと同じ仕組み。OS レベルの設定(NSS モジュール等)が一切不要で、
|
||||
|
|
@ -53,37 +53,37 @@ git がこの方式で `git-upload-pack user/repo` にルーティングして
|
|||
### A. 単一ユーザー + コマンド引数
|
||||
|
||||
```
|
||||
ssh insomnia@host send pod-name "message"
|
||||
ssh yoi@host send pod-name "message"
|
||||
```
|
||||
|
||||
- 相手マシンにシステムユーザー `insomnia` を 1 つ作る
|
||||
- 相手マシンにシステムユーザー `yoi` を 1 つ作る
|
||||
- `authorized_keys` に接続元 Pod の公開鍵を登録
|
||||
- ForceCommand または shell スクリプトが第一引数 (`send`) と
|
||||
第二引数 (`pod-name`) を解釈してローカル workspace のレジストリから
|
||||
Pod の socket を引き、メッセージをルーティング
|
||||
- **導入コスト最低**。ユーザー 1 つ + スクリプト 1 つで動く
|
||||
- 宛先が引数に入るので `insomnia.pod-name@host` の見た目にはならない
|
||||
- 宛先が引数に入るので `yoi.pod-name@host` の見た目にはならない
|
||||
|
||||
### B. 鍵ベースルーティング(gitolite 方式)
|
||||
|
||||
```
|
||||
ssh insomnia@host # 使った鍵でどの Pod 宛か判別
|
||||
ssh yoi@host # 使った鍵でどの Pod 宛か判別
|
||||
```
|
||||
|
||||
- `~insomnia/.ssh/authorized_keys` に Pod ごとのエントリ:
|
||||
- `~yoi/.ssh/authorized_keys` に Pod ごとのエントリ:
|
||||
```
|
||||
command="insomnia-route pod-a",no-port-forwarding,... ssh-ed25519 AAAA... pod-a@remote
|
||||
command="insomnia-route pod-b",no-port-forwarding,... ssh-ed25519 AAAA... pod-b@remote
|
||||
command="yoi-route pod-a",no-port-forwarding,... ssh-ed25519 AAAA... pod-a@remote
|
||||
command="yoi-route pod-b",no-port-forwarding,... ssh-ed25519 AAAA... pod-b@remote
|
||||
```
|
||||
- SSH 接続時に使われた鍵が `command=` で指定されたルーティング先を決定
|
||||
- gitolite / Gitea / Gogs で実証済みのパターン
|
||||
- 接続元は `ssh insomnia@host` だけ。**鍵が宛先を決める**
|
||||
- 接続元は `ssh yoi@host` だけ。**鍵が宛先を決める**
|
||||
- クライアント側 SSH config で alias を作れば見た目を整えられる:
|
||||
```
|
||||
Host pod-a.host-b
|
||||
HostName host-b
|
||||
User insomnia
|
||||
IdentityFile ~/.config/insomnia/keys/pod-a
|
||||
User yoi
|
||||
IdentityFile ~/.config/yoi/keys/pod-a
|
||||
```
|
||||
- 鍵の登録が相互に必要(Pod A が Pod B に送るなら、B のマシンの
|
||||
authorized_keys に A の公開鍵 + route 先を登録)
|
||||
|
|
@ -91,15 +91,15 @@ ssh insomnia@host # 使った鍵でどの Pod 宛か判別
|
|||
### C. 動的ユーザー名
|
||||
|
||||
```
|
||||
ssh insomnia.pod-name@host
|
||||
ssh yoi.pod-name@host
|
||||
```
|
||||
|
||||
- `insomnia.pod-name` を OS レベルで有効なユーザー名として解決する:
|
||||
- `yoi.pod-name` を OS レベルで有効なユーザー名として解決する:
|
||||
- NSS (Name Service Switch) モジュールを書くか `libnss-extrausers` を利用
|
||||
- PAM モジュールで認証をフック
|
||||
- `sshd_config` で `Match User insomnia.*` → `ForceCommand` でルーティング
|
||||
- `sshd_config` で `Match User yoi.*` → `ForceCommand` でルーティング
|
||||
- **最も直感的なアドレッシング**だが OS レベルの設定が必要
|
||||
- insomnia をインストールするだけでは動かない(管理者権限での設定が要る)
|
||||
- yoi をインストールするだけでは動かない(管理者権限での設定が要る)
|
||||
- コンテナ環境ではやりやすい(ユーザー管理を自由にできる)
|
||||
|
||||
### 推奨
|
||||
|
|
@ -143,7 +143,7 @@ Pod 協働では:
|
|||
- broadcast = known-peers を iterate して個別送信
|
||||
- 規模が数十 Pod なら十分実用的
|
||||
- 将来的に gossip protocol で peer 発見を自動化できるが、
|
||||
MVP では手動登録(`insomnia peer add pod-a@host-b`)で十分
|
||||
MVP では手動登録(`yoi peer add pod-a@host-b`)で十分
|
||||
|
||||
## 受信側のルーティング
|
||||
|
||||
|
|
@ -151,16 +151,16 @@ SSH 接続を受けた側が、宛先 Pod のローカル socket にルーティ
|
|||
仕組みが必要。
|
||||
|
||||
```
|
||||
[SSH 接続] → insomnia-route <pod-name>
|
||||
[SSH 接続] → yoi-route <pod-name>
|
||||
↓
|
||||
workspace registry を参照
|
||||
↓
|
||||
/run/insomnia/.../pod-name.sock に転送
|
||||
/run/yoi/.../pod-name.sock に転送
|
||||
↓
|
||||
Pod が受信・処理
|
||||
```
|
||||
|
||||
`insomnia-route` は:
|
||||
`yoi-route` は:
|
||||
1. workspace のレジストリを読んで pod-name の socket path を引く
|
||||
2. socket に接続してメッセージを中継
|
||||
3. 応答を SSH 接続に返す
|
||||
|
|
@ -172,17 +172,17 @@ unique であれば workspace を指定しなくて済む。
|
|||
## Daemon-less リモート Pod 生成(SSH-only モデル)
|
||||
|
||||
リモートホスト上の Pod 生成は **daemon 無しで SSH だけで成立する**。
|
||||
remote 側に必要なのは `insomnia` バイナリと SSH アクセスのみ。
|
||||
remote 側に必要なのは `yoi` バイナリと SSH アクセスのみ。
|
||||
|
||||
### 前提
|
||||
|
||||
- insomnia は環境再現(git clone, コンテナ構築等)を自身の責務としない。
|
||||
- yoi は環境再現(git clone, コンテナ構築等)を自身の責務としない。
|
||||
作業対象のファイルがリモートに既にあるか、ユーザーが任意の手段で
|
||||
用意する前提(git clone, rsync, 手動配置、CI の checkout 等)
|
||||
- insomnia が転送するのは**セッション(会話履歴)と manifest overlay**
|
||||
- yoi が転送するのは**セッション(会話履歴)と manifest overlay**
|
||||
だけ。コードベースの同期は外部に委ねる
|
||||
- コンテナ内で動かすか bare metal で動かすかも insomnia は問わない。
|
||||
`insomnia` バイナリが動くホストの fs 上で活動する主体がある、
|
||||
- コンテナ内で動かすか bare metal で動かすかも yoi は問わない。
|
||||
`yoi` バイナリが動くホストの fs 上で活動する主体がある、
|
||||
それだけが前提
|
||||
|
||||
### フロー
|
||||
|
|
@ -193,7 +193,7 @@ host_a (spawner) host_b (remote)
|
|||
│
|
||||
├── ssh: session データを転送 ────────→ ファイル書き込み
|
||||
├── ssh: profile / one-file manifest 入力を転送 ─→ 必要ならファイル書き込み
|
||||
├── ssh: `insomnia pod --profile ... &` ───────→ Pod プロセス起動、socket 作成
|
||||
├── ssh: `yoi pod --profile ... &` ───────→ Pod プロセス起動、socket 作成
|
||||
├── ssh -L: socket を tunnel ─────────→ Pod B の unix socket
|
||||
│
|
||||
└── localhost:tunnel に接続 ──────────→ Method::Run / Event stream
|
||||
|
|
@ -204,16 +204,16 @@ host_a (spawner) host_b (remote)
|
|||
|
||||
```bash
|
||||
# 1. session + profile/manifest input を転送
|
||||
ssh insomnia@host-b "mkdir -p ~/workspaces/task-123/store"
|
||||
tar cz session/ | ssh insomnia@host-b "tar xz -C ~/workspaces/task-123/store"
|
||||
scp profile.lua insomnia@host-b:~/workspaces/task-123/profile.lua
|
||||
ssh yoi@host-b "mkdir -p ~/workspaces/task-123/store"
|
||||
tar cz session/ | ssh yoi@host-b "tar xz -C ~/workspaces/task-123/store"
|
||||
scp profile.lua yoi@host-b:~/workspaces/task-123/profile.lua
|
||||
|
||||
# 2. Pod を起動(detach)
|
||||
ssh insomnia@host-b "insomnia pod --store ~/workspaces/task-123/store \
|
||||
ssh yoi@host-b "yoi pod --store ~/workspaces/task-123/store \
|
||||
--profile ~/workspaces/task-123/profile.lua &"
|
||||
|
||||
# 3. socket を tunnel で引っ張る
|
||||
ssh -L /tmp/pod-b.sock:/run/insomnia/task-123/pod.sock insomnia@host-b
|
||||
ssh -L /tmp/pod-b.sock:/run/yoi/task-123/pod.sock yoi@host-b
|
||||
|
||||
# 4. あとは /tmp/pod-b.sock にローカルと同じ protocol で繋ぐ
|
||||
```
|
||||
|
|
@ -229,7 +229,7 @@ spawner の `SpawnPod` ツールがこの一連を内部で実行する。LLM
|
|||
成立しないので、workspace の scope 会計は remote には関係しない
|
||||
- **通知**: SSH tunnel が繋がっている限り `Event` stream がそのまま
|
||||
流れる。tunnel が切れたら再接続する
|
||||
- **環境構築は insomnia の責務外**: git clone するか rsync するかは
|
||||
- **環境構築は yoi の責務外**: git clone するか rsync するかは
|
||||
Pod の instruction で指示するか、事前に用意されている前提
|
||||
|
||||
### daemon が必要になるケース
|
||||
|
|
@ -251,7 +251,7 @@ SSH-only モデルの制約が、daemon 導入の動機になる:
|
|||
### リモート側のディレクトリ構成
|
||||
|
||||
```
|
||||
/home/insomnia/ ← insomnia システムユーザーの home
|
||||
/home/yoi/ ← yoi システムユーザーの home
|
||||
├── workspaces/
|
||||
│ ├── <task-or-project-id>/ ← workspace ごとのルート
|
||||
│ │ ├── repo/ ← ユーザーが用意した作業ファイル群
|
||||
|
|
@ -261,8 +261,8 @@ SSH-only モデルの制約が、daemon 導入の動機になる:
|
|||
└── authorized_keys ← 接続元 Pod の公開鍵
|
||||
```
|
||||
|
||||
- `insomnia` システムユーザーが SSH 接続先 + ファイル所有者
|
||||
- `repo/` 配下の準備は insomnia の責務外(git clone, rsync 等は
|
||||
- `yoi` システムユーザーが SSH 接続先 + ファイル所有者
|
||||
- `repo/` 配下の準備は yoi の責務外(git clone, rsync 等は
|
||||
ユーザーや instruction が指示)
|
||||
- `store/` は spawner がセッションデータを書き込む場所
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Context
|
||||
|
||||
INSOMNIA はエージェントが扱うツール数の増加 (built-in tools + MCP サーバ + ユーザ定義) を想定する必要がある。すべてを upfront に context へ展開すると以下が問題になる:
|
||||
Yoi はエージェントが扱うツール数の増加 (built-in tools + MCP サーバ + ユーザ定義) を想定する必要がある。すべてを upfront に context へ展開すると以下が問題になる:
|
||||
|
||||
- **入力トークン消費**: 30-50 ツールで 10-20K tokens を消費しうる (Anthropic 公式ガイド)
|
||||
- **ツール選択精度の低下**: 数十個を超えるとモデルの tool selection accuracy が落ちる
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ Workflow は制約付きの強制的な作業フロー。`/<slug>` で明示的
|
|||
|
||||
### 格納先とファイル形式
|
||||
|
||||
- `.insomnia/workflow/<slug>.md`(ファイル名 = slug がそのまま識別子、`name` field は持たない)
|
||||
- `.insomnia/memory/` は session-derived state 専用、Workflow は配置しない
|
||||
- `.yoi/workflow/<slug>.md`(ファイル名 = slug がそのまま識別子、`name` field は持たない)
|
||||
- `.yoi/memory/` は session-derived state 専用、Workflow は配置しない
|
||||
- frontmatter + Markdown 本文
|
||||
- frontmatter フィールド: `description`, `auto_invoke`, `user_invocable`, `requires`
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@
|
|||
Profile は Lua で書かれる。Rust resolver は selected profile を restricted Lua VM 内で評価し、返り値が Profile-shaped であることを検証してから `PodManifest` に変換する。
|
||||
|
||||
```lua
|
||||
local profile = require("insomnia.profile")
|
||||
local models = require("insomnia.models")
|
||||
local scope = require("insomnia.scope")
|
||||
local compact = require("insomnia.compact")
|
||||
local profile = require("yoi.profile")
|
||||
local models = require("yoi.models")
|
||||
local scope = require("yoi.scope")
|
||||
local compact = require("yoi.compact")
|
||||
|
||||
local model = models.catalog("codex-oauth/gpt-5.5")
|
||||
|
||||
|
|
@ -79,9 +79,9 @@ Profile に入れてはいけないもの:
|
|||
|
||||
`.nix` profile files are no longer supported. Reusable profiles are Lua; complete low-level recipes belong behind `--manifest`.
|
||||
|
||||
Discovery は bundled builtin profiles、user registry (`<config_dir>/profiles.toml`)、project registry (`<project>/.insomnia/profiles.toml`) を読む。後段の default が前段の default を上書きするため、project default は user/default builtin より優先される。unqualified ambiguous names は source-qualified suggestion を出して失敗する。
|
||||
Discovery は bundled builtin profiles、user registry (`<config_dir>/profiles.toml`)、project registry (`<project>/.yoi/profiles.toml`) を読む。後段の default が前段の default を上書きするため、project default は user/default builtin より優先される。unqualified ambiguous names は source-qualified suggestion を出して失敗する。
|
||||
|
||||
Example `.insomnia/profiles.toml`:
|
||||
Example `.yoi/profiles.toml`:
|
||||
|
||||
```toml
|
||||
default = "coder"
|
||||
|
|
@ -103,11 +103,11 @@ Profile evaluation runs with controlled host-provided `require`.
|
|||
|
||||
Host virtual modules:
|
||||
|
||||
- `require("insomnia")`
|
||||
- `require("insomnia.profile")`
|
||||
- `require("insomnia.models")`
|
||||
- `require("insomnia.compact")`
|
||||
- `require("insomnia.scope")`
|
||||
- `require("yoi")`
|
||||
- `require("yoi.profile")`
|
||||
- `require("yoi.models")`
|
||||
- `require("yoi.compact")`
|
||||
- `require("yoi.scope")`
|
||||
|
||||
Profile-local modules can be reused with dotted names such as `require("shared")` or `require("shared.models")`; they resolve only under the selected profile file's directory. Unsafe/unrestricted Lua facilities such as `os`, `io`, `debug`, unrestricted `package`, `dofile`, `loadfile`, `load`, and `collectgarbage` are unavailable by default.
|
||||
|
||||
|
|
@ -146,22 +146,22 @@ One-file Manifest deserialization keeps the Manifest compatibility behavior: unk
|
|||
|
||||
| Prefix | Resolution |
|
||||
|---|---|
|
||||
| `$insomnia` | bundled `resources/prompts/` (`include_dir!`) |
|
||||
| `$yoi` | bundled `resources/prompts/` (`include_dir!`) |
|
||||
| `$user` | `<config_dir>/prompts/` |
|
||||
| `$workspace` | `<project>/.insomnia/prompts/` |
|
||||
| `$workspace` | `<project>/.yoi/prompts/` |
|
||||
|
||||
`.md` extension can be omitted, e.g. `$insomnia/default` resolves to `resources/prompts/default.md`. Missing files are hard errors; prefixes do not fall through.
|
||||
`.md` extension can be omitted, e.g. `$yoi/default` resolves to `resources/prompts/default.md`. Missing files are hard errors; prefixes do not fall through.
|
||||
|
||||
Profile and one-file Manifest CLI paths currently use builtin prompt assets only for initial loader construction. `$insomnia/...` works; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery.
|
||||
Profile and one-file Manifest CLI paths currently use builtin prompt assets only for initial loader construction. `$yoi/...` works; `$user/...` and `$workspace/...` prompt refs need a future explicit prompt-loader source design instead of reviving ambient manifest discovery.
|
||||
|
||||
The rendered instruction body is followed by fixed Rust-provided sections for working boundaries and, when present, `AGENTS.md`. User templates cannot remove the scope section.
|
||||
|
||||
## `insomnia pod` CLI
|
||||
## `yoi pod` CLI
|
||||
|
||||
Normal fresh startup uses profile discovery/default selection:
|
||||
|
||||
```text
|
||||
insomnia pod [--profile <selector>] [--profile-pod-name <name>] [-s/--store <path>]
|
||||
yoi pod [--profile <selector>] [--profile-pod-name <name>] [-s/--store <path>]
|
||||
```
|
||||
|
||||
| Flag | Description |
|
||||
|
|
@ -173,8 +173,8 @@ insomnia pod [--profile <selector>] [--profile-pod-name <name>] [-s/--store <pat
|
|||
Restore/attach uses Pod/session state and does not re-evaluate profile sources.
|
||||
|
||||
```text
|
||||
insomnia pod --pod <name>
|
||||
insomnia pod --session <uuid>
|
||||
yoi pod --pod <name>
|
||||
yoi pod --session <uuid>
|
||||
```
|
||||
|
||||
Spawn children use hidden `--spawn-config-json`, `--adopt`, and `--callback <path>` flags. These are internal handoff details used by `SpawnPod` after the parent has allocated scope and prepared the child config.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Reasoning / Thinking 制御
|
||||
|
||||
manifest の `[worker]` セクションで `reasoning` を指定すると、scheme が provider 各社の wire 形式に投影する。文字列なら **effort label**、数値なら **thinking budget tokens** として扱う。insomnia 側は値の妥当性を検証せず、未知ラベルや provider が拒む値は API 応答で初めて検出される。
|
||||
manifest の `[worker]` セクションで `reasoning` を指定すると、scheme が provider 各社の wire 形式に投影する。文字列なら **effort label**、数値なら **thinking budget tokens** として扱う。yoi 側は値の妥当性を検証せず、未知ラベルや provider が拒む値は API 応答で初めて検出される。
|
||||
|
||||
## 書き方
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ Tier 3: Full Compact(/compact コマンド)
|
|||
**「十分なトークンを削れる場合にだけ実行」** という判断を行い、
|
||||
キャッシュ無効化コスト > 節約トークン数 となるケースを避ける。
|
||||
|
||||
これは Insomnia における条件付き Prune の直接的な先行事例。
|
||||
これは Yoi における条件付き Prune の直接的な先行事例。
|
||||
|
||||
### キャッシュへの影響
|
||||
|
||||
|
|
@ -216,7 +216,7 @@ OpenCode(sst/opencode)でも Prune とキャッシュの問題は未解決
|
|||
|
||||
---
|
||||
|
||||
## Insomnia 設計への示唆
|
||||
## Yoi 設計への示唆
|
||||
|
||||
### 1. Prune は条件付きで実行すべき
|
||||
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@
|
|||
- `packages/opencode/src/session/prompt/anthropic-20250930.txt`(Claude Code 風システムプロンプト)
|
||||
- `opencode-anthropic-auth@0.0.13` ビルトインプラグイン
|
||||
- `claude-code-20250219` beta ヘッダ
|
||||
- 代替検討: `claude -p` (Claude Code の headless mode) を subprocess で呼ぶ方式。ACP ではなく素朴な CLI fork であり、insomnia では採用しない
|
||||
- 代替検討: `claude -p` (Claude Code の headless mode) を subprocess で呼ぶ方式。ACP ではなく素朴な CLI fork であり、yoi では採用しない
|
||||
- https://code.claude.com/docs/en/legal-and-compliance
|
||||
- https://github.com/sst/opencode/pull/18186
|
||||
|
||||
### OpenAI (Codex CLI / Responses)
|
||||
- Codex CLI は Apache-2.0 で公開されている。insomnia の Codex OAuth 経路は、Codex CLI と同じ Responses 系 wire behavior に寄せる
|
||||
- Codex CLI は Apache-2.0 で公開されている。yoi の Codex OAuth 経路は、Codex CLI と同じ Responses 系 wire behavior に寄せる
|
||||
- Codex CLI の認証ストアと conversation header / request compression / SSE behavior を参考にする
|
||||
- OpenCode の `/connect` で ChatGPT ブラウザ認証が通る
|
||||
- コミュニティ評価: 「Anthropic は walled garden、OpenAI はむしろ取り込みに来た」
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
- `claude --print` / `claude -p` は Claude Code の非対話(headless)モード。プロンプトを stdin/引数で受け stdout に返す
|
||||
- **ACP ではなく素朴な subprocess 呼び出し**
|
||||
- OpenClaw と OpenCode コミュニティフォーク (`griffinmartin/opencode-claude-auth`) が採用
|
||||
- insomnia では専用 API integration ではないため採用しない
|
||||
- yoi では専用 API integration ではないため採用しない
|
||||
|
||||
## Ollama の統合機構
|
||||
|
||||
|
|
@ -177,7 +177,7 @@
|
|||
|
||||
## Capability 軸
|
||||
|
||||
モデル/プロバイダごとの機能差を表現する軸。**プロバイダ側高次ツール (web_search / code_interpreter / computer_use / Live Search) は insomnia では使用しない方針**のため capability 軸から除外。
|
||||
モデル/プロバイダごとの機能差を表現する軸。**プロバイダ側高次ツール (web_search / code_interpreter / computer_use / Live Search) は yoi では使用しない方針**のため capability 軸から除外。
|
||||
|
||||
### 1. tool calling
|
||||
parallel tool calls 可否、tool_choice 対応度。DeepSeek reasoner のような「reasoner + tool 非対応」ケースあり。
|
||||
|
|
@ -221,7 +221,7 @@ parallel tool calls 可否、tool_choice 対応度。DeepSeek reasoner のよう
|
|||
|
||||
→ Gemini / Ollama `/v1` は scheme アダプタで「BlockStart → InputJson(全体 1 回) → BlockStop」の**擬似ストリーム化**で共通化可能。
|
||||
|
||||
## insomnia での採用方針
|
||||
## yoi での採用方針
|
||||
|
||||
### 第一級サポート(専用アダプタ)
|
||||
- **Ollama API** — ローカル + `:cloud` サフィックスで透過的にクラウド中継。エンドポイントは `localhost:11434` で統一
|
||||
|
|
@ -239,4 +239,4 @@ parallel tool calls 可否、tool_choice 対応度。DeepSeek reasoner のよう
|
|||
### 実装原則
|
||||
- 認証アダプタ(外部 CLI の認証ストアを読む類)は llm-worker 直下ではなく上位アダプタ層に配置。llm-worker は低レベル基盤に留める原則(project memory)と整合
|
||||
- モデル列挙は `auto_discover` と宣言型の両輪。Ollama は自動、ルーター系は宣言
|
||||
- `ollama launch insomnia` 対応を視野に入れ、env 注入 (`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等) で起動設定を受け入れる作り
|
||||
- `ollama launch yoi` 対応を視野に入れ、env 注入 (`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等) で起動設定を受け入れる作り
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# エージェント向けメモリ機構の外部事例
|
||||
|
||||
調査日: 2026-04-21。本ドキュメントはユーザー依頼の3ソース(OpenAI Codex Chronicle / Shann³ の "AI Knowledge Layer" スレッド / Nous Research Hermes Agent)を中心に、直近で公開されたメモリ機構をまとめ、insomnia に入れる際の比較材料とすることを目的とする。2026年前半は各所からメモリ実装が同時多発的に登場しているので、「どの事例がどのレイヤを担っているか」を見失わないよう、各節で**何を記憶するか/いつ書くか/どう引き出すか/何に保存するか**を揃えて整理する。
|
||||
調査日: 2026-04-21。本ドキュメントはユーザー依頼の3ソース(OpenAI Codex Chronicle / Shann³ の "AI Knowledge Layer" スレッド / Nous Research Hermes Agent)を中心に、直近で公開されたメモリ機構をまとめ、yoi に入れる際の比較材料とすることを目的とする。2026年前半は各所からメモリ実装が同時多発的に登場しているので、「どの事例がどのレイヤを担っているか」を見失わないよう、各節で**何を記憶するか/いつ書くか/どう引き出すか/何に保存するか**を揃えて整理する。
|
||||
|
||||
数値・URLは一次ソースで再確認すること。挙動は研究プレビュー段階のものが多く、変わる前提で読む。
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ Codex CLI に 2026-03 頃から追加された "Memories" 機能と、2026-04-15
|
|||
- 生成タイミングは「スレッドが十分アイドルになってから」(age + idle window で判定)
|
||||
- `memory_summary.md` が **5,000 tokens cap** で system prompt に注入される(`MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT`)
|
||||
|
||||
**非対称の肝**: 抽出 (extract) は structured output で分類を強制、統合 (consolidation) は sub-agent に自由書き込みさせる。分類ブレを extract で封じ、統合の柔軟性は consolidation の agentic 判断に委ねる、という役割分担。insomnia の extract / consolidation 設計の直接の元ネタ。
|
||||
**非対称の肝**: 抽出 (extract) は structured output で分類を強制、統合 (consolidation) は sub-agent に自由書き込みさせる。分類ブレを extract で封じ、統合の柔軟性は consolidation の agentic 判断に委ねる、という役割分担。yoi の extract / consolidation 設計の直接の元ネタ。
|
||||
|
||||
### 保存場所 / 形式
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ Codex CLI に 2026-03 頃から追加された "Memories" 機能と、2026-04-15
|
|||
|
||||
## 2. Shann³ "AI Knowledge Layer"
|
||||
|
||||
https://x.com/shannholmberg/status/2044111115878326444 で提唱している、**エージェントより先に読ませる知識層**という枠組み。エンジニア向けというよりマーケター / コンテンツ運用者向けだが、構造はかなり insomnia に転用しやすい。
|
||||
https://x.com/shannholmberg/status/2044111115878326444 で提唱している、**エージェントより先に読ませる知識層**という枠組み。エンジニア向けというよりマーケター / コンテンツ運用者向けだが、構造はかなり yoi に転用しやすい。
|
||||
|
||||
### 2 層構造
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ MindStudio の比較記事が要点をまとめている。
|
|||
|
||||
### 設計上の示唆
|
||||
|
||||
- **可変 KBL と不変 BF の分離**が強い。insomnia なら前者が Chronicle 的な自動メモリ、後者が `AGENTS.md` / 人間のガイド。
|
||||
- **可変 KBL と不変 BF の分離**が強い。yoi なら前者が Chronicle 的な自動メモリ、後者が `AGENTS.md` / 人間のガイド。
|
||||
- **retrieval を埋め込みではなく決定論的 wikilink でやる**アプローチは、少量ドメインで意外と強い。
|
||||
- エージェントに書かせる `log.md` と `index.md` の append-only 運用は、後で diff / git で検証しやすい。
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ Self-improving agent を名乗るフレーム。メモリ周りは **3 層 + ク
|
|||
> Review the conversation above and consider saving or updating a skill if appropriate.
|
||||
> Focus on: was a non-trivial approach used to complete a task that required trial and error...
|
||||
> **If nothing is worth saving, just say 'Nothing to save.' and stop.**
|
||||
- 末尾の "Nothing to save." 指示が肝。頻繁発火でも中身ゼロの場合は NOP で抜ける設計。insomnia extract の「空配列許容」の直接ソース
|
||||
- 末尾の "Nothing to save." 指示が肝。頻繁発火でも中身ゼロの場合は NOP で抜ける設計。yoi extract の「空配列許容」の直接ソース
|
||||
|
||||
### 書き込み機構
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ Self-improving agent を名乗るフレーム。メモリ周りは **3 層 + ク
|
|||
|
||||
### 設計上の示唆
|
||||
|
||||
- **"skill"= procedural memory として episodic / semantic から分離する**のは insomnia にとっても綺麗。Claude Code の skill と接続できる余地がある。
|
||||
- **"skill"= procedural memory として episodic / semantic から分離する**のは yoi にとっても綺麗。Claude Code の skill と接続できる余地がある。
|
||||
- **FTS5 + LLM 要約ハイブリッド**で、ベクタを入れずにそこそこ回せる事例として参考価値が高い(少量ドメインなら LLM Wiki 論と同じ示唆)。
|
||||
- **Honcho 的 user model** を semantic profile として固定注入する運用は、Codex の memory summary と形式的に同じ。
|
||||
|
||||
|
|
@ -228,7 +228,7 @@ Self-improving agent を名乗るフレーム。メモリ周りは **3 層 + ク
|
|||
|
||||
### OpenClaw
|
||||
|
||||
Peter Steinberger が 2025-11 に出したメッセージングファースト agent。2026-02 に本人は OpenAI 入社、プロジェクトは foundation へ移譲。Hermes Agent の `hermes claw migrate` はここからの移行導線。設計は **Markdown ファイルのみに全状態を持つ**という極端な透明性志向で、insomnia の「ファイル + git」方針との親和性が最も高い。
|
||||
Peter Steinberger が 2025-11 に出したメッセージングファースト agent。2026-02 に本人は OpenAI 入社、プロジェクトは foundation へ移譲。Hermes Agent の `hermes claw migrate` はここからの移行導線。設計は **Markdown ファイルのみに全状態を持つ**という極端な透明性志向で、yoi の「ファイル + git」方針との親和性が最も高い。
|
||||
|
||||
Agent Workspace(`~/.openclaw/workspace/`)構成:
|
||||
|
||||
|
|
@ -263,7 +263,7 @@ Agent Workspace(`~/.openclaw/workspace/`)構成:
|
|||
- **Lock**: `memory/.dreams/short-term-promotion.lock` を `wx` フラグで exclusive create、60s stale 検出 + 10s wait timeout、in-process の Map も併用
|
||||
- **モデルが "覚えている" のはディスクに書かれた内容だけ**、という明示ポリシー。隠れた state 無し
|
||||
|
||||
**insomnia にとって重要**: consolidation を LLM 依存から切り離せる見本。narrative は subagent が生成するが、promotion の判断は純機械(scoring)。insomnia の plan では Scope 外(consolidation は当面 agent 委任)だが、成熟したカテゴリから決定論的 promotion に差し替える upgrade path の参考になる。
|
||||
**yoi にとって重要**: consolidation を LLM 依存から切り離せる見本。narrative は subagent が生成するが、promotion の判断は純機械(scoring)。yoi の plan では Scope 外(consolidation は当面 agent 委任)だが、成熟したカテゴリから決定論的 promotion に差し替える upgrade path の参考になる。
|
||||
|
||||
**GC 観点の追加詳細**(`extensions/memory-core/src/short-term-promotion.ts:1518-1652` 実装より):
|
||||
|
||||
|
|
@ -278,7 +278,7 @@ OpenClaw は「**削除は人間、script は append と退避まで**」とい
|
|||
|
||||
設計上の示唆:
|
||||
|
||||
- Workspace = git リポジトリ 1 本で完結、配置もフラット。insomnia の pod workspace 概念にそのまま借用できる。
|
||||
- Workspace = git リポジトリ 1 本で完結、配置もフラット。yoi の pod workspace 概念にそのまま借用できる。
|
||||
- 秘密は workspace **外**の `~/.openclaw/` 側(auth / credentials / session transcripts / managed skills)に退避する分離設計は、pod sandbox 境界を越えない運用の手本になる。
|
||||
- `memory/YYYY-MM-DD.md` の日次切り分け + 「当日+前日のみ load」は、時系列の自然減衰を Markdown で素直に表現できる良い pattern。
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ OpenClaw は「**削除は人間、script は append と退避まで**」とい
|
|||
|
||||
## 5. Agent Skills 標準(procedural memory の実装単位)
|
||||
|
||||
Hermes / OpenClaw / Claude Code / OpenAI Codex / Cursor / GitHub Copilot 等が揃って採用している **`agentskills.io` オープン標準**。Anthropic が 2025-12 に発表し、2026-03 時点で事実上の共通フォーマットになっている。memory 設計の「手続き的記憶 (procedural memory)」のほぼ全てがこの単位で流通するので、insomnia でも独自フォーマットを避けて素直にこれに乗るのが合理的。
|
||||
Hermes / OpenClaw / Claude Code / OpenAI Codex / Cursor / GitHub Copilot 等が揃って採用している **`agentskills.io` オープン標準**。Anthropic が 2025-12 に発表し、2026-03 時点で事実上の共通フォーマットになっている。memory 設計の「手続き的記憶 (procedural memory)」のほぼ全てがこの単位で流通するので、yoi でも独自フォーマットを避けて素直にこれに乗るのが合理的。
|
||||
|
||||
### SKILL.md の最小仕様
|
||||
|
||||
|
|
@ -365,7 +365,7 @@ enterprise (managed) > personal (~/.claude/skills/) > project (.claude/skills/)
|
|||
|
||||
Skill content のライフサイクルは重要で、**一度発動すると rendered 内容が会話に単一メッセージとして入り、以後再読み込みされない**。auto-compaction 時は各 skill の直近 invocation だけが冒頭 5,000 tokens 維持され、全 skill 合算 25,000 tokens の予算内で新しい順に残す。つまり「skill の本体は session 中ずっと context に居座る」前提で書く必要がある。
|
||||
|
||||
### insomnia への示唆
|
||||
### yoi への示唆
|
||||
|
||||
- **procedural memory は SKILL.md で表現**する。`.claude/skills/` に人間手入れの skill を置き、エージェントが自動生成する手続きは別ディレクトリ(例: `memory/skills/`)で分離、混ざらないようにする。BF/KBL 分離の原則に一致。
|
||||
- **`paths:` によるスコープ絞り**は、前回議論した「Pod の所属スコープ = ディレクトリ階層」と自然に噛む。`paths: ["crates/protocol/**"]` の skill は protocol スコープの pod でだけ発動、という運用が素直にできる。
|
||||
|
|
@ -382,7 +382,7 @@ Skill content のライフサイクルは重要で、**一度発動すると ren
|
|||
|
||||
## 6. プロンプト・スキルの継続的チューニング (empirical prompt tuning pattern)
|
||||
|
||||
メモリや skill の**中身を腐らせない**側の話。公開されている prompt tuning pattern として、agent-facing な指示を新規 subagent に実行させ、実行者の自己申告と指示側メトリクスを突き合わせて反復改善する方法がある。insomnia のように skill を蓄積する設計では、**書いた直後に客観的に試す**仕組みが無いと品質が崩れていく。ここへの素直な当てはめ材料として記録。
|
||||
メモリや skill の**中身を腐らせない**側の話。公開されている prompt tuning pattern として、agent-facing な指示を新規 subagent に実行させ、実行者の自己申告と指示側メトリクスを突き合わせて反復改善する方法がある。yoi のように skill を蓄積する設計では、**書いた直後に客観的に試す**仕組みが無いと品質が崩れていく。ここへの素直な当てはめ材料として記録。
|
||||
|
||||
### 基本思想
|
||||
|
||||
|
|
@ -425,12 +425,12 @@ Claude Code の Task tool 戻り値から:
|
|||
|
||||
> 毎回**新規** AI を dispatch すること。同じセッション再利用は前回の指摘を学習してしまい、指標が腐る。
|
||||
|
||||
### insomnia への示唆
|
||||
### yoi への示唆
|
||||
|
||||
- **skill や lessons を新規追加した直後に、同じ insomnia ハーネス内の別 pod で実行して評価**する自動フロー("skill doctor" 的な存在)を作れる。これは insomnia が pod factory を持っている点と相性がいい。
|
||||
- **skill や lessons を新規追加した直後に、同じ yoi ハーネス内の別 pod で実行して評価**する自動フロー("skill doctor" 的な存在)を作れる。これは yoi が pod factory を持っている点と相性がいい。
|
||||
- 失敗ログを書いた後、「同じ失敗が再現しないか」を新規 pod で試走する検証ステップが、構造的に**メモリ整備の一部**に組み込める。skill 化しない失敗ログでも有効。
|
||||
- 評価指標を自前で定義しておくと、後で他人(or 未来の自分)が skill を更新した時に腐敗検知できる。
|
||||
- 実体は skill 自身として配布されている例がある。insomnia のメンテ用 skill セットのテンプレにも応用できる。
|
||||
- 実体は skill 自身として配布されている例がある。yoi のメンテ用 skill セットのテンプレにも応用できる。
|
||||
|
||||
一次ソースは公開 sanitize branch では省略する。
|
||||
|
||||
|
|
@ -450,19 +450,19 @@ Claude Code の Task tool 戻り値から:
|
|||
| ユーザー編集性 | 不可視 / 読取のみ / 手編集前提 |
|
||||
| スコープ | per-user / per-project / shared across agents |
|
||||
|
||||
insomnia で意思決定すべきポイントはこの対応表:
|
||||
yoi で意思決定すべきポイントはこの対応表:
|
||||
|
||||
- **Pod / Agent の「skill」概念を Hermes 風に明示すべきか**。現状の controller.rs には "sub-agent spawn" はあるが、skill を書き出して再利用する仕組みは無い。
|
||||
- **Codex Chronicle 風の "consolidation モデルを別途設定" 構成**は、insomnia の llm provider policy(Ollama / Codex OAuth / Anthropic)と相性が良い。軽量 extract と重い consolidation を別プロバイダに張れる。
|
||||
- **Codex Chronicle 風の "consolidation モデルを別途設定" 構成**は、yoi の llm provider policy(Ollama / Codex OAuth / Anthropic)と相性が良い。軽量 extract と重い consolidation を別プロバイダに張れる。
|
||||
- **LLM Wiki パターンを採用する場合**、既に `docs/` と `tickets/` が Markdown + git で運用されているので、`memory/` ディレクトリを足して Git で可観測にしておくのが自然。RAG やベクトル化より先に、wikilink / index.md / log.md で足りるか見極めるべき。
|
||||
- **storage 層**: SQLite は既存 crate 構成にもフィット。Cloudflare Agent Memory / Codex の SQLite + 最終 Markdown の 2 段は移植しやすい。
|
||||
- **prompt injection 対策**: Chronicle が注意書きしている通り、観測チャンネルを増やすと攻撃面が広がる。insomnia では pod の sandbox 境界とメモリ生成を同じ境界で括る必要がある。
|
||||
- **prompt injection 対策**: Chronicle が注意書きしている通り、観測チャンネルを増やすと攻撃面が広がる。yoi では pod の sandbox 境界とメモリ生成を同じ境界で括る必要がある。
|
||||
|
||||
---
|
||||
|
||||
## 8. GC 機構の横断比較
|
||||
|
||||
`docs/plan/memory.md` §GC は「consolidation とは別経路で memory を再評価し、drop / merge / split / `replaced` chain 整理を行う」ことを決めた段階で、判断主体と処理種別の仕様をこれから詰める。本節は他プロジェクトの GC 設計を共通の 6 軸で並べて、insomnia で採るべき型の材料とする。
|
||||
`docs/plan/memory.md` §GC は「consolidation とは別経路で memory を再評価し、drop / merge / split / `replaced` chain 整理を行う」ことを決めた段階で、判断主体と処理種別の仕様をこれから詰める。本節は他プロジェクトの GC 設計を共通の 6 軸で並べて、yoi で採るべき型の材料とする。
|
||||
|
||||
### 8.1 比較表
|
||||
|
||||
|
|
@ -499,7 +499,7 @@ insomnia で意思決定すべきポイントはこの対応表:
|
|||
3. **cron / scheduled sweep**(OpenClaw dreaming default `0 3 * * *`, Codex extension retention): 定期的・予測可能。人間 review との組み合わせがしやすい。
|
||||
4. **ingest 時の即時**(Cloudflare supersession): 書き込みの tx 内で完結、後続 GC 走査が要らない。topic key 設計が前提。
|
||||
|
||||
insomnia の plan は (2) consolidation で rewrite 許可を置きつつ、GC は (3) 方向で別経路という構造。これは Codex / OpenClaw の両方と整合する。
|
||||
yoi の plan は (2) consolidation で rewrite 許可を置きつつ、GC は (3) 方向で別経路という構造。これは Codex / OpenClaw の両方と整合する。
|
||||
|
||||
**判断主体の 3 系統**:
|
||||
|
||||
|
|
@ -507,13 +507,13 @@ insomnia の plan は (2) consolidation で rewrite 許可を置きつつ、GC
|
|||
- **決定論 scoring → 閾値 gate → 機械適用**: OpenClaw Deep promotion。LLM の揺れを除き、コストも LLM コールゼロ。ただし対象が append 側のみで、削除には使われていない。
|
||||
- **LLM agentic**: Codex consolidation / Hermes review / Letta sleep-time。判断の柔軟性(block 内部分削除、context 依存の merge)を LLM に委ねる。
|
||||
|
||||
`docs/plan/memory.md` は consolidation が LLM agentic、GC も暫定的に **LLM agentic + Linter Warn 併用**としている。完全に一致する事例は **Codex consolidation の consolidation prompt**(836 行)で、「removed thread id を `MEMORY.md` から部分削除し、blockに他の thread が残っている場合は split / rewrite して保持」という手続きを自然言語で指示している。**insomnia は Linter 側に警告カテゴリ(類似 slug / `replaced` 滞留 / sources 過多 / stale)を先に定義し、GC 実行の agent プロンプトはそれを入力にする**構造が素直。
|
||||
`docs/plan/memory.md` は consolidation が LLM agentic、GC も暫定的に **LLM agentic + Linter Warn 併用**としている。完全に一致する事例は **Codex consolidation の consolidation prompt**(836 行)で、「removed thread id を `MEMORY.md` から部分削除し、blockに他の thread が残っている場合は split / rewrite して保持」という手続きを自然言語で指示している。**yoi は Linter 側に警告カテゴリ(類似 slug / `replaced` 滞留 / sources 過多 / stale)を先に定義し、GC 実行の agent プロンプトはそれを入力にする**構造が素直。
|
||||
|
||||
**処理種別の選択肢**:
|
||||
|
||||
- `drop / merge / split / rewrite` の組み合わせは Codex consolidation が最も自由度高く、Hermes もそれに近い(entry 粒度)。
|
||||
- `replaced` chain の整理は **Cloudflare だけが自動で版チェーン維持**、他は LLM 任せ。insomnia は decision record に `replaced_by` を入れているので、Cloudflare 方式の forward pointer 概念を **人間可読な `replaced_by:` frontmatter** で既に踏襲している。GC 時に chain をどこまで短く畳むか(長大な `a → b → c → d` を `a → d` に圧縮するか)は未決定論点で、Cloudflare は圧縮せず chain を保持する設計。
|
||||
- **`split` は Codex だけが明示**。block 内に複数 thread id が混ざった場合に thread id 単位で分ける。insomnia の「1 件 1 ファイル」方針では split = ファイル分割となり、主題の粒度判断は GC agent に委ねる必要がある。
|
||||
- `replaced` chain の整理は **Cloudflare だけが自動で版チェーン維持**、他は LLM 任せ。yoi は decision record に `replaced_by` を入れているので、Cloudflare 方式の forward pointer 概念を **人間可読な `replaced_by:` frontmatter** で既に踏襲している。GC 時に chain をどこまで短く畳むか(長大な `a → b → c → d` を `a → d` に圧縮するか)は未決定論点で、Cloudflare は圧縮せず chain を保持する設計。
|
||||
- **`split` は Codex だけが明示**。block 内に複数 thread id が混ざった場合に thread id 単位で分ける。yoi の「1 件 1 ファイル」方針では split = ファイル分割となり、主題の粒度判断は GC agent に委ねる必要がある。
|
||||
|
||||
**人間介入点の 3 段**:
|
||||
|
||||
|
|
@ -521,7 +521,7 @@ insomnia の plan は (2) consolidation で rewrite 許可を置きつつ、GC
|
|||
- audit-first(issue を surface し、人間が決断): memory-wiki lint / OpenClaw dreaming-repair
|
||||
- high-stake 限定 gate: LinkedIn CMA
|
||||
|
||||
insomnia の plan は「人間 offer 承認を併用」なので **audit-first に寄る**のが自然。lint 相当の Warn を Linter で出し、LLM consolidation / GC がそれを消費する前に人間が承認 / 拒否できる UI を提供する構造。memory-wiki lint は `reports/lint.md` というシンプルな Markdown 出力なので、そのまま `memory/reports/gc-lint.md` 相当を tick off する実装が参考になる。
|
||||
yoi の plan は「人間 offer 承認を併用」なので **audit-first に寄る**のが自然。lint 相当の Warn を Linter で出し、LLM consolidation / GC がそれを消費する前に人間が承認 / 拒否できる UI を提供する構造。memory-wiki lint は `reports/lint.md` というシンプルな Markdown 出力なので、そのまま `memory/reports/gc-lint.md` 相当を tick off する実装が参考になる。
|
||||
|
||||
**履歴保持の 3 モデル**:
|
||||
|
||||
|
|
@ -529,14 +529,14 @@ insomnia の plan は「人間 offer 承認を併用」なので **audit-first
|
|||
2. **archive 退避(rename)**: OpenClaw dreaming-repair
|
||||
3. **forward pointer / tombstone**: Cloudflare supersession
|
||||
|
||||
insomnia は **git 管理下に memory を置く**前提なので、物理削除を選んでも git log で復元できるのが強み。`replaced_by:` frontmatter が forward pointer の役割を果たしているので、Cloudflare 型と git を足した「**現物は物理削除、frontmatter pointer で chain を参照、git で history**」が最も設計コストに合う。archive 退避は git を前提にすると冗長。
|
||||
yoi は **git 管理下に memory を置く**前提なので、物理削除を選んでも git log で復元できるのが強み。`replaced_by:` frontmatter が forward pointer の役割を果たしているので、Cloudflare 型と git を足した「**現物は物理削除、frontmatter pointer で chain を参照、git で history**」が最も設計コストに合う。archive 退避は git を前提にすると冗長。
|
||||
|
||||
### 8.3 insomnia の GC 仕様を詰めるときの示唆
|
||||
### 8.3 yoi の GC 仕様を詰めるときの示唆
|
||||
|
||||
1. **GC trigger は 2 系統に割る**。(a) 決定論: Linter Warn 群 + age / count / size 閾値の sweep、(b) LLM 判定: consolidation とは別 prompt で Linter の issue リストを入力に渡す。両方が `memory/reports/gc-*.md` 相当を書き、それを次回の GC run が読む、というフィードバックループが OpenClaw lint / Codex consolidation input selection の両方と整合する。
|
||||
2. **Linter に「GC 候補検出」カテゴリを足す**。memory-wiki の lint issue code が参考になる: `stale-page`(90d 超)/ `stale-claim` / `low-confidence` / `orphan` / `duplicate-id` / `broken-wikilink` / `contradiction-present` / `open-question`。insomnia 固有の追加候補: `similar-slug`(類似 slug 乱立、既に plan に記載)/ `replaced-chain-long`(`replaced_by` が 3 段以上)/ `sources-overflow`(1 record の sources が閾値超)/ `knowledge-invoke-frequency-low`(`user_invoke` が一定期間ゼロ)。
|
||||
3. **処理は rewrite 優先、削除は `status: replaced` 経由**(既に plan 方針と一致)。forward pointer は Cloudflare 流、ただし chain 圧縮ルール(例: 「chain が n 段超えたら中間を drop、端のみ残す」)を決めるかは別論点。Cloudflare は圧縮しない、insomnia は git があるので圧縮してよい。
|
||||
4. **char limit は採用しない方が筋が良い**。Hermes の hard limit + LLM self-rewrite は設計最小だが、insomnia は 1 record 1 file なのでファイル内 size 制約は薄く、file 数による grep コストの方が支配的になる。file 数閾値 → GC trigger の方が insomnia の形に合う。
|
||||
2. **Linter に「GC 候補検出」カテゴリを足す**。memory-wiki の lint issue code が参考になる: `stale-page`(90d 超)/ `stale-claim` / `low-confidence` / `orphan` / `duplicate-id` / `broken-wikilink` / `contradiction-present` / `open-question`。yoi 固有の追加候補: `similar-slug`(類似 slug 乱立、既に plan に記載)/ `replaced-chain-long`(`replaced_by` が 3 段以上)/ `sources-overflow`(1 record の sources が閾値超)/ `knowledge-invoke-frequency-low`(`user_invoke` が一定期間ゼロ)。
|
||||
3. **処理は rewrite 優先、削除は `status: replaced` 経由**(既に plan 方針と一致)。forward pointer は Cloudflare 流、ただし chain 圧縮ルール(例: 「chain が n 段超えたら中間を drop、端のみ残す」)を決めるかは別論点。Cloudflare は圧縮しない、yoi は git があるので圧縮してよい。
|
||||
4. **char limit は採用しない方が筋が良い**。Hermes の hard limit + LLM self-rewrite は設計最小だが、yoi は 1 record 1 file なのでファイル内 size 制約は薄く、file 数による grep コストの方が支配的になる。file 数閾値 → GC trigger の方が yoi の形に合う。
|
||||
5. **決定論 scoring を後から差し込む余地を残す**。OpenClaw Deep pass のような「頻度 / 関連度 / 多様性 / 時間減衰 / 整合性 / 概念」の 6 重み + 閾値は、agent LLM の出力が運用で評価可能になった段階で部分的に差し替える upgrade path として最適。初期は consolidation LLM + Linter Warn で十分。
|
||||
6. **削除は git commit 単位で可逆**という前提を明示する。プロジェクトメモリは git 管理下なので、GC が誤って drop してもユーザーは revert できる。これは Codex が持っていない利点で、GC agent の判断を多少攻めても安全マージンがある。
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ reasoning トークンは各ターンの後に破棄される。次ターンに
|
|||
1. `previous_response_id` パラメータで過去のレスポンスを参照
|
||||
2. `response.output` の全アイテムを次の `input` に手動で渡す
|
||||
|
||||
ステートレス利用(`store=false`、ZDR組織)の場合は `include=["reasoning.encrypted_content"]` を指定すれば暗号化された推論コンテンツを受け取り、次リクエストに渡すことで推論を引き継げる。Insomnia は履歴から復元した reasoning item を通常の API message として扱い、独自の turn-boundary filtering はしない。
|
||||
ステートレス利用(`store=false`、ZDR組織)の場合は `include=["reasoning.encrypted_content"]` を指定すれば暗号化された推論コンテンツを受け取り、次リクエストに渡すことで推論を引き継げる。Yoi は履歴から復元した reasoning item を通常の API message として扱い、独自の turn-boundary filtering はしない。
|
||||
|
||||
同一ターン内の function-call loop でも、`reasoning item → function_call → function_call_output → 次の Responses request` の連続性を保つため、履歴上の reasoning item は通常の API message として保持する。ToolResult は wire 上で user 側 item に見えるが、reasoning item の削除境界としては扱わない。
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
# Insomnia × OpenCode 比較レポート
|
||||
# Yoi × OpenCode 比較レポート
|
||||
|
||||
## 概要
|
||||
|
||||
Insomnia(Rust製エージェントプラットフォーム、基礎実装段階)と OpenCode(TypeScript/Bun製AIコーディングアシスタント、本番稼働レベル)の設計を比較し、Insomniaの基礎設計に取り込めるパターンを特定する。
|
||||
Yoi(Rust製エージェントプラットフォーム、基礎実装段階)と OpenCode(TypeScript/Bun製AIコーディングアシスタント、本番稼働レベル)の設計を比較し、Yoiの基礎設計に取り込めるパターンを特定する。
|
||||
|
||||
---
|
||||
|
||||
## 1. アーキテクチャ概観
|
||||
|
||||
### Insomnia(現状)
|
||||
### Yoi(現状)
|
||||
|
||||
```
|
||||
insomnia (stub)
|
||||
└─ insomnia-core Pod / Controller / Protocol / SocketServer
|
||||
yoi (stub)
|
||||
└─ yoi-core Pod / Controller / Protocol / SocketServer
|
||||
└─ llm-worker-persistence Session永続化(JSONL + Blob)
|
||||
└─ llm-worker Worker / Tool / Hook / Subscriber
|
||||
└─ llm-worker-macros #[tool] / #[tool_registry]
|
||||
|
|
@ -41,13 +41,13 @@ packages/desktop (Tauri) Web UIラッパー
|
|||
|
||||
## 2. 設計判断の比較
|
||||
|
||||
| 観点 | Insomnia | OpenCode | 評価 |
|
||||
| 観点 | Yoi | OpenCode | 評価 |
|
||||
|------|----------|----------|------|
|
||||
| **DI** | ジェネリクス `<C: LlmClient, St: Store>` | Effect Service + Layer | Insomnia: コンパイル時保証。OpenCode: 実行時合成の柔軟性。方向性は正しい |
|
||||
| **状態管理** | `RwLock<PodStatus>` + ファイル書き出し | SQLite + Event Bus + SSE | Insomnia: 軽量で正しい。DBは将来の選択肢 |
|
||||
| **プロトコル** | 自前 JSONL (Method/Event) | Hono HTTP API + SSE | Insomnia: Unix Socketに最適化。目的が違う |
|
||||
| **DI** | ジェネリクス `<C: LlmClient, St: Store>` | Effect Service + Layer | Yoi: コンパイル時保証。OpenCode: 実行時合成の柔軟性。方向性は正しい |
|
||||
| **状態管理** | `RwLock<PodStatus>` + ファイル書き出し | SQLite + Event Bus + SSE | Yoi: 軽量で正しい。DBは将来の選択肢 |
|
||||
| **プロトコル** | 自前 JSONL (Method/Event) | Hono HTTP API + SSE | Yoi: Unix Socketに最適化。目的が違う |
|
||||
| **ツール** | `Tool` trait + マクロ生成 | Zod schema + execute関数 | 同等のアプローチ。マクロの方が型安全 |
|
||||
| **フック** | `Hook<K: HookEventKind>` trait 10種 | Plugin hooks (before/after) | Insomnia: 型安全で粒度が細かい。OpenCode: 動的で拡張しやすい |
|
||||
| **フック** | `Hook<K: HookEventKind>` trait 10種 | Plugin hooks (before/after) | Yoi: 型安全で粒度が細かい。OpenCode: 動的で拡張しやすい |
|
||||
| **永続化** | JSONL append-only + Blob | SQLite + Drizzle ORM | 方向性が異なる。両方とも正当な選択 |
|
||||
| **プロバイダ** | 4種(Anthropic/OpenAI/Gemini/Ollama) | 20種+(ai-sdk経由) | 数は後から追加できる。抽象は同レベル |
|
||||
|
||||
|
|
@ -62,9 +62,9 @@ packages/desktop (Tauri) Web UIラッパー
|
|||
- 3段階: `deny` → `allow` → `ask`(ユーザーに確認)
|
||||
- 「always」応答でパターンを永続的に許可
|
||||
|
||||
**Insomniaへの示唆:**
|
||||
**Yoiへの示唆:**
|
||||
|
||||
Insomniaには `Scope`(書き込みディレクトリ制約)があるが、これは静的な境界。
|
||||
Yoiには `Scope`(書き込みディレクトリ制約)があるが、これは静的な境界。
|
||||
ツール単位の動的パーミッションが欠落している。
|
||||
|
||||
```
|
||||
|
|
@ -101,12 +101,12 @@ action = "deny"
|
|||
- 切り捨て分はファイルに保存(7日間保持)
|
||||
- LLMには「出力が大きすぎた。`grep` や `read` で絞り込め」とヒント
|
||||
|
||||
**Insomniaの現状:**
|
||||
**Yoiの現状:**
|
||||
- `llm-worker` に Tool Output の Inline/Stored 閾値(800 bytes)がある
|
||||
- Stored 出力は Blob Storage に退避し、要約を自動生成
|
||||
|
||||
**比較:**
|
||||
Insomnia の方が洗練されている(要約生成まで組み込み済み)。
|
||||
Yoi の方が洗練されている(要約生成まで組み込み済み)。
|
||||
ただし OpenCode の「ヒント付きトランケーション」は追加の視点として有用。
|
||||
|
||||
**取り込み案:**
|
||||
|
|
@ -124,11 +124,11 @@ Insomnia の方が洗練されている(要約生成まで組み込み済み
|
|||
- 構造化要約: Goal / Instructions / Discoveries / Accomplished / Files
|
||||
3. **Replay**: 圧縮後に前回のユーザーメッセージを再送して作業継続
|
||||
|
||||
**Insomniaの現状:**
|
||||
**Yoiの現状:**
|
||||
- Worker は history をそのまま保持
|
||||
- コンテキスト管理の仕組みは未実装
|
||||
|
||||
**これは重要な欠落。** 長時間実行エージェントである Insomnia にとって、コンテキスト管理はコア機能。
|
||||
**これは重要な欠落。** 長時間実行エージェントである Yoi にとって、コンテキスト管理はコア機能。
|
||||
|
||||
**取り込み案:**
|
||||
|
||||
|
|
@ -157,13 +157,13 @@ consolidation: Compact(Agent ベース)
|
|||
- Instance スコープ + Global スコープの二段バス
|
||||
- `publish` / `subscribe` / `subscribeAll` の3操作
|
||||
|
||||
**Insomniaの現状:**
|
||||
**Yoiの現状:**
|
||||
- `broadcast::Sender<Event>` による単一チャネル
|
||||
- Event enum で型安全
|
||||
- Pod 単位のスコープのみ
|
||||
|
||||
**比較:**
|
||||
Insomnia の broadcast channel は Pod 単位では十分。
|
||||
Yoi の broadcast channel は Pod 単位では十分。
|
||||
ただし、**複数 Pod の協調**(Supervisor)段階で Global Bus が必要になる。
|
||||
|
||||
**取り込み案:**
|
||||
|
|
@ -180,7 +180,7 @@ Insomnia の broadcast channel は Pod 単位では十分。
|
|||
- ツール実行前にスナップショット取得
|
||||
- `restore` / `revert` / `diff` 操作
|
||||
|
||||
**Insomniaの現状:**
|
||||
**Yoiの現状:**
|
||||
- Scope(書き込み制約)はあるが、変更追跡・復元は未実装
|
||||
|
||||
**取り込み案:**
|
||||
|
|
@ -200,13 +200,13 @@ Insomnia の broadcast channel は Pod 単位では十分。
|
|||
- `steps` パラメータでサブエージェントの反復回数を制限
|
||||
- 親セッションのコンテキストを子に渡す
|
||||
|
||||
**Insomniaの現状:**
|
||||
**Yoiの現状:**
|
||||
- Pod は独立実行単位。Pod 間通信は未実装
|
||||
- 拡張ポイント表に「Supervisor」として記載
|
||||
|
||||
**比較:**
|
||||
OpenCode の Agent は Session 内のモード切り替え。
|
||||
Insomnia の Pod は完全に独立したプロセス。
|
||||
Yoi の Pod は完全に独立したプロセス。
|
||||
|
||||
**取り込み案:**
|
||||
- OpenCode の `steps`(最大反復回数)は Pod マニフェストに追加する価値あり
|
||||
|
|
@ -225,7 +225,7 @@ Insomnia の Pod は完全に独立したプロセス。
|
|||
- 配列フィールドはマージ(上書きではなく結合)
|
||||
- Plugin の出自を追跡(PluginOrigin)
|
||||
|
||||
**Insomniaの現状:**
|
||||
**Yoiの現状:**
|
||||
- マニフェスト(TOML)のみ。階層なし
|
||||
|
||||
**取り込み案:**
|
||||
|
|
@ -244,7 +244,7 @@ Insomnia の Pod は完全に独立したプロセス。
|
|||
- 遅延初期化(必要時にのみ起動)
|
||||
- graceful degradation(サーバーなし → 無視)
|
||||
|
||||
**Insomniaの現状:**
|
||||
**Yoiの現状:**
|
||||
- LSP の言及なし
|
||||
|
||||
**取り込み案:**
|
||||
|
|
@ -256,7 +256,7 @@ Insomnia の Pod は完全に独立したプロセス。
|
|||
|
||||
## 4. 設計思想の根本的な違い
|
||||
|
||||
### Insomnia: 「Pod は独立した実行単位」
|
||||
### Yoi: 「Pod は独立した実行単位」
|
||||
|
||||
- 各 Pod が完結したプロセス
|
||||
- 協調は外部(Supervisor)が行う
|
||||
|
|
@ -269,7 +269,7 @@ Insomnia の Pod は完全に独立したプロセス。
|
|||
- アプリケーションの哲学に近い
|
||||
|
||||
**この違いは意図的であり、変える必要はない。**
|
||||
Insomnia のアプローチは長時間自律実行に適しており、Pod の独立性がフォールトトレランスと拡張性の基盤になる。
|
||||
Yoi のアプローチは長時間自律実行に適しており、Pod の独立性がフォールトトレランスと拡張性の基盤になる。
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Retrieved: 2026-04-28
|
|||
**必須 (required)**。
|
||||
|
||||
`POST /v1/messages` のボディパラメータとして必須指定。
|
||||
insomnia の現在の実装(`max_tokens: u32`、未指定時 4096 にフォールバック)は仕様と合致している。
|
||||
yoi の現在の実装(`max_tokens: u32`、未指定時 4096 にフォールバック)は仕様と合致している。
|
||||
|
||||
## 3. 型・範囲
|
||||
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ Worker<C, Mutable> Worker<C, Locked>
|
|||
- **需要がある層か**: yes。rig / swiftide / genai は揃って "もう一段下のキャッシュ整合性プリミティブ" を持っていない
|
||||
- **既存と被るか**: 上記 3 案でかわせる
|
||||
- **維持コスト**: API 安定化 + provider 追従が恒常的に乗る。`llm_client/` を外すか genai に寄せるかでだいぶ軽くなる
|
||||
- **タイミング**: insomnia 本体がリリースされ、`Worker` の API が stress test を受ける前に公開すると、後から破壊的変更を強いられる。**先に insomnia をリリースして、production 使用例として参照させてからライブラリ化する**方が安全
|
||||
- **タイミング**: yoi 本体がリリースされ、`Worker` の API が stress test を受ける前に公開すると、後から破壊的変更を強いられる。**先に yoi をリリースして、production 使用例として参照させてからライブラリ化する**方が安全
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ let prompt_cache_key = Some(self.client.state.conversation_id.to_string());
|
|||
シナリオ(マルチテナント等)で意図しないヒット混線を避ける用途
|
||||
で使う。少なくとも害は無いので両 backend で同じ値を送って良い。
|
||||
|
||||
## 5. insomnia での運用
|
||||
## 5. yoi での運用
|
||||
|
||||
- `Request::cache_key: Option<String>` を provider-agnostic な
|
||||
キャッシュヒントとして持つ。`cache_anchor` (Anthropic 用 prefix
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ LLM/AI エージェント時代のコーディングで、Git の粒度が粗
|
|||
- **永続的位置参照**: Tree-sitter ベースの semantic anchor、Sourcegraph の SCIP、`git-blame` の line tracking。DeltaDB は CRDT identity を使うため理論的にこれらより堅牢な anchor を提供できる。
|
||||
- **AI エージェント協働基盤**: OpenAI の "evolving spec" 議論、Anthropic の Computer Use 系、各社の MCP。DeltaDB は「エージェント↔コード↔人間の対話」を VCS 層で受ける狙い。
|
||||
|
||||
## 8. 自プロジェクト(insomnia)への含意メモ
|
||||
## 8. 自プロジェクト(yoi)への含意メモ
|
||||
|
||||
参考材料として残す(採用の可否ではない)。
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user