@prmichaelsen/remember-mcp 3.15.3 → 3.15.5
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/AGENT.md +363 -5
- package/CHANGELOG.md +7 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +50 -0
- package/agent/commands/acp.command-create.md +60 -0
- package/agent/commands/acp.design-create.md +62 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +48 -0
- package/agent/commands/acp.package-create.md +1 -0
- package/agent/commands/acp.package-info.md +1 -0
- package/agent/commands/acp.package-install.md +19 -0
- package/agent/commands/acp.package-list.md +1 -0
- package/agent/commands/acp.package-publish.md +1 -0
- package/agent/commands/acp.package-remove.md +1 -0
- package/agent/commands/acp.package-search.md +1 -0
- package/agent/commands/acp.package-update.md +1 -0
- package/agent/commands/acp.package-validate.md +1 -0
- package/agent/commands/acp.pattern-create.md +60 -0
- package/agent/commands/acp.plan.md +25 -0
- package/agent/commands/acp.proceed.md +621 -75
- package/agent/commands/acp.project-create.md +3 -0
- package/agent/commands/acp.project-info.md +3 -0
- package/agent/commands/acp.project-list.md +3 -1
- package/agent/commands/acp.project-set.md +1 -0
- package/agent/commands/acp.project-update.md +14 -3
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +13 -0
- package/agent/commands/acp.resume.md +3 -1
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +13 -0
- package/agent/commands/acp.sync.md +1 -0
- package/agent/commands/acp.task-create.md +105 -3
- package/agent/commands/acp.update.md +1 -0
- package/agent/commands/acp.validate.md +32 -2
- package/agent/commands/acp.version-check-for-updates.md +1 -0
- package/agent/commands/acp.version-check.md +1 -0
- package/agent/commands/acp.version-update.md +1 -0
- package/agent/commands/command.template.md +23 -0
- package/agent/commands/git.commit.md +1 -0
- package/agent/commands/git.init.md +1 -0
- package/agent/design/complete-tool-set.md +157 -233
- package/agent/design/design.template.md +18 -0
- package/agent/design/user-preferences.md +11 -7
- package/agent/milestones/milestone-19-new-search-ghost-tools.md +46 -0
- package/agent/package.template.yaml +50 -0
- package/agent/patterns/pattern.template.md +18 -0
- package/agent/progress.yaml +162 -6
- package/agent/scripts/acp.common.sh +258 -15
- package/agent/scripts/acp.install.sh +91 -4
- package/agent/scripts/acp.package-create.sh +0 -1
- package/agent/scripts/acp.package-info.sh +19 -1
- package/agent/scripts/acp.package-install-optimized.sh +1 -1
- package/agent/scripts/acp.package-install.sh +388 -38
- package/agent/scripts/acp.package-list.sh +52 -4
- package/agent/scripts/acp.package-remove.sh +77 -1
- package/agent/scripts/acp.package-search.sh +2 -2
- package/agent/scripts/acp.package-update.sh +91 -12
- package/agent/scripts/acp.package-validate.sh +136 -1
- package/agent/scripts/acp.project-info.sh +34 -11
- package/agent/scripts/acp.project-list.sh +4 -0
- package/agent/scripts/acp.project-update.sh +66 -19
- package/agent/scripts/acp.projects-restore.sh +170 -0
- package/agent/scripts/acp.projects-sync.sh +155 -0
- package/agent/scripts/acp.sessions.sh +725 -0
- package/agent/scripts/acp.version-update.sh +21 -3
- package/agent/scripts/acp.yaml-parser.sh +20 -6
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-203-create-search-by-tool.md +143 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-204-add-new-filters-existing-tools.md +77 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-205-add-feel-fields-create-update.md +137 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-206-add-byproperty-bysignificance-modes.md +135 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-207-add-emotional-composites-search-results.md +88 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-208-add-bybroad-byrandom-modes.md +115 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-209-create-ghost-memory-tools.md +192 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-210-create-get-core-tool.md +203 -0
- package/agent/tasks/milestone-19-new-search-ghost-tools/task-211-create-search-space-by-tool.md +182 -0
- package/agent/tasks/task-1-{title}.template.md +19 -0
- package/agent/tasks/unassigned/bug-report-remember-core-e2e-findings.md +99 -0
- package/dist/e2e-helpers.d.ts +26 -0
- package/dist/ghost-persona.e2e.d.ts +8 -0
- package/dist/memory-crud.e2e.d.ts +8 -0
- package/dist/preferences.e2e.d.ts +8 -0
- package/dist/relationships.e2e.d.ts +8 -0
- package/dist/search-modes.e2e.d.ts +8 -0
- package/dist/server-factory.js +2158 -45
- package/dist/server.js +1403 -44
- package/dist/shared-spaces.e2e.d.ts +8 -0
- package/dist/tools/create-ghost-memory.d.ts +70 -0
- package/dist/tools/create-memory.d.ts +175 -0
- package/dist/tools/get-core.d.ts +28 -0
- package/dist/tools/get-core.spec.d.ts +2 -0
- package/dist/tools/ghost-tools.spec.d.ts +2 -0
- package/dist/tools/query-ghost-memory.d.ts +34 -0
- package/dist/tools/query-memory.d.ts +4 -0
- package/dist/tools/search-by.d.ts +147 -0
- package/dist/tools/search-by.spec.d.ts +2 -0
- package/dist/tools/search-ghost-memory-by.d.ts +54 -0
- package/dist/tools/search-ghost-memory.d.ts +53 -0
- package/dist/tools/search-memory.d.ts +19 -0
- package/dist/tools/search-space-by.d.ts +78 -0
- package/dist/tools/search-space-by.spec.d.ts +2 -0
- package/dist/tools/search-space.d.ts +2 -0
- package/dist/tools/update-ghost-memory.d.ts +51 -0
- package/dist/tools/update-memory.d.ts +175 -0
- package/jest.e2e.config.js +11 -0
- package/package.json +2 -2
- package/src/e2e-helpers.ts +86 -0
- package/src/ghost-persona.e2e.ts +215 -0
- package/src/memory-crud.e2e.ts +203 -0
- package/src/preferences.e2e.ts +88 -0
- package/src/relationships.e2e.ts +156 -0
- package/src/search-modes.e2e.ts +184 -0
- package/src/server-factory.ts +56 -0
- package/src/shared-spaces.e2e.ts +204 -0
- package/src/tools/create-ghost-memory.ts +103 -0
- package/src/tools/create-memory.ts +45 -1
- package/src/tools/get-core.spec.ts +223 -0
- package/src/tools/get-core.ts +109 -0
- package/src/tools/ghost-tools.spec.ts +361 -0
- package/src/tools/query-ghost-memory.ts +63 -0
- package/src/tools/query-memory.ts +4 -0
- package/src/tools/search-by.spec.ts +325 -0
- package/src/tools/search-by.ts +298 -0
- package/src/tools/search-ghost-memory-by.ts +80 -0
- package/src/tools/search-ghost-memory.ts +73 -0
- package/src/tools/search-memory.ts +23 -0
- package/src/tools/search-space-by.spec.ts +289 -0
- package/src/tools/search-space-by.ts +173 -0
- package/src/tools/search-space.ts +20 -1
- package/src/tools/update-ghost-memory.ts +86 -0
- package/src/tools/update-memory.ts +45 -1
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# acp.sessions.sh - Global session tracking for concurrent multi-project agent work
|
|
3
|
+
# Part of Agent Context Protocol (ACP)
|
|
4
|
+
# Usage: ./acp.sessions.sh <subcommand> [options]
|
|
5
|
+
#
|
|
6
|
+
# Subcommands:
|
|
7
|
+
# register Register a new session
|
|
8
|
+
# deregister Remove a session
|
|
9
|
+
# list List active sessions
|
|
10
|
+
# clean Remove stale sessions
|
|
11
|
+
# heartbeat Update session activity
|
|
12
|
+
# count Output count of active sessions
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
# Get script directory
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
|
|
19
|
+
# Source common utilities
|
|
20
|
+
source "${SCRIPT_DIR}/acp.common.sh"
|
|
21
|
+
|
|
22
|
+
# Source YAML parser
|
|
23
|
+
source_yaml_parser
|
|
24
|
+
|
|
25
|
+
# Sessions file location
|
|
26
|
+
SESSIONS_FILE="${HOME}/.acp/sessions.yaml"
|
|
27
|
+
SESSIONS_TEMPLATE="${SCRIPT_DIR}/../sessions.template.yaml"
|
|
28
|
+
|
|
29
|
+
# Stale thresholds (in seconds)
|
|
30
|
+
IDLE_THRESHOLD=1800 # 30 minutes
|
|
31
|
+
REMOVE_THRESHOLD=7200 # 2 hours
|
|
32
|
+
|
|
33
|
+
# ============================================================================
|
|
34
|
+
# HELPERS
|
|
35
|
+
# ============================================================================
|
|
36
|
+
|
|
37
|
+
# Generate a unique session ID
|
|
38
|
+
# Falls back to $RANDOM if xxd is unavailable
|
|
39
|
+
generate_session_id() {
|
|
40
|
+
local hex
|
|
41
|
+
if command -v xxd >/dev/null 2>&1; then
|
|
42
|
+
hex=$(head -c 3 /dev/urandom | xxd -p)
|
|
43
|
+
else
|
|
44
|
+
hex=$(printf '%06x' $RANDOM)
|
|
45
|
+
fi
|
|
46
|
+
echo "sess_${hex}"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Ensure sessions.yaml exists, creating from template if needed
|
|
50
|
+
ensure_sessions_file() {
|
|
51
|
+
if [ ! -f "$SESSIONS_FILE" ]; then
|
|
52
|
+
mkdir -p "$(dirname "$SESSIONS_FILE")"
|
|
53
|
+
if [ -f "$SESSIONS_TEMPLATE" ]; then
|
|
54
|
+
cp "$SESSIONS_TEMPLATE" "$SESSIONS_FILE"
|
|
55
|
+
else
|
|
56
|
+
cat > "$SESSIONS_FILE" << 'TMPL'
|
|
57
|
+
# ~/.acp/sessions.yaml
|
|
58
|
+
# Managed by acp.sessions.sh — do not edit manually
|
|
59
|
+
|
|
60
|
+
sessions: []
|
|
61
|
+
|
|
62
|
+
last_updated: null
|
|
63
|
+
TMPL
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Get current timestamp in epoch seconds
|
|
69
|
+
get_epoch() {
|
|
70
|
+
date +%s
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Convert ISO timestamp to epoch seconds
|
|
74
|
+
iso_to_epoch() {
|
|
75
|
+
local ts="$1"
|
|
76
|
+
if [ -z "$ts" ] || [ "$ts" = "null" ]; then
|
|
77
|
+
echo "0"
|
|
78
|
+
return
|
|
79
|
+
fi
|
|
80
|
+
date -d "$ts" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$ts" +%s 2>/dev/null || echo "0"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Format seconds as relative time (e.g., "2m ago", "1h ago")
|
|
84
|
+
format_relative_time() {
|
|
85
|
+
local seconds="$1"
|
|
86
|
+
if [ "$seconds" -lt 60 ]; then
|
|
87
|
+
echo "now"
|
|
88
|
+
elif [ "$seconds" -lt 3600 ]; then
|
|
89
|
+
echo "$((seconds / 60))m ago"
|
|
90
|
+
elif [ "$seconds" -lt 86400 ]; then
|
|
91
|
+
echo "$((seconds / 3600))h ago"
|
|
92
|
+
else
|
|
93
|
+
echo "$((seconds / 86400))d ago"
|
|
94
|
+
fi
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Get session count from parsed YAML
|
|
98
|
+
get_session_count() {
|
|
99
|
+
local count
|
|
100
|
+
count=$(yaml_get_array "$SESSIONS_FILE" "sessions" 2>/dev/null || echo "0")
|
|
101
|
+
echo "$count"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Write sessions.yaml directly from session data arrays
|
|
105
|
+
# This bypasses yaml_delete limitations with array indices
|
|
106
|
+
# Args: space-separated list of indices to EXCLUDE
|
|
107
|
+
write_sessions_excluding() {
|
|
108
|
+
local exclude_indices="$1"
|
|
109
|
+
local now
|
|
110
|
+
now=$(get_timestamp)
|
|
111
|
+
|
|
112
|
+
# Read current session count (AST must already be loaded)
|
|
113
|
+
local count
|
|
114
|
+
count=$(yaml_get_array "$SESSIONS_FILE" "sessions" 2>/dev/null || echo "0")
|
|
115
|
+
|
|
116
|
+
# Build output file
|
|
117
|
+
local tmp_file="${SESSIONS_FILE}.tmp"
|
|
118
|
+
{
|
|
119
|
+
echo "# ~/.acp/sessions.yaml"
|
|
120
|
+
echo "# Managed by acp.sessions.sh — do not edit manually"
|
|
121
|
+
echo ""
|
|
122
|
+
|
|
123
|
+
local has_sessions=false
|
|
124
|
+
local i=0
|
|
125
|
+
while [ "$i" -lt "$count" ]; do
|
|
126
|
+
# Check if this index should be excluded
|
|
127
|
+
local skip=false
|
|
128
|
+
for ex in $exclude_indices; do
|
|
129
|
+
if [ "$ex" = "$i" ]; then
|
|
130
|
+
skip=true
|
|
131
|
+
break
|
|
132
|
+
fi
|
|
133
|
+
done
|
|
134
|
+
|
|
135
|
+
if [ "$skip" = "false" ]; then
|
|
136
|
+
if [ "$has_sessions" = "false" ]; then
|
|
137
|
+
echo "sessions:"
|
|
138
|
+
has_sessions=true
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
local id proj desc started last_activity status milestone task pid terminal remote_url
|
|
142
|
+
id=$(yaml_query ".sessions[${i}].id" 2>/dev/null || echo "")
|
|
143
|
+
proj=$(yaml_query ".sessions[${i}].project" 2>/dev/null || echo "")
|
|
144
|
+
desc=$(yaml_query ".sessions[${i}].description" 2>/dev/null || echo "")
|
|
145
|
+
started=$(yaml_query ".sessions[${i}].started" 2>/dev/null || echo "")
|
|
146
|
+
last_activity=$(yaml_query ".sessions[${i}].last_activity" 2>/dev/null || echo "")
|
|
147
|
+
status=$(yaml_query ".sessions[${i}].status" 2>/dev/null || echo "active")
|
|
148
|
+
milestone=$(yaml_query ".sessions[${i}].current_milestone" 2>/dev/null || echo "")
|
|
149
|
+
task=$(yaml_query ".sessions[${i}].current_task" 2>/dev/null || echo "")
|
|
150
|
+
pid=$(yaml_query ".sessions[${i}].pid" 2>/dev/null || echo "")
|
|
151
|
+
terminal=$(yaml_query ".sessions[${i}].terminal" 2>/dev/null || echo "")
|
|
152
|
+
remote_url=$(yaml_query ".sessions[${i}].remote_url" 2>/dev/null || echo "")
|
|
153
|
+
|
|
154
|
+
# Normalize null values
|
|
155
|
+
[ "$milestone" = "null" ] && milestone=""
|
|
156
|
+
[ "$task" = "null" ] && task=""
|
|
157
|
+
[ "$remote_url" = "null" ] && remote_url=""
|
|
158
|
+
[ "$desc" = "null" ] && desc=""
|
|
159
|
+
|
|
160
|
+
echo " - id: ${id}"
|
|
161
|
+
echo " project: ${proj}"
|
|
162
|
+
echo " description: ${desc}"
|
|
163
|
+
echo " started: ${started}"
|
|
164
|
+
echo " last_activity: ${last_activity}"
|
|
165
|
+
echo " status: ${status}"
|
|
166
|
+
echo " current_milestone: ${milestone}"
|
|
167
|
+
echo " current_task: ${task}"
|
|
168
|
+
echo " pid: ${pid}"
|
|
169
|
+
echo " terminal: ${terminal}"
|
|
170
|
+
echo " remote_url: ${remote_url}"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
i=$((i + 1))
|
|
174
|
+
done
|
|
175
|
+
|
|
176
|
+
if [ "$has_sessions" = "false" ]; then
|
|
177
|
+
echo "sessions: []"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
echo ""
|
|
181
|
+
echo "last_updated: ${now}"
|
|
182
|
+
} > "$tmp_file"
|
|
183
|
+
|
|
184
|
+
mv "$tmp_file" "$SESSIONS_FILE"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# ============================================================================
|
|
188
|
+
# CLEAN SUBCOMMAND
|
|
189
|
+
# ============================================================================
|
|
190
|
+
|
|
191
|
+
# Remove stale sessions (dead PID or timed out)
|
|
192
|
+
do_clean() {
|
|
193
|
+
local verbose="${1:-false}"
|
|
194
|
+
ensure_sessions_file
|
|
195
|
+
|
|
196
|
+
yaml_parse "$SESSIONS_FILE" || return 1
|
|
197
|
+
|
|
198
|
+
local count
|
|
199
|
+
count=$(get_session_count)
|
|
200
|
+
|
|
201
|
+
if [ "$count" = "0" ] || [ -z "$count" ]; then
|
|
202
|
+
if [ "$verbose" = "true" ]; then
|
|
203
|
+
echo "No sessions to clean."
|
|
204
|
+
fi
|
|
205
|
+
return 0
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
local cleaned=0
|
|
209
|
+
local now_epoch
|
|
210
|
+
now_epoch=$(get_epoch)
|
|
211
|
+
local indices_to_remove=""
|
|
212
|
+
local idle_indices=""
|
|
213
|
+
|
|
214
|
+
# Check each session
|
|
215
|
+
local i=0
|
|
216
|
+
while [ "$i" -lt "$count" ]; do
|
|
217
|
+
local pid last_activity status
|
|
218
|
+
|
|
219
|
+
pid=$(yaml_query ".sessions[${i}].pid" 2>/dev/null || echo "")
|
|
220
|
+
last_activity=$(yaml_query ".sessions[${i}].last_activity" 2>/dev/null || echo "")
|
|
221
|
+
status=$(yaml_query ".sessions[${i}].status" 2>/dev/null || echo "active")
|
|
222
|
+
|
|
223
|
+
local should_remove=false
|
|
224
|
+
|
|
225
|
+
# Check 1: PID is dead
|
|
226
|
+
if [ -n "$pid" ] && [ "$pid" != "null" ] && [ "$pid" != "0" ]; then
|
|
227
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
228
|
+
should_remove=true
|
|
229
|
+
if [ "$verbose" = "true" ]; then
|
|
230
|
+
local proj sid
|
|
231
|
+
proj=$(yaml_query ".sessions[${i}].project" 2>/dev/null || echo "unknown")
|
|
232
|
+
sid=$(yaml_query ".sessions[${i}].id" 2>/dev/null || echo "unknown")
|
|
233
|
+
echo " ${sid} ${proj} (PID ${pid} not running)"
|
|
234
|
+
fi
|
|
235
|
+
fi
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# Check 2: Timeout
|
|
239
|
+
if [ "$should_remove" = "false" ] && [ -n "$last_activity" ] && [ "$last_activity" != "null" ]; then
|
|
240
|
+
local activity_epoch elapsed
|
|
241
|
+
activity_epoch=$(iso_to_epoch "$last_activity")
|
|
242
|
+
elapsed=$((now_epoch - activity_epoch))
|
|
243
|
+
|
|
244
|
+
if [ "$elapsed" -gt "$REMOVE_THRESHOLD" ]; then
|
|
245
|
+
should_remove=true
|
|
246
|
+
if [ "$verbose" = "true" ]; then
|
|
247
|
+
local proj sid
|
|
248
|
+
proj=$(yaml_query ".sessions[${i}].project" 2>/dev/null || echo "unknown")
|
|
249
|
+
sid=$(yaml_query ".sessions[${i}].id" 2>/dev/null || echo "unknown")
|
|
250
|
+
echo " ${sid} ${proj} (inactive for $(format_relative_time "$elapsed"))"
|
|
251
|
+
fi
|
|
252
|
+
elif [ "$elapsed" -gt "$IDLE_THRESHOLD" ]; then
|
|
253
|
+
idle_indices="${idle_indices} ${i}"
|
|
254
|
+
fi
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
if [ "$should_remove" = "true" ]; then
|
|
258
|
+
indices_to_remove="${indices_to_remove} ${i}"
|
|
259
|
+
cleaned=$((cleaned + 1))
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
i=$((i + 1))
|
|
263
|
+
done
|
|
264
|
+
|
|
265
|
+
# Mark idle sessions (update in-place via yaml_set before rebuild)
|
|
266
|
+
for idx in $idle_indices; do
|
|
267
|
+
yaml_set ".sessions[${idx}].status" "idle"
|
|
268
|
+
done
|
|
269
|
+
|
|
270
|
+
# Remove flagged sessions by rebuilding file
|
|
271
|
+
if [ -n "$indices_to_remove" ]; then
|
|
272
|
+
write_sessions_excluding "$indices_to_remove"
|
|
273
|
+
elif [ -n "$idle_indices" ]; then
|
|
274
|
+
# If we only marked idle (no removals), persist the status changes
|
|
275
|
+
yaml_set ".last_updated" "$(get_timestamp)"
|
|
276
|
+
yaml_write "$SESSIONS_FILE"
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
if [ "$verbose" = "true" ]; then
|
|
280
|
+
if [ "$cleaned" -gt 0 ]; then
|
|
281
|
+
echo ""
|
|
282
|
+
local remaining=$((count - cleaned))
|
|
283
|
+
echo "Active sessions remaining: ${remaining}"
|
|
284
|
+
else
|
|
285
|
+
echo "No stale sessions found."
|
|
286
|
+
fi
|
|
287
|
+
else
|
|
288
|
+
echo "$cleaned"
|
|
289
|
+
fi
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# ============================================================================
|
|
293
|
+
# REGISTER SUBCOMMAND
|
|
294
|
+
# ============================================================================
|
|
295
|
+
|
|
296
|
+
do_register() {
|
|
297
|
+
local project="" description="" remote_url="" explicit_pid=""
|
|
298
|
+
|
|
299
|
+
while [ $# -gt 0 ]; do
|
|
300
|
+
case "$1" in
|
|
301
|
+
--project) project="$2"; shift 2 ;;
|
|
302
|
+
--description) description="$2"; shift 2 ;;
|
|
303
|
+
--remote-url) remote_url="$2"; shift 2 ;;
|
|
304
|
+
--pid) explicit_pid="$2"; shift 2 ;;
|
|
305
|
+
*) shift ;;
|
|
306
|
+
esac
|
|
307
|
+
done
|
|
308
|
+
|
|
309
|
+
if [ -z "$project" ]; then
|
|
310
|
+
echo "Error: --project is required" >&2
|
|
311
|
+
echo "Usage: $0 register --project <name> [--description <desc>] [--remote-url <url>] [--pid <pid>]" >&2
|
|
312
|
+
return 1
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
ensure_sessions_file
|
|
316
|
+
|
|
317
|
+
# Clean stale sessions first
|
|
318
|
+
do_clean "false" >/dev/null 2>&1 || true
|
|
319
|
+
|
|
320
|
+
# Re-parse after clean
|
|
321
|
+
yaml_parse "$SESSIONS_FILE" || return 1
|
|
322
|
+
|
|
323
|
+
local session_id
|
|
324
|
+
session_id=$(generate_session_id)
|
|
325
|
+
|
|
326
|
+
local pid="${explicit_pid:-$PPID}"
|
|
327
|
+
local terminal
|
|
328
|
+
terminal=$(tty 2>/dev/null || echo "unknown")
|
|
329
|
+
local now
|
|
330
|
+
now=$(get_timestamp)
|
|
331
|
+
|
|
332
|
+
# Auto-infer description if not provided
|
|
333
|
+
if [ -z "$description" ]; then
|
|
334
|
+
description="Working on ${project}"
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# Append new session object to sessions array
|
|
338
|
+
local node_id
|
|
339
|
+
node_id=$(yaml_array_append_object ".sessions")
|
|
340
|
+
yaml_object_set "$node_id" "id" "$session_id" >/dev/null
|
|
341
|
+
yaml_object_set "$node_id" "project" "$project" >/dev/null
|
|
342
|
+
yaml_object_set "$node_id" "description" "$description" >/dev/null
|
|
343
|
+
yaml_object_set "$node_id" "started" "$now" >/dev/null
|
|
344
|
+
yaml_object_set "$node_id" "last_activity" "$now" >/dev/null
|
|
345
|
+
yaml_object_set "$node_id" "status" "active" >/dev/null
|
|
346
|
+
yaml_object_set "$node_id" "current_milestone" "" >/dev/null
|
|
347
|
+
yaml_object_set "$node_id" "current_task" "" >/dev/null
|
|
348
|
+
yaml_object_set "$node_id" "pid" "$pid" >/dev/null
|
|
349
|
+
yaml_object_set "$node_id" "terminal" "$terminal" >/dev/null
|
|
350
|
+
yaml_object_set "$node_id" "remote_url" "${remote_url:-}" >/dev/null
|
|
351
|
+
|
|
352
|
+
yaml_set ".last_updated" "$now"
|
|
353
|
+
yaml_write "$SESSIONS_FILE"
|
|
354
|
+
|
|
355
|
+
echo "Session ${session_id} registered for ${project}."
|
|
356
|
+
echo "$session_id"
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# ============================================================================
|
|
360
|
+
# DEREGISTER SUBCOMMAND
|
|
361
|
+
# ============================================================================
|
|
362
|
+
|
|
363
|
+
do_deregister() {
|
|
364
|
+
local target_id=""
|
|
365
|
+
|
|
366
|
+
while [ $# -gt 0 ]; do
|
|
367
|
+
case "$1" in
|
|
368
|
+
--id) target_id="$2"; shift 2 ;;
|
|
369
|
+
*) shift ;;
|
|
370
|
+
esac
|
|
371
|
+
done
|
|
372
|
+
|
|
373
|
+
ensure_sessions_file
|
|
374
|
+
yaml_parse "$SESSIONS_FILE" || return 1
|
|
375
|
+
|
|
376
|
+
local count
|
|
377
|
+
count=$(get_session_count)
|
|
378
|
+
|
|
379
|
+
if [ "$count" = "0" ] || [ -z "$count" ]; then
|
|
380
|
+
echo "No active sessions."
|
|
381
|
+
return 0
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
# Auto-detect by PID if no --id
|
|
385
|
+
if [ -z "$target_id" ]; then
|
|
386
|
+
local current_pid=$PPID
|
|
387
|
+
local i=0
|
|
388
|
+
while [ "$i" -lt "$count" ]; do
|
|
389
|
+
local pid
|
|
390
|
+
pid=$(yaml_query ".sessions[${i}].pid" 2>/dev/null || echo "")
|
|
391
|
+
if [ "$pid" = "$current_pid" ]; then
|
|
392
|
+
target_id=$(yaml_query ".sessions[${i}].id" 2>/dev/null || echo "")
|
|
393
|
+
break
|
|
394
|
+
fi
|
|
395
|
+
i=$((i + 1))
|
|
396
|
+
done
|
|
397
|
+
fi
|
|
398
|
+
|
|
399
|
+
if [ -z "$target_id" ]; then
|
|
400
|
+
echo "No session found for current process (PPID: $PPID)."
|
|
401
|
+
return 1
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
# Find the session index by ID
|
|
405
|
+
local found_idx=""
|
|
406
|
+
local i=0
|
|
407
|
+
while [ "$i" -lt "$count" ]; do
|
|
408
|
+
local sid
|
|
409
|
+
sid=$(yaml_query ".sessions[${i}].id" 2>/dev/null || echo "")
|
|
410
|
+
if [ "$sid" = "$target_id" ]; then
|
|
411
|
+
found_idx="$i"
|
|
412
|
+
break
|
|
413
|
+
fi
|
|
414
|
+
i=$((i + 1))
|
|
415
|
+
done
|
|
416
|
+
|
|
417
|
+
if [ -n "$found_idx" ]; then
|
|
418
|
+
write_sessions_excluding "$found_idx"
|
|
419
|
+
local remaining=$((count - 1))
|
|
420
|
+
echo "Session ${target_id} deregistered."
|
|
421
|
+
echo "Active sessions remaining: ${remaining}"
|
|
422
|
+
else
|
|
423
|
+
echo "Session ${target_id} not found."
|
|
424
|
+
return 1
|
|
425
|
+
fi
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# ============================================================================
|
|
429
|
+
# LIST SUBCOMMAND
|
|
430
|
+
# ============================================================================
|
|
431
|
+
|
|
432
|
+
do_list() {
|
|
433
|
+
local filter_project=""
|
|
434
|
+
|
|
435
|
+
while [ $# -gt 0 ]; do
|
|
436
|
+
case "$1" in
|
|
437
|
+
--project) filter_project="$2"; shift 2 ;;
|
|
438
|
+
*) shift ;;
|
|
439
|
+
esac
|
|
440
|
+
done
|
|
441
|
+
|
|
442
|
+
ensure_sessions_file
|
|
443
|
+
|
|
444
|
+
# Clean stale sessions first
|
|
445
|
+
do_clean "false" >/dev/null 2>&1 || true
|
|
446
|
+
|
|
447
|
+
# Re-parse after clean
|
|
448
|
+
yaml_parse "$SESSIONS_FILE" || return 1
|
|
449
|
+
|
|
450
|
+
local count
|
|
451
|
+
count=$(get_session_count)
|
|
452
|
+
|
|
453
|
+
if [ "$count" = "0" ] || [ -z "$count" ]; then
|
|
454
|
+
echo "No active sessions."
|
|
455
|
+
return 0
|
|
456
|
+
fi
|
|
457
|
+
|
|
458
|
+
local now_epoch
|
|
459
|
+
now_epoch=$(get_epoch)
|
|
460
|
+
local current_pid=$PPID
|
|
461
|
+
|
|
462
|
+
# Count matching sessions
|
|
463
|
+
local total_matching=0
|
|
464
|
+
local i=0
|
|
465
|
+
while [ "$i" -lt "$count" ]; do
|
|
466
|
+
local proj
|
|
467
|
+
proj=$(yaml_query ".sessions[${i}].project" 2>/dev/null || echo "")
|
|
468
|
+
if [ -z "$filter_project" ] || [ "$proj" = "$filter_project" ]; then
|
|
469
|
+
total_matching=$((total_matching + 1))
|
|
470
|
+
fi
|
|
471
|
+
i=$((i + 1))
|
|
472
|
+
done
|
|
473
|
+
|
|
474
|
+
if [ "$total_matching" = "0" ]; then
|
|
475
|
+
if [ -n "$filter_project" ]; then
|
|
476
|
+
echo "No active sessions for project: ${filter_project}"
|
|
477
|
+
else
|
|
478
|
+
echo "No active sessions."
|
|
479
|
+
fi
|
|
480
|
+
return 0
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
echo "Active Sessions (${total_matching}):"
|
|
484
|
+
echo ""
|
|
485
|
+
|
|
486
|
+
i=0
|
|
487
|
+
while [ "$i" -lt "$count" ]; do
|
|
488
|
+
local proj sid desc started last_activity pid status
|
|
489
|
+
|
|
490
|
+
proj=$(yaml_query ".sessions[${i}].project" 2>/dev/null || echo "")
|
|
491
|
+
|
|
492
|
+
if [ -n "$filter_project" ] && [ "$proj" != "$filter_project" ]; then
|
|
493
|
+
i=$((i + 1))
|
|
494
|
+
continue
|
|
495
|
+
fi
|
|
496
|
+
|
|
497
|
+
sid=$(yaml_query ".sessions[${i}].id" 2>/dev/null || echo "")
|
|
498
|
+
desc=$(yaml_query ".sessions[${i}].description" 2>/dev/null || echo "")
|
|
499
|
+
started=$(yaml_query ".sessions[${i}].started" 2>/dev/null || echo "")
|
|
500
|
+
last_activity=$(yaml_query ".sessions[${i}].last_activity" 2>/dev/null || echo "")
|
|
501
|
+
pid=$(yaml_query ".sessions[${i}].pid" 2>/dev/null || echo "")
|
|
502
|
+
status=$(yaml_query ".sessions[${i}].status" 2>/dev/null || echo "active")
|
|
503
|
+
|
|
504
|
+
local started_ago="" active_ago=""
|
|
505
|
+
if [ -n "$started" ] && [ "$started" != "null" ]; then
|
|
506
|
+
local started_secs=$((now_epoch - $(iso_to_epoch "$started")))
|
|
507
|
+
started_ago="Started $(format_relative_time "$started_secs")"
|
|
508
|
+
fi
|
|
509
|
+
if [ -n "$last_activity" ] && [ "$last_activity" != "null" ]; then
|
|
510
|
+
local active_secs=$((now_epoch - $(iso_to_epoch "$last_activity")))
|
|
511
|
+
active_ago="last active $(format_relative_time "$active_secs")"
|
|
512
|
+
fi
|
|
513
|
+
|
|
514
|
+
local indicator=""
|
|
515
|
+
if [ "$pid" = "$current_pid" ]; then
|
|
516
|
+
indicator=" (this session)"
|
|
517
|
+
fi
|
|
518
|
+
|
|
519
|
+
local status_marker=""
|
|
520
|
+
if [ "$status" = "idle" ]; then
|
|
521
|
+
status_marker=" [idle]"
|
|
522
|
+
fi
|
|
523
|
+
|
|
524
|
+
echo " ${sid} ${proj}${indicator}${status_marker}"
|
|
525
|
+
if [ -n "$desc" ] && [ "$desc" != "null" ]; then
|
|
526
|
+
echo " ${desc}"
|
|
527
|
+
fi
|
|
528
|
+
if [ -n "$started_ago" ] || [ -n "$active_ago" ]; then
|
|
529
|
+
local time_line=""
|
|
530
|
+
[ -n "$started_ago" ] && time_line="$started_ago"
|
|
531
|
+
if [ -n "$active_ago" ]; then
|
|
532
|
+
[ -n "$time_line" ] && time_line="${time_line}, "
|
|
533
|
+
time_line="${time_line}${active_ago}"
|
|
534
|
+
fi
|
|
535
|
+
echo " ${time_line}"
|
|
536
|
+
fi
|
|
537
|
+
echo ""
|
|
538
|
+
|
|
539
|
+
i=$((i + 1))
|
|
540
|
+
done
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
# ============================================================================
|
|
544
|
+
# HEARTBEAT SUBCOMMAND
|
|
545
|
+
# ============================================================================
|
|
546
|
+
|
|
547
|
+
do_heartbeat() {
|
|
548
|
+
local target_id="" new_task="" new_description=""
|
|
549
|
+
|
|
550
|
+
while [ $# -gt 0 ]; do
|
|
551
|
+
case "$1" in
|
|
552
|
+
--id) target_id="$2"; shift 2 ;;
|
|
553
|
+
--task) new_task="$2"; shift 2 ;;
|
|
554
|
+
--description) new_description="$2"; shift 2 ;;
|
|
555
|
+
*) shift ;;
|
|
556
|
+
esac
|
|
557
|
+
done
|
|
558
|
+
|
|
559
|
+
ensure_sessions_file
|
|
560
|
+
yaml_parse "$SESSIONS_FILE" || return 1
|
|
561
|
+
|
|
562
|
+
local count
|
|
563
|
+
count=$(get_session_count)
|
|
564
|
+
|
|
565
|
+
if [ "$count" = "0" ] || [ -z "$count" ]; then
|
|
566
|
+
echo "No active sessions."
|
|
567
|
+
return 1
|
|
568
|
+
fi
|
|
569
|
+
|
|
570
|
+
# Auto-detect by PID if no --id
|
|
571
|
+
if [ -z "$target_id" ]; then
|
|
572
|
+
local current_pid=$PPID
|
|
573
|
+
local i=0
|
|
574
|
+
while [ "$i" -lt "$count" ]; do
|
|
575
|
+
local pid
|
|
576
|
+
pid=$(yaml_query ".sessions[${i}].pid" 2>/dev/null || echo "")
|
|
577
|
+
if [ "$pid" = "$current_pid" ]; then
|
|
578
|
+
target_id=$(yaml_query ".sessions[${i}].id" 2>/dev/null || echo "")
|
|
579
|
+
break
|
|
580
|
+
fi
|
|
581
|
+
i=$((i + 1))
|
|
582
|
+
done
|
|
583
|
+
fi
|
|
584
|
+
|
|
585
|
+
if [ -z "$target_id" ]; then
|
|
586
|
+
echo "No session found for current process (PPID: $PPID)."
|
|
587
|
+
return 1
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
# Find the session and rebuild with updated fields
|
|
591
|
+
local now
|
|
592
|
+
now=$(get_timestamp)
|
|
593
|
+
local found=false
|
|
594
|
+
|
|
595
|
+
local tmp_file="${SESSIONS_FILE}.tmp"
|
|
596
|
+
{
|
|
597
|
+
echo "# ~/.acp/sessions.yaml"
|
|
598
|
+
echo "# Managed by acp.sessions.sh — do not edit manually"
|
|
599
|
+
echo ""
|
|
600
|
+
echo "sessions:"
|
|
601
|
+
|
|
602
|
+
local i=0
|
|
603
|
+
while [ "$i" -lt "$count" ]; do
|
|
604
|
+
local id proj desc started last_activity status milestone task pid terminal remote_url
|
|
605
|
+
id=$(yaml_query ".sessions[${i}].id" 2>/dev/null || echo "")
|
|
606
|
+
proj=$(yaml_query ".sessions[${i}].project" 2>/dev/null || echo "")
|
|
607
|
+
desc=$(yaml_query ".sessions[${i}].description" 2>/dev/null || echo "")
|
|
608
|
+
started=$(yaml_query ".sessions[${i}].started" 2>/dev/null || echo "")
|
|
609
|
+
last_activity=$(yaml_query ".sessions[${i}].last_activity" 2>/dev/null || echo "")
|
|
610
|
+
status=$(yaml_query ".sessions[${i}].status" 2>/dev/null || echo "active")
|
|
611
|
+
milestone=$(yaml_query ".sessions[${i}].current_milestone" 2>/dev/null || echo "")
|
|
612
|
+
task=$(yaml_query ".sessions[${i}].current_task" 2>/dev/null || echo "")
|
|
613
|
+
pid=$(yaml_query ".sessions[${i}].pid" 2>/dev/null || echo "")
|
|
614
|
+
terminal=$(yaml_query ".sessions[${i}].terminal" 2>/dev/null || echo "")
|
|
615
|
+
remote_url=$(yaml_query ".sessions[${i}].remote_url" 2>/dev/null || echo "")
|
|
616
|
+
|
|
617
|
+
# Normalize nulls
|
|
618
|
+
[ "$milestone" = "null" ] && milestone=""
|
|
619
|
+
[ "$task" = "null" ] && task=""
|
|
620
|
+
[ "$remote_url" = "null" ] && remote_url=""
|
|
621
|
+
[ "$desc" = "null" ] && desc=""
|
|
622
|
+
|
|
623
|
+
# Apply updates to the target session
|
|
624
|
+
if [ "$id" = "$target_id" ]; then
|
|
625
|
+
found=true
|
|
626
|
+
last_activity="$now"
|
|
627
|
+
status="active"
|
|
628
|
+
[ -n "$new_task" ] && task="$new_task"
|
|
629
|
+
[ -n "$new_description" ] && desc="$new_description"
|
|
630
|
+
fi
|
|
631
|
+
|
|
632
|
+
echo " - id: ${id}"
|
|
633
|
+
echo " project: ${proj}"
|
|
634
|
+
echo " description: ${desc}"
|
|
635
|
+
echo " started: ${started}"
|
|
636
|
+
echo " last_activity: ${last_activity}"
|
|
637
|
+
echo " status: ${status}"
|
|
638
|
+
echo " current_milestone: ${milestone}"
|
|
639
|
+
echo " current_task: ${task}"
|
|
640
|
+
echo " pid: ${pid}"
|
|
641
|
+
echo " terminal: ${terminal}"
|
|
642
|
+
echo " remote_url: ${remote_url}"
|
|
643
|
+
|
|
644
|
+
i=$((i + 1))
|
|
645
|
+
done
|
|
646
|
+
|
|
647
|
+
echo ""
|
|
648
|
+
echo "last_updated: ${now}"
|
|
649
|
+
} > "$tmp_file"
|
|
650
|
+
|
|
651
|
+
if [ "$found" = "true" ]; then
|
|
652
|
+
mv "$tmp_file" "$SESSIONS_FILE"
|
|
653
|
+
echo "Session ${target_id} updated."
|
|
654
|
+
return 0
|
|
655
|
+
else
|
|
656
|
+
rm -f "$tmp_file"
|
|
657
|
+
echo "Session ${target_id} not found."
|
|
658
|
+
return 1
|
|
659
|
+
fi
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
# ============================================================================
|
|
663
|
+
# COUNT SUBCOMMAND
|
|
664
|
+
# ============================================================================
|
|
665
|
+
|
|
666
|
+
do_count() {
|
|
667
|
+
ensure_sessions_file
|
|
668
|
+
|
|
669
|
+
# Clean stale sessions first
|
|
670
|
+
do_clean "false" >/dev/null 2>&1 || true
|
|
671
|
+
|
|
672
|
+
# Re-parse after clean
|
|
673
|
+
yaml_parse "$SESSIONS_FILE" || return 1
|
|
674
|
+
|
|
675
|
+
local count
|
|
676
|
+
count=$(get_session_count)
|
|
677
|
+
echo "${count:-0}"
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
# ============================================================================
|
|
681
|
+
# MAIN DISPATCH
|
|
682
|
+
# ============================================================================
|
|
683
|
+
|
|
684
|
+
main() {
|
|
685
|
+
local subcommand="${1:-}"
|
|
686
|
+
|
|
687
|
+
if [ -z "$subcommand" ]; then
|
|
688
|
+
echo "Usage: $0 <subcommand> [options]"
|
|
689
|
+
echo ""
|
|
690
|
+
echo "Subcommands:"
|
|
691
|
+
echo " register Register a new session"
|
|
692
|
+
echo " deregister Remove a session"
|
|
693
|
+
echo " list List active sessions"
|
|
694
|
+
echo " clean Remove stale sessions"
|
|
695
|
+
echo " heartbeat Update session activity"
|
|
696
|
+
echo " count Output count of active sessions"
|
|
697
|
+
echo ""
|
|
698
|
+
echo "Options:"
|
|
699
|
+
echo " --project <name> Project name (register, list)"
|
|
700
|
+
echo " --description <desc> Session description (register, heartbeat)"
|
|
701
|
+
echo " --remote-url <url> Remote session URL (register)"
|
|
702
|
+
echo " --id <session-id> Target session (deregister, heartbeat)"
|
|
703
|
+
echo " --task <task-id> Current task (heartbeat)"
|
|
704
|
+
echo " --pid <pid> Explicit PID for stale detection (register)"
|
|
705
|
+
return 1
|
|
706
|
+
fi
|
|
707
|
+
|
|
708
|
+
shift
|
|
709
|
+
|
|
710
|
+
case "$subcommand" in
|
|
711
|
+
register) do_register "$@" ;;
|
|
712
|
+
deregister) do_deregister "$@" ;;
|
|
713
|
+
list) do_list "$@" ;;
|
|
714
|
+
clean) do_clean "true" ;;
|
|
715
|
+
heartbeat) do_heartbeat "$@" ;;
|
|
716
|
+
count) do_count ;;
|
|
717
|
+
*)
|
|
718
|
+
echo "Error: Unknown subcommand '${subcommand}'" >&2
|
|
719
|
+
echo "Run '$0' without arguments for usage." >&2
|
|
720
|
+
return 1
|
|
721
|
+
;;
|
|
722
|
+
esac
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
main "$@"
|