@adverant/nexus-memory-skill 1.3.0 → 2.1.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/README.md +9 -2
- package/SKILL.md +152 -0
- package/hooks/auto-recall.sh +66 -5
- package/hooks/bead-sync.sh +596 -0
- package/hooks/episode-summary.sh +194 -176
- package/hooks/recall-memory.sh +106 -53
- package/hooks/store-memory.sh +105 -2
- package/package.json +1 -1
package/hooks/episode-summary.sh
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
#
|
|
3
|
-
# Nexus Memory -
|
|
3
|
+
# Nexus Memory - Optimized Episode Summary Hook (GraphRAG v2)
|
|
4
4
|
# Captures significant tool uses and generates periodic episode summaries.
|
|
5
|
-
# Uses entity extraction and causal chaining for knowledge graph building.
|
|
6
5
|
#
|
|
7
|
-
#
|
|
8
|
-
# -
|
|
9
|
-
# -
|
|
10
|
-
# -
|
|
11
|
-
# -
|
|
6
|
+
# PERFORMANCE OPTIMIZED:
|
|
7
|
+
# - Smart tool filtering (skip routine operations)
|
|
8
|
+
# - Significance scoring for tools
|
|
9
|
+
# - Adaptive episode thresholds based on activity
|
|
10
|
+
# - Compressed episode summaries for efficiency
|
|
11
|
+
# - Async storage for non-blocking operation
|
|
12
12
|
#
|
|
13
13
|
# Episodes capture:
|
|
14
|
-
# -
|
|
15
|
-
# -
|
|
16
|
-
# -
|
|
17
|
-
# -
|
|
18
|
-
# - Extracted entities
|
|
14
|
+
# - Significant tool uses only
|
|
15
|
+
# - Project context and decisions made
|
|
16
|
+
# - Causal relationships between actions
|
|
17
|
+
# - Extracted entities for knowledge graph
|
|
19
18
|
#
|
|
20
19
|
# Usage:
|
|
21
20
|
# echo '{"tool_name": "Bash", "tool_input": {...}, "tool_output": "..."}' | episode-summary.sh
|
|
@@ -31,7 +30,7 @@
|
|
|
31
30
|
# GraphRAG Enhancement Options:
|
|
32
31
|
# NEXUS_EXTRACT_ENTITIES - Enable entity extraction (default: true)
|
|
33
32
|
# NEXUS_CREATE_RELATIONS - Create knowledge graph relationships (default: true)
|
|
34
|
-
#
|
|
33
|
+
# NEXUS_MIN_SIGNIFICANCE - Minimum significance to store tool use (default: 40)
|
|
35
34
|
#
|
|
36
35
|
|
|
37
36
|
set -o pipefail
|
|
@@ -47,13 +46,14 @@ EPISODE_THRESHOLD="${NEXUS_EPISODE_THRESHOLD:-10}"
|
|
|
47
46
|
# GraphRAG Enhancement Configuration
|
|
48
47
|
EXTRACT_ENTITIES="${NEXUS_EXTRACT_ENTITIES:-true}"
|
|
49
48
|
CREATE_RELATIONS="${NEXUS_CREATE_RELATIONS:-true}"
|
|
50
|
-
|
|
49
|
+
MIN_SIGNIFICANCE="${NEXUS_MIN_SIGNIFICANCE:-40}"
|
|
51
50
|
|
|
52
|
-
# State file for tracking
|
|
51
|
+
# State file for tracking
|
|
53
52
|
STATE_DIR="${HOME}/.claude/session-env"
|
|
54
53
|
COUNTER_FILE="${STATE_DIR}/episode_counter"
|
|
55
54
|
LAST_TOOL_ID_FILE="${STATE_DIR}/last_tool_id"
|
|
56
55
|
SESSION_TOOLS_FILE="${STATE_DIR}/session_tools"
|
|
56
|
+
LAST_EPISODE_FILE="${STATE_DIR}/last_episode_id"
|
|
57
57
|
|
|
58
58
|
# Logging function
|
|
59
59
|
log() {
|
|
@@ -66,43 +66,45 @@ log_error() {
|
|
|
66
66
|
echo "[episode-summary] ERROR: $1" >&2
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
# =========================================================
|
|
70
|
+
# FAST PATH: Early exit checks
|
|
71
|
+
# =========================================================
|
|
72
|
+
|
|
69
73
|
# Skip if no API key
|
|
70
74
|
if [[ -z "$NEXUS_API_KEY" ]]; then
|
|
71
|
-
log "NEXUS_API_KEY not set, skipping episode summary"
|
|
72
|
-
exit 0
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
# Check dependencies
|
|
76
|
-
if ! command -v jq &> /dev/null; then
|
|
77
|
-
log "jq not installed, skipping episode summary"
|
|
78
75
|
exit 0
|
|
79
76
|
fi
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
fi
|
|
78
|
+
# Fast dependency check
|
|
79
|
+
type jq &>/dev/null || exit 0
|
|
80
|
+
type curl &>/dev/null || exit 0
|
|
85
81
|
|
|
86
82
|
# Ensure state directory exists
|
|
87
|
-
mkdir -p "$STATE_DIR"
|
|
83
|
+
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
88
84
|
|
|
89
85
|
# Read input from stdin
|
|
90
86
|
INPUT=$(cat)
|
|
91
87
|
|
|
92
88
|
if [[ -z "$INPUT" ]]; then
|
|
93
|
-
log "No input provided"
|
|
94
89
|
exit 0
|
|
95
90
|
fi
|
|
96
91
|
|
|
97
92
|
log "Received input: ${INPUT:0:100}..."
|
|
98
93
|
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
94
|
+
# =========================================================
|
|
95
|
+
# INPUT PARSING
|
|
96
|
+
# =========================================================
|
|
97
|
+
|
|
98
|
+
# Extract fields with single jq call
|
|
99
|
+
read -r TOOL_NAME FORCE_SUMMARY SESSION_END < <(echo "$INPUT" | jq -r '[
|
|
100
|
+
(.tool_name // ""),
|
|
101
|
+
(.force // "false"),
|
|
102
|
+
(.session_end // "false")
|
|
103
|
+
] | @tsv' 2>/dev/null || echo " false false")
|
|
104
|
+
|
|
105
|
+
TOOL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}' 2>/dev/null)
|
|
106
|
+
TOOL_OUTPUT=$(echo "$INPUT" | jq -r '.tool_output // ""' 2>/dev/null)
|
|
107
|
+
CONTENT=$(echo "$INPUT" | jq -r '.content // .conversation_summary // .prompt // ""' 2>/dev/null)
|
|
106
108
|
|
|
107
109
|
# Get project context
|
|
108
110
|
PROJECT_NAME=$(basename "$(pwd)")
|
|
@@ -110,81 +112,113 @@ PROJECT_DIR=$(pwd)
|
|
|
110
112
|
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
111
113
|
INTERACTION_ID="${PROJECT_NAME}-$(date +%s)-$$"
|
|
112
114
|
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
+
# =========================================================
|
|
116
|
+
# TOOL SIGNIFICANCE SCORING
|
|
117
|
+
# =========================================================
|
|
115
118
|
|
|
116
|
-
|
|
119
|
+
# Tool categories with base significance scores
|
|
120
|
+
get_tool_significance() {
|
|
117
121
|
local tool="$1"
|
|
118
122
|
local input="$2"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
123
|
+
local output="$3"
|
|
124
|
+
local score=50 # Base score
|
|
125
|
+
|
|
126
|
+
# High-value tools (creation/modification)
|
|
127
|
+
case "$tool" in
|
|
128
|
+
Write|Edit|NotebookEdit)
|
|
129
|
+
score=80 # File modifications are significant
|
|
130
|
+
;;
|
|
131
|
+
Bash)
|
|
132
|
+
# Analyze the command
|
|
133
|
+
local cmd=$(echo "$input" | jq -r '.command // ""' 2>/dev/null)
|
|
134
|
+
|
|
135
|
+
# Skip routine read commands
|
|
136
|
+
if echo "$cmd" | grep -qE '^(ls|pwd|cat|head|tail|wc|find|grep|which|echo|date|whoami|type|file)(\s|$)'; then
|
|
137
|
+
score=10
|
|
138
|
+
# Skip routine git read commands
|
|
139
|
+
elif echo "$cmd" | grep -qE '^git\s+(status|log|diff|branch|show|remote|fetch)(\s|$)'; then
|
|
140
|
+
score=15
|
|
141
|
+
# High-value git commands
|
|
142
|
+
elif echo "$cmd" | grep -qE '^git\s+(commit|push|merge|rebase|checkout|reset)(\s|$)'; then
|
|
143
|
+
score=85
|
|
144
|
+
# Package management
|
|
145
|
+
elif echo "$cmd" | grep -qE '^(npm|yarn|pnpm)\s+(install|add|remove|run build|run test)'; then
|
|
146
|
+
score=70
|
|
147
|
+
# Docker commands
|
|
148
|
+
elif echo "$cmd" | grep -qE '^docker\s+(build|run|push|pull|compose)'; then
|
|
149
|
+
score=75
|
|
150
|
+
# kubectl commands
|
|
151
|
+
elif echo "$cmd" | grep -qE '^(kubectl|k3s kubectl)\s+(apply|delete|create|rollout)'; then
|
|
152
|
+
score=80
|
|
153
|
+
else
|
|
154
|
+
score=40 # Unknown commands get medium score
|
|
155
|
+
fi
|
|
156
|
+
;;
|
|
157
|
+
TodoWrite)
|
|
158
|
+
score=60 # Task management
|
|
159
|
+
;;
|
|
160
|
+
WebFetch|WebSearch)
|
|
161
|
+
score=30 # Research/lookup
|
|
162
|
+
;;
|
|
163
|
+
Read|Glob|Grep)
|
|
164
|
+
score=10 # Read-only operations
|
|
165
|
+
;;
|
|
166
|
+
Skill)
|
|
167
|
+
score=75 # Skill invocations are significant
|
|
168
|
+
;;
|
|
169
|
+
*)
|
|
170
|
+
score=50 # Unknown tools get base score
|
|
171
|
+
;;
|
|
172
|
+
esac
|
|
173
|
+
|
|
174
|
+
# Output-based adjustments
|
|
175
|
+
if [[ -n "$output" ]]; then
|
|
176
|
+
# Errors are always significant
|
|
177
|
+
if echo "$output" | grep -qiE '(error|exception|failed|failure)'; then
|
|
178
|
+
score=$((score + 20))
|
|
145
179
|
fi
|
|
146
180
|
|
|
147
|
-
#
|
|
148
|
-
if echo "$
|
|
149
|
-
|
|
150
|
-
return 0
|
|
181
|
+
# Success messages boost significance
|
|
182
|
+
if echo "$output" | grep -qiE '(success|completed|created|fixed|resolved)'; then
|
|
183
|
+
score=$((score + 15))
|
|
151
184
|
fi
|
|
152
185
|
fi
|
|
153
186
|
|
|
154
|
-
#
|
|
155
|
-
|
|
187
|
+
# Clamp to 0-100
|
|
188
|
+
if [[ "$score" -lt 0 ]]; then score=0; fi
|
|
189
|
+
if [[ "$score" -gt 100 ]]; then score=100; fi
|
|
190
|
+
|
|
191
|
+
echo "$score"
|
|
156
192
|
}
|
|
157
193
|
|
|
158
|
-
#
|
|
159
|
-
|
|
160
|
-
|
|
194
|
+
# =========================================================
|
|
195
|
+
# TOOL PROCESSING
|
|
196
|
+
# =========================================================
|
|
197
|
+
|
|
198
|
+
if [[ -n "$TOOL_NAME" ]] && [[ "$TOOL_NAME" != "null" ]]; then
|
|
199
|
+
# Calculate tool significance
|
|
200
|
+
SIGNIFICANCE=$(get_tool_significance "$TOOL_NAME" "$TOOL_INPUT" "$TOOL_OUTPUT")
|
|
201
|
+
log "Tool: $TOOL_NAME, Significance: $SIGNIFICANCE (min: $MIN_SIGNIFICANCE)"
|
|
161
202
|
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
203
|
+
# Get current counter
|
|
204
|
+
CURRENT_COUNT=0
|
|
205
|
+
if [[ -f "$COUNTER_FILE" ]]; then
|
|
206
|
+
CURRENT_COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
|
|
165
207
|
fi
|
|
166
208
|
|
|
167
|
-
|
|
168
|
-
|
|
209
|
+
# Always increment counter
|
|
210
|
+
NEW_COUNT=$((CURRENT_COUNT + 1))
|
|
211
|
+
echo "$NEW_COUNT" > "$COUNTER_FILE"
|
|
169
212
|
|
|
170
|
-
#
|
|
171
|
-
if [[
|
|
172
|
-
|
|
173
|
-
if should_skip_tool "$TOOL_NAME" "$TOOL_INPUT"; then
|
|
174
|
-
# Still increment counter but don't store
|
|
175
|
-
if [[ -f "$COUNTER_FILE" ]]; then
|
|
176
|
-
CURRENT_COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
|
|
177
|
-
else
|
|
178
|
-
CURRENT_COUNT=0
|
|
179
|
-
fi
|
|
180
|
-
echo "$((CURRENT_COUNT + 1))" > "$COUNTER_FILE"
|
|
181
|
-
log "Counter incremented to $((CURRENT_COUNT + 1)) (routine tool skipped)"
|
|
213
|
+
# Skip storing low-significance tools
|
|
214
|
+
if [[ "$SIGNIFICANCE" -lt "$MIN_SIGNIFICANCE" ]]; then
|
|
215
|
+
log "Tool below significance threshold, skipping storage (count: $NEW_COUNT)"
|
|
182
216
|
else
|
|
183
217
|
log "Storing significant tool use: $TOOL_NAME"
|
|
184
218
|
|
|
185
219
|
# Truncate long outputs
|
|
186
|
-
if [[ ${#TOOL_OUTPUT} -gt
|
|
187
|
-
TOOL_OUTPUT="${TOOL_OUTPUT:0:
|
|
220
|
+
if [[ ${#TOOL_OUTPUT} -gt 800 ]]; then
|
|
221
|
+
TOOL_OUTPUT="${TOOL_OUTPUT:0:800}... [truncated]"
|
|
188
222
|
fi
|
|
189
223
|
|
|
190
224
|
# Get previous tool ID for causal chaining
|
|
@@ -193,54 +227,44 @@ if [[ -n "$TOOL_NAME" ]] && [[ "$TOOL_NAME" != "null" ]]; then
|
|
|
193
227
|
PREV_TOOL_ID=$(cat "$LAST_TOOL_ID_FILE" 2>/dev/null)
|
|
194
228
|
fi
|
|
195
229
|
|
|
196
|
-
# Determine
|
|
197
|
-
SIGNIFICANCE="normal"
|
|
198
|
-
if is_significant_output "$TOOL_OUTPUT"; then
|
|
199
|
-
SIGNIFICANCE="high"
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
|
-
# Detect domain for entity extraction
|
|
230
|
+
# Determine domain
|
|
203
231
|
DOMAIN="code"
|
|
204
|
-
|
|
205
|
-
DOMAIN="web"
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
fi
|
|
232
|
+
case "$TOOL_NAME" in
|
|
233
|
+
WebFetch|WebSearch) DOMAIN="web" ;;
|
|
234
|
+
TodoWrite) DOMAIN="planning" ;;
|
|
235
|
+
esac
|
|
209
236
|
|
|
210
|
-
# Build tool
|
|
211
|
-
TOOL_CONTENT="[Tool
|
|
237
|
+
# Build compact tool content
|
|
238
|
+
TOOL_CONTENT="[Tool] $TOOL_NAME (sig: $SIGNIFICANCE)
|
|
212
239
|
Project: $PROJECT_NAME
|
|
213
|
-
|
|
214
|
-
Input: ${TOOL_INPUT:0:500}
|
|
240
|
+
Input: ${TOOL_INPUT:0:400}
|
|
215
241
|
Output: $TOOL_OUTPUT"
|
|
216
242
|
|
|
217
|
-
# Build
|
|
243
|
+
# Build payload
|
|
218
244
|
TOOL_PAYLOAD=$(jq -n \
|
|
219
245
|
--arg content "$TOOL_CONTENT" \
|
|
220
246
|
--arg tool "$TOOL_NAME" \
|
|
221
247
|
--arg project "$PROJECT_NAME" \
|
|
222
|
-
--arg projectDir "$PROJECT_DIR" \
|
|
223
248
|
--arg timestamp "$TIMESTAMP" \
|
|
224
249
|
--arg interactionId "$INTERACTION_ID" \
|
|
225
250
|
--arg prevToolId "$PREV_TOOL_ID" \
|
|
226
|
-
--
|
|
251
|
+
--argjson significance "$SIGNIFICANCE" \
|
|
227
252
|
--arg domain "$DOMAIN" \
|
|
228
253
|
--argjson extractEntities "$EXTRACT_ENTITIES" \
|
|
229
254
|
--argjson createRelations "$CREATE_RELATIONS" \
|
|
230
255
|
'{
|
|
231
256
|
content: $content,
|
|
232
|
-
tags: ["claude-code", "type:tool-use", "tool:\($tool)", "project:\($project)", "
|
|
257
|
+
tags: ["claude-code", "type:tool-use", "tool:\($tool)", "project:\($project)", "sig:\($significance)"],
|
|
233
258
|
metadata: {
|
|
234
259
|
eventType: "tool-use",
|
|
235
260
|
toolName: $tool,
|
|
236
261
|
projectName: $project,
|
|
237
|
-
projectDir: $projectDir,
|
|
238
262
|
timestamp: $timestamp,
|
|
239
263
|
interactionId: $interactionId,
|
|
240
264
|
significance: $significance
|
|
241
265
|
},
|
|
242
266
|
extract_entities: $extractEntities,
|
|
243
|
-
entity_types: ["code_pattern", "error", "file", "function", "
|
|
267
|
+
entity_types: ["code_pattern", "error", "file", "function", "command"],
|
|
244
268
|
domain: $domain,
|
|
245
269
|
create_relationships: $createRelations,
|
|
246
270
|
episodic: {
|
|
@@ -250,49 +274,45 @@ Output: $TOOL_OUTPUT"
|
|
|
250
274
|
}
|
|
251
275
|
}')
|
|
252
276
|
|
|
253
|
-
# Store tool use asynchronously
|
|
277
|
+
# Store tool use asynchronously
|
|
254
278
|
(
|
|
255
|
-
RESPONSE=$(curl -s -
|
|
279
|
+
RESPONSE=$(curl -s -X POST "$NEXUS_API_URL/api/memory/store" \
|
|
256
280
|
-H "Content-Type: application/json" \
|
|
257
281
|
-H "Authorization: Bearer $NEXUS_API_KEY" \
|
|
258
282
|
-H "X-Company-ID: $COMPANY_ID" \
|
|
259
283
|
-H "X-App-ID: $APP_ID" \
|
|
260
284
|
-H "X-User-ID: ${USER:-unknown}" \
|
|
285
|
+
-H "Connection: keep-alive" \
|
|
261
286
|
-d "$TOOL_PAYLOAD" \
|
|
287
|
+
--connect-timeout 2 \
|
|
262
288
|
--max-time 5 2>/dev/null)
|
|
263
289
|
|
|
264
|
-
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
265
|
-
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
266
|
-
|
|
267
290
|
# Save this tool's memory ID for causal chaining
|
|
268
|
-
MEMORY_ID=$(echo "$
|
|
291
|
+
MEMORY_ID=$(echo "$RESPONSE" | jq -r '.data.memoryId // .memoryId // empty' 2>/dev/null)
|
|
269
292
|
if [[ -n "$MEMORY_ID" ]]; then
|
|
270
293
|
echo "$MEMORY_ID" > "$LAST_TOOL_ID_FILE"
|
|
271
294
|
fi
|
|
272
295
|
|
|
273
|
-
# Append to session tools log
|
|
274
|
-
echo "$TOOL_NAME:$SIGNIFICANCE:$
|
|
275
|
-
|
|
296
|
+
# Append to session tools log (compact format)
|
|
297
|
+
echo "$TOOL_NAME:$SIGNIFICANCE:$(date +%H%M%S)" >> "$SESSION_TOOLS_FILE"
|
|
276
298
|
) &
|
|
277
299
|
|
|
278
|
-
log "Tool use stored
|
|
300
|
+
log "Tool use stored"
|
|
279
301
|
fi
|
|
280
|
-
fi
|
|
281
|
-
|
|
282
|
-
# --- STEP 2: Check if we should generate episode summary ---
|
|
283
|
-
|
|
284
|
-
# Get current counter
|
|
285
|
-
if [[ -f "$COUNTER_FILE" ]]; then
|
|
286
|
-
CURRENT_COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
|
|
287
302
|
else
|
|
288
|
-
|
|
303
|
+
# No tool - just get counter
|
|
304
|
+
if [[ -f "$COUNTER_FILE" ]]; then
|
|
305
|
+
CURRENT_COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
|
|
306
|
+
else
|
|
307
|
+
CURRENT_COUNT=0
|
|
308
|
+
fi
|
|
309
|
+
NEW_COUNT=$((CURRENT_COUNT + 1))
|
|
310
|
+
echo "$NEW_COUNT" > "$COUNTER_FILE"
|
|
289
311
|
fi
|
|
290
312
|
|
|
291
|
-
#
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
log "Tool count: $NEW_COUNT (threshold: $EPISODE_THRESHOLD)"
|
|
313
|
+
# =========================================================
|
|
314
|
+
# EPISODE SUMMARY GENERATION
|
|
315
|
+
# =========================================================
|
|
296
316
|
|
|
297
317
|
# Determine if we should generate an episode summary
|
|
298
318
|
SHOULD_SUMMARIZE=false
|
|
@@ -305,8 +325,7 @@ elif [[ "$SESSION_END" == "true" ]]; then
|
|
|
305
325
|
log "Session end summary"
|
|
306
326
|
elif [[ "$NEW_COUNT" -ge "$EPISODE_THRESHOLD" ]]; then
|
|
307
327
|
SHOULD_SUMMARIZE=true
|
|
308
|
-
log "Threshold reached
|
|
309
|
-
# Reset counter
|
|
328
|
+
log "Threshold reached ($NEW_COUNT >= $EPISODE_THRESHOLD)"
|
|
310
329
|
echo "0" > "$COUNTER_FILE"
|
|
311
330
|
fi
|
|
312
331
|
|
|
@@ -316,74 +335,74 @@ if [[ "$SHOULD_SUMMARIZE" != "true" ]]; then
|
|
|
316
335
|
exit 0
|
|
317
336
|
fi
|
|
318
337
|
|
|
319
|
-
#
|
|
338
|
+
# =========================================================
|
|
339
|
+
# BUILD EPISODE SUMMARY
|
|
340
|
+
# =========================================================
|
|
341
|
+
|
|
320
342
|
SESSION_ID=$(date +%Y%m%d-%H%M%S)
|
|
321
343
|
|
|
322
344
|
# Read session tools for summary
|
|
323
345
|
SESSION_TOOLS=""
|
|
346
|
+
SIGNIFICANT_TOOLS=""
|
|
324
347
|
if [[ -f "$SESSION_TOOLS_FILE" ]]; then
|
|
325
|
-
SESSION_TOOLS=$(cat "$SESSION_TOOLS_FILE" 2>/dev/null | tail -
|
|
326
|
-
#
|
|
348
|
+
SESSION_TOOLS=$(cat "$SESSION_TOOLS_FILE" 2>/dev/null | tail -30)
|
|
349
|
+
# Count high-significance tools (sig >= 60)
|
|
350
|
+
SIGNIFICANT_TOOLS=$(echo "$SESSION_TOOLS" | awk -F: '$2 >= 60 {print $1}' | sort | uniq -c | sort -rn | head -5)
|
|
351
|
+
# Clear the session tools file
|
|
327
352
|
echo "" > "$SESSION_TOOLS_FILE"
|
|
328
353
|
fi
|
|
329
354
|
|
|
330
|
-
#
|
|
355
|
+
# Build content from session activity if not provided
|
|
331
356
|
if [[ -z "$CONTENT" ]] || [[ "$CONTENT" == "null" ]]; then
|
|
332
|
-
# Build content from session tools
|
|
333
357
|
if [[ -n "$SESSION_TOOLS" ]]; then
|
|
334
|
-
CONTENT="Session activity
|
|
335
|
-
$SESSION_TOOLS"
|
|
358
|
+
CONTENT="Session activity in $PROJECT_NAME"
|
|
336
359
|
else
|
|
337
360
|
CONTENT="Conversation segment in $PROJECT_NAME project"
|
|
338
361
|
fi
|
|
339
362
|
fi
|
|
340
363
|
|
|
341
364
|
# Truncate very long content
|
|
342
|
-
if [[ ${#CONTENT} -gt
|
|
343
|
-
CONTENT="${CONTENT:0:
|
|
365
|
+
if [[ ${#CONTENT} -gt 3000 ]]; then
|
|
366
|
+
CONTENT="${CONTENT:0:3000}... [truncated]"
|
|
344
367
|
fi
|
|
345
368
|
|
|
346
|
-
# Count
|
|
347
|
-
|
|
369
|
+
# Count tools by significance
|
|
370
|
+
TOTAL_TOOLS=$(echo "$SESSION_TOOLS" | grep -c ':' || echo "0")
|
|
371
|
+
HIGH_SIG_COUNT=$(echo "$SESSION_TOOLS" | awk -F: '$2 >= 60' | wc -l | tr -d ' ')
|
|
348
372
|
|
|
349
|
-
# Generate episode summary
|
|
373
|
+
# Generate compact episode summary
|
|
350
374
|
EPISODE_CONTENT=$(cat <<EOF
|
|
351
|
-
[Episode
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
Directory: $PROJECT_DIR
|
|
355
|
-
Tool Count: $NEW_COUNT
|
|
356
|
-
Significant Events: $SIGNIFICANT_COUNT
|
|
375
|
+
[Episode] $PROJECT_NAME
|
|
376
|
+
Time: $TIMESTAMP
|
|
377
|
+
Tools: $TOTAL_TOOLS total, $HIGH_SIG_COUNT significant
|
|
357
378
|
|
|
358
|
-
|
|
379
|
+
Activity Summary:
|
|
359
380
|
$CONTENT
|
|
360
381
|
|
|
361
|
-
Tools
|
|
362
|
-
$(echo "$
|
|
382
|
+
Top Tools:
|
|
383
|
+
$(echo "$SIGNIFICANT_TOOLS" | head -5 | awk '{print "- " $2 " (" $1 "x)"}')
|
|
363
384
|
|
|
364
385
|
---
|
|
365
|
-
|
|
386
|
+
Auto-generated after $NEW_COUNT interactions.
|
|
366
387
|
EOF
|
|
367
388
|
)
|
|
368
389
|
|
|
369
|
-
log "Generating episode summary
|
|
390
|
+
log "Generating episode summary"
|
|
370
391
|
|
|
371
392
|
# Get previous episode ID for causal chaining
|
|
372
393
|
PREV_EPISODE_ID=""
|
|
373
|
-
LAST_EPISODE_FILE="${STATE_DIR}/last_episode_id"
|
|
374
394
|
if [[ -f "$LAST_EPISODE_FILE" ]]; then
|
|
375
395
|
PREV_EPISODE_ID=$(cat "$LAST_EPISODE_FILE" 2>/dev/null)
|
|
376
396
|
fi
|
|
377
397
|
|
|
378
|
-
# Build the
|
|
398
|
+
# Build the payload
|
|
379
399
|
PAYLOAD=$(jq -n \
|
|
380
400
|
--arg content "$EPISODE_CONTENT" \
|
|
381
401
|
--arg session "$SESSION_ID" \
|
|
382
402
|
--arg project "$PROJECT_NAME" \
|
|
383
|
-
--arg projectDir "$PROJECT_DIR" \
|
|
384
403
|
--arg timestamp "$TIMESTAMP" \
|
|
385
404
|
--argjson toolCount "$NEW_COUNT" \
|
|
386
|
-
--argjson
|
|
405
|
+
--argjson sigCount "$HIGH_SIG_COUNT" \
|
|
387
406
|
--arg prevEpisodeId "$PREV_EPISODE_ID" \
|
|
388
407
|
--argjson extractEntities "$EXTRACT_ENTITIES" \
|
|
389
408
|
--argjson createRelations "$CREATE_RELATIONS" \
|
|
@@ -393,11 +412,10 @@ PAYLOAD=$(jq -n \
|
|
|
393
412
|
metadata: {
|
|
394
413
|
sessionId: $session,
|
|
395
414
|
eventType: "episode",
|
|
396
|
-
projectDir: $projectDir,
|
|
397
415
|
projectName: $project,
|
|
398
416
|
timestamp: $timestamp,
|
|
399
417
|
toolCount: $toolCount,
|
|
400
|
-
|
|
418
|
+
significantToolCount: $sigCount,
|
|
401
419
|
isEpisodeSummary: true
|
|
402
420
|
},
|
|
403
421
|
extract_entities: $extractEntities,
|
|
@@ -415,37 +433,36 @@ PAYLOAD=$(jq -n \
|
|
|
415
433
|
|
|
416
434
|
log "Storing episode to $NEXUS_API_URL/api/memory/store"
|
|
417
435
|
|
|
418
|
-
# Store
|
|
436
|
+
# Store episode
|
|
419
437
|
if [[ "$VERBOSE" == "1" ]]; then
|
|
420
|
-
# Sync mode with output for debugging
|
|
421
438
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$NEXUS_API_URL/api/memory/store" \
|
|
422
439
|
-H "Content-Type: application/json" \
|
|
423
440
|
-H "Authorization: Bearer $NEXUS_API_KEY" \
|
|
424
441
|
-H "X-Company-ID: $COMPANY_ID" \
|
|
425
442
|
-H "X-App-ID: $APP_ID" \
|
|
426
443
|
-H "X-User-ID: ${USER:-unknown}" \
|
|
444
|
+
-H "Connection: keep-alive" \
|
|
427
445
|
-d "$PAYLOAD" \
|
|
428
|
-
--
|
|
446
|
+
--connect-timeout 2 \
|
|
447
|
+
--max-time 8 2>&1)
|
|
429
448
|
|
|
430
449
|
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
431
450
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
|
432
451
|
|
|
433
452
|
log "Response code: $HTTP_CODE"
|
|
434
|
-
log "Response body: ${BODY:0:500}"
|
|
435
453
|
|
|
436
454
|
if [[ "$HTTP_CODE" == "200" ]] || [[ "$HTTP_CODE" == "201" ]]; then
|
|
437
|
-
# Save episode ID for causal chaining
|
|
438
455
|
EPISODE_ID=$(echo "$BODY" | jq -r '.data.memoryId // .memoryId // empty' 2>/dev/null)
|
|
439
456
|
if [[ -n "$EPISODE_ID" ]]; then
|
|
440
457
|
echo "$EPISODE_ID" > "$LAST_EPISODE_FILE"
|
|
441
|
-
log "Saved episode ID
|
|
458
|
+
log "Saved episode ID: $EPISODE_ID"
|
|
442
459
|
fi
|
|
443
460
|
log "Episode stored successfully"
|
|
444
461
|
else
|
|
445
462
|
log_error "Failed to store episode (HTTP $HTTP_CODE)"
|
|
446
463
|
fi
|
|
447
464
|
else
|
|
448
|
-
# Async mode
|
|
465
|
+
# Async mode
|
|
449
466
|
(
|
|
450
467
|
RESPONSE=$(curl -s -X POST "$NEXUS_API_URL/api/memory/store" \
|
|
451
468
|
-H "Content-Type: application/json" \
|
|
@@ -453,10 +470,11 @@ else
|
|
|
453
470
|
-H "X-Company-ID: $COMPANY_ID" \
|
|
454
471
|
-H "X-App-ID: $APP_ID" \
|
|
455
472
|
-H "X-User-ID: ${USER:-unknown}" \
|
|
473
|
+
-H "Connection: keep-alive" \
|
|
456
474
|
-d "$PAYLOAD" \
|
|
475
|
+
--connect-timeout 2 \
|
|
457
476
|
--max-time 5 2>/dev/null)
|
|
458
477
|
|
|
459
|
-
# Save episode ID for causal chaining
|
|
460
478
|
EPISODE_ID=$(echo "$RESPONSE" | jq -r '.data.memoryId // .memoryId // empty' 2>/dev/null)
|
|
461
479
|
if [[ -n "$EPISODE_ID" ]]; then
|
|
462
480
|
echo "$EPISODE_ID" > "$LAST_EPISODE_FILE"
|