@onlooker-community/ecosystem 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-npm.yml +0 -1
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/hooks/hooks.json +22 -0
- package/package.json +1 -1
- package/scripts/hooks/session-end-tracker.sh +35 -0
- package/scripts/hooks/session-start-tracker.sh +39 -0
- package/scripts/lib/session-tracker.sh +166 -0
- package/test/bats/config.bats +28 -0
- package/test/bats/session-tracker.bats +75 -0
- package/test/fixtures/hook-inputs/session-end-other.json +7 -0
- package/test/fixtures/hook-inputs/session-start-compact.json +8 -0
- package/test/fixtures/hook-inputs/session-start-startup.json +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0](https://github.com/onlooker-community/ecosystem/compare/v0.3.3...v0.4.0) (2026-05-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **hooks:** add SessionStart and SessionEnd session trackers ([#10](https://github.com/onlooker-community/ecosystem/issues/10)) ([a48d680](https://github.com/onlooker-community/ecosystem/commit/a48d680dd24c98e79ef1c0401b07483ecebf9e8b))
|
|
9
|
+
|
|
3
10
|
## [0.3.3](https://github.com/onlooker-community/ecosystem/compare/v0.3.2...v0.3.3) (2026-05-22)
|
|
4
11
|
|
|
5
12
|
|
package/hooks/hooks.json
CHANGED
|
@@ -61,6 +61,28 @@
|
|
|
61
61
|
}
|
|
62
62
|
]
|
|
63
63
|
}
|
|
64
|
+
],
|
|
65
|
+
"SessionStart": [
|
|
66
|
+
{
|
|
67
|
+
"matcher": "*",
|
|
68
|
+
"hooks": [
|
|
69
|
+
{
|
|
70
|
+
"type": "command",
|
|
71
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/session-start-tracker.sh"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"SessionEnd": [
|
|
77
|
+
{
|
|
78
|
+
"matcher": "*",
|
|
79
|
+
"hooks": [
|
|
80
|
+
{
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/session-end-tracker.sh"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
64
86
|
]
|
|
65
87
|
}
|
|
66
88
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Onlooker Session End Tracker
|
|
3
|
+
# Invoked by SessionEnd (matcher: *) when a session ends.
|
|
4
|
+
#
|
|
5
|
+
# Emits session.end with duration and turn count, then cleans up hook bus dirs.
|
|
6
|
+
# Default SessionEnd budget is 1.5s — keep this hook fast.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# echo "$INPUT" | session-end-tracker.sh
|
|
10
|
+
|
|
11
|
+
set -uo pipefail # No -e: never block session termination
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
source "$SCRIPT_DIR/../lib/validate-path.sh"
|
|
15
|
+
source "$SCRIPT_DIR/../lib/onlooker-schema.sh"
|
|
16
|
+
source "$SCRIPT_DIR/../lib/tool-history.sh"
|
|
17
|
+
source "$SCRIPT_DIR/../lib/session-tracker.sh"
|
|
18
|
+
|
|
19
|
+
hook_register "session-end-tracker" "Session End Tracker" "Records session.end and cleans up session resources"
|
|
20
|
+
|
|
21
|
+
INPUT=$(cat)
|
|
22
|
+
hook_set_context "$INPUT" "SessionEnd"
|
|
23
|
+
|
|
24
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
|
25
|
+
|
|
26
|
+
PAYLOAD=$(session_tracker_build_end_payload "$SESSION_ID" "$INPUT")
|
|
27
|
+
if [[ -n "$PAYLOAD" ]]; then
|
|
28
|
+
session_tracker_emit "$SESSION_ID" "session.end" "$PAYLOAD" \
|
|
29
|
+
|| hook_failure "Failed to emit session.end"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
hook_bus_cleanup
|
|
33
|
+
|
|
34
|
+
hook_success
|
|
35
|
+
exit 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Onlooker Session Start Tracker
|
|
3
|
+
# Invoked by SessionStart (matcher: *) when a session starts or resumes.
|
|
4
|
+
#
|
|
5
|
+
# Initializes per-session tracker state and emits session.start for
|
|
6
|
+
# startup, resume, and clear sources (compact is metadata-only).
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# echo "$INPUT" | session-start-tracker.sh
|
|
10
|
+
|
|
11
|
+
set -uo pipefail # No -e: never block session startup
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
source "$SCRIPT_DIR/../lib/validate-path.sh"
|
|
15
|
+
source "$SCRIPT_DIR/../lib/onlooker-schema.sh"
|
|
16
|
+
source "$SCRIPT_DIR/../lib/tool-history.sh"
|
|
17
|
+
source "$SCRIPT_DIR/../lib/session-tracker.sh"
|
|
18
|
+
|
|
19
|
+
hook_register "session-start-tracker" "Session Start Tracker" "Records session.start and initializes session tracker"
|
|
20
|
+
|
|
21
|
+
INPUT=$(cat)
|
|
22
|
+
hook_set_context "$INPUT" "SessionStart"
|
|
23
|
+
|
|
24
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
|
25
|
+
SOURCE=$(echo "$INPUT" | jq -r '.source // "startup"')
|
|
26
|
+
|
|
27
|
+
session_tracker_record_start "$SESSION_ID" "$INPUT" || hook_failure "Failed to record session start metadata"
|
|
28
|
+
|
|
29
|
+
# Compaction reuses the session; do not emit another session.start.
|
|
30
|
+
if [[ "$SOURCE" != "compact" ]]; then
|
|
31
|
+
PAYLOAD=$(session_tracker_build_start_payload "$INPUT")
|
|
32
|
+
if [[ -n "$PAYLOAD" ]]; then
|
|
33
|
+
session_tracker_emit "$SESSION_ID" "session.start" "$PAYLOAD" \
|
|
34
|
+
|| hook_failure "Failed to emit session.start"
|
|
35
|
+
fi
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
hook_success
|
|
39
|
+
exit 0
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Session lifecycle helpers — session.start / session.end canonical events.
|
|
3
|
+
#
|
|
4
|
+
# Source after validate-path.sh, onlooker-schema.sh, and tool-history.sh:
|
|
5
|
+
# source "$CLAUDE_PLUGIN_ROOT/scripts/lib/session-tracker.sh"
|
|
6
|
+
|
|
7
|
+
# Milliseconds since epoch (macOS-compatible).
|
|
8
|
+
session_tracker_now_ms() {
|
|
9
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
10
|
+
python3 -c 'import time; print(int(time.time() * 1000))' 2>/dev/null || date +%s000
|
|
11
|
+
else
|
|
12
|
+
date +%s%3N 2>/dev/null || date +%s000
|
|
13
|
+
fi
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# Optional git_branch and git_commit for a working directory (empty when not a repo).
|
|
17
|
+
session_tracker_git_context() {
|
|
18
|
+
local cwd="${1:-}"
|
|
19
|
+
local branch="" commit=""
|
|
20
|
+
[[ -z "$cwd" ]] && return 0
|
|
21
|
+
|
|
22
|
+
if git -C "$cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
23
|
+
branch=$(git -C "$cwd" branch --show-current 2>/dev/null) || branch=""
|
|
24
|
+
commit=$(git -C "$cwd" rev-parse --short HEAD 2>/dev/null) || commit=""
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
printf '%s\n%s' "$branch" "$commit"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Map Claude Code SessionEnd reason to schema end_reason.
|
|
31
|
+
session_tracker_map_end_reason() {
|
|
32
|
+
local reason="${1:-other}"
|
|
33
|
+
case "$reason" in
|
|
34
|
+
clear | logout | prompt_input_exit) echo "user_exit" ;;
|
|
35
|
+
timeout) echo "timeout" ;;
|
|
36
|
+
error) echo "error" ;;
|
|
37
|
+
task_complete) echo "task_complete" ;;
|
|
38
|
+
*) echo "unknown" ;;
|
|
39
|
+
esac
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Merge session start metadata into the per-session tracker file.
|
|
43
|
+
# Usage: session_tracker_record_start "$SESSION_ID" "$INPUT_JSON"
|
|
44
|
+
session_tracker_record_start() {
|
|
45
|
+
local session_id="${1:-}"
|
|
46
|
+
local input_json="${2:-}"
|
|
47
|
+
[[ -z "$session_id" || "$session_id" == "null" || -z "$input_json" ]] && return 0
|
|
48
|
+
|
|
49
|
+
turn_state_ensure_session "$session_id" || return 1
|
|
50
|
+
|
|
51
|
+
local tracker_file="$ONLOOKER_SESSION_TRACKERS_DIR/$session_id"
|
|
52
|
+
local now_ms source model cwd transcript_path agent_type
|
|
53
|
+
now_ms=$(session_tracker_now_ms)
|
|
54
|
+
source=$(echo "$input_json" | jq -r '.source // ""' 2>/dev/null) || source=""
|
|
55
|
+
model=$(echo "$input_json" | jq -r '.model // ""' 2>/dev/null) || model=""
|
|
56
|
+
cwd=$(echo "$input_json" | jq -r '.cwd // ""' 2>/dev/null) || cwd=""
|
|
57
|
+
transcript_path=$(echo "$input_json" | jq -r '.transcript_path // ""' 2>/dev/null) || transcript_path=""
|
|
58
|
+
agent_type=$(echo "$input_json" | jq -r '.agent_type // ""' 2>/dev/null) || agent_type=""
|
|
59
|
+
|
|
60
|
+
local temp_file
|
|
61
|
+
temp_file=$(mktemp)
|
|
62
|
+
if ! jq \
|
|
63
|
+
--argjson start_ms "$now_ms" \
|
|
64
|
+
--arg source "$source" \
|
|
65
|
+
--arg model "$model" \
|
|
66
|
+
--arg cwd "$cwd" \
|
|
67
|
+
--arg transcript "$transcript_path" \
|
|
68
|
+
--arg agent_type "$agent_type" \
|
|
69
|
+
'.start_time_ms = $start_ms
|
|
70
|
+
| .start_source = (if $source != "" then $source else .start_source end)
|
|
71
|
+
| .model = (if $model != "" then $model else .model end)
|
|
72
|
+
| .cwd = (if $cwd != "" then $cwd else .cwd end)
|
|
73
|
+
| .transcript_path = (if $transcript != "" then $transcript else .transcript_path end)
|
|
74
|
+
| .agent_type = (if $agent_type != "" then $agent_type else .agent_type end)' \
|
|
75
|
+
"$tracker_file" >"$temp_file" 2>/dev/null; then
|
|
76
|
+
rm -f "$temp_file"
|
|
77
|
+
return 1
|
|
78
|
+
fi
|
|
79
|
+
mv "$temp_file" "$tracker_file"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Build session.start payload JSON from hook input and tracker state.
|
|
83
|
+
# Usage: payload=$(session_tracker_build_start_payload "$INPUT_JSON")
|
|
84
|
+
session_tracker_build_start_payload() {
|
|
85
|
+
local input_json="${1:-}"
|
|
86
|
+
local cwd
|
|
87
|
+
cwd=$(echo "$input_json" | jq -r '.cwd // ""' 2>/dev/null) || cwd=""
|
|
88
|
+
[[ -z "$cwd" ]] && cwd="$(pwd)"
|
|
89
|
+
|
|
90
|
+
local git_lines branch commit
|
|
91
|
+
git_lines=$(session_tracker_git_context "$cwd")
|
|
92
|
+
branch=$(echo "$git_lines" | sed -n '1p')
|
|
93
|
+
commit=$(echo "$git_lines" | sed -n '2p')
|
|
94
|
+
|
|
95
|
+
jq -n \
|
|
96
|
+
--arg wd "$cwd" \
|
|
97
|
+
--arg branch "$branch" \
|
|
98
|
+
--arg commit "$commit" \
|
|
99
|
+
'{
|
|
100
|
+
working_directory: $wd
|
|
101
|
+
}
|
|
102
|
+
+ (if $branch != "" then {git_branch: $branch} else {} end)
|
|
103
|
+
+ (if $commit != "" then {git_commit: $commit} else {} end)'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# Build session.end payload JSON from hook input and tracker file.
|
|
107
|
+
# Usage: payload=$(session_tracker_build_end_payload "$SESSION_ID" "$INPUT_JSON")
|
|
108
|
+
session_tracker_build_end_payload() {
|
|
109
|
+
local session_id="${1:-}"
|
|
110
|
+
local input_json="${2:-}"
|
|
111
|
+
[[ -z "$session_id" || "$session_id" == "null" ]] && return 1
|
|
112
|
+
|
|
113
|
+
local tracker_file="$ONLOOKER_SESSION_TRACKERS_DIR/$session_id"
|
|
114
|
+
local now_ms start_ms turn_count reason end_reason duration_ms
|
|
115
|
+
now_ms=$(session_tracker_now_ms)
|
|
116
|
+
reason=$(echo "$input_json" | jq -r '.reason // "other"' 2>/dev/null) || reason="other"
|
|
117
|
+
end_reason=$(session_tracker_map_end_reason "$reason")
|
|
118
|
+
|
|
119
|
+
if [[ -f "$tracker_file" ]]; then
|
|
120
|
+
start_ms=$(jq -r '.start_time_ms // 0' "$tracker_file" 2>/dev/null) || start_ms=0
|
|
121
|
+
turn_count=$(jq -r '.turn_number // 1' "$tracker_file" 2>/dev/null) || turn_count=1
|
|
122
|
+
else
|
|
123
|
+
start_ms=0
|
|
124
|
+
turn_count=1
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
if [[ "$start_ms" =~ ^[0-9]+$ ]] && (( start_ms > 0 )); then
|
|
128
|
+
duration_ms=$((now_ms - start_ms))
|
|
129
|
+
else
|
|
130
|
+
duration_ms=0
|
|
131
|
+
fi
|
|
132
|
+
(( duration_ms < 0 )) && duration_ms=0
|
|
133
|
+
|
|
134
|
+
jq -n \
|
|
135
|
+
--argjson duration_ms "$duration_ms" \
|
|
136
|
+
--argjson turn_count "$turn_count" \
|
|
137
|
+
--arg end_reason "$end_reason" \
|
|
138
|
+
'{
|
|
139
|
+
duration_ms: $duration_ms,
|
|
140
|
+
turn_count: $turn_count,
|
|
141
|
+
end_reason: $end_reason
|
|
142
|
+
}'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# Emit a validated canonical session event and append to logs.
|
|
146
|
+
# Usage: session_tracker_emit "$SESSION_ID" "session.start" "$payload_json"
|
|
147
|
+
session_tracker_emit() {
|
|
148
|
+
local session_id="${1:-}"
|
|
149
|
+
local event_type="${2:-}"
|
|
150
|
+
local payload_json="${3:-}"
|
|
151
|
+
[[ -z "$session_id" || -z "$event_type" || -z "$payload_json" ]] && return 0
|
|
152
|
+
|
|
153
|
+
local params event
|
|
154
|
+
params=$(jq -n \
|
|
155
|
+
--arg plugin "${ONLOOKER_PLUGIN_NAME:-onlooker}" \
|
|
156
|
+
--arg sid "$session_id" \
|
|
157
|
+
--arg type "$event_type" \
|
|
158
|
+
--argjson payload "$payload_json" \
|
|
159
|
+
'{plugin: $plugin, session_id: $sid, event_type: $type, payload: $payload}')
|
|
160
|
+
|
|
161
|
+
event=$(printf '%s' "$params" | ONLOOKER_DIR="$ONLOOKER_DIR" ONLOOKER_PLUGIN_NAME="$ONLOOKER_PLUGIN_NAME" \
|
|
162
|
+
node "${_ONLOOKER_EVENT_JS:-${CLAUDE_PLUGIN_ROOT:-}/scripts/lib/onlooker-event.mjs}" emit 2>/dev/null) || return 1
|
|
163
|
+
|
|
164
|
+
tool_history_append "$session_id" "$event" || return 1
|
|
165
|
+
onlooker_append_event "$event" || return 1
|
|
166
|
+
}
|
package/test/bats/config.bats
CHANGED
|
@@ -84,6 +84,34 @@ setup_file() {
|
|
|
84
84
|
[[ "$hook_cmd" == *tool-history-tracker.sh ]]
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
@test "hooks.json SessionStart references session-start-tracker" {
|
|
88
|
+
run jq -e '.hooks.SessionStart[0].matcher == "*"' "${REPO_ROOT}/hooks/hooks.json"
|
|
89
|
+
[ "$status" -eq 0 ]
|
|
90
|
+
|
|
91
|
+
local hook_cmd
|
|
92
|
+
hook_cmd=$(jq -r '.hooks.SessionStart[0].hooks[0].command' "${REPO_ROOT}/hooks/hooks.json")
|
|
93
|
+
[[ "$hook_cmd" == *session-start-tracker.sh ]]
|
|
94
|
+
|
|
95
|
+
local script_path="${hook_cmd//\$CLAUDE_PLUGIN_ROOT/$REPO_ROOT}"
|
|
96
|
+
script_path="${script_path//\"/}"
|
|
97
|
+
run test -x "$script_path"
|
|
98
|
+
[ "$status" -eq 0 ]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@test "hooks.json SessionEnd references session-end-tracker" {
|
|
102
|
+
run jq -e '.hooks.SessionEnd[0].matcher == "*"' "${REPO_ROOT}/hooks/hooks.json"
|
|
103
|
+
[ "$status" -eq 0 ]
|
|
104
|
+
|
|
105
|
+
local hook_cmd
|
|
106
|
+
hook_cmd=$(jq -r '.hooks.SessionEnd[0].hooks[0].command' "${REPO_ROOT}/hooks/hooks.json")
|
|
107
|
+
[[ "$hook_cmd" == *session-end-tracker.sh ]]
|
|
108
|
+
|
|
109
|
+
local script_path="${hook_cmd//\$CLAUDE_PLUGIN_ROOT/$REPO_ROOT}"
|
|
110
|
+
script_path="${script_path//\"/}"
|
|
111
|
+
run test -x "$script_path"
|
|
112
|
+
[ "$status" -eq 0 ]
|
|
113
|
+
}
|
|
114
|
+
|
|
87
115
|
@test "plugin.json is valid JSON" {
|
|
88
116
|
run jq -e '.name and .version' "${REPO_ROOT}/.claude-plugin/plugin.json"
|
|
89
117
|
[ "$status" -eq 0 ]
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
setup() {
|
|
4
|
+
# shellcheck source=../helpers/setup.bash
|
|
5
|
+
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
|
|
6
|
+
load_validate_path
|
|
7
|
+
export CLAUDE_PLUGIN_ROOT="${REPO_ROOT}"
|
|
8
|
+
# shellcheck source=../../scripts/lib/session-tracker.sh
|
|
9
|
+
source "${REPO_ROOT}/scripts/lib/onlooker-schema.sh"
|
|
10
|
+
source "${REPO_ROOT}/scripts/lib/tool-history.sh"
|
|
11
|
+
source "${REPO_ROOT}/scripts/lib/session-tracker.sh"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@test "session-start-tracker emits session.start for startup source" {
|
|
15
|
+
local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/session-start-startup.json"
|
|
16
|
+
local history_file="${ONLOOKER_SESSION_HISTORY_DIR}/session-start-001.jsonl"
|
|
17
|
+
rm -f "$history_file"
|
|
18
|
+
|
|
19
|
+
run bash -c "cat '${fixture}' | '${REPO_ROOT}/scripts/hooks/session-start-tracker.sh' 2>/dev/null"
|
|
20
|
+
[ "$status" -eq 0 ]
|
|
21
|
+
|
|
22
|
+
[ -f "$history_file" ]
|
|
23
|
+
jq -e '.event_type == "session.start"
|
|
24
|
+
and .session_id == "session-start-001"
|
|
25
|
+
and .payload.working_directory == "/project/repo"' \
|
|
26
|
+
"$history_file" >/dev/null
|
|
27
|
+
|
|
28
|
+
local tracker="${ONLOOKER_SESSION_TRACKERS_DIR}/session-start-001"
|
|
29
|
+
jq -e '.start_source == "startup" and (.start_time_ms | type) == "number"' "$tracker" >/dev/null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@test "session-start-tracker does not emit session.start for compact source" {
|
|
33
|
+
local fixture="${REPO_ROOT}/test/fixtures/hook-inputs/session-start-compact.json"
|
|
34
|
+
local history_file="${ONLOOKER_SESSION_HISTORY_DIR}/session-start-002.jsonl"
|
|
35
|
+
rm -f "$history_file"
|
|
36
|
+
|
|
37
|
+
run bash -c "cat '${fixture}' | '${REPO_ROOT}/scripts/hooks/session-start-tracker.sh' 2>/dev/null"
|
|
38
|
+
[ "$status" -eq 0 ]
|
|
39
|
+
[ ! -f "$history_file" ]
|
|
40
|
+
|
|
41
|
+
local tracker="${ONLOOKER_SESSION_TRACKERS_DIR}/session-start-002"
|
|
42
|
+
jq -e '.start_source == "compact"' "$tracker" >/dev/null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@test "session-end-tracker emits session.end with duration and turn count" {
|
|
46
|
+
local start_fixture="${REPO_ROOT}/test/fixtures/hook-inputs/session-start-startup.json"
|
|
47
|
+
local end_fixture="${REPO_ROOT}/test/fixtures/hook-inputs/session-end-other.json"
|
|
48
|
+
local session_id="session-end-001"
|
|
49
|
+
local tracker="${ONLOOKER_SESSION_TRACKERS_DIR}/${session_id}"
|
|
50
|
+
local history_file="${ONLOOKER_SESSION_HISTORY_DIR}/${session_id}.jsonl"
|
|
51
|
+
|
|
52
|
+
rm -f "$history_file" "$tracker"
|
|
53
|
+
|
|
54
|
+
# Seed tracker as if session had been running
|
|
55
|
+
turn_state_ensure_session "$session_id"
|
|
56
|
+
local past_ms
|
|
57
|
+
past_ms=$(python3 -c 'import time; print(int((time.time() - 2) * 1000))' 2>/dev/null || echo 0)
|
|
58
|
+
jq --argjson start_ms "$past_ms" '.start_time_ms = $start_ms | .turn_number = 3' "$tracker" >"${tracker}.tmp"
|
|
59
|
+
mv "${tracker}.tmp" "$tracker"
|
|
60
|
+
|
|
61
|
+
run bash -c "cat '${end_fixture}' | '${REPO_ROOT}/scripts/hooks/session-end-tracker.sh' 2>/dev/null"
|
|
62
|
+
[ "$status" -eq 0 ]
|
|
63
|
+
|
|
64
|
+
jq -e '.event_type == "session.end"
|
|
65
|
+
and .session_id == "session-end-001"
|
|
66
|
+
and .payload.turn_count == 3
|
|
67
|
+
and .payload.end_reason == "unknown"
|
|
68
|
+
and (.payload.duration_ms | type) == "number"
|
|
69
|
+
and .payload.duration_ms >= 0' \
|
|
70
|
+
"$history_file" >/dev/null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@test "session_tracker_map_end_reason maps logout to user_exit" {
|
|
74
|
+
[ "$(session_tracker_map_end_reason logout)" = "user_exit" ]
|
|
75
|
+
}
|