opencode-claude-memory 1.5.2 โ 1.6.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 +19 -0
- package/bin/opencode-memory +426 -9
- package/package.json +1 -1
- package/src/index.ts +165 -16
- package/src/memory.ts +44 -15
- package/src/memoryScan.ts +130 -0
- package/src/paths.ts +6 -3
- package/src/prompt.ts +187 -112
- package/src/recall.ts +81 -44
package/README.md
CHANGED
|
@@ -122,6 +122,15 @@ The shell hook defines an `opencode()` function that delegates to `opencode-memo
|
|
|
122
122
|
|
|
123
123
|
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.
|
|
124
124
|
|
|
125
|
+
Key modules ported from Claude Code's `src/memdir/`:
|
|
126
|
+
|
|
127
|
+
| Module | Source | Purpose |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `memoryScan.ts` | `memoryScan.ts` | Recursive directory scan + frontmatter header parsing |
|
|
130
|
+
| `recall.ts` | `findRelevantMemories.ts` | Memory recall via keyword scoring (heuristic, no LLM side-query) |
|
|
131
|
+
| `prompt.ts` | `memoryTypes.ts` + `memdir.ts` | System prompt sections, type taxonomy, truncation |
|
|
132
|
+
| `memory.ts` | `memdir.ts` | `truncateEntrypoint()` aligned with `truncateEntrypointContent()` |
|
|
133
|
+
|
|
125
134
|
## ๐ฅ Who this is for
|
|
126
135
|
|
|
127
136
|
- You use **both Claude Code and OpenCode**.
|
|
@@ -215,6 +224,16 @@ Supported memory types:
|
|
|
215
224
|
- `memory_search`: search by keyword
|
|
216
225
|
- `memory_read`: read full memory content
|
|
217
226
|
|
|
227
|
+
## ๐งช Development
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Run tests
|
|
231
|
+
bun test
|
|
232
|
+
|
|
233
|
+
# No build needed โ raw TS consumed by OpenCode
|
|
234
|
+
# Release: push to main triggers semantic-release โ npm publish
|
|
235
|
+
```
|
|
236
|
+
|
|
218
237
|
## ๐ License
|
|
219
238
|
|
|
220
239
|
[MIT](LICENSE) ยฉ [kuitos](https://github.com/kuitos)
|
package/bin/opencode-memory
CHANGED
|
@@ -40,16 +40,52 @@
|
|
|
40
40
|
|
|
41
41
|
set -euo pipefail
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
resolve_script_path() {
|
|
44
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
45
|
+
python3 - "${BASH_SOURCE[0]}" <<'PY'
|
|
46
|
+
import os
|
|
47
|
+
import sys
|
|
48
|
+
|
|
49
|
+
print(os.path.realpath(sys.argv[1]))
|
|
50
|
+
PY
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
printf '%s\n' "${BASH_SOURCE[0]}"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
SCRIPT_PATH="$(resolve_script_path)"
|
|
58
|
+
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)"
|
|
44
59
|
PACKAGE_JSON="$SCRIPT_DIR/../package.json"
|
|
45
60
|
|
|
61
|
+
resolve_package_json() {
|
|
62
|
+
if [ -f "$PACKAGE_JSON" ]; then
|
|
63
|
+
printf '%s\n' "$PACKAGE_JSON"
|
|
64
|
+
return 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if command -v npm >/dev/null 2>&1; then
|
|
68
|
+
local npm_root
|
|
69
|
+
npm_root=$(npm root -g 2>/dev/null || true)
|
|
70
|
+
if [ -n "$npm_root" ] && [ -f "$npm_root/opencode-claude-memory/package.json" ]; then
|
|
71
|
+
printf '%s\n' "$npm_root/opencode-claude-memory/package.json"
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
printf '%s\n' "$PACKAGE_JSON"
|
|
77
|
+
}
|
|
78
|
+
|
|
46
79
|
print_wrapper_version() {
|
|
47
80
|
local version
|
|
81
|
+
local package_json
|
|
82
|
+
|
|
83
|
+
package_json="$(resolve_package_json)"
|
|
48
84
|
|
|
49
|
-
version=$(awk -F'"' '/^[[:space:]]*"version"[[:space:]]*:/ { print $4; exit }' "$
|
|
85
|
+
version=$(awk -F'"' '/^[[:space:]]*"version"[[:space:]]*:/ { print $4; exit }' "$package_json")
|
|
50
86
|
|
|
51
87
|
if [ -z "$version" ]; then
|
|
52
|
-
echo "[opencode-memory] ERROR: Cannot read package version from $
|
|
88
|
+
echo "[opencode-memory] ERROR: Cannot read package version from $package_json" >&2
|
|
53
89
|
exit 1
|
|
54
90
|
fi
|
|
55
91
|
|
|
@@ -184,6 +220,35 @@ find_real_opencode() {
|
|
|
184
220
|
|
|
185
221
|
REAL_OPENCODE="$(find_real_opencode)"
|
|
186
222
|
|
|
223
|
+
is_opencode_subcommand() {
|
|
224
|
+
case "$1" in
|
|
225
|
+
completion|acp|mcp|attach|run|debug|providers|auth|agent|upgrade|uninstall|serve|web|models|stats|export|import|github|pr|session|plugin|plug|db)
|
|
226
|
+
return 0
|
|
227
|
+
;;
|
|
228
|
+
*)
|
|
229
|
+
return 1
|
|
230
|
+
;;
|
|
231
|
+
esac
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
extract_working_dir_from_args() {
|
|
235
|
+
local previous=""
|
|
236
|
+
for arg in "$@"; do
|
|
237
|
+
if [ "$previous" = "--dir" ]; then
|
|
238
|
+
printf '%s\n' "$arg"
|
|
239
|
+
return 0
|
|
240
|
+
fi
|
|
241
|
+
previous="$arg"
|
|
242
|
+
done
|
|
243
|
+
|
|
244
|
+
if [ "$#" -gt 0 ] && [ -n "${1:-}" ] && [[ "${1:-}" != -* ]] && ! is_opencode_subcommand "$1" && [ -d "$1" ]; then
|
|
245
|
+
printf '%s\n' "$1"
|
|
246
|
+
return 0
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
return 1
|
|
250
|
+
}
|
|
251
|
+
|
|
187
252
|
# ============================================================================
|
|
188
253
|
# Configuration
|
|
189
254
|
# ============================================================================
|
|
@@ -201,8 +266,9 @@ AUTODREAM_SCAN_LIMIT="${OPENCODE_MEMORY_AUTODREAM_SCAN_LIMIT:-200}"
|
|
|
201
266
|
AUTODREAM_MODEL="${OPENCODE_MEMORY_AUTODREAM_MODEL:-$EXTRACT_MODEL}"
|
|
202
267
|
AUTODREAM_AGENT="${OPENCODE_MEMORY_AUTODREAM_AGENT:-$EXTRACT_AGENT}"
|
|
203
268
|
AUTODREAM_STALE_LOCK_SECS=$((60 * 60))
|
|
269
|
+
SESSION_WAIT_SECONDS="${OPENCODE_MEMORY_SESSION_WAIT_SECONDS:-5}"
|
|
204
270
|
|
|
205
|
-
WORKING_DIR="${OPENCODE_MEMORY_DIR:-$(pwd)}"
|
|
271
|
+
WORKING_DIR="${OPENCODE_MEMORY_DIR:-$(extract_working_dir_from_args "$@" || pwd)}"
|
|
206
272
|
|
|
207
273
|
TMP_BASE_DIR="${TMPDIR:-/tmp}"
|
|
208
274
|
while [ "$TMP_BASE_DIR" != "/" ] && [ "${TMP_BASE_DIR%/}" != "$TMP_BASE_DIR" ]; do
|
|
@@ -407,6 +473,351 @@ get_latest_session_id() {
|
|
|
407
473
|
fi
|
|
408
474
|
}
|
|
409
475
|
|
|
476
|
+
get_session_target_id() {
|
|
477
|
+
local before_json="$1"
|
|
478
|
+
local started_at_ms="$2"
|
|
479
|
+
local workdir="$3"
|
|
480
|
+
local project_dir="$4"
|
|
481
|
+
local after_json
|
|
482
|
+
|
|
483
|
+
after_json=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT") || return 1
|
|
484
|
+
|
|
485
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
486
|
+
python3 - "$before_json" "$after_json" "$started_at_ms" "$workdir" "$project_dir" <<'PY'
|
|
487
|
+
import json
|
|
488
|
+
import os
|
|
489
|
+
import sys
|
|
490
|
+
|
|
491
|
+
before_raw, after_raw, started_at_ms_raw, workdir, project_dir = sys.argv[1:6]
|
|
492
|
+
|
|
493
|
+
def parse(raw):
|
|
494
|
+
try:
|
|
495
|
+
data = json.loads(raw)
|
|
496
|
+
return data if isinstance(data, list) else []
|
|
497
|
+
except Exception:
|
|
498
|
+
return []
|
|
499
|
+
|
|
500
|
+
def timestamp(item):
|
|
501
|
+
time_obj = item.get("time") if isinstance(item.get("time"), dict) else {}
|
|
502
|
+
for key in ("updated", "created"):
|
|
503
|
+
value = item.get(key)
|
|
504
|
+
if value is not None:
|
|
505
|
+
try:
|
|
506
|
+
return int(value)
|
|
507
|
+
except Exception:
|
|
508
|
+
pass
|
|
509
|
+
value = time_obj.get(key)
|
|
510
|
+
if value is not None:
|
|
511
|
+
try:
|
|
512
|
+
return int(value)
|
|
513
|
+
except Exception:
|
|
514
|
+
pass
|
|
515
|
+
return 0
|
|
516
|
+
|
|
517
|
+
def normalize(path):
|
|
518
|
+
if not path:
|
|
519
|
+
return ""
|
|
520
|
+
return os.path.realpath(path)
|
|
521
|
+
|
|
522
|
+
before = parse(before_raw)
|
|
523
|
+
after = parse(after_raw)
|
|
524
|
+
started_at_ms = int(started_at_ms_raw or "0")
|
|
525
|
+
before_ids = {item.get("id") for item in before if item.get("id")}
|
|
526
|
+
workdir = normalize(workdir)
|
|
527
|
+
project_dir = normalize(project_dir)
|
|
528
|
+
|
|
529
|
+
def in_scope(item):
|
|
530
|
+
directory = normalize(item.get("directory", ""))
|
|
531
|
+
return directory != "" and directory in {workdir, project_dir}
|
|
532
|
+
|
|
533
|
+
def choose(candidates):
|
|
534
|
+
ranked = sorted(
|
|
535
|
+
[item for item in candidates if item.get("id")],
|
|
536
|
+
key=lambda item: timestamp(item),
|
|
537
|
+
reverse=True,
|
|
538
|
+
)
|
|
539
|
+
if ranked:
|
|
540
|
+
print(ranked[0]["id"])
|
|
541
|
+
return True
|
|
542
|
+
return False
|
|
543
|
+
|
|
544
|
+
new_sessions = [item for item in after if item.get("id") not in before_ids]
|
|
545
|
+
updated_sessions = [item for item in after if timestamp(item) > started_at_ms]
|
|
546
|
+
|
|
547
|
+
for pool in (
|
|
548
|
+
[item for item in new_sessions if in_scope(item)],
|
|
549
|
+
[item for item in updated_sessions if in_scope(item)],
|
|
550
|
+
[item for item in after if in_scope(item)],
|
|
551
|
+
):
|
|
552
|
+
if choose(pool):
|
|
553
|
+
break
|
|
554
|
+
PY
|
|
555
|
+
return 0
|
|
556
|
+
fi
|
|
557
|
+
|
|
558
|
+
get_latest_session_id
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
get_transcripts_dir() {
|
|
562
|
+
printf '%s\n' "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/transcripts"
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
get_storage_session_diff_dir() {
|
|
566
|
+
printf '%s\n' "$HOME/.local/share/opencode/storage/session_diff"
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
resolve_session_directory() {
|
|
570
|
+
local session_id="$1"
|
|
571
|
+
local output_file
|
|
572
|
+
|
|
573
|
+
output_file=$(mktemp)
|
|
574
|
+
if ! "$REAL_OPENCODE" export "$session_id" >"$output_file" 2>/dev/null; then
|
|
575
|
+
rm -f "$output_file"
|
|
576
|
+
return 1
|
|
577
|
+
fi
|
|
578
|
+
|
|
579
|
+
if [ ! -s "$output_file" ]; then
|
|
580
|
+
rm -f "$output_file"
|
|
581
|
+
return 1
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
585
|
+
python3 - "$output_file" <<'PY'
|
|
586
|
+
import json
|
|
587
|
+
from pathlib import Path
|
|
588
|
+
import sys
|
|
589
|
+
|
|
590
|
+
raw = Path(sys.argv[1]).read_text()
|
|
591
|
+
start = raw.find('{')
|
|
592
|
+
if start == -1:
|
|
593
|
+
raise SystemExit(1)
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
data = json.loads(raw[start:])
|
|
597
|
+
except Exception:
|
|
598
|
+
raise SystemExit(1)
|
|
599
|
+
|
|
600
|
+
directory = ((data.get("info") or {}).get("directory") or "")
|
|
601
|
+
if directory:
|
|
602
|
+
print(directory)
|
|
603
|
+
PY
|
|
604
|
+
local status=$?
|
|
605
|
+
rm -f "$output_file"
|
|
606
|
+
return $status
|
|
607
|
+
fi
|
|
608
|
+
|
|
609
|
+
rm -f "$output_file"
|
|
610
|
+
return 1
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
get_scoped_artifact_session_id_since() {
|
|
614
|
+
local timestamp_file="$1"
|
|
615
|
+
local workdir="$2"
|
|
616
|
+
local project_dir="$3"
|
|
617
|
+
|
|
618
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
619
|
+
return 1
|
|
620
|
+
fi
|
|
621
|
+
|
|
622
|
+
local candidates
|
|
623
|
+
candidates=$(python3 - "$timestamp_file" "$HOME" "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" <<'PY'
|
|
624
|
+
import os
|
|
625
|
+
import sys
|
|
626
|
+
|
|
627
|
+
timestamp_file, home_dir, claude_dir = sys.argv[1:4]
|
|
628
|
+
|
|
629
|
+
try:
|
|
630
|
+
threshold = os.path.getmtime(timestamp_file)
|
|
631
|
+
except OSError:
|
|
632
|
+
raise SystemExit(1)
|
|
633
|
+
|
|
634
|
+
sources = [
|
|
635
|
+
(os.path.join(home_dir, '.local', 'share', 'opencode', 'storage', 'session_diff'), '.json'),
|
|
636
|
+
(os.path.join(claude_dir, 'transcripts'), '.jsonl'),
|
|
637
|
+
]
|
|
638
|
+
|
|
639
|
+
latest = {}
|
|
640
|
+
for directory, suffix in sources:
|
|
641
|
+
if not os.path.isdir(directory):
|
|
642
|
+
continue
|
|
643
|
+
for entry in os.scandir(directory):
|
|
644
|
+
if not entry.is_file() or not entry.name.endswith(suffix):
|
|
645
|
+
continue
|
|
646
|
+
try:
|
|
647
|
+
mtime = entry.stat().st_mtime
|
|
648
|
+
except OSError:
|
|
649
|
+
continue
|
|
650
|
+
if mtime <= threshold:
|
|
651
|
+
continue
|
|
652
|
+
session_id = entry.name[:-len(suffix)]
|
|
653
|
+
latest[session_id] = max(latest.get(session_id, -1), mtime)
|
|
654
|
+
|
|
655
|
+
for session_id, mtime in sorted(latest.items(), key=lambda item: item[1], reverse=True):
|
|
656
|
+
print(session_id)
|
|
657
|
+
PY
|
|
658
|
+
) || return 1
|
|
659
|
+
|
|
660
|
+
local session_id=""
|
|
661
|
+
local session_dir=""
|
|
662
|
+
local normalized_session_dir=""
|
|
663
|
+
local normalized_workdir=""
|
|
664
|
+
local normalized_project_dir=""
|
|
665
|
+
normalized_workdir=$(python3 - "$workdir" <<'PY'
|
|
666
|
+
import os, sys
|
|
667
|
+
print(os.path.realpath(sys.argv[1]))
|
|
668
|
+
PY
|
|
669
|
+
)
|
|
670
|
+
normalized_project_dir=$(python3 - "$project_dir" <<'PY'
|
|
671
|
+
import os, sys
|
|
672
|
+
print(os.path.realpath(sys.argv[1]))
|
|
673
|
+
PY
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
while IFS= read -r session_id; do
|
|
677
|
+
[ -n "$session_id" ] || continue
|
|
678
|
+
session_dir=$(resolve_session_directory "$session_id" || true)
|
|
679
|
+
[ -n "$session_dir" ] || continue
|
|
680
|
+
normalized_session_dir=$(python3 - "$session_dir" <<'PY'
|
|
681
|
+
import os, sys
|
|
682
|
+
print(os.path.realpath(sys.argv[1]))
|
|
683
|
+
PY
|
|
684
|
+
)
|
|
685
|
+
if [ "$normalized_session_dir" = "$normalized_workdir" ] || [ "$normalized_session_dir" = "$normalized_project_dir" ]; then
|
|
686
|
+
printf '%s\n' "$session_id"
|
|
687
|
+
return 0
|
|
688
|
+
fi
|
|
689
|
+
done <<EOF
|
|
690
|
+
$candidates
|
|
691
|
+
EOF
|
|
692
|
+
|
|
693
|
+
return 1
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
get_latest_storage_session_id_since() {
|
|
697
|
+
local timestamp_file="$1"
|
|
698
|
+
local storage_dir
|
|
699
|
+
storage_dir=$(get_storage_session_diff_dir)
|
|
700
|
+
|
|
701
|
+
if [ ! -d "$storage_dir" ]; then
|
|
702
|
+
return 1
|
|
703
|
+
fi
|
|
704
|
+
|
|
705
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
706
|
+
python3 - "$storage_dir" "$timestamp_file" <<'PY'
|
|
707
|
+
import os
|
|
708
|
+
import sys
|
|
709
|
+
|
|
710
|
+
storage_dir, timestamp_file = sys.argv[1:3]
|
|
711
|
+
|
|
712
|
+
try:
|
|
713
|
+
threshold = os.path.getmtime(timestamp_file)
|
|
714
|
+
except OSError:
|
|
715
|
+
raise SystemExit(1)
|
|
716
|
+
|
|
717
|
+
latest = None
|
|
718
|
+
latest_mtime = -1.0
|
|
719
|
+
|
|
720
|
+
for entry in os.scandir(storage_dir):
|
|
721
|
+
if not entry.is_file() or not entry.name.endswith('.json'):
|
|
722
|
+
continue
|
|
723
|
+
try:
|
|
724
|
+
mtime = entry.stat().st_mtime
|
|
725
|
+
except OSError:
|
|
726
|
+
continue
|
|
727
|
+
if mtime <= threshold:
|
|
728
|
+
continue
|
|
729
|
+
if mtime > latest_mtime:
|
|
730
|
+
latest_mtime = mtime
|
|
731
|
+
latest = entry.name[:-5]
|
|
732
|
+
|
|
733
|
+
if latest:
|
|
734
|
+
print(latest)
|
|
735
|
+
PY
|
|
736
|
+
return 0
|
|
737
|
+
fi
|
|
738
|
+
|
|
739
|
+
return 1
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
get_latest_transcript_session_id_since() {
|
|
743
|
+
local timestamp_file="$1"
|
|
744
|
+
local transcripts_dir
|
|
745
|
+
transcripts_dir=$(get_transcripts_dir)
|
|
746
|
+
|
|
747
|
+
if [ ! -d "$transcripts_dir" ]; then
|
|
748
|
+
return 1
|
|
749
|
+
fi
|
|
750
|
+
|
|
751
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
752
|
+
python3 - "$transcripts_dir" "$timestamp_file" <<'PY'
|
|
753
|
+
import os
|
|
754
|
+
import sys
|
|
755
|
+
|
|
756
|
+
transcripts_dir, timestamp_file = sys.argv[1:3]
|
|
757
|
+
|
|
758
|
+
try:
|
|
759
|
+
threshold = os.path.getmtime(timestamp_file)
|
|
760
|
+
except OSError:
|
|
761
|
+
raise SystemExit(1)
|
|
762
|
+
|
|
763
|
+
latest = None
|
|
764
|
+
latest_mtime = -1.0
|
|
765
|
+
|
|
766
|
+
for entry in os.scandir(transcripts_dir):
|
|
767
|
+
if not entry.is_file() or not entry.name.endswith('.jsonl'):
|
|
768
|
+
continue
|
|
769
|
+
try:
|
|
770
|
+
mtime = entry.stat().st_mtime
|
|
771
|
+
except OSError:
|
|
772
|
+
continue
|
|
773
|
+
if mtime <= threshold:
|
|
774
|
+
continue
|
|
775
|
+
if mtime > latest_mtime:
|
|
776
|
+
latest_mtime = mtime
|
|
777
|
+
latest = entry.name[:-6]
|
|
778
|
+
|
|
779
|
+
if latest:
|
|
780
|
+
print(latest)
|
|
781
|
+
PY
|
|
782
|
+
return 0
|
|
783
|
+
fi
|
|
784
|
+
|
|
785
|
+
return 1
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
main_prompt_requests_ignore_memory() {
|
|
789
|
+
if [ "${1:-}" != "run" ]; then
|
|
790
|
+
return 1
|
|
791
|
+
fi
|
|
792
|
+
|
|
793
|
+
local joined
|
|
794
|
+
joined=$(printf '%s\n' "$*" | tr '[:upper:]' '[:lower:]')
|
|
795
|
+
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
|
+
}
|
|
797
|
+
|
|
798
|
+
wait_for_session_target_id() {
|
|
799
|
+
local before_json="$1"
|
|
800
|
+
local started_at_ms="$2"
|
|
801
|
+
local wait_seconds="${3:-5}"
|
|
802
|
+
local attempt=0
|
|
803
|
+
local session_id=""
|
|
804
|
+
|
|
805
|
+
while [ "$attempt" -lt "$wait_seconds" ]; do
|
|
806
|
+
session_id=$(get_session_target_id "$before_json" "$started_at_ms" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" || true)
|
|
807
|
+
if [ -z "$session_id" ]; then
|
|
808
|
+
session_id=$(get_scoped_artifact_session_id_since "$TIMESTAMP_FILE" "$WORKING_DIR" "$PROJECT_SCOPE_DIR" || true)
|
|
809
|
+
fi
|
|
810
|
+
if [ -n "$session_id" ]; then
|
|
811
|
+
printf '%s\n' "$session_id"
|
|
812
|
+
return 0
|
|
813
|
+
fi
|
|
814
|
+
sleep 1
|
|
815
|
+
attempt=$((attempt + 1))
|
|
816
|
+
done
|
|
817
|
+
|
|
818
|
+
return 1
|
|
819
|
+
}
|
|
820
|
+
|
|
410
821
|
file_mtime_secs() {
|
|
411
822
|
local file="$1"
|
|
412
823
|
if [ ! -f "$file" ]; then
|
|
@@ -565,7 +976,7 @@ run_extraction_if_needed() {
|
|
|
565
976
|
log "Extracting memories from session $session_id..."
|
|
566
977
|
log "Extraction log: $EXTRACT_LOG_FILE"
|
|
567
978
|
|
|
568
|
-
local cmd=("$REAL_OPENCODE" run -s "$session_id" --fork)
|
|
979
|
+
local cmd=("$REAL_OPENCODE" run -s "$session_id" --fork --dir "$WORKING_DIR")
|
|
569
980
|
if [ -n "$EXTRACT_MODEL" ]; then
|
|
570
981
|
cmd+=(-m "$EXTRACT_MODEL")
|
|
571
982
|
fi
|
|
@@ -623,7 +1034,7 @@ run_autodream_if_needed() {
|
|
|
623
1034
|
log "Auto-dream firing (${hours_since}h since last consolidation, ${touched_count} sessions touched)"
|
|
624
1035
|
log "Auto-dream log: $AUTODREAM_LOG_FILE"
|
|
625
1036
|
|
|
626
|
-
local cmd=("$REAL_OPENCODE" run -s "$session_id" --fork)
|
|
1037
|
+
local cmd=("$REAL_OPENCODE" run -s "$session_id" --fork --dir "$WORKING_DIR")
|
|
627
1038
|
if [ -n "$AUTODREAM_MODEL" ]; then
|
|
628
1039
|
cmd+=(-m "$AUTODREAM_MODEL")
|
|
629
1040
|
fi
|
|
@@ -657,10 +1068,16 @@ run_post_session_tasks() {
|
|
|
657
1068
|
|
|
658
1069
|
# Step 0: Create timestamp marker before running opencode
|
|
659
1070
|
TIMESTAMP_FILE=$(mktemp)
|
|
1071
|
+
SESSION_CAPTURE_STARTED_AT_MS=$(( $(date +%s) * 1000 ))
|
|
1072
|
+
PRE_SESSION_JSON=$(get_session_list_json "$AUTODREAM_SCAN_LIMIT" 2>/dev/null || true)
|
|
660
1073
|
|
|
661
1074
|
# Step 1: Run the real opencode with all original arguments, capture exit code
|
|
662
1075
|
opencode_exit=0
|
|
663
|
-
|
|
1076
|
+
if main_prompt_requests_ignore_memory "$@"; then
|
|
1077
|
+
OPENCODE_MEMORY_IGNORE=1 "$REAL_OPENCODE" "$@" || opencode_exit=$?
|
|
1078
|
+
else
|
|
1079
|
+
"$REAL_OPENCODE" "$@" || opencode_exit=$?
|
|
1080
|
+
fi
|
|
664
1081
|
|
|
665
1082
|
# Step 2: If no maintenance task is enabled, exit early
|
|
666
1083
|
if [ "$EXTRACT_ENABLED" = "0" ] && [ "$AUTODREAM_ENABLED" = "0" ]; then
|
|
@@ -668,8 +1085,8 @@ if [ "$EXTRACT_ENABLED" = "0" ] && [ "$AUTODREAM_ENABLED" = "0" ]; then
|
|
|
668
1085
|
exit $opencode_exit
|
|
669
1086
|
fi
|
|
670
1087
|
|
|
671
|
-
# Step 3:
|
|
672
|
-
session_id=$(
|
|
1088
|
+
# Step 3: Capture the session ID for this invocation
|
|
1089
|
+
session_id=$(wait_for_session_target_id "$PRE_SESSION_JSON" "$SESSION_CAPTURE_STARTED_AT_MS" "$SESSION_WAIT_SECONDS" || true)
|
|
673
1090
|
if [ -z "$session_id" ]; then
|
|
674
1091
|
log "No session found, skipping post-session memory maintenance"
|
|
675
1092
|
cleanup_timestamp
|
package/package.json
CHANGED