Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions macos/Sources/Features/Terminal/TerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1961,6 +1961,8 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
base.command = "codex resume \(session.id)"
case .opencode:
base.command = "opencode --session \(session.id)"
case .copilotCli:
base.command = "copilot --resume \(session.id)"
}

if WorktrunkPreferences.worktreeTabsEnabled {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ enum AgentHookInstaller {
marker: wrapperMarker,
content: buildCodexWrapper()
)
ensureFile(
url: AgentStatusPaths.copilotCliWrapperPath,
mode: 0o755,
marker: wrapperMarker,
content: buildCopilotCliWrapper()
)

ensureFile(
url: AgentStatusPaths.opencodeGlobalPluginPath,
Expand Down Expand Up @@ -295,6 +301,61 @@ enum AgentHookInstaller {
"""
}

private static func buildCopilotCliWrapper() -> String {
let binDir = AgentStatusPaths.binDir.path
let eventsDir = AgentStatusPaths.eventsCacheDir.path
return """
#!/bin/bash
\(wrapperMarker)
# Wrapper for Copilot CLI: emits Start/Stop lifecycle events.

\(pathAugmentSnippet())

find_real_binary() {
local name="$1"
local IFS=:
for dir in $PATH; do
[ -z "$dir" ] && continue
dir="${dir%/}"
if [ "$dir" = "\(binDir)" ]; then
continue
fi
if [ -x "$dir/$name" ] && [ ! -d "$dir/$name" ]; then
printf "%s\\n" "$dir/$name"
return 0
fi
done
return 1
}

REAL_BIN="$(find_real_binary "copilot")"
if [ -z "$REAL_BIN" ]; then
echo "Ghostree: copilot not found in PATH. Install it and ensure it is on PATH, then retry." >&2
exit 127
fi

_EVENTS_DIR="${GHOSTREE_AGENT_EVENTS_DIR:-\(eventsDir)}"

# Emit synthetic Start event
printf '{\"timestamp\":\"%s\",\"eventType\":\"Start\",\"cwd\":\"%s\"}\\n' \\
"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \\
"$(pwd -P 2>/dev/null || pwd)" \\
>> "$_EVENTS_DIR/agent-events.jsonl" 2>/dev/null

# Run copilot and capture exit code
"$REAL_BIN" "$@"
_EXIT=$?

# Emit synthetic Stop event
printf '{\"timestamp\":\"%s\",\"eventType\":\"Stop\",\"cwd\":\"%s\"}\\n' \\
"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \\
"$(pwd -P 2>/dev/null || pwd)" \\
>> "$_EVENTS_DIR/agent-events.jsonl" 2>/dev/null

exit $_EXIT
"""
}

private static func buildOpenCodePlugin() -> String {
let marker = AgentStatusPaths.opencodePluginMarker
return """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ enum AgentStatusPaths {
binDir.appendingPathComponent("codex")
}

static var copilotCliWrapperPath: URL {
binDir.appendingPathComponent("copilot")
}

static var opencodePluginMarker: String { "// Ghostree opencode plugin v5" }

/** @see https://opencode.ai/docs/plugins */
Expand Down
6 changes: 6 additions & 0 deletions macos/Sources/Features/Worktrunk/WorktrunkPreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum WorktrunkAgent: String, CaseIterable, Identifiable {
case claude
case codex
case opencode
case copilotCli

var id: String { rawValue }

Expand All @@ -13,6 +14,7 @@ enum WorktrunkAgent: String, CaseIterable, Identifiable {
case .claude: return "Claude Code"
case .codex: return "Codex"
case .opencode: return "OpenCode"
case .copilotCli: return "Copilot"
}
}

Expand All @@ -21,6 +23,7 @@ enum WorktrunkAgent: String, CaseIterable, Identifiable {
case .claude: return "claude"
case .codex: return "codex"
case .opencode: return "opencode"
case .copilotCli: return "copilot"
}
}

Expand Down Expand Up @@ -76,6 +79,7 @@ enum WorktrunkDefaultAction: String, CaseIterable, Identifiable {
case claude
case codex
case opencode
case copilotCli

var id: String { rawValue }

Expand All @@ -85,6 +89,7 @@ enum WorktrunkDefaultAction: String, CaseIterable, Identifiable {
case .claude: return "Claude Code"
case .codex: return "Codex"
case .opencode: return "OpenCode"
case .copilotCli: return "Copilot"
}
}

Expand All @@ -94,6 +99,7 @@ enum WorktrunkDefaultAction: String, CaseIterable, Identifiable {
case .claude: return .claude
case .codex: return .codex
case .opencode: return .opencode
case .copilotCli: return .copilotCli
}
}

Expand Down
Loading
Loading