718 lines
22 KiB
Markdown
718 lines
22 KiB
Markdown
# Worker Hook システム
|
||
|
||
Workerのフックシステムは、LLMとの対話プロセスの各段階でカスタムロジックを実行し、システムの動作を柔軟に制御できる強力な機能です。
|
||
|
||
## 概要
|
||
|
||
Hooksシステムは、WorkerがLLMとの対話で実行する各フェーズ(メッセージ処理、ツール実行、ターン完了)でユーザー定義の処理を挿入できる機能です。これにより、以下のような高度なカスタマイズが可能になります:
|
||
|
||
- **メッセージの前処理**: ユーザーメッセージにコンテキスト情報を追加
|
||
- **ツール実行後の処理**: ファイル操作後の自動コミットやフォーマット
|
||
- **ターン完了時の処理**: 統計記録や追加情報の提供
|
||
- **ストリーミング制御**: リアルタイム応答の変更・拡張
|
||
|
||
## アーキテクチャ
|
||
|
||
### コア型
|
||
|
||
```rust
|
||
// worker-types/src/lib.rs
|
||
pub trait WorkerHook: Send + Sync {
|
||
fn name(&self) -> &str;
|
||
fn hook_type(&self) -> &str;
|
||
fn matcher(&self) -> &str;
|
||
async fn execute(&self, context: HookContext) -> (HookContext, HookResult);
|
||
}
|
||
|
||
pub struct HookManager {
|
||
hooks: HashMap<HookEvent, Vec<Box<dyn WorkerHook>>>,
|
||
}
|
||
|
||
pub enum HookEvent {
|
||
OnMessageSend,
|
||
PreToolUse,
|
||
PostToolUse,
|
||
OnTurnCompleted,
|
||
}
|
||
```
|
||
|
||
### Hook実行フロー
|
||
|
||
```
|
||
1. ユーザーメッセージ受信
|
||
2. OnMessageSend hooks 実行
|
||
3. プロンプト構築・LLMへ送信
|
||
4. ストリーミング応答開始
|
||
5. ツール呼び出し検出
|
||
├── PreToolUse hooks 実行
|
||
├── ツール実行
|
||
└── PostToolUse hooks 実行
|
||
6. 応答完了
|
||
7. OnTurnCompleted hooks 実行
|
||
8. 結果返却
|
||
```
|
||
|
||
## Hook の種類
|
||
|
||
### フェーズ別Hook
|
||
|
||
| フェーズ | タイプ名 | 実行タイミング | ストリーミング |
|
||
|---------|----------|---------------|---------------|
|
||
| メッセージ送信時 | `OnMessageSend` | ユーザーメッセージをLLMに送信する直前 | × |
|
||
| ツール実行前 | `PreToolUse` | 特定のツール実行直前 | ○ |
|
||
| ツール実行後 | `PostToolUse` | 特定のツール実行完了後 | ○ |
|
||
| ターン完了時 | `OnTurnCompleted` | AIの応答が完了した時点 | ○ |
|
||
|
||
### マッチャーパターン
|
||
|
||
`PreToolUse`、`PostToolUse`および`OnTurnCompleted`では、`matcher`パラメータで特定のツールに対してのみHookを実行できます:
|
||
|
||
```rust
|
||
// Edit または Create ツールの実行後のみ実行
|
||
#[hook(hook_type = "PostToolUse", matcher = "Edit|Create")]
|
||
|
||
// すべてのツールで実行(matcher省略時)
|
||
#[hook(hook_type = "OnTurnCompleted")]
|
||
|
||
// 正規表現マッチング
|
||
#[hook(hook_type = "PostToolUse", matcher = r"^(Read|Write)File.*")]
|
||
```
|
||
|
||
## Hook の作成方法
|
||
|
||
### 基本構文
|
||
|
||
```rust
|
||
use worker::types::{HookContext, HookResult, Role};
|
||
use worker_macros::hook;
|
||
|
||
#[hook(hook_type = "フェーズ名", matcher = "パターン")]
|
||
pub async fn your_hook_name(mut context: HookContext) -> HookResult {
|
||
// カスタムロジックをここに実装
|
||
HookResult::Continue
|
||
}
|
||
```
|
||
|
||
### マクロを使わない実装
|
||
|
||
```rust
|
||
use worker::types::{WorkerHook, HookContext, HookResult};
|
||
use async_trait::async_trait;
|
||
|
||
pub struct CustomHook {
|
||
name: String,
|
||
}
|
||
|
||
impl CustomHook {
|
||
pub fn new(name: String) -> Self {
|
||
Self { name }
|
||
}
|
||
}
|
||
|
||
#[async_trait]
|
||
impl WorkerHook for CustomHook {
|
||
fn name(&self) -> &str {
|
||
&self.name
|
||
}
|
||
|
||
fn hook_type(&self) -> &str {
|
||
"OnMessageSend"
|
||
}
|
||
|
||
fn matcher(&self) -> &str {
|
||
""
|
||
}
|
||
|
||
async fn execute(&self, mut context: HookContext) -> (HookContext, HookResult) {
|
||
// Hook処理をここに実装
|
||
(context, HookResult::Continue)
|
||
}
|
||
}
|
||
```
|
||
|
||
## HookContext API
|
||
|
||
`HookContext`は、Hook内で使用できる情報とメソッドを提供します:
|
||
|
||
### 利用可能なデータ
|
||
|
||
```rust
|
||
pub struct HookContext {
|
||
pub content: String, // 処理対象のコンテンツ
|
||
pub workspace_path: String, // 現在のワークスペースパス
|
||
pub message_history: Vec<Message>, // これまでの会話履歴
|
||
pub tools: Vec<DynamicToolDefinition>, // 利用可能なツール一覧
|
||
pub variables: HashMap<String, String>, // Hook間で共有可能な変数
|
||
pub tool_name: Option<String>, // 実行中のツール名(ツール関連フックのみ)
|
||
pub tool_args: Option<serde_json::Value>, // ツール実行引数(ツール関連フックのみ)
|
||
pub tool_result: Option<String>, // ツール実行結果(PostToolUseのみ)
|
||
}
|
||
```
|
||
|
||
### 操作メソッド
|
||
|
||
```rust
|
||
impl HookContext {
|
||
// ワークスペースでコマンドを実行
|
||
pub async fn run_command(&self, command: &str) -> Result<String, Box<dyn std::error::Error>>;
|
||
|
||
// ツールを実行(将来実装予定)
|
||
pub async fn run_tool(&self, tool_name: &str, args: serde_json::Value) -> Result<String, Box<dyn std::error::Error>>;
|
||
|
||
// メッセージを履歴に追加
|
||
pub fn add_message(&mut self, content: String, role: Role);
|
||
|
||
// コンテンツを書き換え
|
||
pub fn set_content(&mut self, content: String);
|
||
|
||
// 変数の設定・取得
|
||
pub fn set_variable(&mut self, key: String, value: String);
|
||
pub fn get_variable(&self, key: &str) -> Option<&String>;
|
||
|
||
// ツール情報の取得
|
||
pub fn get_tool(&self, tool_name: &str) -> Option<&DynamicToolDefinition>;
|
||
pub fn list_tool_names(&self) -> Vec<&String>;
|
||
}
|
||
```
|
||
|
||
### ストリーミング用メソッド
|
||
|
||
```rust
|
||
impl HookContext {
|
||
// ストリーミング中にメッセージを送信
|
||
pub fn stream_message(&self, content: String, role: Role);
|
||
|
||
// ストリーミング中にシステム通知を送信
|
||
pub fn stream_system_message(&self, content: String);
|
||
|
||
// ストリーミング中にデバッグ情報を送信
|
||
pub fn stream_debug(&self, title: String, data: serde_json::Value);
|
||
}
|
||
```
|
||
|
||
## HookResult の種類
|
||
|
||
Hook関数は以下のいずれかの結果を返す必要があります:
|
||
|
||
```rust
|
||
pub enum HookResult {
|
||
// 処理を続行
|
||
Continue,
|
||
|
||
// コンテンツを変更して続行
|
||
ModifyContent(String),
|
||
|
||
// システムメッセージを追加して続行
|
||
AddMessage(String, Role),
|
||
|
||
// 複数のメッセージを追加して続行
|
||
AddMessages(Vec<Message>),
|
||
|
||
// ターンを強制完了
|
||
Complete,
|
||
|
||
// エラーでターンを終了
|
||
Error(String),
|
||
|
||
// Hook処理をスキップ(デバッグ用)
|
||
Skip,
|
||
}
|
||
```
|
||
|
||
## 実用例
|
||
|
||
### 1. タイムスタンプ付きメッセージ
|
||
|
||
```rust
|
||
/// メッセージ送信時に現在時刻を追加するHook
|
||
#[hook(hook_type = "OnMessageSend")]
|
||
pub async fn add_timestamp_hook(mut context: HookContext) -> HookResult {
|
||
let timestamp = chrono::Local::now().format("%H:%M:%S").to_string();
|
||
let enhanced_content = format!("[{}] {}", timestamp, context.content);
|
||
HookResult::ModifyContent(enhanced_content)
|
||
}
|
||
```
|
||
|
||
### 2. ファイル操作後の自動Git追跡
|
||
|
||
```rust
|
||
/// ファイル編集後にGitステータスを確認するHook
|
||
#[hook(hook_type = "PostToolUse", matcher = "Edit|Create|Write")]
|
||
pub async fn file_change_notification_hook(mut context: HookContext) -> HookResult {
|
||
match context.run_command("git status --porcelain").await {
|
||
Ok(output) => {
|
||
if !output.trim().is_empty() {
|
||
context.add_message(
|
||
format!("📝 ファイル変更を検出:\n{}", output),
|
||
Role::System,
|
||
);
|
||
HookResult::Continue
|
||
} else {
|
||
HookResult::Continue
|
||
}
|
||
}
|
||
Err(_) => HookResult::Continue,
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 長い応答への注意喚起
|
||
|
||
```rust
|
||
/// 長い応答が生成された際に注意を促すHook
|
||
#[hook(hook_type = "OnTurnCompleted")]
|
||
pub async fn long_response_warning_hook(context: HookContext) -> HookResult {
|
||
if context.content.len() > 2000 {
|
||
HookResult::AddMessage(
|
||
"⚠️ 長い応答が生成されました。スクロールして全体を確認してください。".to_string(),
|
||
Role::System,
|
||
)
|
||
} else {
|
||
HookResult::Continue
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. ツール実行前のバリデーション
|
||
|
||
```rust
|
||
/// 危険なコマンドの実行前に確認を行うHook
|
||
#[hook(hook_type = "PreToolUse", matcher = "Execute|Shell")]
|
||
pub async fn dangerous_command_hook(context: HookContext) -> HookResult {
|
||
if let Some(args) = &context.tool_args {
|
||
if let Some(command) = args.get("command").and_then(|v| v.as_str()) {
|
||
let dangerous_commands = ["rm -rf", "format", "dd if="];
|
||
|
||
for dangerous in &dangerous_commands {
|
||
if command.contains(dangerous) {
|
||
return HookResult::Error(format!(
|
||
"危険なコマンド「{}」の実行が阻止されました: {}",
|
||
dangerous, command
|
||
));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
HookResult::Continue
|
||
}
|
||
```
|
||
|
||
### 5. 自動読み取りHook (TUI用)
|
||
|
||
```rust
|
||
/// ファイル作成・編集後に自動的にファイル内容を読み取るHook
|
||
#[hook(hook_type = "PostToolUse", matcher = "Edit|Create|Write")]
|
||
pub async fn auto_read_hook(mut context: HookContext) -> HookResult {
|
||
// ツール実行結果からファイルパスを抽出
|
||
if let Some(tool_result) = &context.tool_result {
|
||
if let Ok(result_data) = serde_json::from_str::<serde_json::Value>(tool_result) {
|
||
if let Some(file_path) = result_data.get("file_path").and_then(|v| v.as_str()) {
|
||
// ファイル内容を読み取って表示
|
||
match tokio::fs::read_to_string(file_path).await {
|
||
Ok(content) => {
|
||
context.stream_system_message(format!(
|
||
"📄 **{}** の内容:\n```\n{}\n```",
|
||
file_path, content
|
||
));
|
||
}
|
||
Err(e) => {
|
||
context.stream_system_message(format!(
|
||
"❌ ファイル読み取りエラー ({}): {}",
|
||
file_path, e
|
||
));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
HookResult::Continue
|
||
}
|
||
```
|
||
|
||
## Hook の登録と管理
|
||
|
||
### Workerへの登録
|
||
|
||
```rust
|
||
use worker::Worker;
|
||
|
||
// 単一のHookを登録
|
||
worker.register_hook(Box::new(YourHook::new()));
|
||
|
||
// 複数のHookを一括登録
|
||
let hooks = vec![
|
||
Box::new(TimestampHook::new()),
|
||
Box::new(FileNotificationHook::new()),
|
||
Box::new(DebugHook::new()),
|
||
];
|
||
worker.register_hooks(hooks);
|
||
|
||
// TUI用デフォルトHookの登録
|
||
let tui_hooks = crate::tui::hooks::get_default_hooks();
|
||
worker.register_hooks(tui_hooks);
|
||
```
|
||
|
||
### Hook の実行順序
|
||
|
||
同じフェーズで複数のHookが登録されている場合、登録順に実行されます。先に実行されたHookの結果(コンテキストの変更など)は、後続のHookに引き継がれます。
|
||
|
||
```rust
|
||
// 実行順序の例
|
||
worker.register_hook(Box::new(TimestampHook)); // 1番目
|
||
worker.register_hook(Box::new(ValidationHook)); // 2番目
|
||
worker.register_hook(Box::new(LoggingHook)); // 3番目
|
||
```
|
||
|
||
### Hook の中断
|
||
|
||
`HookResult::Complete`または`HookResult::Error`を返すHookがあると、それ以降のHookは実行されず、処理が中断されます。
|
||
|
||
### Hook の動的管理
|
||
|
||
```rust
|
||
impl Worker {
|
||
// Hook一覧を取得
|
||
pub fn list_hooks(&self) -> Vec<(&str, &str)>; // (name, hook_type)
|
||
|
||
// 特定のHookを削除
|
||
pub fn remove_hook(&mut self, hook_name: &str) -> bool;
|
||
|
||
// フェーズ別Hookを削除
|
||
pub fn remove_hooks_by_phase(&mut self, hook_type: &str);
|
||
|
||
// すべてのHookをクリア
|
||
pub fn clear_hooks(&mut self);
|
||
}
|
||
```
|
||
|
||
## ストリーミング処理
|
||
|
||
### ストリーミング中のHook実行
|
||
|
||
```rust
|
||
// worker/src/lib.rs の process_with_shared_state より
|
||
stream! {
|
||
// ... LLM応答処理中 ...
|
||
|
||
// ツール呼び出し検出時
|
||
if let Some(tool_calls) = &response.tool_calls {
|
||
for tool_call in tool_calls {
|
||
// PreToolUse hooks 実行
|
||
let (context, hook_result) = execute_hooks(
|
||
HookEvent::PreToolUse,
|
||
tool_call.name.clone()
|
||
).await;
|
||
|
||
match hook_result {
|
||
HookResult::Error(msg) => {
|
||
yield Ok(StreamEvent::Error(msg));
|
||
continue;
|
||
}
|
||
HookResult::Complete => break,
|
||
_ => {}
|
||
}
|
||
|
||
// ツール実行
|
||
let result = execute_tool(tool_call).await;
|
||
|
||
// PostToolUse hooks 実行(ストリーミング中)
|
||
let (context, hook_result) = execute_hooks(
|
||
HookEvent::PostToolUse,
|
||
tool_call.name.clone()
|
||
).await;
|
||
|
||
// Hook結果を即座にストリーミング
|
||
if let HookResult::AddMessage(msg, role) = hook_result {
|
||
yield Ok(StreamEvent::HookMessage {
|
||
hook_name: "PostToolUse".to_string(),
|
||
content: msg,
|
||
role,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## ベストプラクティス
|
||
|
||
### 1. エラーハンドリング
|
||
|
||
```rust
|
||
#[hook(hook_type = "OnTurnCompleted")]
|
||
pub async fn robust_hook(mut context: HookContext) -> HookResult {
|
||
match context.run_command("potentially_failing_command").await {
|
||
Ok(output) => {
|
||
// 成功時の処理
|
||
context.add_message(format!("実行完了: {}", output), Role::System);
|
||
HookResult::Continue
|
||
}
|
||
Err(error) => {
|
||
// エラーログを記録するが、処理は継続
|
||
tracing::warn!("Hook実行時にエラー: {}", error);
|
||
HookResult::Continue
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. パフォーマンス配慮
|
||
|
||
```rust
|
||
#[hook(hook_type = "OnTurnCompleted")]
|
||
pub async fn performance_aware_hook(context: HookContext) -> HookResult {
|
||
// 重い処理は条件分岐で制限
|
||
if context.content.len() > 10000 {
|
||
// 大きなコンテンツの場合はスキップ
|
||
return HookResult::Skip;
|
||
}
|
||
|
||
// 非同期処理は適切にawaitする
|
||
let result = tokio::time::timeout(
|
||
Duration::from_secs(5),
|
||
expensive_operation(&context)
|
||
).await;
|
||
|
||
match result {
|
||
Ok(output) => HookResult::AddMessage(output, Role::System),
|
||
Err(_) => {
|
||
tracing::warn!("Hook処理がタイムアウトしました");
|
||
HookResult::Continue
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 設定可能なHook
|
||
|
||
```rust
|
||
#[hook(hook_type = "OnMessageSend")]
|
||
pub async fn configurable_hook(mut context: HookContext) -> HookResult {
|
||
// 環境変数で動作を制御
|
||
let enabled = std::env::var("HOOK_ENABLED")
|
||
.unwrap_or_default()
|
||
.parse::<bool>()
|
||
.unwrap_or(false);
|
||
|
||
if !enabled {
|
||
return HookResult::Skip;
|
||
}
|
||
|
||
// 設定ファイルからオプション読み込み
|
||
let config_path = format!("{}/.nia/hook_config.json", context.workspace_path);
|
||
if let Ok(config_content) = tokio::fs::read_to_string(&config_path).await {
|
||
if let Ok(config) = serde_json::from_str::<HookConfig>(&config_content) {
|
||
// 設定に基づく処理
|
||
return process_with_config(&mut context, &config).await;
|
||
}
|
||
}
|
||
|
||
HookResult::Continue
|
||
}
|
||
```
|
||
|
||
### 4. 条件付きHook
|
||
|
||
```rust
|
||
#[hook(hook_type = "PostToolUse", matcher = ".*")]
|
||
pub async fn conditional_hook(context: HookContext) -> HookResult {
|
||
// ワークスペースの種類に応じて処理を変更
|
||
let is_git_repo = context.run_command("git status").await.is_ok();
|
||
let is_rust_project = tokio::fs::metadata(
|
||
format!("{}/Cargo.toml", context.workspace_path)
|
||
).await.is_ok();
|
||
|
||
match (is_git_repo, is_rust_project) {
|
||
(true, true) => {
|
||
// Rustプロジェクト + Git
|
||
context.run_command("cargo fmt").await.ok();
|
||
HookResult::AddMessage("Rustコードをフォーマットしました".to_string(), Role::System)
|
||
}
|
||
(true, false) => {
|
||
// その他のGitプロジェクト
|
||
HookResult::AddMessage("Gitプロジェクトで作業中です".to_string(), Role::System)
|
||
}
|
||
_ => HookResult::Continue
|
||
}
|
||
}
|
||
```
|
||
|
||
## デバッグとテスト
|
||
|
||
### Hook のテスト
|
||
|
||
```rust
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use worker::types::*;
|
||
|
||
#[tokio::test]
|
||
async fn test_timestamp_hook() {
|
||
let mut context = HookContext {
|
||
content: "Hello, world!".to_string(),
|
||
workspace_path: "/tmp".to_string(),
|
||
message_history: vec![],
|
||
tools: vec![],
|
||
variables: HashMap::new(),
|
||
tool_name: None,
|
||
tool_args: None,
|
||
tool_result: None,
|
||
};
|
||
|
||
let result = add_timestamp_hook(context).await;
|
||
|
||
match result {
|
||
HookResult::ModifyContent(content) => {
|
||
assert!(content.contains("Hello, world!"));
|
||
assert!(content.contains("["));
|
||
assert!(content.contains("]"));
|
||
}
|
||
_ => panic!("Expected ModifyContent"),
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Hook のデバッグ
|
||
|
||
```rust
|
||
#[hook(hook_type = "OnMessageSend")]
|
||
pub async fn debug_hook(context: HookContext) -> HookResult {
|
||
tracing::debug!(
|
||
"Hook実行 - コンテンツ長: {}, ツール数: {}, 履歴数: {}",
|
||
context.content.len(),
|
||
context.tools.len(),
|
||
context.message_history.len()
|
||
);
|
||
|
||
// デバッグ情報をストリーミング
|
||
context.stream_debug(
|
||
"Hook Debug Info".to_string(),
|
||
serde_json::json!({
|
||
"content_length": context.content.len(),
|
||
"tool_count": context.tools.len(),
|
||
"history_count": context.message_history.len(),
|
||
"workspace": context.workspace_path
|
||
})
|
||
);
|
||
|
||
HookResult::Continue
|
||
}
|
||
```
|
||
|
||
## 高度なHookパターン
|
||
|
||
### 状態を持つHook
|
||
|
||
```rust
|
||
pub struct StatefulHook {
|
||
counter: Arc<Mutex<u32>>,
|
||
}
|
||
|
||
impl StatefulHook {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
counter: Arc::new(Mutex::new(0)),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[async_trait]
|
||
impl WorkerHook for StatefulHook {
|
||
fn name(&self) -> &str { "stateful_hook" }
|
||
fn hook_type(&self) -> &str { "OnTurnCompleted" }
|
||
fn matcher(&self) -> &str { "" }
|
||
|
||
async fn execute(&self, mut context: HookContext) -> (HookContext, HookResult) {
|
||
let mut count = self.counter.lock().unwrap();
|
||
*count += 1;
|
||
|
||
context.set_variable("turn_count".to_string(), count.to_string());
|
||
|
||
if *count % 10 == 0 {
|
||
(
|
||
context,
|
||
HookResult::AddMessage(
|
||
format!("🎉 {}回目のターンです!", count),
|
||
Role::System
|
||
)
|
||
)
|
||
} else {
|
||
(context, HookResult::Continue)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### チェーン可能なHook
|
||
|
||
```rust
|
||
pub struct HookChain {
|
||
hooks: Vec<Box<dyn WorkerHook>>,
|
||
}
|
||
|
||
impl HookChain {
|
||
pub fn new() -> Self {
|
||
Self { hooks: Vec::new() }
|
||
}
|
||
|
||
pub fn add_hook(mut self, hook: Box<dyn WorkerHook>) -> Self {
|
||
self.hooks.push(hook);
|
||
self
|
||
}
|
||
}
|
||
|
||
#[async_trait]
|
||
impl WorkerHook for HookChain {
|
||
fn name(&self) -> &str { "hook_chain" }
|
||
fn hook_type(&self) -> &str { "OnMessageSend" }
|
||
fn matcher(&self) -> &str { "" }
|
||
|
||
async fn execute(&self, mut context: HookContext) -> (HookContext, HookResult) {
|
||
for hook in &self.hooks {
|
||
let (new_context, result) = hook.execute(context).await;
|
||
context = new_context;
|
||
|
||
match result {
|
||
HookResult::Continue | HookResult::Skip => continue,
|
||
other => return (context, other),
|
||
}
|
||
}
|
||
|
||
(context, HookResult::Continue)
|
||
}
|
||
}
|
||
```
|
||
|
||
## よくある質問
|
||
|
||
### Q: Hookはどのように読み込まれますか?
|
||
|
||
A: 現在、HookはRustコードとしてコンパイル時に組み込まれます。`#[hook]`マクロを使用するか、`WorkerHook`トレイトを実装して`worker.register_hook()`で登録します。
|
||
|
||
### Q: Hook内でファイルシステムにアクセスできますか?
|
||
|
||
A: はい。`run_command`メソッドを使用してシェルコマンドを実行できるほか、Rustの標準ライブラリやtokioを使用した直接的なファイル操作も可能です。
|
||
|
||
### Q: Hook間でデータを共有できますか?
|
||
|
||
A: `HookContext`の`variables`を使用して、同一ターン内のHook間でデータを共有できます。永続的なデータ共有には、ファイルやデータベースを使用してください。
|
||
|
||
### Q: ストリーミング中にHookの結果を表示できますか?
|
||
|
||
A: はい。`context.stream_message()`や`context.stream_system_message()`を使用することで、ストリーミング中にリアルタイムでメッセージを送信できます。
|
||
|
||
### Q: Hookでエラーが発生した場合はどうなりますか?
|
||
|
||
A: `HookResult::Error`を返すと、そのターンは中断されます。継続したい場合は、エラーをログに記録して`HookResult::Continue`を返してください。
|
||
|
||
## 関連ドキュメント
|
||
|
||
- [worker.md](worker.md) - Worker全体の文書
|
||
- [worker-macro.md](worker-macro.md) - マクロシステム
|
||
- `worker/src/lib.rs` - Hook実装コード
|
||
- `worker-types/src/lib.rs` - Hook型定義
|
||
- `nia-cli/src/tui/hooks/` - TUI用Hook実装例 |