@remembrance-ai/opencode-plugin 0.1.52 → 0.1.55
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 +21 -7
- package/opencode.json +2 -2
- package/package.json +1 -1
- package/scripts/hook-core.mjs +690 -21
- package/servers/remembrance-mcp.mjs +564 -67
- package/skills/remembrancer/references/remembrance-payloads.md +6 -4
- package/skills/remembrancer/references/remembrance-setup.md +48 -25
- package/src/index.mjs +84 -5
|
@@ -263,11 +263,13 @@ privately, then let an organization admin use the reviewed public-propagation
|
|
|
263
263
|
flow for a redacted, public-safe version when appropriate.
|
|
264
264
|
|
|
265
265
|
If host policy blocks repository-derived content before the request reaches
|
|
266
|
-
Remembrance, do not retry it through another network transport.
|
|
266
|
+
Remembrance, do not retry it through another network transport. Report the fixed
|
|
267
|
+
content-free denial alert and do not create a handoff automatically. Only when
|
|
268
|
+
an organization admin explicitly requests one, use local MCP
|
|
267
269
|
`queue_private_skill_import` or the bundled
|
|
268
|
-
`scripts/queue-private-skill-import.mjs` helper, then have
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
`scripts/queue-private-skill-import.mjs` helper, then have the admin upload the
|
|
271
|
+
mode-0600 JSON at **Dashboard > Skills > Import**. A local handoff is not a
|
|
272
|
+
server submission receipt.
|
|
271
273
|
|
|
272
274
|
## New skill idea
|
|
273
275
|
|
|
@@ -121,15 +121,18 @@ CODEX_CLI="${CODEX_CLI:-$(command -v codex || true)}"
|
|
|
121
121
|
This command handles both first install and update. If zsh says
|
|
122
122
|
"codex: command not found", it discovers the current ChatGPT desktop bundle or
|
|
123
123
|
the legacy Codex app bundle without requiring a shell alias. The final command
|
|
124
|
-
opens Codex CLI so its secure hook review can be completed immediately
|
|
124
|
+
opens Codex CLI so its secure hook review can be completed immediately when
|
|
125
|
+
Codex requires it.
|
|
125
126
|
|
|
126
127
|
Codex will not execute plugin hooks until their exact definitions are trusted.
|
|
127
|
-
In the Codex window opened by the installer,
|
|
128
|
-
|
|
128
|
+
In the Codex window opened by the installer, if Codex shows a **Hooks need
|
|
129
|
+
review** screen, choose **Review hooks** and trust only the Remembrance
|
|
129
130
|
`SessionStart`, `UserPromptSubmit`, `PostToolUse`, and `Stop` hooks.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
If no review screen appears, continue: Codex may be reusing an existing valid
|
|
132
|
+
trust decision. Changed hook definitions show the same review screen again;
|
|
133
|
+
never use the automation-only trust bypass for normal installation. Fully
|
|
134
|
+
restart Codex, submit one prompt, use one Remembrance tool, complete one turn,
|
|
135
|
+
and run `run_connection_doctor`.
|
|
133
136
|
|
|
134
137
|
OpenClaw:
|
|
135
138
|
|
|
@@ -187,10 +190,11 @@ Integrations & MCP**, and verify query, invocation, feedback, and contribution
|
|
|
187
190
|
receipts in both local and cloud runs.
|
|
188
191
|
|
|
189
192
|
After installing any native plugin, restart the agent app/session and approve
|
|
190
|
-
the runtime's trust request. For Codex, complete the
|
|
191
|
-
above
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
the runtime's trust request when one appears. For Codex, complete the hook
|
|
194
|
+
review above if Codex requests it; unchanged, previously trusted definitions
|
|
195
|
+
may not show another review screen. Changed hook definitions show the review
|
|
196
|
+
screen again. A currently running Codex or Claude thread usually cannot
|
|
197
|
+
hot-load newly installed plugin tools.
|
|
194
198
|
|
|
195
199
|
## Enterprise/org key setup
|
|
196
200
|
|
|
@@ -347,7 +351,8 @@ The MCP annotation for `propose_private_skill` is non-read-only and
|
|
|
347
351
|
closed-world: it makes a network request, but can change only the authenticated
|
|
348
352
|
organization's private review queue and cannot change publicly visible internet
|
|
349
353
|
state. Closed-world does not mean zero-network. Only
|
|
350
|
-
`queue_private_skill_import` is
|
|
354
|
+
`queue_private_skill_import` is a local, zero-network handoff tool, and it
|
|
355
|
+
should run only when an organization admin explicitly requests that handoff.
|
|
351
356
|
|
|
352
357
|
Example text to merge into the tenant-specific guardian policy:
|
|
353
358
|
|
|
@@ -382,12 +387,14 @@ over a user's local `[auto_review].policy`. See the official Codex
|
|
|
382
387
|
[managed configuration](https://learn.chatgpt.com/docs/enterprise/managed-configuration#configure-automatic-review-policy)
|
|
383
388
|
documentation for the current schema and deployment options.
|
|
384
389
|
|
|
385
|
-
If the organization does not approve direct egress, keep the host denial.
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
390
|
+
If the organization does not approve direct egress, keep the host denial. The
|
|
391
|
+
plugin reports one content-free local alert, does not retry, and does not create
|
|
392
|
+
a handoff automatically. Only when an organization admin explicitly requests a
|
|
393
|
+
handoff, use `queue_private_skill_import` locally or run the bundled
|
|
394
|
+
`scripts/queue-private-skill-import.mjs` helper, then have the admin upload the
|
|
395
|
+
mode-0600 JSON at **Dashboard > Skills > Import**. The handoff never contains an
|
|
396
|
+
API key or organization id, never contacts Remembrance, and does not count as
|
|
397
|
+
submitted until the dashboard returns an import batch receipt.
|
|
391
398
|
|
|
392
399
|
If Codex still sees `<your org key>` after restart, remove stale
|
|
393
400
|
`REMEMBRANCE_API_KEY` exports from shell profiles such as `~/.zshrc` and
|
|
@@ -776,6 +783,17 @@ npx skills add dreamarkinc/remembrance-skills --skill remembrancer
|
|
|
776
783
|
The entry skill is REST-only and self-contained. The same skill directory can
|
|
777
784
|
be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
|
|
778
785
|
|
|
786
|
+
## Verified client updates
|
|
787
|
+
|
|
788
|
+
Native plugins and local MCP check the credential-free public release manifest
|
|
789
|
+
at startup and through `run_connection_doctor`. The check sends no API key,
|
|
790
|
+
fails open when the registry is unavailable, and never runs a command returned
|
|
791
|
+
by the network. When a newer verified release exists, the agent must ask before
|
|
792
|
+
running the update command bundled with its installed client, then tell the user
|
|
793
|
+
which Codex, Claude Code, Cursor, OpenClaw, VS Code, OpenCode, or MCP host must
|
|
794
|
+
be reloaded, reopened, or fully restarted. Set
|
|
795
|
+
`REMEMBRANCE_CLIENT_UPDATE_CHECK=0` to disable only this advisory check.
|
|
796
|
+
|
|
779
797
|
## Validate after setup
|
|
780
798
|
|
|
781
799
|
1. Start a fresh agent session.
|
|
@@ -830,10 +848,12 @@ be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
|
|
|
830
848
|
that should not appear anonymously.
|
|
831
849
|
13. With an organization submission key, submit one disposable redacted skill
|
|
832
850
|
through propose_private_skill and verify it appears only in that
|
|
833
|
-
organization's review queue.
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
851
|
+
organization's review queue. In a separate negative scenario, simulate a
|
|
852
|
+
host-policy denial and verify the plugin reports the fixed content-free alert
|
|
853
|
+
once, persists no blocked content, performs no retry, and creates no handoff.
|
|
854
|
+
Test queue_private_skill_import only as a separate administrator-requested
|
|
855
|
+
manual handoff, with its local receipt distinguished from a server import
|
|
856
|
+
receipt.
|
|
837
857
|
14. If using local MCP, verify local_signing_identity in get_connection_status.
|
|
838
858
|
A missing opaque identity initializes automatically on the first signed
|
|
839
859
|
contribution; bootstrap_agent_identity with no arguments is an optional
|
|
@@ -843,10 +863,13 @@ be copied to ".agents/skills/remembrancer/SKILL.md" for compatible providers.
|
|
|
843
863
|
|
|
844
864
|
- "Plugin installed, but no tools": restart the agent app/session; confirm the
|
|
845
865
|
plugin is enabled and contains the runtime-specific manifest. For Codex,
|
|
846
|
-
launch the installer-provided command
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
866
|
+
launch the installer-provided command. If Codex shows a hook review, choose
|
|
867
|
+
**Review hooks** and trust only the listed Remembrance hooks; if it does not,
|
|
868
|
+
continue because Codex may be reusing an existing valid trust decision.
|
|
869
|
+
Fully restart Codex, submit one prompt, use one Remembrance tool, complete one
|
|
870
|
+
turn, and run `run_connection_doctor`. If the lifecycle remains incomplete,
|
|
871
|
+
update or reinstall the plugin and repeat the check. Updates that change
|
|
872
|
+
hooks show the review screen again.
|
|
850
873
|
- "Agent has tools but does not use them": first verify a concrete query receipt,
|
|
851
874
|
then test a short contextual follow-up such as "fix these issues". Native
|
|
852
875
|
prompt hooks should inject a full-conversation query reminder, and completion
|
package/src/index.mjs
CHANGED
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
import { readFileSync } from "node:fs";
|
|
38
38
|
import process from "node:process";
|
|
39
39
|
import {
|
|
40
|
+
HOST_POLICY_ALERT_TEXT,
|
|
41
|
+
checkForClientUpdate,
|
|
40
42
|
clearHighMatchSurfaceForExplicitSelection,
|
|
41
43
|
clearHighMatchSurfaceIfOpened,
|
|
42
44
|
debugLog,
|
|
@@ -44,11 +46,13 @@ import {
|
|
|
44
46
|
directSelectionFromResponse,
|
|
45
47
|
highMatchFromResponse,
|
|
46
48
|
markCurrentEngagementHandled,
|
|
49
|
+
markHostPolicyAlertDelivered,
|
|
47
50
|
recordDirectiveFollowThroughForTool,
|
|
48
51
|
recordDirectSelectionSurface,
|
|
49
52
|
recordDirectiveSurface,
|
|
50
53
|
recordHighMatchSurface,
|
|
51
54
|
recordPluginLifecycleHealth,
|
|
55
|
+
recordHostPolicyDenial,
|
|
52
56
|
recordRegistryUse,
|
|
53
57
|
recordTaskEligibility,
|
|
54
58
|
recordValueEpisodeSurface,
|
|
@@ -182,6 +186,12 @@ export function toolNameFromEvent(input, output) {
|
|
|
182
186
|
input?.toolName,
|
|
183
187
|
output?.tool,
|
|
184
188
|
output?.toolName,
|
|
189
|
+
input?.properties?.tool,
|
|
190
|
+
input?.properties?.toolName,
|
|
191
|
+
input?.properties?.permission,
|
|
192
|
+
output?.properties?.tool,
|
|
193
|
+
output?.properties?.toolName,
|
|
194
|
+
output?.properties?.permission,
|
|
185
195
|
]) {
|
|
186
196
|
if (typeof candidate === "string" && candidate.trim()) {
|
|
187
197
|
return candidate;
|
|
@@ -221,11 +231,36 @@ export const Remembrance = async (context = {}) => {
|
|
|
221
231
|
const queriedMessages = new Set();
|
|
222
232
|
const messageSessions = new Map();
|
|
223
233
|
const pendingGuidance = new Map();
|
|
234
|
+
const pendingClientUpdates = new Map();
|
|
224
235
|
const userMessages = new Map();
|
|
225
236
|
const pendingTextParts = new Map();
|
|
226
237
|
|
|
238
|
+
async function observeHostPolicyDenial({
|
|
239
|
+
eventType,
|
|
240
|
+
sessionId,
|
|
241
|
+
toolName,
|
|
242
|
+
value,
|
|
243
|
+
}) {
|
|
244
|
+
const observation = recordHostPolicyDenial(
|
|
245
|
+
{
|
|
246
|
+
surface: SURFACE,
|
|
247
|
+
sessionId,
|
|
248
|
+
eventType,
|
|
249
|
+
toolName,
|
|
250
|
+
value,
|
|
251
|
+
pluginVersion: version,
|
|
252
|
+
},
|
|
253
|
+
env,
|
|
254
|
+
);
|
|
255
|
+
if (!observation) return false;
|
|
256
|
+
await notify(client, HOST_POLICY_ALERT_TEXT, "error");
|
|
257
|
+
markHostPolicyAlertDelivered(SURFACE, sessionId, observation.id, env);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
227
261
|
function clearSessionState(sessionId) {
|
|
228
262
|
pendingGuidance.delete(sessionId);
|
|
263
|
+
pendingClientUpdates.delete(sessionId);
|
|
229
264
|
for (const [messageId, trackedSessionId] of messageSessions) {
|
|
230
265
|
if (trackedSessionId !== sessionId) continue;
|
|
231
266
|
messageSessions.delete(messageId);
|
|
@@ -265,10 +300,24 @@ export const Remembrance = async (context = {}) => {
|
|
|
265
300
|
try {
|
|
266
301
|
const sessionId = sessionIdFromEvent(event, event);
|
|
267
302
|
recordLifecycle("session_start", sessionId);
|
|
303
|
+
const clientUpdate = await (
|
|
304
|
+
context.clientUpdateCheck ?? checkForClientUpdate
|
|
305
|
+
)(
|
|
306
|
+
{
|
|
307
|
+
surface: SURFACE,
|
|
308
|
+
currentVersion: version,
|
|
309
|
+
},
|
|
310
|
+
env,
|
|
311
|
+
).catch(() => null);
|
|
312
|
+
if (clientUpdate?.notice) {
|
|
313
|
+
pendingClientUpdates.set(sessionId, clientUpdate.notice);
|
|
314
|
+
}
|
|
268
315
|
await notify(
|
|
269
316
|
client,
|
|
270
|
-
|
|
271
|
-
|
|
317
|
+
clientUpdate?.latest_version
|
|
318
|
+
? `Remembrance ${version} is active; ${clientUpdate.latest_version} is available. The agent will ask before updating, and the host must be restarted afterward.`
|
|
319
|
+
: `Remembrance ${version} is active. Relevant memory is added before eligible turns; ` +
|
|
320
|
+
"run_connection_doctor verifies the active connection and gives one exact next step without exposing the key.",
|
|
272
321
|
);
|
|
273
322
|
} catch (error) {
|
|
274
323
|
debugLog(env, "opencode_session_created_failed", {
|
|
@@ -334,7 +383,13 @@ export const Remembrance = async (context = {}) => {
|
|
|
334
383
|
try {
|
|
335
384
|
const sessionId = sessionIdFromEvent(input, output);
|
|
336
385
|
const guidance = pendingGuidance.get(sessionId);
|
|
337
|
-
|
|
386
|
+
const updateNotice = pendingClientUpdates.get(sessionId);
|
|
387
|
+
if (!Array.isArray(output?.system)) return;
|
|
388
|
+
if (updateNotice) {
|
|
389
|
+
output.system.push(updateNotice);
|
|
390
|
+
pendingClientUpdates.delete(sessionId);
|
|
391
|
+
}
|
|
392
|
+
if (!guidance) return;
|
|
338
393
|
output.system.push(guidance.text);
|
|
339
394
|
pendingGuidance.delete(sessionId);
|
|
340
395
|
// Delivery happened: only now is the guidance actually in the model's
|
|
@@ -453,6 +508,22 @@ export const Remembrance = async (context = {}) => {
|
|
|
453
508
|
await handleSessionIdle(event);
|
|
454
509
|
return;
|
|
455
510
|
}
|
|
511
|
+
if (
|
|
512
|
+
event.type === "permission.replied" ||
|
|
513
|
+
event.type === "session.error"
|
|
514
|
+
) {
|
|
515
|
+
const serialized = JSON.stringify(event);
|
|
516
|
+
const inferredTool = /remembrance/i.test(serialized)
|
|
517
|
+
? toolNameFromEvent(event, event) || "remembrance"
|
|
518
|
+
: toolNameFromEvent(event, event);
|
|
519
|
+
await observeHostPolicyDenial({
|
|
520
|
+
eventType: event.type,
|
|
521
|
+
sessionId: sessionIdFromEvent(event, event),
|
|
522
|
+
toolName: inferredTool,
|
|
523
|
+
value: event,
|
|
524
|
+
});
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
456
527
|
if (event.type === "session.deleted") {
|
|
457
528
|
clearSessionState(sessionIdFromEvent(event, event));
|
|
458
529
|
}
|
|
@@ -468,11 +539,19 @@ export const Remembrance = async (context = {}) => {
|
|
|
468
539
|
"tool.execute.after": async (input, output) => {
|
|
469
540
|
try {
|
|
470
541
|
const sessionId = sessionIdFromEvent(input, output);
|
|
471
|
-
recordLifecycle("tool_observer", sessionId);
|
|
472
542
|
const tool = toolNameFromEvent(input, output);
|
|
473
543
|
if (!tool) return;
|
|
474
544
|
const normalizedTool = tool.toLowerCase();
|
|
475
|
-
if (toolResponseIndicatesFailure(output))
|
|
545
|
+
if (toolResponseIndicatesFailure(output)) {
|
|
546
|
+
await observeHostPolicyDenial({
|
|
547
|
+
eventType: "tool.execute.after",
|
|
548
|
+
sessionId,
|
|
549
|
+
toolName: tool,
|
|
550
|
+
value: output,
|
|
551
|
+
});
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
recordLifecycle("tool_observer", sessionId);
|
|
476
555
|
if (normalizedTool.endsWith("query_skills")) {
|
|
477
556
|
if (queryResponseHasMatches(output)) {
|
|
478
557
|
recordRegistryUse(sessionId, env);
|