@remembrance-ai/opencode-plugin 0.1.62 → 0.1.63
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/opencode.json +2 -2
- package/package.json +1 -1
- package/scripts/hook-core.mjs +73 -0
- package/servers/remembrance-mcp.mjs +84 -4
package/opencode.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"command": [
|
|
7
7
|
"npx",
|
|
8
8
|
"-y",
|
|
9
|
-
"@remembrance-ai/mcp-server@0.1.
|
|
9
|
+
"@remembrance-ai/mcp-server@0.1.63"
|
|
10
10
|
],
|
|
11
11
|
"enabled": true,
|
|
12
12
|
"environment": {
|
|
@@ -16,6 +16,6 @@
|
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"plugin": [
|
|
19
|
-
"@remembrance-ai/opencode-plugin@0.1.
|
|
19
|
+
"@remembrance-ai/opencode-plugin@0.1.63"
|
|
20
20
|
]
|
|
21
21
|
}
|
package/package.json
CHANGED
package/scripts/hook-core.mjs
CHANGED
|
@@ -4041,6 +4041,7 @@ export function recordPluginLifecycleHealth(
|
|
|
4041
4041
|
hostVersion = null,
|
|
4042
4042
|
credentialSource = null,
|
|
4043
4043
|
sessionId = null,
|
|
4044
|
+
hookTrust = null,
|
|
4044
4045
|
},
|
|
4045
4046
|
env = process.env,
|
|
4046
4047
|
) {
|
|
@@ -4072,6 +4073,7 @@ export function recordPluginLifecycleHealth(
|
|
|
4072
4073
|
hostVersion,
|
|
4073
4074
|
credentialSource,
|
|
4074
4075
|
sessionId: null,
|
|
4076
|
+
hookTrust,
|
|
4075
4077
|
},
|
|
4076
4078
|
env,
|
|
4077
4079
|
);
|
|
@@ -4164,6 +4166,10 @@ export function recordPluginLifecycleHealth(
|
|
|
4164
4166
|
api_destination_fingerprint: apiDestinationFingerprint,
|
|
4165
4167
|
evidence_origin: evidenceOrigin,
|
|
4166
4168
|
release_run_id: releaseRunId || null,
|
|
4169
|
+
hook_trust:
|
|
4170
|
+
sanitizePluginHookTrust(hookTrust) ??
|
|
4171
|
+
sanitizePluginHookTrust(existing.hook_trust) ??
|
|
4172
|
+
null,
|
|
4167
4173
|
components: {
|
|
4168
4174
|
...currentComponents,
|
|
4169
4175
|
[normalizedComponent]: now,
|
|
@@ -4190,6 +4196,73 @@ export function recordPluginLifecycleHealth(
|
|
|
4190
4196
|
}
|
|
4191
4197
|
}
|
|
4192
4198
|
|
|
4199
|
+
function sanitizePluginHookTrust(value) {
|
|
4200
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
4201
|
+
const status = String(value.status ?? "")
|
|
4202
|
+
.trim()
|
|
4203
|
+
.toLowerCase();
|
|
4204
|
+
if (!["trusted", "review_required", "unavailable"].includes(status)) {
|
|
4205
|
+
return null;
|
|
4206
|
+
}
|
|
4207
|
+
const allowedEvents = new Set([
|
|
4208
|
+
"SessionStart",
|
|
4209
|
+
"UserPromptSubmit",
|
|
4210
|
+
"PostToolUse",
|
|
4211
|
+
"Stop",
|
|
4212
|
+
]);
|
|
4213
|
+
const reviewEvents = Array.isArray(value.review_events)
|
|
4214
|
+
? [...new Set(value.review_events)]
|
|
4215
|
+
.filter((event) => allowedEvents.has(event))
|
|
4216
|
+
.slice(0, 4)
|
|
4217
|
+
: [];
|
|
4218
|
+
const hooks = Array.isArray(value.hooks)
|
|
4219
|
+
? value.hooks
|
|
4220
|
+
.filter(
|
|
4221
|
+
(entry) =>
|
|
4222
|
+
entry &&
|
|
4223
|
+
typeof entry === "object" &&
|
|
4224
|
+
!Array.isArray(entry) &&
|
|
4225
|
+
allowedEvents.has(entry.event),
|
|
4226
|
+
)
|
|
4227
|
+
.slice(0, 4)
|
|
4228
|
+
.map((entry) => ({
|
|
4229
|
+
event: entry.event,
|
|
4230
|
+
enabled: entry.enabled === true,
|
|
4231
|
+
trust_status: [
|
|
4232
|
+
"managed",
|
|
4233
|
+
"missing",
|
|
4234
|
+
"modified",
|
|
4235
|
+
"trusted",
|
|
4236
|
+
"unknown",
|
|
4237
|
+
"untrusted",
|
|
4238
|
+
].includes(entry.trust_status)
|
|
4239
|
+
? entry.trust_status
|
|
4240
|
+
: "unknown",
|
|
4241
|
+
}))
|
|
4242
|
+
: [];
|
|
4243
|
+
return {
|
|
4244
|
+
status,
|
|
4245
|
+
checked_at: safeText(value.checked_at ?? "", 64),
|
|
4246
|
+
review_events: reviewEvents,
|
|
4247
|
+
hooks,
|
|
4248
|
+
reason:
|
|
4249
|
+
status === "unavailable" &&
|
|
4250
|
+
[
|
|
4251
|
+
"app_server_closed",
|
|
4252
|
+
"app_server_initialize_failed",
|
|
4253
|
+
"app_server_unavailable",
|
|
4254
|
+
"check_disabled",
|
|
4255
|
+
"codex_executable_not_found",
|
|
4256
|
+
"hooks_list_failed",
|
|
4257
|
+
"hooks_list_timeout",
|
|
4258
|
+
"hooks_list_too_large",
|
|
4259
|
+
"plugin_hooks_not_listed",
|
|
4260
|
+
].includes(value.reason)
|
|
4261
|
+
? value.reason
|
|
4262
|
+
: null,
|
|
4263
|
+
};
|
|
4264
|
+
}
|
|
4265
|
+
|
|
4193
4266
|
function prunePluginHealthSessionMarkers(surface, keepPath, env) {
|
|
4194
4267
|
const directory = pluginHealthDir(env);
|
|
4195
4268
|
try {
|
|
@@ -4706,7 +4706,17 @@ var HOST_POLICY_OPERATION_CLASSES = /* @__PURE__ */ new Set([
|
|
|
4706
4706
|
"other"
|
|
4707
4707
|
]);
|
|
4708
4708
|
var CODEX_HOOK_REVIEW_COMMAND = `CODEX_CLI="\${CODEX_CLI:-$(command -v codex || true)}"; [ -x "$CODEX_CLI" ] || CODEX_CLI="/Applications/ChatGPT.app/Contents/Resources/codex"; [ -x "$CODEX_CLI" ] || CODEX_CLI="/Applications/Codex.app/Contents/Resources/codex"; [ -x "$CODEX_CLI" ] || { printf '%s\\n' "Codex CLI not found. Install the Codex CLI, or install or update the ChatGPT desktop app on macOS, then try again." >&2; exit 1; }; "$CODEX_CLI"`;
|
|
4709
|
-
function codexHookReviewAction(followupTool) {
|
|
4709
|
+
function codexHookReviewAction(followupTool, reviewEvents = []) {
|
|
4710
|
+
const allowedEvents = /* @__PURE__ */ new Set([
|
|
4711
|
+
"SessionStart",
|
|
4712
|
+
"UserPromptSubmit",
|
|
4713
|
+
"PostToolUse",
|
|
4714
|
+
"Stop"
|
|
4715
|
+
]);
|
|
4716
|
+
const events = [...new Set(reviewEvents)].filter((event) => allowedEvents.has(event)).slice(0, 4);
|
|
4717
|
+
if (events.length > 0) {
|
|
4718
|
+
return `Open Terminal and run the command below. Choose Review hooks, open only the Remembrance ${events.join(", ")} hook${events.length === 1 ? "" : "s"}, and press t to trust ${events.length === 1 ? "it" : "them"}. Then fully quit and reopen Codex, submit one prompt, use one Remembrance tool, complete one turn, and call ${followupTool} again.`;
|
|
4719
|
+
}
|
|
4710
4720
|
return `Open Terminal and launch Codex with the command below. If Codex shows a Hooks need review screen, choose Review hooks and trust only the Remembrance SessionStart, UserPromptSubmit, PostToolUse, and Stop hooks. If no review screen appears, continue: Codex may be reusing an existing valid trust decision. Fully restart Codex, submit one prompt, use one Remembrance tool, complete one turn, and call ${followupTool} again. If the lifecycle warning remains, update or reinstall Remembrance and repeat this check.`;
|
|
4711
4721
|
}
|
|
4712
4722
|
function buildConnectionDoctorReport(input) {
|
|
@@ -5000,6 +5010,28 @@ function pluginLifecycleCheck(pluginHealth, issues) {
|
|
|
5000
5010
|
"The native plugin startup and prompt lifecycle is active."
|
|
5001
5011
|
);
|
|
5002
5012
|
}
|
|
5013
|
+
if (stringValue(pluginHealth.surface) === "codex" && issues.includes("hook_trust_required")) {
|
|
5014
|
+
const hookTrust = record(pluginHealth.hook_trust);
|
|
5015
|
+
const allowedEvents = /* @__PURE__ */ new Set([
|
|
5016
|
+
"SessionStart",
|
|
5017
|
+
"UserPromptSubmit",
|
|
5018
|
+
"PostToolUse",
|
|
5019
|
+
"Stop"
|
|
5020
|
+
]);
|
|
5021
|
+
const reviewEvents = Array.isArray(hookTrust.review_events) ? hookTrust.review_events.filter(
|
|
5022
|
+
(event) => typeof event === "string" && allowedEvents.has(event)
|
|
5023
|
+
).slice(0, 4) : [];
|
|
5024
|
+
const eventSummary = reviewEvents.length > 0 ? `the updated Remembrance ${reviewEvents.join(", ")} hook${reviewEvents.length === 1 ? "" : "s"}` : "updated Remembrance hooks";
|
|
5025
|
+
return warning(
|
|
5026
|
+
"plugin_lifecycle",
|
|
5027
|
+
`Codex is waiting for trust approval for ${eventSummary}.`,
|
|
5028
|
+
"review_codex_hook_trust",
|
|
5029
|
+
codexHookReviewAction("run_connection_doctor", reviewEvents),
|
|
5030
|
+
issues,
|
|
5031
|
+
false,
|
|
5032
|
+
CODEX_HOOK_REVIEW_COMMAND
|
|
5033
|
+
);
|
|
5034
|
+
}
|
|
5003
5035
|
if (pluginHealth.status === "partial") {
|
|
5004
5036
|
const codex = stringValue(pluginHealth.surface) === "codex";
|
|
5005
5037
|
return warning(
|
|
@@ -10730,13 +10762,23 @@ function localPluginLifecycleHealth(options) {
|
|
|
10730
10762
|
});
|
|
10731
10763
|
}
|
|
10732
10764
|
const coreLifecycleObserved = components.session_start.observed && components.prompt_hook.observed;
|
|
10733
|
-
|
|
10765
|
+
const hookTrust = normalizedPluginHookTrust(marker.hook_trust);
|
|
10766
|
+
const hookTrustReviewEvents = hookTrust?.status === "review_required" ? new Set(hookTrust.review_events) : /* @__PURE__ */ new Set();
|
|
10767
|
+
if (hookTrustReviewEvents.size > 0) {
|
|
10768
|
+
issues.push({
|
|
10769
|
+
code: "hook_trust_required",
|
|
10770
|
+
action: codexHookReviewAction("get_connection_status", [
|
|
10771
|
+
...hookTrustReviewEvents
|
|
10772
|
+
])
|
|
10773
|
+
});
|
|
10774
|
+
}
|
|
10775
|
+
if (coreLifecycleObserved && !components.tool_observer.observed && !hookTrustReviewEvents.has("PostToolUse")) {
|
|
10734
10776
|
issues.push({
|
|
10735
10777
|
code: "tool_observer_not_observed",
|
|
10736
10778
|
action: surface === "codex" ? `Use a Remembrance MCP tool, then call get_connection_status again. If this remains missing, ${codexHookReviewAction("get_connection_status")}` : "No native tool observer has run in this session yet. Use a Remembrance MCP tool, then call get_connection_status again."
|
|
10737
10779
|
});
|
|
10738
10780
|
}
|
|
10739
|
-
if (coreLifecycleObserved && !components.completion_hook.observed) {
|
|
10781
|
+
if (coreLifecycleObserved && !components.completion_hook.observed && !hookTrustReviewEvents.has("Stop")) {
|
|
10740
10782
|
issues.push({
|
|
10741
10783
|
code: "completion_hook_not_observed",
|
|
10742
10784
|
action: surface === "codex" ? `Complete one turn, then call get_connection_status again. If this remains missing, ${codexHookReviewAction("get_connection_status")}` : "No native completion hook has run in this session yet. Complete one turn, then call get_connection_status again."
|
|
@@ -10795,11 +10837,49 @@ function localPluginLifecycleHealth(options) {
|
|
|
10795
10837
|
matches_mcp: markerDestinationFingerprint && options.apiDestinationFingerprint ? markerDestinationFingerprint === options.apiDestinationFingerprint : null,
|
|
10796
10838
|
mcp_source: options.apiDestinationSource ?? null
|
|
10797
10839
|
},
|
|
10840
|
+
hook_trust: hookTrust,
|
|
10798
10841
|
components,
|
|
10799
10842
|
issues,
|
|
10800
10843
|
explanation: "Each host session is tracked independently. SessionStart and the prompt hook must run first; tool and completion observations appear after those lifecycle events become eligible."
|
|
10801
10844
|
};
|
|
10802
10845
|
}
|
|
10846
|
+
function normalizedPluginHookTrust(value) {
|
|
10847
|
+
if (!isRecord3(value)) return null;
|
|
10848
|
+
const status = value.status;
|
|
10849
|
+
if (status !== "trusted" && status !== "review_required" && status !== "unavailable") {
|
|
10850
|
+
return null;
|
|
10851
|
+
}
|
|
10852
|
+
const allowedEvents = /* @__PURE__ */ new Set([
|
|
10853
|
+
"SessionStart",
|
|
10854
|
+
"UserPromptSubmit",
|
|
10855
|
+
"PostToolUse",
|
|
10856
|
+
"Stop"
|
|
10857
|
+
]);
|
|
10858
|
+
const allowedStatuses = /* @__PURE__ */ new Set([
|
|
10859
|
+
"managed",
|
|
10860
|
+
"missing",
|
|
10861
|
+
"modified",
|
|
10862
|
+
"trusted",
|
|
10863
|
+
"unknown",
|
|
10864
|
+
"untrusted"
|
|
10865
|
+
]);
|
|
10866
|
+
const reviewEvents = Array.isArray(value.review_events) ? [...new Set(value.review_events)].filter(
|
|
10867
|
+
(event) => typeof event === "string" && allowedEvents.has(event)
|
|
10868
|
+
).slice(0, 4) : [];
|
|
10869
|
+
const hooks = Array.isArray(value.hooks) ? value.hooks.filter(isRecord3).filter(
|
|
10870
|
+
(entry) => typeof entry.event === "string" && allowedEvents.has(entry.event)
|
|
10871
|
+
).slice(0, 4).map((entry) => ({
|
|
10872
|
+
event: String(entry.event),
|
|
10873
|
+
enabled: entry.enabled === true,
|
|
10874
|
+
trust_status: typeof entry.trust_status === "string" && allowedStatuses.has(entry.trust_status) ? entry.trust_status : "unknown"
|
|
10875
|
+
})) : [];
|
|
10876
|
+
return {
|
|
10877
|
+
status,
|
|
10878
|
+
checked_at: typeof value.checked_at === "string" && Number.isFinite(Date.parse(value.checked_at)) ? new Date(value.checked_at).toISOString() : null,
|
|
10879
|
+
review_events: reviewEvents,
|
|
10880
|
+
hooks
|
|
10881
|
+
};
|
|
10882
|
+
}
|
|
10803
10883
|
function apiDestinationStatus(baseUrl, source) {
|
|
10804
10884
|
const normalized = baseUrl.replace(/\/+$/, "");
|
|
10805
10885
|
return {
|
|
@@ -11003,7 +11083,7 @@ async function checkClientUpdate(input) {
|
|
|
11003
11083
|
// src/server.ts
|
|
11004
11084
|
var MAX_REMOTE_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
11005
11085
|
var DOCTOR_PROBE_TIMEOUT_MS = 7500;
|
|
11006
|
-
var SERVER_VERSION = true ? "0.1.
|
|
11086
|
+
var SERVER_VERSION = true ? "0.1.63" : "0.0.0-dev";
|
|
11007
11087
|
var tools = toolDefinitions;
|
|
11008
11088
|
var doctorCliRequested = process.argv[2] === "doctor";
|
|
11009
11089
|
var inputBuffer = Buffer.alloc(0);
|