memoryrouter-codex 1.0.0 → 1.1.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 -13
- package/RESEARCH.md +6 -2
- package/lib/hooks.cjs +0 -1
- package/lib/installer.cjs +3 -2
- package/package.json +1 -1
- package/runtime/config.cjs +0 -5
- package/runtime/constants.cjs +1 -5
- package/runtime/hook.cjs +18 -68
package/README.md
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Native Codex integration for durable MemoryRouter memory. It uses Codex's current MCP, lifecycle hooks, and `AGENTS.md` surfaces rather than pretending that model-selected MCP calls alone are automatic memory.
|
|
4
4
|
|
|
5
|
+
> **Release status:** `memoryrouter-codex@1.1.0` is the published npm release. Its automatic capture is message-only: one user request plus the final assistant response, with no tool payloads.
|
|
6
|
+
|
|
5
7
|
## What this integration does
|
|
6
8
|
|
|
7
9
|
| Layer | Behavior |
|
|
8
10
|
| --- | --- |
|
|
9
11
|
| `UserPromptSubmit` hook | Searches MemoryRouter before every prompt and injects bounded, project-scoped context as developer context. |
|
|
10
|
-
| `
|
|
12
|
+
| `Stop` hook | Captures only the user request and final assistant response after each completed turn. Tool names, inputs, and results are excluded. |
|
|
11
13
|
| `SubagentStop` hook | Captures the stable final outcome of completed subagents without parsing their unstable transcript files. |
|
|
12
|
-
| `PreCompact` / compact `SessionStart` | Checkpoints
|
|
13
|
-
| `SessionEnd` | Best-effort final checkpoint within Codex's three-second SessionEnd budget. |
|
|
14
|
+
| `PreCompact` / compact `SessionStart` | Checkpoints only the current user request and final assistant response when present, then recalls context before Codex continues after compaction. |
|
|
15
|
+
| `SessionEnd` | Best-effort message-only final checkpoint within Codex's three-second SessionEnd budget. |
|
|
14
16
|
| Remote MCP | OAuth-based explicit search/store through `https://mcp.memoryrouter.ai/mcp`. |
|
|
15
17
|
| Local MCP fallback | API-key-authenticated stdio tools with strict project filtering, handles, local suppression, status, and full-vault clear confirmation. |
|
|
16
18
|
| `AGENTS.md` | Tells Codex when to recall/store and how MemoryRouter coexists with native Memories. |
|
|
@@ -37,7 +39,7 @@ Obtain a MemoryRouter memory key from your account. Pass it on stdin so it does
|
|
|
37
39
|
|
|
38
40
|
```bash
|
|
39
41
|
printf '%s' "$MEMORYROUTER_API_KEY" | \
|
|
40
|
-
npx -y memoryrouter-codex@1.
|
|
42
|
+
npx -y memoryrouter-codex@1.1.0 init --scope user --auth oauth --api-key-stdin
|
|
41
43
|
|
|
42
44
|
codex mcp login memoryrouter
|
|
43
45
|
memoryrouter-codex doctor --scope user
|
|
@@ -59,7 +61,7 @@ If you prefer not to persist the key, omit `--api-key-stdin` and ensure `MEMORYR
|
|
|
59
61
|
```bash
|
|
60
62
|
cd /path/to/repository
|
|
61
63
|
printf '%s' "$MEMORYROUTER_API_KEY" | \
|
|
62
|
-
npx -y memoryrouter-codex@1.
|
|
64
|
+
npx -y memoryrouter-codex@1.1.0 init --scope project --auth oauth --api-key-stdin
|
|
63
65
|
codex mcp login memoryrouter
|
|
64
66
|
```
|
|
65
67
|
|
|
@@ -69,7 +71,7 @@ This writes managed blocks/files under the repository's `AGENTS.md` and `.codex/
|
|
|
69
71
|
|
|
70
72
|
```bash
|
|
71
73
|
printf '%s' "$MEMORYROUTER_API_KEY" | \
|
|
72
|
-
npx -y memoryrouter-codex@1.
|
|
74
|
+
npx -y memoryrouter-codex@1.1.0 init --scope user --auth api-key --api-key-stdin
|
|
73
75
|
```
|
|
74
76
|
|
|
75
77
|
This uses a local stdio MCP server and the MemoryRouter API. It does not put the memory key in Codex config. The fallback tools are:
|
|
@@ -126,6 +128,10 @@ Automatic memories use an envelope containing project handle, stable memory hand
|
|
|
126
128
|
|
|
127
129
|
Set `retrievalScope` to `"vault"` in the private config only if you intentionally want unscoped automatic recall.
|
|
128
130
|
|
|
131
|
+
## Historical import boundary
|
|
132
|
+
|
|
133
|
+
The Codex package captures future work after installation. It does not ship a deterministic importer for ChatGPT exports or prior Codex conversations. A local Codex workflow can inspect an extracted export and curate selected facts, but that is experimental guided curation, not checkpointed/resumable/reconciled archive ingestion. Remote MCP OAuth connection by itself never imports history.
|
|
134
|
+
|
|
129
135
|
## Handles, deduplication, retries, and forgetting
|
|
130
136
|
|
|
131
137
|
- Handles (`mrh_...`) are content-derived from project, kind, and normalized content.
|
|
@@ -142,7 +148,7 @@ Set `retrievalScope` to `"vault"` in the private config only if you intentionall
|
|
|
142
148
|
- Hook `additionalContextLimit`: approximately 3,500 tokens, so Codex can spill unexpectedly large context safely instead of consuming the whole model window.
|
|
143
149
|
- Candidate search default: 12 before strict project filtering.
|
|
144
150
|
- All hook entrypoints exit zero on missing credentials, malformed input, API errors, and timeouts. Memory never blocks Codex work.
|
|
145
|
-
- Hook status is recorded without secrets in `~/.memoryrouter/codex/health.json`. Search/store/forget outcomes (metadata only, never memory content or keys) append to a rotating 5 MiB local `~/.memoryrouter/codex/audit.jsonl`. `memory_status` exposes both locations. This is a host-local integration audit, not a server-wide team access log. Session
|
|
151
|
+
- Hook status is recorded without secrets in `~/.memoryrouter/codex/health.json`. Search/store/forget outcomes (metadata only, never memory content or keys) append to a rotating 5 MiB local `~/.memoryrouter/codex/audit.jsonl`. `memory_status` exposes both locations. This is a host-local integration audit, not a server-wide team access log. Session state and any legacy pre-1.1 trace files older than 30 days are pruned at session start. Version 1.1 creates no tool trace files.
|
|
146
152
|
- Set `MEMORYROUTER_DEBUG=1` for hook errors on stderr. Debug is off by default.
|
|
147
153
|
|
|
148
154
|
## Codex native Memories coexistence
|
|
@@ -172,7 +178,7 @@ Live verification with official `@openai/codex` **0.146.0** established:
|
|
|
172
178
|
5. Codex generates an authorization URL with PKCE S256, `memories:read memories:write`, the exact RFC 8707 resource, and a loopback callback.
|
|
173
179
|
6. The browser consent/token step was deliberately not completed by the package test because it is account-specific. Therefore `doctor` verifies discovery compatibility but never reports an OAuth session as logged in without user consent. Use `codex mcp login memoryrouter` and `/mcp verbose` to complete/verify it.
|
|
174
180
|
|
|
175
|
-
The
|
|
181
|
+
The production server supports modern stateless MCP `2026-07-28` and still accepts `2025-11-25`, `2025-06-18`, and `2025-03-26` for handshake-era clients. Codex 0.146 initializes the compatibility path successfully. Do not infer Codex support from protocol negotiation alone; verify OAuth login, `/mcp verbose`, and the actual tool inventory.
|
|
176
182
|
|
|
177
183
|
## Doctor
|
|
178
184
|
|
|
@@ -189,15 +195,15 @@ Doctor checks Codex version, managed config, runtime version, hook count, `AGENT
|
|
|
189
195
|
Upgrade idempotently by re-running the pinned/new package:
|
|
190
196
|
|
|
191
197
|
```bash
|
|
192
|
-
npx -y memoryrouter-codex@1.
|
|
198
|
+
npx -y memoryrouter-codex@1.1.0 init --scope user
|
|
193
199
|
```
|
|
194
200
|
|
|
195
201
|
Uninstall:
|
|
196
202
|
|
|
197
203
|
```bash
|
|
198
|
-
npx -y memoryrouter-codex@1.
|
|
204
|
+
npx -y memoryrouter-codex@1.1.0 uninstall --scope user
|
|
199
205
|
# or
|
|
200
|
-
npx -y memoryrouter-codex@1.
|
|
206
|
+
npx -y memoryrouter-codex@1.1.0 uninstall --scope project --project-dir /repo
|
|
201
207
|
```
|
|
202
208
|
|
|
203
209
|
Uninstall deliberately retains `~/.memoryrouter/codex/` because another project installation may use the credentials and local suppression state. Delete that private directory manually only when no integration uses it.
|
|
@@ -205,8 +211,8 @@ Uninstall deliberately retains `~/.memoryrouter/codex/` because another project
|
|
|
205
211
|
## Honest limitations
|
|
206
212
|
|
|
207
213
|
- Hooks are the deterministic automatic path. Remote MCP alone remains model-invoked, not guaranteed every turn.
|
|
208
|
-
- The transcript file path supplied to hooks is documented as unstable, so this integration does **not** parse Codex transcript JSONL.
|
|
209
|
-
-
|
|
214
|
+
- The transcript file path supplied to hooks is documented as unstable, so this integration does **not** parse Codex transcript JSONL. Version 1.1 captures only the stable user prompt and `last_assistant_message`.
|
|
215
|
+
- Tool-call records are intentionally excluded. MemoryRouter does not store tool names, tool IDs, tool inputs, tool results, commands, patches, or other `PostToolUse` payloads from automatic Codex capture. Important outcomes must appear in the final assistant response to be retained.
|
|
210
216
|
- Some specialized tool paths can opt out of hooks. Codex documents hooks as a useful extension surface, not a complete enforcement boundary.
|
|
211
217
|
- SessionEnd can happen after a 30-minute idle period and has a three-second maximum. It is a best-effort checkpoint; normal `Stop` capture is the primary write path.
|
|
212
218
|
- Project scoping is strict for this package's automatic retrieval/local MCP. The existing hosted remote MCP server has no first-class project argument, so use local tools/hooks when cross-project isolation is required.
|
package/RESEARCH.md
CHANGED
|
@@ -17,7 +17,7 @@ Verified 2026-07-31. This is engineering evidence, not public marketing copy.
|
|
|
17
17
|
## Platform findings used in the architecture
|
|
18
18
|
|
|
19
19
|
- Current Codex supports command hooks for `SessionStart`, `SessionEnd`, `PreToolUse`, `PostToolUse`, `PreCompact`, `PostCompact`, `UserPromptSubmit`, `SubagentStart`, `SubagentStop`, and `Stop`.
|
|
20
|
-
- Hook transcript paths are explicitly documented as unstable.
|
|
20
|
+
- Hook transcript paths are explicitly documented as unstable. Version 1.0 used stable event fields plus an append-only local `PostToolUse` trace. Version 1.1 removes that trace and its hook entirely: completed-turn storage is composed only from the `UserPromptSubmit` prompt and Stop's stable `last_assistant_message`.
|
|
21
21
|
- Hooks are enabled by default and project hooks require trust. `features.codex_hooks` is only a deprecated alias; the current canonical key is `features.hooks`.
|
|
22
22
|
- Remote MCP OAuth no longer needs `experimental_use_rmcp_client` in current Codex. That key is absent from the current configuration reference.
|
|
23
23
|
- The local CLI, desktop Codex host, and IDE extension share Codex config. ChatGPT web uses hosted plugin tools and does not inherit local config.
|
|
@@ -54,4 +54,8 @@ npm run typecheck
|
|
|
54
54
|
npm pack --dry-run
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
The integration suite covers config migration/backup, foreign-config preservation, corrupt config fail-closed behavior, idempotent upgrades, uninstall, project/worktree identity, project-vs-vault filtering, native-memory config coexistence, OAuth/local fallback configuration, read retries, write deduplication, hook fail-open behavior,
|
|
57
|
+
The integration suite covers config migration/backup, foreign-config preservation, corrupt config fail-closed behavior, idempotent upgrades, uninstall, project/worktree identity, project-vs-vault filtering, native-memory config coexistence, OAuth/local fallback configuration, read retries, write deduplication, hook fail-open behavior, prompt/final-only turn and checkpoint capture, explicit exclusion of tool payloads, context budgets, local suppression, and MCP tool annotations.
|
|
58
|
+
|
|
59
|
+
## Version 1.1 capture decision
|
|
60
|
+
|
|
61
|
+
`PostToolUse` is not registered by the installer in 1.1. The runtime no longer defines or reads trace files, and health records no longer count tool calls. Normal Stop capture requires `last_assistant_message`; PreCompact and SessionEnd may preserve the cached user prompt when no final response is available. `SubagentStop` remains separate and stores only its stable final outcome plus non-payload agent identifiers.
|
package/lib/hooks.cjs
CHANGED
|
@@ -52,7 +52,6 @@ function managedGroups(runtimeDir, scope) {
|
|
|
52
52
|
return {
|
|
53
53
|
SessionStart: [{ matcher: 'startup|resume|clear|compact', hooks: [handler('session-start', 8, 'Recalling MemoryRouter context', 3500)] }],
|
|
54
54
|
UserPromptSubmit: [{ hooks: [handler('inject', 8, 'Searching project memory', 3500)] }],
|
|
55
|
-
PostToolUse: [{ hooks: [handler('trace', 3)] }],
|
|
56
55
|
Stop: [{ hooks: [handler('capture', 18, 'Saving durable project memory')] }],
|
|
57
56
|
SubagentStop: [{ hooks: [handler('capture-subagent', 15, 'Saving durable subagent outcome')] }],
|
|
58
57
|
PreCompact: [{ matcher: 'manual|auto', hooks: [handler('flush', 18, 'Saving memory before compaction')] }],
|
package/lib/installer.cjs
CHANGED
|
@@ -11,7 +11,7 @@ const {
|
|
|
11
11
|
installToml, uninstallToml, installAgents, uninstallAgents, readText, replaceBlock,
|
|
12
12
|
hasUnmanagedMemoryRouterTable, TOML_START, TOML_END, AGENTS_START, AGENTS_END,
|
|
13
13
|
} = require('./managed.cjs');
|
|
14
|
-
const { installHooks, uninstallHooks, isOwnedHook, parseHooks } = require('./hooks.cjs');
|
|
14
|
+
const { installHooks, uninstallHooks, isOwnedHook, managedGroups, parseHooks } = require('./hooks.cjs');
|
|
15
15
|
|
|
16
16
|
function resolveLayout(options = {}) {
|
|
17
17
|
const env = options.env || process.env;
|
|
@@ -176,7 +176,8 @@ async function doctor(options = {}) {
|
|
|
176
176
|
let hooks = null;
|
|
177
177
|
try { hooks = hooksText ? JSON.parse(hooksText) : null; } catch { /* handled */ }
|
|
178
178
|
const owned = hooks ? Object.values(hooks.hooks || {}).flat().flatMap(group => group.hooks || []).filter(isOwnedHook) : [];
|
|
179
|
-
|
|
179
|
+
const expectedOwned = Object.values(managedGroups(layout.runtimeDir, layout.scope)).flat().flatMap(group => group.hooks || []).length;
|
|
180
|
+
addCheck(checks, 'hooks', owned.length === expectedOwned ? 'pass' : 'error', owned.length ? `${owned.length} of ${expectedOwned} managed lifecycle hooks found.` : 'Managed lifecycle hooks are missing or hooks.json is invalid.');
|
|
180
181
|
addCheck(checks, 'agents', agentsText.includes(AGENTS_START) ? 'pass' : 'warn', agentsText.includes(AGENTS_START) ? 'Managed AGENTS.md guidance is present.' : 'Managed AGENTS.md guidance is missing.');
|
|
181
182
|
|
|
182
183
|
const cfg = loadConfig(options.env);
|
package/package.json
CHANGED
package/runtime/config.cjs
CHANGED
|
@@ -74,10 +74,6 @@ function sessionStatePath(sessionId, env = process.env) {
|
|
|
74
74
|
return path.join(stateDir(env), `${safeId(sessionId)}.json`);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function tracePath(sessionId, turnId, env = process.env) {
|
|
78
|
-
return path.join(stateDir(env), `${safeId(sessionId)}-${safeId(turnId)}.trace.jsonl`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
77
|
function loadSessionState(sessionId, env = process.env) {
|
|
82
78
|
try {
|
|
83
79
|
const value = JSON.parse(fs.readFileSync(sessionStatePath(sessionId, env), 'utf8'));
|
|
@@ -154,7 +150,6 @@ module.exports = {
|
|
|
154
150
|
safeId,
|
|
155
151
|
stateDir,
|
|
156
152
|
sessionStatePath,
|
|
157
|
-
tracePath,
|
|
158
153
|
loadSessionState,
|
|
159
154
|
saveSessionState,
|
|
160
155
|
pruneState,
|
package/runtime/constants.cjs
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const INTEGRATION_VERSION = '1.
|
|
3
|
+
const INTEGRATION_VERSION = '1.1.0';
|
|
4
4
|
const RUNTIME_SCHEMA_VERSION = 1;
|
|
5
5
|
const API_BASE = 'https://api.memoryrouter.ai';
|
|
6
6
|
const MCP_ENDPOINT = 'https://mcp.memoryrouter.ai/mcp';
|
|
7
7
|
const DEFAULT_CONTEXT_CHARS = 12000;
|
|
8
8
|
const DEFAULT_SEARCH_RESULTS = 12;
|
|
9
|
-
const MAX_TRACE_RECORD_CHARS = 8000;
|
|
10
|
-
const MAX_TRACE_TOTAL_CHARS = 60000;
|
|
11
9
|
const ENVELOPE_HEADER = '[MemoryRouter Codex Memory v1]';
|
|
12
10
|
|
|
13
11
|
module.exports = {
|
|
@@ -17,7 +15,5 @@ module.exports = {
|
|
|
17
15
|
MCP_ENDPOINT,
|
|
18
16
|
DEFAULT_CONTEXT_CHARS,
|
|
19
17
|
DEFAULT_SEARCH_RESULTS,
|
|
20
|
-
MAX_TRACE_RECORD_CHARS,
|
|
21
|
-
MAX_TRACE_TOTAL_CHARS,
|
|
22
18
|
ENVELOPE_HEADER,
|
|
23
19
|
};
|
package/runtime/hook.cjs
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const { loadConfig, getApiKey, loadSessionState, saveSessionState, tracePath, pruneState, recordHealth } = require('./config.cjs');
|
|
4
|
+
const { loadConfig, getApiKey, loadSessionState, saveSessionState, pruneState, recordHealth } = require('./config.cjs');
|
|
6
5
|
const { resolveProject } = require('./project.cjs');
|
|
7
6
|
const { searchScoped, storeScoped, memoryHandle } = require('./memory.cjs');
|
|
8
|
-
const { MAX_TRACE_RECORD_CHARS, MAX_TRACE_TOTAL_CHARS } = require('./constants.cjs');
|
|
9
7
|
|
|
10
8
|
function debug(...values) {
|
|
11
9
|
if (process.env.MEMORYROUTER_DEBUG) console.error('[memoryrouter-codex]', ...values);
|
|
@@ -22,7 +20,7 @@ function readStdin(timeoutMs = 1500) {
|
|
|
22
20
|
});
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
function truncate(value, limit =
|
|
23
|
+
function truncate(value, limit = 8000) {
|
|
26
24
|
let text;
|
|
27
25
|
try { text = typeof value === 'string' ? value : JSON.stringify(value); } catch { text = String(value); }
|
|
28
26
|
if (text.length <= limit) return text;
|
|
@@ -38,61 +36,16 @@ function emitContext(eventName, context) {
|
|
|
38
36
|
return true;
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
function
|
|
42
|
-
if (!input.session_id || !input.turn_id) return;
|
|
43
|
-
const record = {
|
|
44
|
-
at: new Date().toISOString(),
|
|
45
|
-
turn_id: input.turn_id,
|
|
46
|
-
tool_name: input.tool_name,
|
|
47
|
-
tool_use_id: input.tool_use_id,
|
|
48
|
-
input: truncate(input.tool_input),
|
|
49
|
-
response: truncate(input.tool_response),
|
|
50
|
-
};
|
|
51
|
-
const file = tracePath(input.session_id, input.turn_id, env);
|
|
52
|
-
fs.appendFileSync(file, `${JSON.stringify(record)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'a' });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function readTrace(sessionId, turnId, env = process.env) {
|
|
56
|
-
const file = tracePath(sessionId, turnId, env);
|
|
57
|
-
let text;
|
|
58
|
-
try { text = fs.readFileSync(file, 'utf8'); } catch { return []; }
|
|
59
|
-
const records = [];
|
|
60
|
-
let total = 0;
|
|
61
|
-
for (const line of text.split('\n')) {
|
|
62
|
-
if (!line.trim()) continue;
|
|
63
|
-
try {
|
|
64
|
-
const record = JSON.parse(line);
|
|
65
|
-
const size = JSON.stringify(record).length;
|
|
66
|
-
if (total + size > MAX_TRACE_TOTAL_CHARS) break;
|
|
67
|
-
records.push(record);
|
|
68
|
-
total += size;
|
|
69
|
-
} catch { /* tolerate partial concurrent append */ }
|
|
70
|
-
}
|
|
71
|
-
return records;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function removeTrace(sessionId, turnId, env = process.env) {
|
|
75
|
-
try { fs.unlinkSync(tracePath(sessionId, turnId, env)); } catch { /* absent */ }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function turnContent(input, state, records, partial = false) {
|
|
39
|
+
function turnContent(input, state) {
|
|
79
40
|
const prompt = state.prompts?.[input.turn_id] || state.lastPrompt || '';
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`- ${record.tool_name || 'tool'} (${record.tool_use_id || 'unknown'})`,
|
|
89
|
-
` input: ${record.input}`,
|
|
90
|
-
` result: ${record.response}`,
|
|
91
|
-
].join('\n'));
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (input.last_assistant_message) parts.push(`Final assistant response:\n${truncate(input.last_assistant_message, 60000)}`);
|
|
95
|
-
return parts.filter(Boolean).join('\n\n');
|
|
41
|
+
return [
|
|
42
|
+
prompt ? `User request:
|
|
43
|
+
${truncate(prompt, 40000)}` : null,
|
|
44
|
+
input.last_assistant_message
|
|
45
|
+
? `Final assistant response:
|
|
46
|
+
${truncate(input.last_assistant_message, 60000)}`
|
|
47
|
+
: null,
|
|
48
|
+
].filter(Boolean).join('\n\n');
|
|
96
49
|
}
|
|
97
50
|
|
|
98
51
|
async function handleInject(input, context) {
|
|
@@ -142,9 +95,10 @@ async function captureTurn(input, context, partial = false) {
|
|
|
142
95
|
if (!partial && state.capturedTurns?.[turnId]) {
|
|
143
96
|
return { ok: true, skipped: 'deduplicated', handle: state.capturedTurns[turnId] };
|
|
144
97
|
}
|
|
145
|
-
const
|
|
146
|
-
if (!
|
|
147
|
-
|
|
98
|
+
const prompt = state.prompts?.[turnId] || state.lastPrompt || '';
|
|
99
|
+
if (!partial && !input.last_assistant_message) return { ok: true, skipped: 'empty' };
|
|
100
|
+
if (partial && !prompt && !input.last_assistant_message) return { ok: true, skipped: 'empty' };
|
|
101
|
+
const content = turnContent({ ...input, turn_id: turnId }, state);
|
|
148
102
|
const kind = partial ? 'checkpoint' : 'turn';
|
|
149
103
|
const handle = memoryHandle(context.project.handle, kind, content);
|
|
150
104
|
if ((state.capturedHandles || []).includes(handle)) return { ok: true, skipped: 'deduplicated', handle };
|
|
@@ -166,14 +120,14 @@ async function captureTurn(input, context, partial = false) {
|
|
|
166
120
|
if (!partial && state.prompts) delete state.prompts[turnId];
|
|
167
121
|
state.lastCaptureAt = new Date().toISOString();
|
|
168
122
|
saveSessionState(sessionId, state, context.env);
|
|
169
|
-
if (!partial) removeTrace(sessionId, turnId, context.env);
|
|
170
123
|
}
|
|
171
124
|
recordHealth(partial ? 'flush' : 'capture', {
|
|
172
125
|
ok: result.ok,
|
|
173
126
|
status: result.status,
|
|
174
127
|
project: context.project.handle,
|
|
175
128
|
handle,
|
|
176
|
-
|
|
129
|
+
hasPrompt: Boolean(prompt),
|
|
130
|
+
hasFinalResponse: Boolean(input.last_assistant_message),
|
|
177
131
|
}, context.env);
|
|
178
132
|
return result;
|
|
179
133
|
}
|
|
@@ -222,7 +176,6 @@ async function run(kind, input, options = {}) {
|
|
|
222
176
|
switch (kind) {
|
|
223
177
|
case 'inject': return handleInject(input, context);
|
|
224
178
|
case 'session-start': return handleSessionStart(input, context);
|
|
225
|
-
case 'trace': appendTrace(input, env); return;
|
|
226
179
|
case 'capture': return captureTurn(input, context, false);
|
|
227
180
|
case 'capture-subagent': return captureSubagent(input, context);
|
|
228
181
|
case 'flush': return captureTurn(input, context, true);
|
|
@@ -232,7 +185,7 @@ async function run(kind, input, options = {}) {
|
|
|
232
185
|
|
|
233
186
|
async function main() {
|
|
234
187
|
const kind = process.argv[2];
|
|
235
|
-
const wallClock = setTimeout(() => process.exit(0), kind === '
|
|
188
|
+
const wallClock = setTimeout(() => process.exit(0), kind === 'inject' ? 7500 : 18000);
|
|
236
189
|
wallClock.unref();
|
|
237
190
|
let input = {};
|
|
238
191
|
try { input = JSON.parse((await readStdin()) || '{}'); } catch { debug('invalid hook JSON'); }
|
|
@@ -247,9 +200,6 @@ module.exports = {
|
|
|
247
200
|
readStdin,
|
|
248
201
|
truncate,
|
|
249
202
|
emitContext,
|
|
250
|
-
appendTrace,
|
|
251
|
-
readTrace,
|
|
252
|
-
removeTrace,
|
|
253
203
|
turnContent,
|
|
254
204
|
handleInject,
|
|
255
205
|
handleSessionStart,
|