@remembrance-ai/opencode-plugin 0.2.1 → 0.2.3
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 +64 -6
- package/opencode.json +2 -2
- package/package.json +1 -1
- package/scripts/hook-core.mjs +1405 -396
- package/servers/remembrance-mcp.mjs +692 -97
- package/skills/remembrancer/SKILL.md +29 -0
- package/skills/remembrancer/references/remembrance-setup.md +866 -107
- package/src/index.mjs +74 -29
package/src/index.mjs
CHANGED
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
// host's event surface allows:
|
|
13
13
|
//
|
|
14
14
|
// SESSION START (event -> session.created): records lifecycle health under the
|
|
15
|
-
// "opencode" surface
|
|
16
|
-
//
|
|
15
|
+
// "opencode" surface, records a session created without a parent session
|
|
16
|
+
// as a main session and a child session's parent (the child belongs to its
|
|
17
|
+
// root session's tree), and logs the activation notice, so a user can see
|
|
18
|
+
// the plugin loaded and knows to call run_connection_doctor.
|
|
17
19
|
//
|
|
18
20
|
// PROMPT (chat.message -> experimental.chat.system.transform): queries from
|
|
19
21
|
// the current user turn, then injects the bounded result into the system
|
|
@@ -49,12 +51,14 @@ import {
|
|
|
49
51
|
acknowledgePrivateAuthorizationReminders,
|
|
50
52
|
directSelectionFromResponse,
|
|
51
53
|
highMatchFromResponse,
|
|
54
|
+
isRecordedMainSession,
|
|
52
55
|
markHostPolicyAlertDelivered,
|
|
53
56
|
observeSuccessfulCompletionTool,
|
|
54
57
|
recordDirectiveFollowThroughForTool,
|
|
55
58
|
recordDirectSelectionUse,
|
|
56
59
|
recordDirectiveSurface,
|
|
57
60
|
recordHighMatchSurface,
|
|
61
|
+
recordMainSession,
|
|
58
62
|
recordPluginLifecycleHealth,
|
|
59
63
|
recordHostPolicyDenial,
|
|
60
64
|
recordQueryFeedbackObligation,
|
|
@@ -65,7 +69,9 @@ import {
|
|
|
65
69
|
reportDirectiveEvent,
|
|
66
70
|
reportTaskOutcomesOnStop,
|
|
67
71
|
resolveApiCredential,
|
|
72
|
+
recordSessionParent,
|
|
68
73
|
runPromptHook,
|
|
74
|
+
sessionTreeLedgerId,
|
|
69
75
|
toolResponseIndicatesFailure,
|
|
70
76
|
valueEpisodeFromResponse,
|
|
71
77
|
writePromptedCount,
|
|
@@ -354,6 +360,21 @@ export const Remembrance = async (context = {}) => {
|
|
|
354
360
|
try {
|
|
355
361
|
const sessionId = sessionIdFromEvent(event, event);
|
|
356
362
|
recordLifecycle("session_start", sessionId);
|
|
363
|
+
// OpenCode runs a task subagent in a child session, created with its
|
|
364
|
+
// parent's id (properties.info.parentID), and that session's
|
|
365
|
+
// chat.message reads like a user prompt. Only a session created with no
|
|
366
|
+
// parent is a main session. Any other one, a child or a session no
|
|
367
|
+
// process of this plugin saw created, gets no user decision about
|
|
368
|
+
// retained drafts; they wait, unacknowledged, for a main session. A
|
|
369
|
+
// child's recorded parent puts it in its root session's tree
|
|
370
|
+
// (sessionTreeLedgerId); a child whose link was never seen keeps its
|
|
371
|
+
// drafts as its own, and they reach the user once it has been idle.
|
|
372
|
+
const info = event?.properties?.info;
|
|
373
|
+
if (info?.id === sessionId && !info.parentID) {
|
|
374
|
+
recordMainSession(sessionId, env);
|
|
375
|
+
} else if (info?.id === sessionId) {
|
|
376
|
+
recordSessionParent(sessionId, info.parentID, env);
|
|
377
|
+
}
|
|
357
378
|
const [, clientUpdate] = await Promise.all([
|
|
358
379
|
(context.warmSession ?? warmPrincipalSession)(
|
|
359
380
|
{
|
|
@@ -396,6 +417,7 @@ export const Remembrance = async (context = {}) => {
|
|
|
396
417
|
messageSessions.set(messageKey, sessionId);
|
|
397
418
|
turnContext.delete(sessionId);
|
|
398
419
|
recordLifecycle("prompt_hook", sessionId);
|
|
420
|
+
const ledgerId = sessionTreeLedgerId(sessionId, env);
|
|
399
421
|
const result = await (context.runPromptHook ?? runPromptHook)(prompt, {
|
|
400
422
|
env,
|
|
401
423
|
contextAdditions: pendingClientUpdates.has(sessionId)
|
|
@@ -410,13 +432,14 @@ export const Remembrance = async (context = {}) => {
|
|
|
410
432
|
identity: { provider: SURFACE, model: "opencode" },
|
|
411
433
|
reportDirectiveShown: false,
|
|
412
434
|
deferReminderAcknowledgement: true,
|
|
413
|
-
sessionId,
|
|
435
|
+
sessionId: ledgerId,
|
|
436
|
+
subagentSession: !isRecordedMainSession(sessionId, env),
|
|
414
437
|
nativeSessionId,
|
|
415
438
|
nativeContextBridge: context.nativeContextBridge,
|
|
416
439
|
userAgent: "@remembrance-ai/opencode-plugin",
|
|
417
440
|
});
|
|
418
441
|
if (result?.eligible) {
|
|
419
|
-
recordTaskEligibility(
|
|
442
|
+
recordTaskEligibility(ledgerId, env);
|
|
420
443
|
}
|
|
421
444
|
const guidance = result?.context ?? "";
|
|
422
445
|
if (guidance.trim()) {
|
|
@@ -431,6 +454,7 @@ export const Remembrance = async (context = {}) => {
|
|
|
431
454
|
// injection observable as an absence of use rather than as silent
|
|
432
455
|
// over-reporting.
|
|
433
456
|
pendingGuidance.set(sessionId, {
|
|
457
|
+
ledgerId,
|
|
434
458
|
directive: result?.directive ?? null,
|
|
435
459
|
highMatch: result?.highMatch ?? null,
|
|
436
460
|
text: guidance.trim(),
|
|
@@ -442,7 +466,7 @@ export const Remembrance = async (context = {}) => {
|
|
|
442
466
|
} else if (result?.consumed) {
|
|
443
467
|
// An empty task-shape search delivers nothing (owner decision 34C);
|
|
444
468
|
// like any completed search it still ends an earlier directive.
|
|
445
|
-
recordDirectiveSurface(
|
|
469
|
+
recordDirectiveSurface(ledgerId, null, env);
|
|
446
470
|
}
|
|
447
471
|
}
|
|
448
472
|
|
|
@@ -485,7 +509,11 @@ export const Remembrance = async (context = {}) => {
|
|
|
485
509
|
if (delivered.length > 0) turnContext.set(sessionId, delivered);
|
|
486
510
|
output.system.push(...delivered);
|
|
487
511
|
if (!guidance) return;
|
|
488
|
-
acknowledgePrivateAuthorizationReminders(
|
|
512
|
+
acknowledgePrivateAuthorizationReminders(
|
|
513
|
+
guidance.text,
|
|
514
|
+
env,
|
|
515
|
+
guidance.ledgerId,
|
|
516
|
+
);
|
|
489
517
|
pendingGuidance.delete(sessionId);
|
|
490
518
|
if (guidance.deliveredContextAdditionIds.includes("client_update")) {
|
|
491
519
|
pendingClientUpdates.delete(sessionId);
|
|
@@ -493,12 +521,20 @@ export const Remembrance = async (context = {}) => {
|
|
|
493
521
|
// Delivery happened: only now is the guidance actually in the model's
|
|
494
522
|
// context, so only now does it count as registry use.
|
|
495
523
|
if (guidance.countsAsUse) {
|
|
496
|
-
recordRegistryUse(
|
|
497
|
-
recordHighMatchSurface(
|
|
498
|
-
recordValueEpisodeSurface(
|
|
524
|
+
recordRegistryUse(guidance.ledgerId, env);
|
|
525
|
+
recordHighMatchSurface(guidance.ledgerId, guidance.highMatch, env);
|
|
526
|
+
recordValueEpisodeSurface(
|
|
527
|
+
guidance.ledgerId,
|
|
528
|
+
guidance.valueEpisode,
|
|
529
|
+
env,
|
|
530
|
+
);
|
|
499
531
|
}
|
|
500
|
-
recordQueryFeedbackObligation(
|
|
501
|
-
|
|
532
|
+
recordQueryFeedbackObligation(
|
|
533
|
+
guidance.ledgerId,
|
|
534
|
+
guidance.queryFeedback,
|
|
535
|
+
env,
|
|
536
|
+
);
|
|
537
|
+
recordDirectiveSurface(guidance.ledgerId, guidance.directive, env);
|
|
502
538
|
if (guidance.directive) {
|
|
503
539
|
await reportDirectiveEvent(
|
|
504
540
|
{
|
|
@@ -577,16 +613,20 @@ export const Remembrance = async (context = {}) => {
|
|
|
577
613
|
try {
|
|
578
614
|
sessionId = sessionIdFromEvent(event, event);
|
|
579
615
|
recordLifecycle("completion_hook", sessionId);
|
|
580
|
-
|
|
616
|
+
const ledgerId = sessionTreeLedgerId(sessionId, env);
|
|
617
|
+
await reportTaskOutcomesOnStop(ledgerId, event, {
|
|
581
618
|
env,
|
|
582
619
|
userAgent: "@remembrance-ai/opencode-plugin",
|
|
583
620
|
});
|
|
584
|
-
const decision = decideStop(
|
|
621
|
+
const decision = decideStop(
|
|
622
|
+
{ session_id: ledgerId },
|
|
623
|
+
{ env, subagentSession: !isRecordedMainSession(sessionId, env) },
|
|
624
|
+
);
|
|
585
625
|
if (decision?.allow !== false || !decision?.reason) return;
|
|
586
626
|
const delivered = await notify(client, decision.reason);
|
|
587
627
|
if (!delivered) return;
|
|
588
|
-
writePromptedCount(
|
|
589
|
-
acknowledgePrivateAuthorizationReminders(decision.reason, env);
|
|
628
|
+
writePromptedCount(ledgerId, decision.useCount ?? 1, env);
|
|
629
|
+
acknowledgePrivateAuthorizationReminders(decision.reason, env, ledgerId);
|
|
590
630
|
} catch (error) {
|
|
591
631
|
debugLog(env, "opencode_completion_hook_failed", {
|
|
592
632
|
error: String(error),
|
|
@@ -659,33 +699,38 @@ export const Remembrance = async (context = {}) => {
|
|
|
659
699
|
const normalizedTool = tool.toLowerCase();
|
|
660
700
|
const toolArguments = toolArgumentsFromEvent(input);
|
|
661
701
|
if (toolResponseIndicatesFailure(output)) {
|
|
702
|
+
// The call's own arguments let a denial park that exact draft.
|
|
662
703
|
await observeHostPolicyDenial({
|
|
663
704
|
eventType: "tool.execute.after",
|
|
664
705
|
sessionId,
|
|
665
706
|
toolName: tool,
|
|
666
|
-
value:
|
|
707
|
+
value: {
|
|
708
|
+
...(output && typeof output === "object" ? output : { output }),
|
|
709
|
+
tool_input: toolArguments,
|
|
710
|
+
},
|
|
667
711
|
});
|
|
668
712
|
return;
|
|
669
713
|
}
|
|
670
714
|
recordLifecycle("tool_observer", sessionId);
|
|
715
|
+
const ledgerId = sessionTreeLedgerId(sessionId, env);
|
|
671
716
|
if (normalizedTool.endsWith("query_skills")) {
|
|
672
717
|
if (queryResponseHasMatches(output)) {
|
|
673
|
-
recordRegistryUse(
|
|
718
|
+
recordRegistryUse(ledgerId, env);
|
|
674
719
|
}
|
|
675
|
-
recordHighMatchSurface(
|
|
720
|
+
recordHighMatchSurface(ledgerId, highMatchFromResponse(output), env);
|
|
676
721
|
recordValueEpisodeSurface(
|
|
677
|
-
|
|
722
|
+
ledgerId,
|
|
678
723
|
valueEpisodeFromResponse(output),
|
|
679
724
|
env,
|
|
680
725
|
);
|
|
681
726
|
observeSuccessfulCompletionTool(
|
|
682
|
-
|
|
727
|
+
ledgerId,
|
|
683
728
|
tool,
|
|
684
729
|
toolArguments,
|
|
685
730
|
output,
|
|
686
731
|
env,
|
|
687
732
|
);
|
|
688
|
-
await recordDirectiveFollowThroughForTool(
|
|
733
|
+
await recordDirectiveFollowThroughForTool(ledgerId, tool, output, {
|
|
689
734
|
env,
|
|
690
735
|
});
|
|
691
736
|
return;
|
|
@@ -693,19 +738,19 @@ export const Remembrance = async (context = {}) => {
|
|
|
693
738
|
if (normalizedTool.endsWith("invoke_skill")) {
|
|
694
739
|
const selection = directSelectionFromResponse(output);
|
|
695
740
|
if (!selection) return;
|
|
696
|
-
recordDirectSelectionUse(
|
|
741
|
+
recordDirectSelectionUse(ledgerId, selection, env);
|
|
697
742
|
recordValueEpisodeSurface(
|
|
698
|
-
|
|
743
|
+
ledgerId,
|
|
699
744
|
valueEpisodeFromResponse(output),
|
|
700
745
|
env,
|
|
701
746
|
);
|
|
702
747
|
clearHighMatchSurfaceForExplicitSelection(
|
|
703
|
-
|
|
748
|
+
ledgerId,
|
|
704
749
|
selection.slug,
|
|
705
750
|
env,
|
|
706
751
|
);
|
|
707
752
|
observeSuccessfulCompletionTool(
|
|
708
|
-
|
|
753
|
+
ledgerId,
|
|
709
754
|
tool,
|
|
710
755
|
toolArguments,
|
|
711
756
|
output,
|
|
@@ -715,7 +760,7 @@ export const Remembrance = async (context = {}) => {
|
|
|
715
760
|
}
|
|
716
761
|
if (normalizedTool.endsWith("prepare_private_lesson_candidate")) {
|
|
717
762
|
observeSuccessfulCompletionTool(
|
|
718
|
-
|
|
763
|
+
ledgerId,
|
|
719
764
|
tool,
|
|
720
765
|
toolArguments,
|
|
721
766
|
output,
|
|
@@ -742,7 +787,7 @@ export const Remembrance = async (context = {}) => {
|
|
|
742
787
|
nativeSessionId: nativeSessionIdFromEvent(input, null),
|
|
743
788
|
});
|
|
744
789
|
observeSuccessfulCompletionTool(
|
|
745
|
-
|
|
790
|
+
ledgerId,
|
|
746
791
|
tool,
|
|
747
792
|
toolArguments,
|
|
748
793
|
output,
|
|
@@ -750,9 +795,9 @@ export const Remembrance = async (context = {}) => {
|
|
|
750
795
|
);
|
|
751
796
|
return;
|
|
752
797
|
}
|
|
753
|
-
clearHighMatchSurfaceIfOpened(
|
|
798
|
+
clearHighMatchSurfaceIfOpened(ledgerId, tool, toolArguments, env);
|
|
754
799
|
observeSuccessfulCompletionTool(
|
|
755
|
-
|
|
800
|
+
ledgerId,
|
|
756
801
|
tool,
|
|
757
802
|
toolArguments,
|
|
758
803
|
output,
|