opencode-immune 1.0.19 → 1.0.20

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.
Files changed (2) hide show
  1. package/dist/plugin.js +42 -31
  2. package/package.json +1 -1
package/dist/plugin.js CHANGED
@@ -902,44 +902,37 @@ function createEventHandler(state) {
902
902
  }
903
903
  // ═══════════════════════════════════════════════════════════════════════════════
904
904
  // HOOK 9: MULTI-CYCLE AUTOMATION (PRE_COMMIT + CYCLE_COMPLETE)
905
+ // Uses experimental.text.complete to detect signal markers in assistant output.
906
+ // chat.message only fires for user messages (output.message is UserMessage),
907
+ // so signal detection MUST use text.complete which fires for assistant text.
905
908
  // ═══════════════════════════════════════════════════════════════════════════════
906
909
  const MAX_CYCLES = 10;
907
910
  const PRE_COMMIT_MARKER = "0-ULTRAWORK: PRE_COMMIT";
908
911
  const CYCLE_COMPLETE_MARKER = "0-ULTRAWORK: CYCLE_COMPLETE";
909
912
  const NEXT_TASK_PATTERN = /Next task:\s*(.+)/;
910
- const RATE_LIMIT_MESSAGE_PATTERN = /too many requests|rate_limit|rate limit/i;
911
913
  const ALL_CYCLES_COMPLETE_MARKER = "0-ULTRAWORK: ALL_CYCLES_COMPLETE";
912
914
  /**
913
- * chat.message part: scans assistant messages for PRE_COMMIT and CYCLE_COMPLETE markers.
915
+ * experimental.text.complete: scans completed assistant text for signal markers.
914
916
  *
915
917
  * PRE_COMMIT → executes /commit via client.session.command()
916
918
  * CYCLE_COMPLETE → creates a new session and sends bootstrap prompt
919
+ * ALL_CYCLES_COMPLETE → clears ultrawork marker
917
920
  */
