@remnic/plugin-codex 9.3.612 → 9.3.614

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.
@@ -1,257 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Remnic SessionStart hook for Codex.
3
- # Recalls project context and user preferences at session start.
4
- # Tries auto mode (45s) then falls back to minimal mode (20s).
5
- # Starts daemon if not running.
6
-
7
- set -euo pipefail
8
-
9
- ensure_migrated() {
10
- if [ -f "${HOME}/.remnic/.migrated-from-engram" ]; then
11
- return 0
12
- fi
13
- if [ ! -d "${HOME}/.engram" ] && [ ! -f "${HOME}/.config/engram/config.json" ]; then
14
- return 0
15
- fi
16
- if command -v remnic >/dev/null 2>&1; then
17
- remnic migrate >/dev/null 2>&1 || true
18
- elif command -v engram >/dev/null 2>&1; then
19
- engram migrate >/dev/null 2>&1 || true
20
- fi
21
- }
22
-
23
- ensure_migrated
24
-
25
- REMNIC_HOST="${REMNIC_HOST:-${ENGRAM_HOST:-127.0.0.1}}"
26
- REMNIC_PORT="${REMNIC_PORT:-${ENGRAM_PORT:-4318}}"
27
- REMNIC_URL="http://${REMNIC_HOST}:${REMNIC_PORT}/engram/v1/recall"
28
- REMNIC_HEALTH_URL="http://${REMNIC_HOST}:${REMNIC_PORT}/engram/v1/health"
29
- TOKEN_FILES=("${HOME}/.remnic/tokens.json" "${HOME}/.engram/tokens.json")
30
-
31
- LOG="${HOME}/.remnic/logs/remnic-session-recall.log"
32
- mkdir -p "$(dirname "$LOG")"
33
- log() { echo "$(date '+%F %T') [codex-session-start] $*" >> "$LOG"; }
34
-
35
- # Read token from per-plugin token store
36
- REMNIC_TOKEN=""
37
- for TOKEN_FILE in "${TOKEN_FILES[@]}"; do
38
- [ ! -f "$TOKEN_FILE" ] && continue
39
- REMNIC_TOKEN="$(node -e "
40
- const store = JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));
41
- const tokens = store.tokens || [];
42
- const cxc = tokens.find(t => t.connector === 'codex-cli');
43
- const cx = tokens.find(t => t.connector === 'codex');
44
- const oc = tokens.find(t => t.connector === 'openclaw');
45
- let tok = (cxc && cxc.token) || (cx && cx.token) || (oc && oc.token) || '';
46
- if (!tok) { tok = store['codex-cli'] || store['codex'] || store['openclaw'] || ''; }
47
- process.stdout.write(tok);
48
- " "$TOKEN_FILE" 2>/dev/null || echo "")"
49
- [ -n "$REMNIC_TOKEN" ] && break
50
- done
51
-
52
- # Fallback to env var
53
- [ -z "$REMNIC_TOKEN" ] && REMNIC_TOKEN="${OPENCLAW_REMNIC_ACCESS_TOKEN:-${OPENCLAW_ENGRAM_ACCESS_TOKEN:-}}"
54
-
55
- INPUT="$(cat)"
56
- SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.session_id||'')" "$INPUT" 2>/dev/null || echo "")"
57
- CWD="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.cwd||'')" "$INPUT" 2>/dev/null || echo "")"
58
- PROJECT_NAME="$(basename "$CWD" 2>/dev/null || echo "unknown")"
59
-
60
- # Resolve git context for the session's cwd (issue #569 PR 6).
61
- # Produces either a JSON `codingContext` object to embed in the recall
62
- # request, or an empty string when the cwd is not inside a git repo.
63
- # Any failure silently drops back to no-context (CLAUDE.md #30 escape hatch).
64
- CODING_CONTEXT_JSON=""
65
- if [ -n "$CWD" ] && [ -d "$CWD" ] && command -v git >/dev/null 2>&1; then
66
- REMNIC_GIT_TOP="$(git -C "$CWD" rev-parse --show-toplevel 2>/dev/null || echo "")"
67
- if [ -n "$REMNIC_GIT_TOP" ]; then
68
- REMNIC_GIT_BRANCH="$(git -C "$REMNIC_GIT_TOP" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")"
69
- [ "$REMNIC_GIT_BRANCH" = "HEAD" ] && REMNIC_GIT_BRANCH=""
70
- REMNIC_GIT_ORIGIN="$(git -C "$REMNIC_GIT_TOP" remote get-url origin 2>/dev/null || echo "")"
71
- REMNIC_GIT_DEFAULT_BRANCH="$(git -C "$REMNIC_GIT_TOP" symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's|^refs/remotes/origin/||' || echo "")"
72
- CODING_CONTEXT_JSON="$(REMNIC_GIT_TOP="$REMNIC_GIT_TOP" REMNIC_GIT_BRANCH="$REMNIC_GIT_BRANCH" REMNIC_GIT_ORIGIN="$REMNIC_GIT_ORIGIN" REMNIC_GIT_DEFAULT_BRANCH="$REMNIC_GIT_DEFAULT_BRANCH" node -e "
73
- // Mirror @remnic/core's resolveGitContext for projectId derivation.
74
- // FNV-1a 32-bit stable hash.
75
- const rootPath = process.env.REMNIC_GIT_TOP || '';
76
- const branch = process.env.REMNIC_GIT_BRANCH || null;
77
- const origin = process.env.REMNIC_GIT_ORIGIN || '';
78
- const defaultBranch = process.env.REMNIC_GIT_DEFAULT_BRANCH || null;
79
- function stableHash(input) {
80
- let hash = 0x811c9dc5;
81
- for (let i = 0; i < input.length; i++) {
82
- hash ^= input.charCodeAt(i);
83
- hash = Math.imul(hash, 0x01000193) >>> 0;
84
- }
85
- return hash.toString(16).padStart(8, '0');
86
- }
87
- // Mirrors packages/remnic-core/src/coding/git-context.ts
88
- // normalizeOriginUrl. Keep the two in sync so the hook-computed
89
- // projectId matches what the daemon computes on the same origin.
90
- function normalizeOriginUrl(raw) {
91
- let u = (raw || '').trim();
92
- if (!u) return '';
93
- // Case-insensitive .git strip — matches the TS canonical form.
94
- if (/\\.git\$/i.test(u)) u = u.slice(0, -4);
95
- // Windows drive-letter: short-circuit scp parsing.
96
- if (/^[A-Za-z]:[\\\\/]/.test(u)) return u.toLowerCase();
97
- // Protocol form: handles ssh://, https://, file:///, bracketed
98
- // IPv6 hosts, optional user, optional port, and empty host
99
- // (file:///path).
100
- const proto = /^[a-z][a-z0-9+.-]*:\\/\\/(?:[^@/]+@)?(\\[[^\\]]+\\]|[^/:]*)(?::(\\d+))?(\\/.*)?\$/i.exec(u);
101
- if (proto) {
102
- let host = proto[1] || '';
103
- const wasBracketed = host.startsWith('[') && host.endsWith(']');
104
- if (wasBracketed) host = host.slice(1, -1);
105
- const port = proto[2];
106
- const p = (proto[3] || '').replace(/^\\/+/, '');
107
- const hostPort = port
108
- ? (wasBracketed ? '[' + host + ']:' + port : host + ':' + port)
109
- : host;
110
- const prefix = hostPort.length > 0 ? hostPort : 'localhost';
111
- return (prefix + '/' + p).toLowerCase();
112
- }
113
- // scp form: [user@]host:path — user@ optional, bracketed IPv6 host
114
- // supported. A matched path starting with // is a protocol-URL
115
- // leftover and is rejected.
116
- const scp = /^(?:([^@\\s\\/]+)@)?(\\[[^\\]]+\\]|[^:@\\s\\/]+):(.+)\$/.exec(u);
117
- if (scp) {
118
- let host = scp[2] || '';
119
- if (host.startsWith('[') && host.endsWith(']')) host = host.slice(1, -1);
120
- const p = scp[3] || '';
121
- if (p.startsWith('//')) return u.toLowerCase();
122
- return (host + '/' + p.replace(/^\\/+/, '')).toLowerCase();
123
- }
124
- return u.toLowerCase();
125
- }
126
- const normalized = normalizeOriginUrl(origin);
127
- const projectId = normalized ? 'origin:' + stableHash(normalized) : 'root:' + stableHash(rootPath);
128
- process.stdout.write(JSON.stringify({
129
- projectId,
130
- branch: branch || null,
131
- rootPath,
132
- defaultBranch: defaultBranch || null,
133
- }));
134
- " 2>/dev/null || echo "")"
135
- fi
136
- fi
137
-
138
- log "session=$SESSION_ID project=$PROJECT_NAME coding-context=${CODING_CONTEXT_JSON:+yes}"
139
-
140
- # Health check — start daemon if not running
141
- if ! curl -sf --max-time 2 "$REMNIC_HEALTH_URL" >/dev/null 2>&1; then
142
- log "daemon not responding, attempting start..."
143
- if command -v remnic >/dev/null 2>&1; then
144
- remnic daemon start >/dev/null 2>&1 &
145
- elif command -v engram >/dev/null 2>&1; then
146
- engram daemon start >/dev/null 2>&1 &
147
- fi
148
- sleep 2
149
- if ! curl -sf --max-time 2 "$REMNIC_HEALTH_URL" >/dev/null 2>&1; then
150
- log "daemon still not responding after start attempt"
151
- echo '{"continue":true,"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"[Remnic: daemon not running — start with: remnic daemon start]"}}'
152
- exit 0
153
- fi
154
- fi
155
-
156
- if [ -z "$REMNIC_TOKEN" ]; then
157
- log "skipping: no token found"
158
- echo '{"continue":true,"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"[Remnic: no auth token — run: remnic connectors install codex-cli]"}}'
159
- exit 0
160
- fi
161
-
162
- QUERY="Starting a new coding session in project: ${PROJECT_NAME}. Recall relevant memories, preferences, decisions, patterns, and context about this project and the user."
163
-
164
- REQUEST_BODY="$(REMNIC_CODING_CONTEXT_JSON="$CODING_CONTEXT_JSON" node -e "
165
- const body = {
166
- query: process.argv[1],
167
- sessionKey: process.argv[2],
168
- topK: 12,
169
- mode: 'auto',
170
- };
171
- const raw = process.env.REMNIC_CODING_CONTEXT_JSON || '';
172
- if (raw) {
173
- try { body.codingContext = JSON.parse(raw); } catch (_) {
174
- // Context envelope was provided but failed to parse. Explicitly
175
- // clear any previously-attached context for this session so a
176
- // malformed envelope does not silently keep stale state.
177
- body.codingContext = null;
178
- }
179
- } else {
180
- // No git context resolvable for this cwd. Explicitly clear any
181
- // previously-attached context so a session that moves out of a repo
182
- // does not keep routing to the old project namespace.
183
- body.codingContext = null;
184
- }
185
- process.stdout.write(JSON.stringify(body));
186
- " "$QUERY" "$SESSION_ID" 2>/dev/null)"
187
-
188
- [ -z "$REQUEST_BODY" ] && echo '{"continue":true}' && exit 0
189
-
190
- log "attempting full recall (auto mode)..."
191
- RAW="$(curl -s -w "\n%{http_code}" --max-time 45 \
192
- -X POST "$REMNIC_URL" \
193
- -H "Authorization: Bearer ${REMNIC_TOKEN}" \
194
- -H "Content-Type: application/json" \
195
- -H "X-Engram-Client-Id: codex" \
196
- -d "$REQUEST_BODY" 2>/dev/null)"
197
- CURL_EXIT=$?
198
- HTTP_STATUS="$(echo "$RAW" | tail -1)"
199
- RESPONSE="$(echo "$RAW" | sed '$d')"
200
-
201
- if [ $CURL_EXIT -ne 0 ] || ! [[ "$HTTP_STATUS" =~ ^2 ]] || [ -z "$RESPONSE" ]; then
202
- log "full recall failed (curl=$CURL_EXIT http=$HTTP_STATUS) — falling back to minimal"
203
- MINIMAL_BODY="$(REMNIC_CODING_CONTEXT_JSON="$CODING_CONTEXT_JSON" node -e "
204
- const body = {
205
- query: process.argv[1],
206
- sessionKey: process.argv[2],
207
- topK: 8,
208
- mode: 'minimal',
209
- };
210
- const raw = process.env.REMNIC_CODING_CONTEXT_JSON || '';
211
- if (raw) {
212
- try { body.codingContext = JSON.parse(raw); } catch (_) {
213
- body.codingContext = null;
214
- }
215
- } else {
216
- body.codingContext = null;
217
- }
218
- process.stdout.write(JSON.stringify(body));
219
- " "$QUERY" "$SESSION_ID" 2>/dev/null)"
220
- RAW="$(curl -s -w "\n%{http_code}" --max-time 20 \
221
- -X POST "$REMNIC_URL" \
222
- -H "Authorization: Bearer ${REMNIC_TOKEN}" \
223
- -H "Content-Type: application/json" \
224
- -H "X-Engram-Client-Id: codex" \
225
- -d "${MINIMAL_BODY:-$REQUEST_BODY}" 2>/dev/null)"
226
- CURL_EXIT=$?
227
- HTTP_STATUS="$(echo "$RAW" | tail -1)"
228
- RESPONSE="$(echo "$RAW" | sed '$d')"
229
- [[ "$CURL_EXIT" -eq 0 && "$HTTP_STATUS" =~ ^2 ]] && log "minimal recall succeeded" || { log "minimal recall also failed"; CURL_EXIT=1; }
230
- fi
231
-
232
- if [ $CURL_EXIT -eq 0 ] && [[ "$HTTP_STATUS" =~ ^2 ]] && [ -n "$RESPONSE" ]; then
233
- CONTEXT="$(node -e "
234
- const d = JSON.parse(process.argv[1]);
235
- const ctx = d.context || '';
236
- const count = d.count || 0;
237
- const mode = d.mode || '';
238
- if (ctx) {
239
- const label = '[Remnic Memory Recall — ' + count + ' memories' + (mode ? ', ' + mode + ' mode' : '') + ']';
240
- process.stdout.write(label + '\n\n' + ctx);
241
- } else {
242
- process.stdout.write('[Remnic: no relevant memories found for this session]');
243
- }
244
- " "$RESPONSE" 2>/dev/null || echo "[Remnic: recall parse error]")"
245
- log "recall complete: $(echo "$CONTEXT" | head -1)"
246
- else
247
- CONTEXT="[Remnic: server unreachable — continuing without memory recall]"
248
- log "$CONTEXT"
249
- fi
250
-
251
- node -e "
252
- const context = process.argv[1];
253
- process.stdout.write(JSON.stringify({
254
- continue: true,
255
- hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: context }
256
- }));
257
- " "$CONTEXT"
@@ -1,114 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Remnic UserPromptSubmit hook for Codex.
3
- # Recalls per-prompt context using the user's message as query.
4
- # Skips short prompts (<4 words). Minimal mode, 20s timeout.
5
-
6
- set -euo pipefail
7
-
8
- ensure_migrated() {
9
- if [ -f "${HOME}/.remnic/.migrated-from-engram" ]; then
10
- return 0
11
- fi
12
- if [ ! -d "${HOME}/.engram" ] && [ ! -f "${HOME}/.config/engram/config.json" ]; then
13
- return 0
14
- fi
15
- if command -v remnic >/dev/null 2>&1; then
16
- remnic migrate >/dev/null 2>&1 || true
17
- elif command -v engram >/dev/null 2>&1; then
18
- engram migrate >/dev/null 2>&1 || true
19
- fi
20
- }
21
-
22
- ensure_migrated
23
-
24
- REMNIC_HOST="${REMNIC_HOST:-${ENGRAM_HOST:-127.0.0.1}}"
25
- REMNIC_PORT="${REMNIC_PORT:-${ENGRAM_PORT:-4318}}"
26
- REMNIC_URL="http://${REMNIC_HOST}:${REMNIC_PORT}/engram/v1/recall"
27
-
28
- LOG="${HOME}/.remnic/logs/remnic-user-prompt-recall.log"
29
- mkdir -p "$(dirname "$LOG")"
30
- log() { echo "$(date '+%F %T') [codex-user-prompt] $*" >> "$LOG"; }
31
-
32
- # Read token
33
- REMNIC_TOKEN=""
34
- for TOKEN_FILE in "${HOME}/.remnic/tokens.json" "${HOME}/.engram/tokens.json"; do
35
- [ ! -f "$TOKEN_FILE" ] && continue
36
- REMNIC_TOKEN="$(node -e "
37
- const fs = require('fs');
38
- const tokenFile = process.argv[1];
39
- const store = JSON.parse(fs.readFileSync(tokenFile, 'utf8'));
40
- const tokens = store.tokens || [];
41
- const cxc = tokens.find(t => t.connector === 'codex-cli');
42
- const cx = tokens.find(t => t.connector === 'codex');
43
- const oc = tokens.find(t => t.connector === 'openclaw');
44
- let tok = (cxc && cxc.token) || (cx && cx.token) || (oc && oc.token) || '';
45
- if (!tok) { tok = store['codex-cli'] || store['codex'] || store['openclaw'] || ''; }
46
- process.stdout.write(tok);
47
- " "$TOKEN_FILE" 2>/dev/null || echo "")"
48
- [ -n "$REMNIC_TOKEN" ] && break
49
- done
50
- [ -z "$REMNIC_TOKEN" ] && REMNIC_TOKEN="${OPENCLAW_REMNIC_ACCESS_TOKEN:-${OPENCLAW_ENGRAM_ACCESS_TOKEN:-}}"
51
-
52
- INPUT="$(cat)"
53
-
54
- if [ -z "$REMNIC_TOKEN" ]; then
55
- echo '{"continue":true}'
56
- exit 0
57
- fi
58
-
59
- SESSION_ID="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.session_id||'')" "$INPUT" 2>/dev/null || echo "")"
60
- PROMPT="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(d.prompt||'')" "$INPUT" 2>/dev/null || echo "")"
61
-
62
- # Skip very short prompts
63
- WORD_COUNT="$(echo "$PROMPT" | wc -w | tr -d ' ')"
64
- if [ "$WORD_COUNT" -lt 4 ]; then
65
- echo '{"continue":true}'
66
- exit 0
67
- fi
68
-
69
- log "session=$SESSION_ID words=$WORD_COUNT"
70
-
71
- REQUEST_BODY="$(node -e "process.stdout.write(JSON.stringify({
72
- query: process.argv[1],
73
- sessionKey: process.argv[2],
74
- topK: 8,
75
- mode: 'minimal'
76
- }))" "$PROMPT" "$SESSION_ID" 2>/dev/null)"
77
-
78
- [ -z "$REQUEST_BODY" ] && echo '{"continue":true}' && exit 0
79
-
80
- RAW="$(curl -s -w "\n%{http_code}" --max-time 20 \
81
- -X POST "$REMNIC_URL" \
82
- -H "Authorization: Bearer ${REMNIC_TOKEN}" \
83
- -H "Content-Type: application/json" \
84
- -H "X-Engram-Client-Id: codex" \
85
- -d "$REQUEST_BODY" 2>/dev/null)"
86
- CURL_EXIT=$?
87
- HTTP_STATUS="$(echo "$RAW" | tail -1)"
88
- RESPONSE="$(echo "$RAW" | sed '$d')"
89
-
90
- if [ $CURL_EXIT -ne 0 ] || ! [[ "$HTTP_STATUS" =~ ^2 ]] || [ -z "$RESPONSE" ]; then
91
- log "recall failed (curl=$CURL_EXIT http=$HTTP_STATUS)"
92
- echo '{"continue":true}'
93
- exit 0
94
- fi
95
-
96
- node -e "
97
- const d = JSON.parse(process.argv[1]);
98
- const ctx = d.context || '';
99
- const count = d.count || 0;
100
- if (!ctx || count === 0) {
101
- process.stdout.write(JSON.stringify({continue: true}));
102
- } else {
103
- process.stdout.write(JSON.stringify({
104
- continue: true,
105
- hookSpecificOutput: {
106
- hookEventName: 'UserPromptSubmit',
107
- additionalContext: '<remnic-memory count=\"' + count + '\">\n' + ctx + '\n</remnic-memory>'
108
- }
109
- }));
110
- }
111
- " "$RESPONSE" 2>/dev/null || echo '{"continue":true}'
112
-
113
- COUNT="$(node -e "const d=JSON.parse(process.argv[1]); process.stdout.write(String(d.count||0))" "$RESPONSE" 2>/dev/null || echo "?")"
114
- log "done: ${COUNT} memories injected"