From 6a431f57be219cdc62553ce349bdcd43813e04a5 Mon Sep 17 00:00:00 2001 From: hopeatina Date: Sun, 23 Aug 2026 11:47:32 -0500 Subject: [PATCH] fix(hooks): honor offline receipt capture --- .cursor-plugin/plugin.json | 2 +- README.md | 4 +++ package-lock.json | 4 +-- package.json | 2 +- scripts/hooks/session-summary-bridge.mjs | 5 ++++ scripts/hooks/session-summary-bridge.test.mjs | 30 +++++++++++++++++++ 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 573098a..ab3c3e4 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "@useorgx/cursor-plugin", "description": "OrgX execution control plane for Cursor with bootstrap, resumable workstreams, proof, decisions, Work Graph hooks, and specialist agents.", - "version": "0.1.4", + "version": "0.1.5", "author": { "name": "OrgX Team", "email": "team@useorgx.com" diff --git a/README.md b/README.md index 10475c5..1e81c05 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,10 @@ capture. The Wizard owns the durable queue, acknowledgement, retry, and AWR delivery path. The adapter never forwards prompts, tool inputs or outputs, transcript paths, user email, or error text. +Set `ORGX_SESSION_SUMMARY_AUTO_FLUSH=off` for a deliberately offline run. The +adapter and Wizard retain the capture without starting a delivery worker; +`orgx-wizard hooks flush` can replay it later with server acknowledgement. + Cursor cloud agents support the prompt, tool, subagent, and `stop` subset but do not run the local `sessionStart` or `sessionEnd` hooks. Cloud proof is therefore capability-bounded: `RunEnd` can issue a run receipt when the shared diff --git a/package-lock.json b/package-lock.json index 61fb22a..1e97977 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@useorgx/cursor-plugin", - "version": "0.1.4", + "version": "0.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@useorgx/cursor-plugin", - "version": "0.1.4", + "version": "0.1.5", "license": "MIT", "dependencies": { "@sentry/node": "10.65.0" diff --git a/package.json b/package.json index 2c5a3c8..fd44dbb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@useorgx/cursor-plugin", - "version": "0.1.4", + "version": "0.1.5", "description": "OrgX plugin for Cursor with MCP, rules, skills, passive Work Graph hooks, commands, and specialist agents.", "type": "module", "private": false, diff --git a/scripts/hooks/session-summary-bridge.mjs b/scripts/hooks/session-summary-bridge.mjs index 2047fa3..cb04399 100644 --- a/scripts/hooks/session-summary-bridge.mjs +++ b/scripts/hooks/session-summary-bridge.mjs @@ -78,7 +78,12 @@ function defaultHookPath(env) { ); } +function autoFlushDisabled(value) { + return ['off', 'false', '0'].includes(String(value ?? '').trim().toLowerCase()); +} + function triggerFlush({ env, spawnImpl, queueDir }) { + if (autoFlushDisabled(env.ORGX_SESSION_SUMMARY_AUTO_FLUSH)) return false; try { const args = ['hooks', 'flush', '--background', '--limit=25']; if (queueDir) args.push(`--queue=${queueDir}`); diff --git a/scripts/hooks/session-summary-bridge.test.mjs b/scripts/hooks/session-summary-bridge.test.mjs index cb2b1e4..78760b6 100644 --- a/scripts/hooks/session-summary-bridge.test.mjs +++ b/scripts/hooks/session-summary-bridge.test.mjs @@ -102,6 +102,36 @@ test('delegates to the installed Wizard hook and starts fallback delivery', asyn } }); +test('keeps an offline run queued without starting fallback delivery', async () => { + const dir = mkdtempSync(join(tmpdir(), 'orgx-cursor-bridge-')); + const hookPath = join(dir, 'orgx-session-summary.mjs'); + writeFileSync(hookPath, 'export async function main() {}\n', 'utf8'); + let spawns = 0; + try { + const result = await bridgeCursorSessionSummary({ + event: 'run_end', + payload: { conversation_id: 'conversation-offline' }, + hookPath, + env: { + PATH: process.env.PATH, + ORGX_SESSION_SUMMARY_AUTO_FLUSH: 'off', + }, + moduleLoader: async () => ({ + main: async () => ({ ok: true, queued: true, delivery_triggered: false }), + }), + spawnImpl: () => { + spawns += 1; + throw new Error('offline capture must not spawn'); + }, + }); + + assert.equal(result.fallback_delivery_triggered, false); + assert.equal(spawns, 0); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + test('reports a missing shared hook without inventing capture', async () => { const result = await bridgeCursorSessionSummary({ event: 'run_end',