11import Foundation
22
33enum AgentHookInstaller {
4- private static let notifyScriptMarker = " # Ghostree agent notification hook v5 "
5- private static let wrapperMarker = " # Ghostree agent wrapper v2 "
4+ private static let notifyScriptMarker = " # Ghostree agent notification hook v6 "
5+ private static let claudeSettingsMarker = " \" _v \" :2 "
6+ private static let wrapperMarker = " # Ghostree agent wrapper v3 "
67
78 static func ensureInstalled( ) {
89 if ProcessInfo . processInfo. environment [ " GHOSTREE_DISABLE_AGENT_HOOKS " ] == " 1 " {
@@ -35,7 +36,7 @@ enum AgentHookInstaller {
3536 ensureFile (
3637 url: AgentStatusPaths . claudeSettingsPath,
3738 mode: 0o644 ,
38- marker: " bash ' " ,
39+ marker: claudeSettingsMarker ,
3940 content: buildClaudeSettings ( notifyPath: AgentStatusPaths . notifyHookPath. path)
4041 )
4142 ensureFile (
@@ -129,7 +130,12 @@ enum AgentHookInstaller {
129130 [ " $EVENT_TYPE " = " SessionEnd " ] && EVENT_TYPE= " SessionEnd "
130131 [ -z " $EVENT_TYPE " ] && exit 0
131132
132- CWD= " $(pwd -P 2>/dev/null || pwd) "
133+ JSON_CWD=$(echo " $INPUT " | grep -oE ' " cwd " [[:space:]]*:[[:space:]]* " [^ " ]* " ' | grep -oE ' " [^ " ]* " $' | tr -d ' " ')
134+ if [ -n " $JSON_CWD " ]; then
135+ CWD= " $JSON_CWD "
136+ else
137+ CWD= " $(pwd -P 2>/dev/null || pwd) "
138+ fi
133139 TS= " $(perl -MTime::HiRes=time -MPOSIX=strftime -e '$t=time; $s=int($t); $ms=int(($t-$s)*1000); print strftime( \" %Y-%m-%dT%H:%M:%S \" , gmtime($s)).sprintf( \" .%03dZ \" , $ms);') "
134140 ESC_CWD= " ${CWD// \\ \\ / \\ \\ \\ \\ } "
135141 ESC_CWD= " ${ESC_CWD// \\ \" / \\ \\ \\ \" } "
@@ -142,6 +148,7 @@ enum AgentHookInstaller {
142148 let escapedNotifyPath = notifyPath. replacingOccurrences ( of: " ' " , with: " ' \\ '' " )
143149 let command = " bash ' \( escapedNotifyPath) ' "
144150 let settings : [ String : Any ] = [
151+ " _v " : 2 ,
145152 " hooks " : [
146153 " UserPromptSubmit " : [
147154 [ " hooks " : [ [ " type " : " command " , " command " : command] ] ] ,
@@ -152,6 +159,9 @@ enum AgentHookInstaller {
152159 " PermissionRequest " : [
153160 [ " matcher " : " * " , " hooks " : [ [ " type " : " command " , " command " : command] ] ] ,
154161 ] ,
162+ " PermissionResponse " : [
163+ [ " hooks " : [ [ " type " : " command " , " command " : command] ] ] ,
164+ ] ,
155165 " SessionEnd " : [
156166 [ " hooks " : [ [ " type " : " command " , " command " : command] ] ] ,
157167 ] ,
@@ -226,6 +236,7 @@ enum AgentHookInstaller {
226236 private static func buildCodexWrapper( ) -> String {
227237 let binDir = AgentStatusPaths . binDir. path
228238 let notifyPath = AgentStatusPaths . notifyHookPath. path
239+ let eventsDir = AgentStatusPaths . eventsCacheDir. path
229240 return """
230241 #!/bin/bash
231242 \( wrapperMarker)
@@ -256,6 +267,12 @@ enum AgentHookInstaller {
256267 exit 127
257268 fi
258269
270+ # Emit synthetic Start event for Codex
271+ printf '{ " timestamp " : " %s " , " eventType " : " Start " , " cwd " : " %s " } \\ n' \
272+ " $(date -u +%Y-%m-%dT%H:%M:%S.000Z) " \
273+ " $(pwd -P 2>/dev/null || pwd) " \
274+ >> " ${GHOSTREE_AGENT_EVENTS_DIR:- \( eventsDir) }/agent-events.jsonl " 2>/dev/null
275+
259276 exec " $REAL_BIN " -c 'notify=[ " bash " , " \( notifyPath) " ]' " $@ "
260277 """
261278 }
@@ -273,21 +290,22 @@ enum AgentHookInstaller {
273290 import fs from " node:fs " ;
274291 import path from " node:path " ;
275292
276- export const GhostreeNotifyPlugin = async ({ client }) => {
277- if (globalThis.__ghostreeOpencodeNotifyPluginV3 ) return {};
278- globalThis.__ghostreeOpencodeNotifyPluginV3 = true;
279-
293+ export const GhostreeNotifyPlugin = async ({ client, directory, worktree }) => {
294+ if (globalThis.__ghostreeOpencodeNotifyPluginV4 ) return {};
295+ globalThis.__ghostreeOpencodeNotifyPluginV4 = true;
296+
280297 const eventsDir = process?.env?.GHOSTREE_AGENT_EVENTS_DIR;
281298 if (!eventsDir) return {};
282299 const logPath = path.join(eventsDir, " agent-events.jsonl " );
283-
300+ const cwd = worktree || directory || process.cwd();
301+
284302 const append = (eventType) => {
285303 try {
286304 fs.mkdirSync(eventsDir, { recursive: true });
287305 const payload = {
288306 timestamp: new Date().toISOString(),
289307 eventType,
290- cwd: process.cwd() ,
308+ cwd,
291309 };
292310 fs.appendFileSync(logPath, JSON.stringify(payload) + " \\ n " );
293311 } catch {
@@ -334,6 +352,13 @@ enum AgentHookInstaller {
334352 };
335353
336354 const handleStop = async (sessionID) => {
355+ if (!sessionID && currentState === " busy " && !stopSent) {
356+ currentState = " idle " ;
357+ stopSent = true;
358+ append( " Stop " );
359+ rootSessionID = null;
360+ return;
361+ }
337362 const sid = normalizeSessionID(sessionID);
338363 if (rootSessionID && sid !== rootSessionID) return;
339364 if (currentState === " busy " && !stopSent) {
0 commit comments