opencode-claude-memory 1.6.2 → 1.6.3
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 +38 -0
- package/bin/opencode-memory +55 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,30 @@ Claude Code writes memory → OpenCode reads it. OpenCode writes memory → Clau
|
|
|
29
29
|
|
|
30
30
|
## 🚀 Quick Start
|
|
31
31
|
|
|
32
|
+
### Prerequisites
|
|
33
|
+
|
|
34
|
+
- `opencode`
|
|
35
|
+
- `python3` available in `PATH`
|
|
36
|
+
|
|
37
|
+
`python3` is a runtime dependency for the wrapper's scoped session detection and fork cleanup logic.
|
|
38
|
+
If it is missing or not executable, post-session maintenance becomes less reliable: session targeting can fall back to less precise heuristics, and fork cleanup is skipped for safety.
|
|
39
|
+
|
|
40
|
+
Common install commands:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# macOS (Homebrew)
|
|
44
|
+
brew install python
|
|
45
|
+
|
|
46
|
+
# Ubuntu / Debian
|
|
47
|
+
sudo apt-get update && sudo apt-get install -y python3
|
|
48
|
+
|
|
49
|
+
# Fedora
|
|
50
|
+
sudo dnf install -y python3
|
|
51
|
+
|
|
52
|
+
# Arch Linux
|
|
53
|
+
sudo pacman -S python
|
|
54
|
+
```
|
|
55
|
+
|
|
32
56
|
### 1. Install
|
|
33
57
|
|
|
34
58
|
```bash
|
|
@@ -41,6 +65,8 @@ This installs:
|
|
|
41
65
|
- The `opencode-memory` **CLI** — wraps opencode with automatic memory extraction + auto-dream consolidation
|
|
42
66
|
- A **shell hook** — defines an `opencode()` function in your `.zshrc`/`.bashrc` that delegates to `opencode-memory`
|
|
43
67
|
|
|
68
|
+
If `python3` is not installed yet, install it first using the commands above before enabling the shell hook.
|
|
69
|
+
|
|
44
70
|
### 2. Configure
|
|
45
71
|
|
|
46
72
|
```jsonc
|
|
@@ -118,6 +144,18 @@ The shell hook defines an `opencode()` function that delegates to `opencode-memo
|
|
|
118
144
|
8. Maintenance runs **in the background** unless `OPENCODE_MEMORY_FOREGROUND=1`
|
|
119
145
|
9. Terminal maintenance logs are shown in foreground mode by default, or can be forced on/off with `OPENCODE_MEMORY_TERMINAL_LOG=1|0`
|
|
120
146
|
|
|
147
|
+
### Runtime dependencies
|
|
148
|
+
|
|
149
|
+
The wrapper expects `python3` to be available at runtime.
|
|
150
|
+
|
|
151
|
+
It is used for:
|
|
152
|
+
|
|
153
|
+
- scoped session selection from `opencode session list`
|
|
154
|
+
- parsing `opencode export` output to resolve session directories
|
|
155
|
+
- safely identifying and cleaning up forked extraction / auto-dream sessions
|
|
156
|
+
|
|
157
|
+
Without `python3`, the plugin tools still load, but wrapper maintenance is degraded and fork cleanup is intentionally skipped to avoid deleting the wrong session.
|
|
158
|
+
|
|
121
159
|
### Compatibility details
|
|
122
160
|
|
|
123
161
|
The implementation ports core logic from Claude Code for path hashing, git-root/worktree handling, memory format, and memory prompting behavior, so both tools can operate on the same files safely.
|
package/bin/opencode-memory
CHANGED
|
@@ -478,17 +478,18 @@ get_session_target_id() {
|
|
|
478
478
|
local started_at_ms="$2"
|
|
479
479
|
local workdir="$3"
|
|
480
480
|
local project_dir="$4"
|
|
481
|
+
local allow_existing_fallback="${5:-1}"
|
|
481
482
|
local after_json
|
|
482
483
|
|
|
483
484
|
after_json=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT") || return 1
|
|
484
485
|
|
|
485
486
|
if command -v python3 >/dev/null 2>&1; then
|
|
486
|
-
python3 - "$before_json" "$after_json" "$started_at_ms" "$workdir" "$project_dir" <<'PY'
|
|
487
|
+
python3 - "$before_json" "$after_json" "$started_at_ms" "$workdir" "$project_dir" "$allow_existing_fallback" <<'PY'
|
|
487
488
|
import json
|
|
488
489
|
import os
|
|
489
490
|
import sys
|
|
490
491
|
|
|
491
|
-
before_raw, after_raw, started_at_ms_raw, workdir, project_dir = sys.argv[1:
|
|
492
|
+
before_raw, after_raw, started_at_ms_raw, workdir, project_dir, allow_existing_fallback_raw = sys.argv[1:7]
|
|
492
493
|
|
|
493
494
|
def parse(raw):
|
|
494
495
|
try:
|
|
@@ -522,6 +523,7 @@ def normalize(path):
|
|
|
522
523
|
before = parse(before_raw)
|
|
523
524
|
after = parse(after_raw)
|
|
524
525
|
started_at_ms = int(started_at_ms_raw or "0")
|
|
526
|
+
allow_existing_fallback = allow_existing_fallback_raw == "1"
|
|
525
527
|
before_ids = {item.get("id") for item in before if item.get("id")}
|
|
526
528
|
workdir = normalize(workdir)
|
|
527
529
|
project_dir = normalize(project_dir)
|
|
@@ -544,11 +546,15 @@ def choose(candidates):
|
|
|
544
546
|
new_sessions = [item for item in after if item.get("id") not in before_ids]
|
|
545
547
|
updated_sessions = [item for item in after if timestamp(item) > started_at_ms]
|
|
546
548
|
|
|
547
|
-
|
|
549
|
+
candidate_pools = [
|
|
548
550
|
[item for item in new_sessions if in_scope(item)],
|
|
549
551
|
[item for item in updated_sessions if in_scope(item)],
|
|
550
|
-
|
|
551
|
-
|
|
552
|
+
]
|
|
553
|
+
|
|
554
|
+
if allow_existing_fallback:
|
|
555
|
+
candidate_pools.append([item for item in after if in_scope(item)])
|
|
556
|
+
|
|
557
|
+
for pool in candidate_pools:
|
|
552
558
|
if choose(pool):
|
|
553
559
|
break
|
|
554
560
|
PY
|
|
@@ -795,17 +801,19 @@ main_prompt_requests_ignore_memory() {
|
|
|
795
801
|
printf '%s\n' "$joined" | grep -Eq "(ignore|don't use|do not use|without|skip)[[:space:]]+(the[[:space:]]+)?memory|memory[[:space:]]+((should|must)[[:space:]]+be[[:space:]]+)?ignored"
|
|
796
802
|
}
|
|
797
803
|
|
|
798
|
-
|
|
804
|
+
wait_for_scoped_session_id_since() {
|
|
799
805
|
local before_json="$1"
|
|
800
806
|
local started_at_ms="$2"
|
|
801
|
-
local
|
|
807
|
+
local timestamp_file="$3"
|
|
808
|
+
local wait_seconds="${4:-5}"
|
|
809
|
+
local allow_existing_fallback="${5:-1}"
|
|
802
810
|
local attempt=0
|
|
803
811
|
local session_id=""
|
|
804
812
|
|
|
805
813
|
while [ "$attempt" -lt "$wait_seconds" ]; do
|
|
806
|
-
session_id=$(get_session_target_id "$before_json" "$started_at_ms" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" || true)
|
|
814
|
+
session_id=$(get_session_target_id "$before_json" "$started_at_ms" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" "$allow_existing_fallback" || true)
|
|
807
815
|
if [ -z "$session_id" ]; then
|
|
808
|
-
session_id=$(get_scoped_artifact_session_id_since "$
|
|
816
|
+
session_id=$(get_scoped_artifact_session_id_since "$timestamp_file" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" || true)
|
|
809
817
|
fi
|
|
810
818
|
if [ -n "$session_id" ]; then
|
|
811
819
|
printf '%s\n' "$session_id"
|
|
@@ -818,6 +826,14 @@ wait_for_session_target_id() {
|
|
|
818
826
|
return 1
|
|
819
827
|
}
|
|
820
828
|
|
|
829
|
+
wait_for_session_target_id() {
|
|
830
|
+
local before_json="$1"
|
|
831
|
+
local started_at_ms="$2"
|
|
832
|
+
local wait_seconds="${3:-5}"
|
|
833
|
+
|
|
834
|
+
wait_for_scoped_session_id_since "$before_json" "$started_at_ms" "$TIMESTAMP_FILE" "$wait_seconds"
|
|
835
|
+
}
|
|
836
|
+
|
|
821
837
|
file_mtime_secs() {
|
|
822
838
|
local file="$1"
|
|
823
839
|
if [ ! -f "$file" ]; then
|
|
@@ -958,58 +974,32 @@ rollback_consolidation_lock() {
|
|
|
958
974
|
|
|
959
975
|
cleanup_forked_sessions() {
|
|
960
976
|
local before_json="$1"
|
|
977
|
+
local started_at_ms="$2"
|
|
978
|
+
local timestamp_file="$3"
|
|
961
979
|
|
|
962
|
-
if
|
|
980
|
+
if [ -z "$before_json" ] || [ -z "$started_at_ms" ] || [ -z "$timestamp_file" ]; then
|
|
963
981
|
return 0
|
|
964
982
|
fi
|
|
965
983
|
|
|
966
|
-
|
|
967
|
-
after_json=$(get_session_list_json 10 2>/dev/null || true)
|
|
968
|
-
|
|
969
|
-
if [ -z "$before_json" ] || [ -z "$after_json" ]; then
|
|
984
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
970
985
|
return 0
|
|
971
986
|
fi
|
|
972
987
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
def parse(raw):
|
|
980
|
-
try:
|
|
981
|
-
data = json.loads(raw)
|
|
982
|
-
return data if isinstance(data, list) else []
|
|
983
|
-
except Exception:
|
|
984
|
-
return []
|
|
985
|
-
|
|
986
|
-
before_raw, after_raw, workdir, project_dir = sys.argv[1:5]
|
|
987
|
-
before = parse(before_raw)
|
|
988
|
-
after = parse(after_raw)
|
|
988
|
+
if ! python3 - <<'PY' >/dev/null 2>&1
|
|
989
|
+
pass
|
|
990
|
+
PY
|
|
991
|
+
then
|
|
992
|
+
return 0
|
|
993
|
+
fi
|
|
989
994
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
project_dir = os.path.realpath(project_dir)
|
|
995
|
+
local fork_id
|
|
996
|
+
fork_id=$(wait_for_scoped_session_id_since "$before_json" "$started_at_ms" "$timestamp_file" "$SESSION_WAIT_SECONDS" 0 || true)
|
|
993
997
|
|
|
994
|
-
|
|
995
|
-
sid = item.get("id", "")
|
|
996
|
-
if not sid or sid in before_ids:
|
|
997
|
-
continue
|
|
998
|
-
directory = item.get("directory", "")
|
|
999
|
-
if not directory:
|
|
1000
|
-
continue
|
|
1001
|
-
d = os.path.realpath(directory)
|
|
1002
|
-
if d in (workdir, project_dir):
|
|
1003
|
-
print(sid)
|
|
1004
|
-
PY
|
|
1005
|
-
) || return 0
|
|
998
|
+
[ -n "$fork_id" ] || return 0
|
|
1006
999
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
log "Cleaned up forked session $fork_id"
|
|
1011
|
-
fi
|
|
1012
|
-
done <<< "$fork_ids"
|
|
1000
|
+
if "$REAL_OPENCODE" session delete "$fork_id" >/dev/null 2>&1; then
|
|
1001
|
+
log "Cleaned up forked session $fork_id"
|
|
1002
|
+
fi
|
|
1013
1003
|
}
|
|
1014
1004
|
|
|
1015
1005
|
session_has_conversation() {
|
|
@@ -1060,7 +1050,11 @@ run_extraction_if_needed() {
|
|
|
1060
1050
|
cmd+=("$EXTRACT_PROMPT")
|
|
1061
1051
|
|
|
1062
1052
|
local pre_fork_json
|
|
1063
|
-
pre_fork_json=$(get_session_list_json
|
|
1053
|
+
pre_fork_json=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT" 2>/dev/null || true)
|
|
1054
|
+
local fork_timestamp_file
|
|
1055
|
+
fork_timestamp_file=$(mktemp)
|
|
1056
|
+
local fork_started_at_ms
|
|
1057
|
+
fork_started_at_ms=$(( $(date +%s) * 1000 ))
|
|
1064
1058
|
|
|
1065
1059
|
if "${cmd[@]}" >> "$EXTRACT_LOG_FILE" 2>&1; then
|
|
1066
1060
|
log "Memory extraction completed successfully"
|
|
@@ -1069,7 +1063,8 @@ run_extraction_if_needed() {
|
|
|
1069
1063
|
log "Memory extraction failed (exit code $code). Check $EXTRACT_LOG_FILE for details"
|
|
1070
1064
|
fi
|
|
1071
1065
|
|
|
1072
|
-
cleanup_forked_sessions "$pre_fork_json"
|
|
1066
|
+
cleanup_forked_sessions "$pre_fork_json" "$fork_started_at_ms" "$fork_timestamp_file"
|
|
1067
|
+
rm -f "$fork_timestamp_file"
|
|
1073
1068
|
release_simple_lock "$EXTRACT_LOCK_FILE"
|
|
1074
1069
|
}
|
|
1075
1070
|
|
|
@@ -1122,7 +1117,11 @@ run_autodream_if_needed() {
|
|
|
1122
1117
|
cmd+=("$AUTODREAM_PROMPT")
|
|
1123
1118
|
|
|
1124
1119
|
local pre_fork_json
|
|
1125
|
-
pre_fork_json=$(get_session_list_json
|
|
1120
|
+
pre_fork_json=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT" 2>/dev/null || true)
|
|
1121
|
+
local fork_timestamp_file
|
|
1122
|
+
fork_timestamp_file=$(mktemp)
|
|
1123
|
+
local fork_started_at_ms
|
|
1124
|
+
fork_started_at_ms=$(( $(date +%s) * 1000 ))
|
|
1126
1125
|
|
|
1127
1126
|
if "${cmd[@]}" >> "$AUTODREAM_LOG_FILE" 2>&1; then
|
|
1128
1127
|
log "Auto-dream consolidation completed successfully"
|
|
@@ -1133,7 +1132,8 @@ run_autodream_if_needed() {
|
|
|
1133
1132
|
rollback_consolidation_lock "$CONSOLIDATION_PRIOR_MTIME"
|
|
1134
1133
|
fi
|
|
1135
1134
|
|
|
1136
|
-
cleanup_forked_sessions "$pre_fork_json"
|
|
1135
|
+
cleanup_forked_sessions "$pre_fork_json" "$fork_started_at_ms" "$fork_timestamp_file"
|
|
1136
|
+
rm -f "$fork_timestamp_file"
|
|
1137
1137
|
}
|
|
1138
1138
|
|
|
1139
1139
|
run_post_session_tasks() {
|
package/package.json
CHANGED