918
- function createMultiCycleHandler(state) {
921
+ function createTextCompleteHandler(state) {
919
922
  return async (input, output) => {
920
923
  const sessionID = input.sessionID;
921
- // Extract text content from parts
922
- const parts = output.parts ?? [];
923
- let messageContent = "";
924
- for (const p of parts) {
925
- if ("type" in p && p.type === "text" && "text" in p) {
926
- messageContent += " " + p.text;
927
- }
928
- }
929
- messageContent = messageContent.trim();
930
- if (!messageContent)
924
+ const text = output.text ?? "";
925
+ if (!text)
931
926
  return;
932
- const messageRole = output.message?.role;
933
- // ── ALL_CYCLES_COMPLETE: clear ultrawork marker (checked before managed session guard) ──
934
- if (messageContent.includes(ALL_CYCLES_COMPLETE_MARKER)) {
927
+ // ── ALL_CYCLES_COMPLETE: clear ultrawork marker ──
928
+ if (text.includes(ALL_CYCLES_COMPLETE_MARKER)) {
935
929
  await clearUltraworkMarker(state);
936
- console.log("[opencode-immune] Multi-Cycle: ALL_CYCLES_COMPLETE detected, ultrawork marker cleared.");
930
+ console.log("[opencode-immune] Multi-Cycle: ALL_CYCLES_COMPLETE detected in text.complete, ultrawork marker cleared.");
937
931
  }
938
- // ── PRE_COMMIT: execute /commit (checked before managed session guard) ──
939
- if (messageContent.includes(PRE_COMMIT_MARKER) && !state.commitPending && sessionID) {
932
+ // ── PRE_COMMIT: execute /commit ──
933
+ if (text.includes(PRE_COMMIT_MARKER) && !state.commitPending && sessionID) {
940
934
  state.commitPending = true;
941
- console.log("[opencode-immune] Multi-Cycle: PRE_COMMIT detected, executing /commit...");
942
- // Small delay to let the message finish rendering
935
+ console.log("[opencode-immune] Multi-Cycle: PRE_COMMIT detected in text.complete, executing /commit...");
943
936
  setTimeout(async () => {
944
937
  try {
945
938
  await state.input.client.session.command({
@@ -959,29 +952,26 @@ function createMultiCycleHandler(state) {
959
952
  }
960
953
  }, 2_000);
961
954
  }
962
- // ── CYCLE_COMPLETE: create new session (checked before managed session guard) ──
963
- if (messageContent.includes(CYCLE_COMPLETE_MARKER)) {
955
+ // ── CYCLE_COMPLETE: create new session ──
956
+ if (text.includes(CYCLE_COMPLETE_MARKER)) {
964
957
  state.cycleCount++;
965
958
  if (state.cycleCount >= MAX_CYCLES) {
966
959
  console.log(`[opencode-immune] Multi-Cycle: MAX_CYCLES (${MAX_CYCLES}) reached. Not creating new session.`);
967
960
  await clearUltraworkMarker(state);
968
961
  return;
969
962
  }
970
- // Extract next task description
971
- const taskMatch = messageContent.match(NEXT_TASK_PATTERN);
963
+ const taskMatch = text.match(NEXT_TASK_PATTERN);
972
964
  const nextTask = taskMatch?.[1]?.trim() ?? "Continue processing task backlog";
973
- console.log(`[opencode-immune] Multi-Cycle: CYCLE_COMPLETE detected (cycle ${state.cycleCount}/${MAX_CYCLES}). ` +
965
+ console.log(`[opencode-immune] Multi-Cycle: CYCLE_COMPLETE detected in text.complete (cycle ${state.cycleCount}/${MAX_CYCLES}). ` +
974
966
  `Creating new session for: "${nextTask}"`);
975
967
  // Delay to let commit finish
976
968
  setTimeout(async () => {
977
969
  try {
978
- // Create a new session
979
970
  const createResult = await state.input.client.session.create({
980
971
  body: {
981
972
  title: `Ultrawork Cycle ${state.cycleCount + 1}`,
982
973
  },
983
974
  });
984
- // Extract new session ID from the response
985
975
  const newSessionData = createResult?.data;
986
976
  const newSessionID = newSessionData?.id;
987
977
  if (!newSessionID) {
@@ -990,7 +980,6 @@ function createMultiCycleHandler(state) {
990
980
  }
991
981
  console.log(`[opencode-immune] Multi-Cycle: New session created: ${newSessionID}`);
992
982
  await addManagedUltraworkSession(state, newSessionID);
993
- // Send bootstrap prompt to the new session
994
983
  await state.input.client.session.promptAsync({
995
984
  body: {
996
985
  agent: ULTRAWORK_AGENT,
@@ -1008,14 +997,35 @@ function createMultiCycleHandler(state) {
1008
997
  catch (err) {
1009
998
  console.error("[opencode-immune] Multi-Cycle: Failed to create new session or send prompt:", err);
1010
999
  }
1011
- }, 8_000); // 8s delay: let /commit finish first
1000
+ }, 8_000);
1012
1001
  }
1002
+ };
1003
+ }
1004
+ /**
1005
+ * chat.message part: handles rate-limit text detection in managed root sessions.
1006
+ * NOTE: chat.message fires for user messages only (output.message = UserMessage).
1007
+ * Signal detection (PRE_COMMIT/CYCLE_COMPLETE) moved to experimental.text.complete.
1008
+ */
1009
+ function createMultiCycleHandler(state) {
1010
+ return async (input, output) => {
1011
+ const sessionID = input.sessionID;
1012
+ // Extract text content from parts
1013
+ const parts = output.parts ?? [];
1014
+ let messageContent = "";
1015
+ for (const p of parts) {
1016
+ if ("type" in p && p.type === "text" && "text" in p) {
1017
+ messageContent += " " + p.text;
1018
+ }
1019
+ }
1020
+ messageContent = messageContent.trim();
1021
+ if (!messageContent)
1022
+ return;
1013
1023
  if (!isManagedRootUltraworkSession(state, sessionID))
1014
1024
  return;
1015
1025
  const managedSession = getManagedSession(state, sessionID);
1016
1026
  // Rate-limit message detection (only for managed root sessions)
1027
+ const RATE_LIMIT_MESSAGE_PATTERN = /too many requests|rate_limit|rate limit/i;
1017
1028
  if (sessionID &&
1018
- messageRole === "assistant" &&
1019
1029
  RATE_LIMIT_MESSAGE_PATTERN.test(messageContent)) {
1020
1030
  if (managedSession && !managedSession.fallbackModel) {
1021
1031
  await setSessionFallbackModel(state, sessionID, RATE_LIMIT_FALLBACK_MODEL);
@@ -1166,6 +1176,7 @@ async function server(input) {
1166
1176
  "tool.execute.after": withErrorBoundary("tool.execute.after", compositeToolAfter(toolAfterHandlers)),
1167
1177
  "experimental.chat.system.transform": withErrorBoundary("experimental.chat.system.transform", createSystemTransform(state)),
1168
1178
  "experimental.session.compacting": withErrorBoundary("experimental.session.compacting", createCompactionHandler(state)),
1179
+ "experimental.text.complete": withErrorBoundary("experimental.text.complete", createTextCompleteHandler(state)),
1169
1180
  };
1170
1181
  }
1171
1182
  exports.default = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-immune",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
5
5
  "exports": {
6
6
  "./server": "./dist/plugin.js"