generated from nhcarrigan/template
feat: consume worktree field from status line hook events
Parse structured WorktreeInfo (name, path, branch, original_repo_directory) from WorktreeCreate/Remove hook events and emit a dedicated claude:worktree event. Store per-conversation worktree state and display an emerald branch badge in the status bar so users can see at a glance which worktree and branch each session is running on. Closes #206
This commit is contained in:
@@ -296,6 +296,24 @@ pub struct AgentStartEvent {
|
|||||||
pub model: Option<String>,
|
pub model: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WorktreeInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub path: String,
|
||||||
|
pub branch: String,
|
||||||
|
pub original_repo_directory: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WorktreeEvent {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub conversation_id: Option<String>,
|
||||||
|
/// "create" or "remove"
|
||||||
|
pub event_type: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub worktree: Option<WorktreeInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct AgentEndEvent {
|
pub struct AgentEndEvent {
|
||||||
pub tool_use_id: String,
|
pub tool_use_id: String,
|
||||||
|
|||||||
+118
-15
@@ -17,7 +17,7 @@ use crate::types::{
|
|||||||
AgentEndEvent, AgentStartEvent, CharacterState, ClaudeMessage, ConnectionEvent,
|
AgentEndEvent, AgentStartEvent, CharacterState, ClaudeMessage, ConnectionEvent,
|
||||||
ConnectionStatus, ContentBlock, MessageCost, OutputEvent, PermissionPromptEvent,
|
ConnectionStatus, ContentBlock, MessageCost, OutputEvent, PermissionPromptEvent,
|
||||||
PermissionPromptEventItem, QuestionOption, SessionEvent, StateChangeEvent, TodoItem,
|
PermissionPromptEventItem, QuestionOption, SessionEvent, StateChangeEvent, TodoItem,
|
||||||
TodoUpdateEvent, UserQuestionEvent, WorkingDirectoryEvent,
|
TodoUpdateEvent, UserQuestionEvent, WorkingDirectoryEvent, WorktreeEvent, WorktreeInfo,
|
||||||
};
|
};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
@@ -956,9 +956,10 @@ fn handle_stderr(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hook events are informational — emit with distinct types instead of error
|
// Hook events are informational — emit with distinct types instead of error
|
||||||
let line_type = if line.contains("[WorktreeCreate Hook]")
|
let is_worktree_create = line.contains("[WorktreeCreate Hook]");
|
||||||
|| line.contains("[WorktreeRemove Hook]")
|
let is_worktree_remove = line.contains("[WorktreeRemove Hook]");
|
||||||
{
|
|
||||||
|
let line_type = if is_worktree_create || is_worktree_remove {
|
||||||
"worktree"
|
"worktree"
|
||||||
} else if line.contains("[ConfigChange Hook]") {
|
} else if line.contains("[ConfigChange Hook]") {
|
||||||
"config-change"
|
"config-change"
|
||||||
@@ -966,17 +967,56 @@ fn handle_stderr(
|
|||||||
"error"
|
"error"
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = app.emit(
|
// For worktree hooks, parse structured data and emit a dedicated event
|
||||||
"claude:output",
|
if is_worktree_create || is_worktree_remove {
|
||||||
OutputEvent {
|
let worktree_info = parse_worktree_hook(&line);
|
||||||
line_type: line_type.to_string(),
|
let event_type = if is_worktree_create { "create" } else { "remove" };
|
||||||
content: line,
|
let friendly_content = if let Some(ref info) = worktree_info {
|
||||||
tool_name: None,
|
if is_worktree_create {
|
||||||
conversation_id: conversation_id.clone(),
|
format!(
|
||||||
cost: None,
|
"Worktree created: {} (branch: {}) at {}",
|
||||||
parent_tool_use_id: None,
|
info.name, info.branch, info.path
|
||||||
},
|
)
|
||||||
);
|
} else {
|
||||||
|
format!("Worktree removed: {} (branch: {})", info.name, info.branch)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = app.emit(
|
||||||
|
"claude:worktree",
|
||||||
|
WorktreeEvent {
|
||||||
|
conversation_id: conversation_id.clone(),
|
||||||
|
event_type: event_type.to_string(),
|
||||||
|
worktree: worktree_info,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = app.emit(
|
||||||
|
"claude:output",
|
||||||
|
OutputEvent {
|
||||||
|
line_type: "worktree".to_string(),
|
||||||
|
content: friendly_content,
|
||||||
|
tool_name: None,
|
||||||
|
conversation_id: conversation_id.clone(),
|
||||||
|
cost: None,
|
||||||
|
parent_tool_use_id: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let _ = app.emit(
|
||||||
|
"claude:output",
|
||||||
|
OutputEvent {
|
||||||
|
line_type: line_type.to_string(),
|
||||||
|
content: line,
|
||||||
|
tool_name: None,
|
||||||
|
conversation_id: conversation_id.clone(),
|
||||||
|
cost: None,
|
||||||
|
parent_tool_use_id: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -991,6 +1031,29 @@ struct SubagentStartData {
|
|||||||
parent_tool_use_id: Option<String>,
|
parent_tool_use_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_worktree_hook(line: &str) -> Option<WorktreeInfo> {
|
||||||
|
// Parse: [WorktreeCreate/Remove Hook] name=worktree-abc, path=/tmp/worktrees/worktree-abc,
|
||||||
|
// branch=feat/my-feature, original_repo_directory=/home/naomi/code/project, session_id=xxx
|
||||||
|
|
||||||
|
let extract = |key: &str| -> Option<String> {
|
||||||
|
let after_key = line.split(&format!("{}=", key)).nth(1)?;
|
||||||
|
let value = after_key.split(',').next()?.trim().to_string();
|
||||||
|
if value.is_empty() { None } else { Some(value) }
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = extract("name")?;
|
||||||
|
let path = extract("path")?;
|
||||||
|
let branch = extract("branch")?;
|
||||||
|
let original_repo_directory = extract("original_repo_directory")?;
|
||||||
|
|
||||||
|
Some(WorktreeInfo {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
branch,
|
||||||
|
original_repo_directory,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_subagent_start_hook(line: &str) -> Option<SubagentStartData> {
|
fn parse_subagent_start_hook(line: &str) -> Option<SubagentStartData> {
|
||||||
// Parse: [SubagentStart Hook] agent_id=agent-xxx, agent_type=general-purpose, parent_tool_use_id=Some("toolu_xxx"), ...
|
// Parse: [SubagentStart Hook] agent_id=agent-xxx, agent_type=general-purpose, parent_tool_use_id=Some("toolu_xxx"), ...
|
||||||
|
|
||||||
@@ -2913,4 +2976,44 @@ mod tests {
|
|||||||
*pending_since.lock() = None;
|
*pending_since.lock() = None;
|
||||||
assert!(pending_since.lock().is_none(), "pending_since cleared on Result");
|
assert!(pending_since.lock().is_none(), "pending_since cleared on Result");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_worktree_hook_create_with_all_fields() {
|
||||||
|
let line = r#"[WorktreeCreate Hook] name=worktree-abc, path=/tmp/worktrees/worktree-abc, branch=feat/my-feature, original_repo_directory=/home/naomi/code/project, session_id=123"#;
|
||||||
|
let result = parse_worktree_hook(line);
|
||||||
|
|
||||||
|
assert!(result.is_some());
|
||||||
|
let info = result.unwrap();
|
||||||
|
assert_eq!(info.name, "worktree-abc");
|
||||||
|
assert_eq!(info.path, "/tmp/worktrees/worktree-abc");
|
||||||
|
assert_eq!(info.branch, "feat/my-feature");
|
||||||
|
assert_eq!(info.original_repo_directory, "/home/naomi/code/project");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_worktree_hook_remove_with_all_fields() {
|
||||||
|
let line = r#"[WorktreeRemove Hook] name=worktree-xyz, path=/tmp/worktrees/worktree-xyz, branch=fix/bug-123, original_repo_directory=/home/naomi/code/other, session_id=456"#;
|
||||||
|
let result = parse_worktree_hook(line);
|
||||||
|
|
||||||
|
assert!(result.is_some());
|
||||||
|
let info = result.unwrap();
|
||||||
|
assert_eq!(info.name, "worktree-xyz");
|
||||||
|
assert_eq!(info.branch, "fix/bug-123");
|
||||||
|
assert_eq!(info.original_repo_directory, "/home/naomi/code/other");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_worktree_hook_missing_field_returns_none() {
|
||||||
|
// Missing branch field — should return None
|
||||||
|
let line = r#"[WorktreeCreate Hook] name=worktree-abc, path=/tmp/worktrees/worktree-abc, original_repo_directory=/home/naomi/code/project, session_id=123"#;
|
||||||
|
let result = parse_worktree_hook(line);
|
||||||
|
assert!(result.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_worktree_hook_invalid_returns_none() {
|
||||||
|
let line = "[WorktreeCreate Hook] no structured data here";
|
||||||
|
let result = parse_worktree_hook(line);
|
||||||
|
assert!(result.is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
let connectionStatus: ConnectionStatus = $state("disconnected");
|
let connectionStatus: ConnectionStatus = $state("disconnected");
|
||||||
let workingDirectory = $state("");
|
let workingDirectory = $state("");
|
||||||
|
let worktreeInfo: import("$lib/types/worktree").WorktreeInfo | null = $state(null);
|
||||||
let selectedDirectory = $state("/home/naomi");
|
let selectedDirectory = $state("/home/naomi");
|
||||||
let isConnecting = $state(false);
|
let isConnecting = $state(false);
|
||||||
let grantedToolsList: string[] = $state([]);
|
let grantedToolsList: string[] = $state([]);
|
||||||
@@ -115,6 +116,10 @@
|
|||||||
workingDirectory = dir;
|
workingDirectory = dir;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
claudeStore.worktreeInfo.subscribe((info) => {
|
||||||
|
worktreeInfo = info;
|
||||||
|
});
|
||||||
|
|
||||||
claudeStore.grantedTools.subscribe((tools) => {
|
claudeStore.grantedTools.subscribe((tools) => {
|
||||||
grantedToolsList = Array.from(tools);
|
grantedToolsList = Array.from(tools);
|
||||||
});
|
});
|
||||||
@@ -392,6 +397,18 @@
|
|||||||
{workingDirectory}
|
{workingDirectory}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if worktreeInfo}
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-1 px-2 py-0.5 rounded-full bg-emerald-500/15 border border-emerald-500/30 text-emerald-400 text-xs"
|
||||||
|
title="Worktree: {worktreeInfo.name} | Base: {worktreeInfo.original_repo_directory}"
|
||||||
|
>
|
||||||
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||||
|
</svg>
|
||||||
|
{worktreeInfo.branch}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm text-gray-600">cwd:</span>
|
<span class="text-sm text-gray-600">cwd:</span>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export const claudeStore = {
|
|||||||
grantedTools: conversationsStore.grantedTools,
|
grantedTools: conversationsStore.grantedTools,
|
||||||
pendingRetryMessage: conversationsStore.pendingRetryMessage,
|
pendingRetryMessage: conversationsStore.pendingRetryMessage,
|
||||||
attachments: conversationsStore.attachments,
|
attachments: conversationsStore.attachments,
|
||||||
|
worktreeInfo: conversationsStore.worktreeInfo,
|
||||||
|
|
||||||
// New conversation-aware subscriptions
|
// New conversation-aware subscriptions
|
||||||
conversations: conversationsStore.conversations,
|
conversations: conversationsStore.conversations,
|
||||||
@@ -70,6 +71,9 @@ export const claudeStore = {
|
|||||||
// Draft text (per-tab input persistence)
|
// Draft text (per-tab input persistence)
|
||||||
setDraftText: conversationsStore.setDraftText,
|
setDraftText: conversationsStore.setDraftText,
|
||||||
|
|
||||||
|
// Worktree info (per-conversation)
|
||||||
|
setWorktreeInfo: conversationsStore.setWorktreeInfo,
|
||||||
|
|
||||||
// Conversation management
|
// Conversation management
|
||||||
createConversation: conversationsStore.createConversation,
|
createConversation: conversationsStore.createConversation,
|
||||||
deleteConversation: conversationsStore.deleteConversation,
|
deleteConversation: conversationsStore.deleteConversation,
|
||||||
|
|||||||
@@ -562,6 +562,54 @@ describe("draft text persistence", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("worktreeInfo state management", () => {
|
||||||
|
it("initialises worktreeInfo as null", () => {
|
||||||
|
const conversation = { worktreeInfo: null };
|
||||||
|
expect(conversation.worktreeInfo).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores worktreeInfo when a worktree is created", () => {
|
||||||
|
const info = {
|
||||||
|
name: "worktree-abc",
|
||||||
|
path: "/tmp/worktrees/worktree-abc",
|
||||||
|
branch: "feat/my-feature",
|
||||||
|
original_repo_directory: "/home/naomi/code/project",
|
||||||
|
};
|
||||||
|
const conversation = { worktreeInfo: null as typeof info | null };
|
||||||
|
conversation.worktreeInfo = info;
|
||||||
|
|
||||||
|
expect(conversation.worktreeInfo?.branch).toBe("feat/my-feature");
|
||||||
|
expect(conversation.worktreeInfo?.name).toBe("worktree-abc");
|
||||||
|
expect(conversation.worktreeInfo?.original_repo_directory).toBe("/home/naomi/code/project");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears worktreeInfo when a worktree is removed", () => {
|
||||||
|
const info = {
|
||||||
|
name: "worktree-abc",
|
||||||
|
path: "/tmp/worktrees/worktree-abc",
|
||||||
|
branch: "feat/my-feature",
|
||||||
|
original_repo_directory: "/home/naomi/code/project",
|
||||||
|
};
|
||||||
|
const conversation = { worktreeInfo: info as typeof info | null };
|
||||||
|
conversation.worktreeInfo = null;
|
||||||
|
|
||||||
|
expect(conversation.worktreeInfo).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stores worktreeInfo independently per conversation", () => {
|
||||||
|
const conversations = new Map([
|
||||||
|
["conv-1", { worktreeInfo: null as { branch: string } | null }],
|
||||||
|
["conv-2", { worktreeInfo: null as { branch: string } | null }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const conv1 = conversations.get("conv-1");
|
||||||
|
if (conv1) conv1.worktreeInfo = { branch: "feat/one" };
|
||||||
|
|
||||||
|
expect(conversations.get("conv-1")?.worktreeInfo?.branch).toBe("feat/one");
|
||||||
|
expect(conversations.get("conv-2")?.worktreeInfo).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("isProcessing state management", () => {
|
describe("isProcessing state management", () => {
|
||||||
it("starts as false by default", () => {
|
it("starts as false by default", () => {
|
||||||
const conversation = { id: "conv-1", isProcessing: false };
|
const conversation = { id: "conv-1", isProcessing: false };
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
Attachment,
|
Attachment,
|
||||||
} from "$lib/types/messages";
|
} from "$lib/types/messages";
|
||||||
import type { CharacterState } from "$lib/types/states";
|
import type { CharacterState } from "$lib/types/states";
|
||||||
|
import type { WorktreeInfo } from "$lib/types/worktree";
|
||||||
import { cleanupConversationTracking } from "$lib/tauri";
|
import { cleanupConversationTracking } from "$lib/tauri";
|
||||||
import { characterState } from "$lib/stores/character";
|
import { characterState } from "$lib/stores/character";
|
||||||
import { sessionsStore } from "$lib/stores/sessions";
|
import { sessionsStore } from "$lib/stores/sessions";
|
||||||
@@ -41,6 +42,7 @@ export interface Conversation {
|
|||||||
successSoundFired: boolean;
|
successSoundFired: boolean;
|
||||||
taskStartSoundFired: boolean;
|
taskStartSoundFired: boolean;
|
||||||
draftText: string;
|
draftText: string;
|
||||||
|
worktreeInfo: WorktreeInfo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TAB_NAMES = [
|
const TAB_NAMES = [
|
||||||
@@ -165,6 +167,7 @@ function createConversationsStore() {
|
|||||||
successSoundFired: false,
|
successSoundFired: false,
|
||||||
taskStartSoundFired: false,
|
taskStartSoundFired: false,
|
||||||
draftText: "",
|
draftText: "",
|
||||||
|
worktreeInfo: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +223,7 @@ function createConversationsStore() {
|
|||||||
const pendingQuestion = derived(activeConversation, ($conv) => $conv?.pendingQuestion || null);
|
const pendingQuestion = derived(activeConversation, ($conv) => $conv?.pendingQuestion || null);
|
||||||
const scrollPosition = derived(activeConversation, ($conv) => $conv?.scrollPosition ?? -1);
|
const scrollPosition = derived(activeConversation, ($conv) => $conv?.scrollPosition ?? -1);
|
||||||
const attachments = derived(activeConversation, ($conv) => $conv?.attachments || []);
|
const attachments = derived(activeConversation, ($conv) => $conv?.attachments || []);
|
||||||
|
const worktreeInfo = derived(activeConversation, ($conv) => $conv?.worktreeInfo ?? null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Expose derived stores for compatibility
|
// Expose derived stores for compatibility
|
||||||
@@ -235,6 +239,7 @@ function createConversationsStore() {
|
|||||||
pendingRetryMessage: { subscribe: pendingRetryMessage.subscribe },
|
pendingRetryMessage: { subscribe: pendingRetryMessage.subscribe },
|
||||||
scrollPosition: { subscribe: scrollPosition.subscribe },
|
scrollPosition: { subscribe: scrollPosition.subscribe },
|
||||||
attachments: { subscribe: attachments.subscribe },
|
attachments: { subscribe: attachments.subscribe },
|
||||||
|
worktreeInfo: { subscribe: worktreeInfo.subscribe },
|
||||||
|
|
||||||
// New conversation-specific stores
|
// New conversation-specific stores
|
||||||
conversations: { subscribe: conversations.subscribe },
|
conversations: { subscribe: conversations.subscribe },
|
||||||
@@ -976,6 +981,16 @@ function createConversationsStore() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setWorktreeInfo: (conversationId: string, info: WorktreeInfo | null) => {
|
||||||
|
conversations.update((convs) => {
|
||||||
|
const conv = convs.get(conversationId);
|
||||||
|
if (conv) {
|
||||||
|
conv.worktreeInfo = info;
|
||||||
|
}
|
||||||
|
return convs;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Add initialization helper
|
// Add initialization helper
|
||||||
initialize: () => {
|
initialize: () => {
|
||||||
ensureInitialized();
|
ensureInitialized();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
} from "$lib/types/messages";
|
} from "$lib/types/messages";
|
||||||
import type { CharacterState } from "$lib/types/states";
|
import type { CharacterState } from "$lib/types/states";
|
||||||
import type { AgentStartPayload, AgentEndPayload } from "$lib/types/agents";
|
import type { AgentStartPayload, AgentEndPayload } from "$lib/types/agents";
|
||||||
|
import type { WorktreeEvent } from "$lib/types/worktree";
|
||||||
import { agentStore } from "$lib/stores/agents";
|
import { agentStore } from "$lib/stores/agents";
|
||||||
import { todos } from "$lib/stores/todos";
|
import { todos } from "$lib/stores/todos";
|
||||||
import {
|
import {
|
||||||
@@ -563,6 +564,18 @@ export async function initializeTauriListeners() {
|
|||||||
});
|
});
|
||||||
unlisteners.push(agentEndUnlisten);
|
unlisteners.push(agentEndUnlisten);
|
||||||
|
|
||||||
|
const worktreeUnlisten = await listen<WorktreeEvent>("claude:worktree", (event) => {
|
||||||
|
const { conversation_id, event_type, worktree } = event.payload;
|
||||||
|
const targetConversationId = conversation_id || get(claudeStore.activeConversationId);
|
||||||
|
if (targetConversationId) {
|
||||||
|
claudeStore.setWorktreeInfo(
|
||||||
|
targetConversationId,
|
||||||
|
event_type === "create" && worktree ? worktree : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
unlisteners.push(worktreeUnlisten);
|
||||||
|
|
||||||
const questionUnlisten = await listen<UserQuestionEvent>("claude:question", (event) => {
|
const questionUnlisten = await listen<UserQuestionEvent>("claude:question", (event) => {
|
||||||
const questionEvent = event.payload;
|
const questionEvent = event.payload;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export interface WorktreeInfo {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
branch: string;
|
||||||
|
original_repo_directory: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorktreeEvent {
|
||||||
|
conversation_id?: string;
|
||||||
|
/** "create" or "remove" */
|
||||||
|
event_type: string;
|
||||||
|
worktree?: WorktreeInfo;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user