opencode-claude-memory 1.2.0 → 1.3.1
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 +30 -10
- package/bin/opencode-memory +360 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,8 @@ Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Clau
|
|
|
24
24
|
Install + enable plugin, then keep using `opencode` as usual.
|
|
25
25
|
- **Local-first, no migration**
|
|
26
26
|
Memory stays as local Markdown files in the same directory Claude Code already uses.
|
|
27
|
+
- **Auto-dream consolidation**
|
|
28
|
+
Periodically runs a background memory consolidation pass (Claude-style auto-dream gating).
|
|
27
29
|
|
|
28
30
|
## 🚀 Quick Start
|
|
29
31
|
|
|
@@ -36,7 +38,7 @@ opencode-memory install # one-time: installs shell hook
|
|
|
36
38
|
|
|
37
39
|
This installs:
|
|
38
40
|
- The **plugin** — memory tools + system prompt injection
|
|
39
|
-
- The `opencode-memory` **CLI** — wraps opencode with
|
|
41
|
+
- The `opencode-memory` **CLI** — wraps opencode with automatic memory extraction + auto-dream consolidation
|
|
40
42
|
- A **shell hook** — defines an `opencode()` function in your `.zshrc`/`.bashrc` that delegates to `opencode-memory`
|
|
41
43
|
|
|
42
44
|
### 2. Configure
|
|
@@ -54,7 +56,7 @@ This installs:
|
|
|
54
56
|
opencode
|
|
55
57
|
```
|
|
56
58
|
|
|
57
|
-
That’s it. Memory extraction runs in the background after each session.
|
|
59
|
+
That’s it. Memory extraction runs in the background after each session, and auto-dream consolidation is checked with time/session gates.
|
|
58
60
|
|
|
59
61
|
To uninstall:
|
|
60
62
|
|
|
@@ -92,8 +94,10 @@ graph LR
|
|
|
92
94
|
B --> C[opencode-memory finds real binary]
|
|
93
95
|
C --> D[Runs opencode normally]
|
|
94
96
|
D --> E[You exit]
|
|
95
|
-
E --> F[
|
|
96
|
-
F --> G[
|
|
97
|
+
E --> F[Extract memories if needed]
|
|
98
|
+
F --> G[Evaluate auto-dream gate]
|
|
99
|
+
G --> H[Consolidate memories if gate passes]
|
|
100
|
+
H --> I[Memories saved to ~/.claude/projects/]
|
|
97
101
|
```
|
|
98
102
|
|
|
99
103
|
The shell hook defines an `opencode()` function that delegates to `opencode-memory`:
|
|
@@ -101,8 +105,11 @@ The shell hook defines an `opencode()` function that delegates to `opencode-memo
|
|
|
101
105
|
1. Shell function intercepts `opencode` command (higher priority than PATH)
|
|
102
106
|
2. `opencode-memory` finds the real `opencode` binary in PATH
|
|
103
107
|
3. Runs it with all your arguments
|
|
104
|
-
4. After you exit,
|
|
105
|
-
5.
|
|
108
|
+
4. After you exit, it checks whether the session already wrote memory files
|
|
109
|
+
5. If needed, it forks the session with a memory extraction prompt
|
|
110
|
+
6. It evaluates the auto-dream gate (default: at least 24h since last consolidation and 5 touched sessions)
|
|
111
|
+
7. If the gate passes, it runs a background consolidation pass to merge/prune memories
|
|
112
|
+
8. Maintenance runs **in the background** unless `OPENCODE_MEMORY_FOREGROUND=1`
|
|
106
113
|
|
|
107
114
|
### Compatibility details
|
|
108
115
|
|
|
@@ -137,22 +144,35 @@ File-based memory is transparent, local-first, easy to inspect/diff/back up, and
|
|
|
137
144
|
|
|
138
145
|
Yes. Set `OPENCODE_MEMORY_EXTRACT=0`.
|
|
139
146
|
|
|
147
|
+
### Can I disable auto-dream?
|
|
148
|
+
|
|
149
|
+
Yes. Set `OPENCODE_MEMORY_AUTODREAM=0`. You can also tune gates with:
|
|
150
|
+
- `OPENCODE_MEMORY_AUTODREAM_MIN_HOURS`
|
|
151
|
+
- `OPENCODE_MEMORY_AUTODREAM_MIN_SESSIONS`
|
|
152
|
+
|
|
140
153
|
## 🔧 Configuration
|
|
141
154
|
|
|
142
155
|
### Environment variables
|
|
143
156
|
|
|
144
|
-
- `OPENCODE_MEMORY_EXTRACT` (default `1`): set `0` to disable
|
|
145
|
-
- `OPENCODE_MEMORY_FOREGROUND` (default `0`): set `1` to run
|
|
157
|
+
- `OPENCODE_MEMORY_EXTRACT` (default `1`): set `0` to disable automatic memory extraction
|
|
158
|
+
- `OPENCODE_MEMORY_FOREGROUND` (default `0`): set `1` to run maintenance in foreground
|
|
146
159
|
- `OPENCODE_MEMORY_MODEL`: override model used for extraction
|
|
147
160
|
- `OPENCODE_MEMORY_AGENT`: override agent used for extraction
|
|
161
|
+
- `OPENCODE_MEMORY_AUTODREAM` (default `1`): set `0` to disable auto-dream consolidation
|
|
162
|
+
- `OPENCODE_MEMORY_AUTODREAM_MIN_HOURS` (default `24`): min hours between consolidation runs
|
|
163
|
+
- `OPENCODE_MEMORY_AUTODREAM_MIN_SESSIONS` (default `5`): min touched sessions since last consolidation
|
|
164
|
+
- `OPENCODE_MEMORY_AUTODREAM_MODEL`: override model used for auto-dream
|
|
165
|
+
- `OPENCODE_MEMORY_AUTODREAM_AGENT`: override agent used for auto-dream
|
|
148
166
|
|
|
149
167
|
### Logs
|
|
150
168
|
|
|
151
|
-
|
|
169
|
+
Logs are written to `$TMPDIR/opencode-memory-logs/`:
|
|
170
|
+
- `extract-*.log`: automatic memory extraction
|
|
171
|
+
- `dream-*.log`: auto-dream consolidation
|
|
152
172
|
|
|
153
173
|
### Concurrency safety
|
|
154
174
|
|
|
155
|
-
|
|
175
|
+
Lock files prevent concurrent extraction/consolidation runs per project root. Stale locks are cleaned up automatically.
|
|
156
176
|
|
|
157
177
|
## 📝 Memory format
|
|
158
178
|
|
package/bin/opencode-memory
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# opencode-memory — Wrapper for OpenCode with
|
|
3
|
+
# opencode-memory — Wrapper for OpenCode with post-session memory maintenance.
|
|
4
4
|
#
|
|
5
5
|
# Installs a shell hook (function) that intercepts the `opencode` command,
|
|
6
|
-
# then wraps the real binary with post-session
|
|
6
|
+
# then wraps the real binary with post-session extraction and auto-dream.
|
|
7
7
|
#
|
|
8
8
|
# Subcommands:
|
|
9
9
|
# opencode-memory install — Install shell hook to ~/.zshrc or ~/.bashrc
|
|
10
10
|
# opencode-memory uninstall — Remove shell hook
|
|
11
|
-
# opencode-memory [args...] — Run opencode with memory
|
|
11
|
+
# opencode-memory [args...] — Run opencode with post-session memory maintenance
|
|
12
12
|
#
|
|
13
13
|
# How it works:
|
|
14
14
|
# 1. Shell hook defines `opencode()` function that delegates to `opencode-memory`
|
|
15
15
|
# 2. `opencode-memory` finds the real `opencode` binary in PATH
|
|
16
16
|
# 3. Runs it normally with all your arguments
|
|
17
17
|
# 4. After you exit, finds the most recent session
|
|
18
|
-
# 5.
|
|
19
|
-
# 6.
|
|
18
|
+
# 5. Optionally runs memory extraction (conversation -> memories)
|
|
19
|
+
# 6. Optionally runs auto-dream consolidation (memory pruning/merge)
|
|
20
20
|
#
|
|
21
21
|
# Requirements:
|
|
22
22
|
# - Real `opencode` CLI reachable in PATH
|
|
23
|
-
# - `jq` for
|
|
24
|
-
# - The opencode-memory plugin installed (provides
|
|
23
|
+
# - `jq` for auto-dream gate/session counting
|
|
24
|
+
# - The opencode-memory plugin installed (provides memory_* tools)
|
|
25
25
|
#
|
|
26
26
|
# Environment variables:
|
|
27
|
-
# OPENCODE_MEMORY_EXTRACT=0
|
|
28
|
-
# OPENCODE_MEMORY_FOREGROUND=1
|
|
29
|
-
# OPENCODE_MEMORY_MODEL=...
|
|
30
|
-
# OPENCODE_MEMORY_AGENT=...
|
|
31
|
-
#
|
|
27
|
+
# OPENCODE_MEMORY_EXTRACT=0 — Disable post-session extraction
|
|
28
|
+
# OPENCODE_MEMORY_FOREGROUND=1 — Run maintenance in foreground (debug)
|
|
29
|
+
# OPENCODE_MEMORY_MODEL=... — Extraction model override
|
|
30
|
+
# OPENCODE_MEMORY_AGENT=... — Extraction agent override
|
|
31
|
+
# OPENCODE_MEMORY_AUTODREAM=0 — Disable auto-dream consolidation
|
|
32
|
+
# OPENCODE_MEMORY_AUTODREAM_MIN_HOURS=24 — Min hours between auto-dream runs
|
|
33
|
+
# OPENCODE_MEMORY_AUTODREAM_MIN_SESSIONS=5 — Min touched sessions since last consolidation
|
|
34
|
+
# OPENCODE_MEMORY_AUTODREAM_MODEL=... — Auto-dream model override
|
|
35
|
+
# OPENCODE_MEMORY_AUTODREAM_AGENT=... — Auto-dream agent override
|
|
36
|
+
# OPENCODE_MEMORY_DIR=... — Override working directory for opencode
|
|
32
37
|
#
|
|
33
38
|
|
|
34
39
|
set -euo pipefail
|
|
@@ -161,20 +166,51 @@ EXTRACT_ENABLED="${OPENCODE_MEMORY_EXTRACT:-1}"
|
|
|
161
166
|
FOREGROUND="${OPENCODE_MEMORY_FOREGROUND:-0}"
|
|
162
167
|
EXTRACT_MODEL="${OPENCODE_MEMORY_MODEL:-}"
|
|
163
168
|
EXTRACT_AGENT="${OPENCODE_MEMORY_AGENT:-}"
|
|
169
|
+
|
|
170
|
+
AUTODREAM_ENABLED="${OPENCODE_MEMORY_AUTODREAM:-1}"
|
|
171
|
+
AUTODREAM_MIN_HOURS="${OPENCODE_MEMORY_AUTODREAM_MIN_HOURS:-24}"
|
|
172
|
+
AUTODREAM_MIN_SESSIONS="${OPENCODE_MEMORY_AUTODREAM_MIN_SESSIONS:-5}"
|
|
173
|
+
AUTODREAM_SCAN_LIMIT="${OPENCODE_MEMORY_AUTODREAM_SCAN_LIMIT:-200}"
|
|
174
|
+
AUTODREAM_MODEL="${OPENCODE_MEMORY_AUTODREAM_MODEL:-$EXTRACT_MODEL}"
|
|
175
|
+
AUTODREAM_AGENT="${OPENCODE_MEMORY_AUTODREAM_AGENT:-$EXTRACT_AGENT}"
|
|
176
|
+
AUTODREAM_STALE_LOCK_SECS=$((60 * 60))
|
|
177
|
+
|
|
164
178
|
WORKING_DIR="${OPENCODE_MEMORY_DIR:-$(pwd)}"
|
|
165
179
|
|
|
166
|
-
|
|
167
|
-
|
|
180
|
+
TMP_BASE_DIR="${TMPDIR:-/tmp}"
|
|
181
|
+
while [ "$TMP_BASE_DIR" != "/" ] && [ "${TMP_BASE_DIR%/}" != "$TMP_BASE_DIR" ]; do
|
|
182
|
+
TMP_BASE_DIR="${TMP_BASE_DIR%/}"
|
|
183
|
+
done
|
|
184
|
+
if [ -z "$TMP_BASE_DIR" ]; then
|
|
185
|
+
TMP_BASE_DIR="/"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# Scope lock files at project root granularity (not per-subdirectory).
|
|
189
|
+
PROJECT_SCOPE_DIR="$WORKING_DIR"
|
|
190
|
+
if git -C "$WORKING_DIR" rev-parse --show-toplevel >/dev/null 2>&1; then
|
|
191
|
+
PROJECT_SCOPE_DIR="$(git -C "$WORKING_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$WORKING_DIR")"
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
PROJECT_KEY="$(printf '%s' "$PROJECT_SCOPE_DIR" | cksum | awk '{print $1}')"
|
|
195
|
+
|
|
196
|
+
# Lock files (prevent concurrent work on the same project)
|
|
197
|
+
LOCK_DIR="$TMP_BASE_DIR/opencode-memory-locks"
|
|
168
198
|
mkdir -p "$LOCK_DIR"
|
|
169
|
-
|
|
199
|
+
EXTRACT_LOCK_FILE="$LOCK_DIR/${PROJECT_KEY}.extract.lock"
|
|
170
200
|
|
|
171
|
-
|
|
172
|
-
|
|
201
|
+
STATE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/opencode-memory"
|
|
202
|
+
mkdir -p "$STATE_DIR"
|
|
203
|
+
CONSOLIDATION_LOCK_FILE="$STATE_DIR/${PROJECT_KEY}.consolidate-lock"
|
|
204
|
+
|
|
205
|
+
# Logs
|
|
206
|
+
LOG_DIR="$TMP_BASE_DIR/opencode-memory-logs"
|
|
173
207
|
mkdir -p "$LOG_DIR"
|
|
174
|
-
|
|
208
|
+
TASK_LOG_PREFIX="$(date +%Y%m%d-%H%M%S)-${PROJECT_KEY}"
|
|
209
|
+
EXTRACT_LOG_FILE="$LOG_DIR/extract-${TASK_LOG_PREFIX}.log"
|
|
210
|
+
AUTODREAM_LOG_FILE="$LOG_DIR/dream-${TASK_LOG_PREFIX}.log"
|
|
175
211
|
|
|
176
212
|
# ============================================================================
|
|
177
|
-
#
|
|
213
|
+
# Prompts
|
|
178
214
|
# ============================================================================
|
|
179
215
|
|
|
180
216
|
# Adapted from Claude Code's extraction prompt, simplified for OpenCode's
|
|
@@ -217,6 +253,44 @@ For each memory worth saving, call `memory_save` with:
|
|
|
217
253
|
5. Be selective: 0-3 memories per session is typical. Quality over quantity.
|
|
218
254
|
6. Do NOT save a memory about the extraction process itself.'
|
|
219
255
|
|
|
256
|
+
# Periodic memory consolidation inspired by Claude Code auto-dream.
|
|
257
|
+
AUTODREAM_PROMPT="$(cat <<'EOF'
|
|
258
|
+
You are performing an auto-dream memory consolidation pass.
|
|
259
|
+
|
|
260
|
+
Goal: tighten and de-duplicate memory files so future sessions can orient faster.
|
|
261
|
+
|
|
262
|
+
## Available tools
|
|
263
|
+
- memory_list
|
|
264
|
+
- memory_search
|
|
265
|
+
- memory_read
|
|
266
|
+
- memory_save
|
|
267
|
+
- memory_delete
|
|
268
|
+
|
|
269
|
+
## Phase 1 — Orient
|
|
270
|
+
1. Use memory_list to inspect current memory inventory.
|
|
271
|
+
2. Identify overlapping or stale entries that can be merged/updated/deleted.
|
|
272
|
+
|
|
273
|
+
## Phase 2 — Consolidate
|
|
274
|
+
1. Merge duplicates into a single stronger memory using memory_save.
|
|
275
|
+
2. Rewrite vague descriptions so retrieval is easier and more precise.
|
|
276
|
+
3. For feedback/project entries, ensure content is structured as:
|
|
277
|
+
- main rule/fact
|
|
278
|
+
- **Why:**
|
|
279
|
+
- **How to apply:**
|
|
280
|
+
|
|
281
|
+
## Phase 3 — Prune
|
|
282
|
+
1. Delete memories that are clearly obsolete, contradictory, or low-value.
|
|
283
|
+
2. Keep total memory set concise and high signal.
|
|
284
|
+
|
|
285
|
+
## Guardrails
|
|
286
|
+
- Do NOT invent facts.
|
|
287
|
+
- If confidence is low, keep existing memory instead of guessing.
|
|
288
|
+
- If memory quality is already strong, make no changes and explicitly say so.
|
|
289
|
+
|
|
290
|
+
Return a short summary of what you updated, merged, or removed.
|
|
291
|
+
EOF
|
|
292
|
+
)"
|
|
293
|
+
|
|
220
294
|
# ============================================================================
|
|
221
295
|
# Helper Functions
|
|
222
296
|
# ============================================================================
|
|
@@ -225,85 +299,310 @@ log() {
|
|
|
225
299
|
echo "[opencode-memory] $*" >&2
|
|
226
300
|
}
|
|
227
301
|
|
|
302
|
+
is_positive_int() {
|
|
303
|
+
[[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -gt 0 ]
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if ! is_positive_int "$AUTODREAM_MIN_HOURS"; then
|
|
307
|
+
log "Invalid OPENCODE_MEMORY_AUTODREAM_MIN_HOURS=$AUTODREAM_MIN_HOURS, using default 24"
|
|
308
|
+
AUTODREAM_MIN_HOURS=24
|
|
309
|
+
fi
|
|
310
|
+
if ! is_positive_int "$AUTODREAM_MIN_SESSIONS"; then
|
|
311
|
+
log "Invalid OPENCODE_MEMORY_AUTODREAM_MIN_SESSIONS=$AUTODREAM_MIN_SESSIONS, using default 5"
|
|
312
|
+
AUTODREAM_MIN_SESSIONS=5
|
|
313
|
+
fi
|
|
314
|
+
if ! is_positive_int "$AUTODREAM_SCAN_LIMIT"; then
|
|
315
|
+
log "Invalid OPENCODE_MEMORY_AUTODREAM_SCAN_LIMIT=$AUTODREAM_SCAN_LIMIT, using default 200"
|
|
316
|
+
AUTODREAM_SCAN_LIMIT=200
|
|
317
|
+
fi
|
|
318
|
+
|
|
228
319
|
has_new_memories() {
|
|
229
|
-
# Check if any memory file was modified during the session
|
|
230
|
-
# Checks all projects' memory directories for files newer than the timestamp marker
|
|
320
|
+
# Check if any memory file was modified during the session.
|
|
231
321
|
local mem_base="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/projects"
|
|
232
|
-
|
|
233
322
|
if [ ! -d "$mem_base" ]; then
|
|
234
323
|
return 1
|
|
235
324
|
fi
|
|
236
325
|
|
|
237
|
-
# Find any .md file under projects/*/memory/ newer than our timestamp
|
|
238
326
|
local newer_files
|
|
239
327
|
newer_files=$(find "$mem_base" -path "*/memory/*.md" -newer "$TIMESTAMP_FILE" 2>/dev/null | head -1)
|
|
240
|
-
|
|
241
328
|
[ -n "$newer_files" ]
|
|
242
329
|
}
|
|
243
330
|
|
|
331
|
+
cleanup_timestamp() {
|
|
332
|
+
rm -f "$TIMESTAMP_FILE"
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
get_session_list_json() {
|
|
336
|
+
local limit="$1"
|
|
337
|
+
local output
|
|
338
|
+
|
|
339
|
+
if output=$("$REAL_OPENCODE" session list --format json -n "$limit" 2>/dev/null); then
|
|
340
|
+
echo "$output"
|
|
341
|
+
return 0
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
if output=$("$REAL_OPENCODE" session list --format json 2>/dev/null); then
|
|
345
|
+
echo "$output"
|
|
346
|
+
return 0
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
return 1
|
|
350
|
+
}
|
|
351
|
+
|
|
244
352
|
get_latest_session_id() {
|
|
245
353
|
local session_json
|
|
246
|
-
session_json=$(
|
|
354
|
+
session_json=$(get_session_list_json 1) || return 1
|
|
247
355
|
|
|
248
|
-
# Parse with jq if available, fallback to grep
|
|
356
|
+
# Parse with jq if available, fallback to grep.
|
|
249
357
|
if command -v jq &>/dev/null; then
|
|
250
358
|
echo "$session_json" | jq -r '.[0].id // empty'
|
|
251
359
|
else
|
|
252
|
-
# Rough fallback: extract first "id" value
|
|
253
360
|
echo "$session_json" | grep -o '"id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"id"[[:space:]]*:[[:space:]]*"\([^"]*\)"/\1/'
|
|
254
361
|
fi
|
|
255
362
|
}
|
|
256
363
|
|
|
257
|
-
|
|
258
|
-
|
|
364
|
+
file_mtime_secs() {
|
|
365
|
+
local file="$1"
|
|
366
|
+
if [ ! -f "$file" ]; then
|
|
367
|
+
echo 0
|
|
368
|
+
return 0
|
|
369
|
+
fi
|
|
370
|
+
|
|
371
|
+
if stat -c %Y "$file" >/dev/null 2>&1; then
|
|
372
|
+
stat -c %Y "$file"
|
|
373
|
+
return 0
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
stat -f %m "$file"
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
# Claude-style lock/mtime semantics:
|
|
380
|
+
# - lock file CONTENT (PID) = current holder
|
|
381
|
+
# - lock file MTIME = last successful consolidation timestamp
|
|
382
|
+
read_last_consolidated_at_secs() {
|
|
383
|
+
file_mtime_secs "$CONSOLIDATION_LOCK_FILE"
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
set_file_mtime_secs() {
|
|
387
|
+
local file="$1"
|
|
388
|
+
local secs="$2"
|
|
389
|
+
|
|
390
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
391
|
+
python3 - "$file" "$secs" <<'PY'
|
|
392
|
+
import os
|
|
393
|
+
import sys
|
|
394
|
+
|
|
395
|
+
path = sys.argv[1]
|
|
396
|
+
secs = int(sys.argv[2])
|
|
397
|
+
os.utime(path, (secs, secs))
|
|
398
|
+
PY
|
|
399
|
+
return 0
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
# Best effort fallback; may not be portable across all environments.
|
|
403
|
+
touch -d "@$secs" "$file" >/dev/null 2>&1 || true
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
acquire_simple_lock() {
|
|
407
|
+
local lock_file="$1"
|
|
408
|
+
local lock_name="$2"
|
|
409
|
+
|
|
410
|
+
if [ -f "$lock_file" ]; then
|
|
259
411
|
local lock_pid
|
|
260
|
-
lock_pid=$(cat "$
|
|
412
|
+
lock_pid=$(cat "$lock_file" 2>/dev/null || true)
|
|
261
413
|
if [ -n "$lock_pid" ] && kill -0 "$lock_pid" 2>/dev/null; then
|
|
262
|
-
log "Another
|
|
414
|
+
log "Another $lock_name is already running (PID $lock_pid), skipping"
|
|
263
415
|
return 1
|
|
264
416
|
fi
|
|
265
|
-
|
|
266
|
-
rm -f "$LOCK_FILE"
|
|
417
|
+
rm -f "$lock_file"
|
|
267
418
|
fi
|
|
268
|
-
|
|
419
|
+
|
|
420
|
+
echo $$ > "$lock_file"
|
|
269
421
|
return 0
|
|
270
422
|
}
|
|
271
423
|
|
|
272
|
-
|
|
273
|
-
|
|
424
|
+
release_simple_lock() {
|
|
425
|
+
local lock_file="$1"
|
|
426
|
+
rm -f "$lock_file"
|
|
274
427
|
}
|
|
275
428
|
|
|
276
|
-
|
|
277
|
-
|
|
429
|
+
count_sessions_touched_since_ms() {
|
|
430
|
+
local since_ms="$1"
|
|
431
|
+
local exclude_session_id="$2"
|
|
432
|
+
|
|
433
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
434
|
+
echo ""
|
|
435
|
+
return 1
|
|
436
|
+
fi
|
|
437
|
+
|
|
438
|
+
local session_json
|
|
439
|
+
session_json=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT") || {
|
|
440
|
+
echo ""
|
|
441
|
+
return 1
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
echo "$session_json" | jq -r --argjson since "$since_ms" --arg exclude "$exclude_session_id" '
|
|
445
|
+
[ .[]
|
|
446
|
+
| select((.id // "") != $exclude)
|
|
447
|
+
| select(((.time.updated // .time.created // 0) | tonumber) > $since)
|
|
448
|
+
] | length
|
|
449
|
+
'
|
|
278
450
|
}
|
|
279
451
|
|
|
280
|
-
|
|
452
|
+
CONSOLIDATION_PRIOR_MTIME=0
|
|
453
|
+
|
|
454
|
+
try_acquire_consolidation_lock() {
|
|
455
|
+
local path="$CONSOLIDATION_LOCK_FILE"
|
|
456
|
+
local now mtime holder age
|
|
457
|
+
|
|
458
|
+
# Returns prior mtime via CONSOLIDATION_PRIOR_MTIME so callers can rollback
|
|
459
|
+
# on failure (preserving last successful consolidation time semantics).
|
|
460
|
+
|
|
461
|
+
now=$(date +%s)
|
|
462
|
+
mtime=0
|
|
463
|
+
holder=""
|
|
464
|
+
|
|
465
|
+
if [ -f "$path" ]; then
|
|
466
|
+
mtime=$(file_mtime_secs "$path")
|
|
467
|
+
holder=$(cat "$path" 2>/dev/null || true)
|
|
468
|
+
age=$((now - mtime))
|
|
469
|
+
|
|
470
|
+
if [ "$age" -lt "$AUTODREAM_STALE_LOCK_SECS" ] && [ -n "$holder" ] && kill -0 "$holder" 2>/dev/null; then
|
|
471
|
+
log "Auto-dream lock held by live PID $holder (${age}s old), skipping"
|
|
472
|
+
return 1
|
|
473
|
+
fi
|
|
474
|
+
fi
|
|
475
|
+
|
|
476
|
+
mkdir -p "$(dirname "$path")"
|
|
477
|
+
echo $$ > "$path"
|
|
478
|
+
|
|
479
|
+
local verify
|
|
480
|
+
verify=$(cat "$path" 2>/dev/null || true)
|
|
481
|
+
if [ "$verify" != "$$" ]; then
|
|
482
|
+
return 1
|
|
483
|
+
fi
|
|
484
|
+
|
|
485
|
+
CONSOLIDATION_PRIOR_MTIME="$mtime"
|
|
486
|
+
return 0
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
rollback_consolidation_lock() {
|
|
490
|
+
local prior_mtime="$1"
|
|
491
|
+
local path="$CONSOLIDATION_LOCK_FILE"
|
|
492
|
+
|
|
493
|
+
if [ "$prior_mtime" -eq 0 ]; then
|
|
494
|
+
rm -f "$path"
|
|
495
|
+
return 0
|
|
496
|
+
fi
|
|
497
|
+
|
|
498
|
+
: > "$path"
|
|
499
|
+
set_file_mtime_secs "$path" "$prior_mtime"
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
run_extraction_if_needed() {
|
|
281
503
|
local session_id="$1"
|
|
504
|
+
local memory_written_during_session="$2"
|
|
505
|
+
|
|
506
|
+
if [ "$EXTRACT_ENABLED" = "0" ]; then
|
|
507
|
+
return 0
|
|
508
|
+
fi
|
|
509
|
+
|
|
510
|
+
if [ "$memory_written_during_session" = "1" ]; then
|
|
511
|
+
log "Main agent already wrote memories during session, skipping extraction"
|
|
512
|
+
return 0
|
|
513
|
+
fi
|
|
514
|
+
|
|
515
|
+
if ! acquire_simple_lock "$EXTRACT_LOCK_FILE" "memory extraction"; then
|
|
516
|
+
return 0
|
|
517
|
+
fi
|
|
282
518
|
|
|
283
519
|
log "Extracting memories from session $session_id..."
|
|
284
|
-
log "
|
|
520
|
+
log "Extraction log: $EXTRACT_LOG_FILE"
|
|
285
521
|
|
|
286
|
-
# Build the opencode run command
|
|
287
522
|
local cmd=("$REAL_OPENCODE" run -s "$session_id" --fork)
|
|
288
|
-
|
|
289
523
|
if [ -n "$EXTRACT_MODEL" ]; then
|
|
290
524
|
cmd+=(-m "$EXTRACT_MODEL")
|
|
291
525
|
fi
|
|
292
|
-
|
|
293
526
|
if [ -n "$EXTRACT_AGENT" ]; then
|
|
294
527
|
cmd+=(--agent "$EXTRACT_AGENT")
|
|
295
528
|
fi
|
|
296
|
-
|
|
297
529
|
cmd+=("$EXTRACT_PROMPT")
|
|
298
530
|
|
|
299
|
-
|
|
300
|
-
if "${cmd[@]}" >> "$LOG_FILE" 2>&1; then
|
|
531
|
+
if "${cmd[@]}" >> "$EXTRACT_LOG_FILE" 2>&1; then
|
|
301
532
|
log "Memory extraction completed successfully"
|
|
302
533
|
else
|
|
303
|
-
|
|
534
|
+
local code=$?
|
|
535
|
+
log "Memory extraction failed (exit code $code). Check $EXTRACT_LOG_FILE for details"
|
|
536
|
+
fi
|
|
537
|
+
|
|
538
|
+
release_simple_lock "$EXTRACT_LOCK_FILE"
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
run_autodream_if_needed() {
|
|
542
|
+
local session_id="$1"
|
|
543
|
+
|
|
544
|
+
if [ "$AUTODREAM_ENABLED" = "0" ]; then
|
|
545
|
+
return 0
|
|
546
|
+
fi
|
|
547
|
+
|
|
548
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
549
|
+
log "jq not found, skipping auto-dream (requires JSON session parsing)"
|
|
550
|
+
return 0
|
|
551
|
+
fi
|
|
552
|
+
|
|
553
|
+
local last_at now hours_since since_ms touched_count
|
|
554
|
+
last_at=$(read_last_consolidated_at_secs)
|
|
555
|
+
now=$(date +%s)
|
|
556
|
+
|
|
557
|
+
hours_since=$(((now - last_at) / 3600))
|
|
558
|
+
if [ "$hours_since" -lt "$AUTODREAM_MIN_HOURS" ]; then
|
|
559
|
+
return 0
|
|
560
|
+
fi
|
|
561
|
+
|
|
562
|
+
since_ms=$((last_at * 1000))
|
|
563
|
+
touched_count=$(count_sessions_touched_since_ms "$since_ms" "$session_id" || true)
|
|
564
|
+
if [ -z "$touched_count" ]; then
|
|
565
|
+
log "Unable to inspect touched sessions, skipping auto-dream"
|
|
566
|
+
return 0
|
|
567
|
+
fi
|
|
568
|
+
|
|
569
|
+
if [ "$touched_count" -lt "$AUTODREAM_MIN_SESSIONS" ]; then
|
|
570
|
+
return 0
|
|
571
|
+
fi
|
|
572
|
+
|
|
573
|
+
if ! try_acquire_consolidation_lock; then
|
|
574
|
+
return 0
|
|
575
|
+
fi
|
|
576
|
+
|
|
577
|
+
log "Auto-dream firing (${hours_since}h since last consolidation, ${touched_count} sessions touched)"
|
|
578
|
+
log "Auto-dream log: $AUTODREAM_LOG_FILE"
|
|
579
|
+
|
|
580
|
+
local cmd=("$REAL_OPENCODE" run -s "$session_id" --fork)
|
|
581
|
+
if [ -n "$AUTODREAM_MODEL" ]; then
|
|
582
|
+
cmd+=(-m "$AUTODREAM_MODEL")
|
|
583
|
+
fi
|
|
584
|
+
if [ -n "$AUTODREAM_AGENT" ]; then
|
|
585
|
+
cmd+=(--agent "$AUTODREAM_AGENT")
|
|
586
|
+
fi
|
|
587
|
+
cmd+=("$AUTODREAM_PROMPT")
|
|
588
|
+
|
|
589
|
+
if "${cmd[@]}" >> "$AUTODREAM_LOG_FILE" 2>&1; then
|
|
590
|
+
log "Auto-dream consolidation completed successfully"
|
|
591
|
+
# Keep lock mtime at "now" to represent last consolidated timestamp.
|
|
592
|
+
else
|
|
593
|
+
local code=$?
|
|
594
|
+
log "Auto-dream consolidation failed (exit code $code). Rolling back gate timestamp"
|
|
595
|
+
rollback_consolidation_lock "$CONSOLIDATION_PRIOR_MTIME"
|
|
596
|
+
return 0
|
|
304
597
|
fi
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
run_post_session_tasks() {
|
|
601
|
+
local session_id="$1"
|
|
602
|
+
local memory_written_during_session="$2"
|
|
305
603
|
|
|
306
|
-
|
|
604
|
+
run_extraction_if_needed "$session_id" "$memory_written_during_session"
|
|
605
|
+
run_autodream_if_needed "$session_id"
|
|
307
606
|
}
|
|
308
607
|
|
|
309
608
|
# ============================================================================
|
|
@@ -317,45 +616,36 @@ TIMESTAMP_FILE=$(mktemp)
|
|
|
317
616
|
opencode_exit=0
|
|
318
617
|
"$REAL_OPENCODE" "$@" || opencode_exit=$?
|
|
319
618
|
|
|
320
|
-
# Step 2:
|
|
321
|
-
if [ "$EXTRACT_ENABLED" = "0" ]; then
|
|
619
|
+
# Step 2: If no maintenance task is enabled, exit early
|
|
620
|
+
if [ "$EXTRACT_ENABLED" = "0" ] && [ "$AUTODREAM_ENABLED" = "0" ]; then
|
|
322
621
|
cleanup_timestamp
|
|
323
622
|
exit $opencode_exit
|
|
324
623
|
fi
|
|
325
624
|
|
|
326
625
|
# Step 3: Get the most recent session ID
|
|
327
|
-
session_id=$(get_latest_session_id)
|
|
328
|
-
|
|
626
|
+
session_id=$(get_latest_session_id || true)
|
|
329
627
|
if [ -z "$session_id" ]; then
|
|
330
|
-
log "No session found, skipping memory
|
|
628
|
+
log "No session found, skipping post-session memory maintenance"
|
|
331
629
|
cleanup_timestamp
|
|
332
630
|
exit $opencode_exit
|
|
333
631
|
fi
|
|
334
632
|
|
|
335
|
-
# Step
|
|
633
|
+
# Step 4: Check whether main session already wrote memory files
|
|
634
|
+
memory_written_during_session=0
|
|
336
635
|
if has_new_memories; then
|
|
337
|
-
|
|
338
|
-
cleanup_timestamp
|
|
339
|
-
exit $opencode_exit
|
|
636
|
+
memory_written_during_session=1
|
|
340
637
|
fi
|
|
341
638
|
|
|
342
|
-
#
|
|
343
|
-
|
|
344
|
-
cleanup_timestamp
|
|
345
|
-
exit $opencode_exit
|
|
346
|
-
fi
|
|
639
|
+
# Timestamp file is no longer needed after the check above.
|
|
640
|
+
cleanup_timestamp
|
|
347
641
|
|
|
348
|
-
# Step 5: Run
|
|
642
|
+
# Step 5: Run tasks (foreground for debug, background by default)
|
|
349
643
|
if [ "$FOREGROUND" = "1" ]; then
|
|
350
|
-
|
|
351
|
-
run_extraction "$session_id"
|
|
352
|
-
cleanup_timestamp
|
|
644
|
+
run_post_session_tasks "$session_id" "$memory_written_during_session"
|
|
353
645
|
else
|
|
354
|
-
|
|
355
|
-
run_extraction "$session_id" &
|
|
646
|
+
run_post_session_tasks "$session_id" "$memory_written_during_session" &
|
|
356
647
|
disown
|
|
357
|
-
log "
|
|
358
|
-
cleanup_timestamp
|
|
648
|
+
log "Post-session memory maintenance started in background (PID $!)"
|
|
359
649
|
fi
|
|
360
650
|
|
|
361
651
|
exit $opencode_exit
|
package/package.json
CHANGED