opencode-immune 1.0.44 → 1.0.46
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/dist/plugin.js +172 -91
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -59,10 +59,10 @@ async function checkPluginUpdate(state) {
|
|
|
59
59
|
state.pluginUpdateMessage =
|
|
60
60
|
`[PLUGIN UPDATE] opencode-immune ${currentVersion} → ${latest} is available. ` +
|
|
61
61
|
`Please inform the user: a plugin update is available. They should restart opencode to get the latest version.`;
|
|
62
|
-
|
|
62
|
+
writePluginLog(state, "warn", `[opencode-immune] Plugin update available: ${currentVersion} → ${latest}.`);
|
|
63
63
|
}
|
|
64
64
|
else if (latest) {
|
|
65
|
-
|
|
65
|
+
writePluginLog(state, "info", `[opencode-immune] Plugin version ${currentVersion} is up to date.`);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
catch {
|
|
@@ -70,6 +70,7 @@ async function checkPluginUpdate(state) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
function createState(input) {
|
|
73
|
+
activeLogDirectory = input.directory;
|
|
73
74
|
return {
|
|
74
75
|
input,
|
|
75
76
|
recoveryContext: null,
|
|
@@ -92,6 +93,8 @@ function createState(input) {
|
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
95
|
const ULTRAWORK_AGENT = "0-ultrawork";
|
|
96
|
+
const DIAGNOSTIC_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
97
|
+
let activeLogDirectory = null;
|
|
95
98
|
const MANAGED_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
96
99
|
const PROVIDER_RETRY_WATCHDOG_MS = 30_000;
|
|
97
100
|
const CHILD_FALLBACK_REQUEST_TTL_MS = 10 * 60 * 1000;
|
|
@@ -130,6 +133,7 @@ async function writeDiagnosticLog(state, event, data = {}) {
|
|
|
130
133
|
try {
|
|
131
134
|
const cacheDir = (0, path_1.join)(state.input.directory, ".opencode", "state");
|
|
132
135
|
await (0, promises_1.mkdir)(cacheDir, { recursive: true });
|
|
136
|
+
await rotateDiagnosticLogIfNeeded(state.diagnosticsLogPath);
|
|
133
137
|
const line = JSON.stringify({ ts: new Date().toISOString(), event, ...data });
|
|
134
138
|
await (0, promises_1.appendFile)(state.diagnosticsLogPath, `${line}\n`, "utf-8");
|
|
135
139
|
}
|
|
@@ -137,6 +141,68 @@ async function writeDiagnosticLog(state, event, data = {}) {
|
|
|
137
141
|
// diagnostics must never affect runtime behavior
|
|
138
142
|
}
|
|
139
143
|
}
|
|
144
|
+
async function rotateDiagnosticLogIfNeeded(logPath) {
|
|
145
|
+
try {
|
|
146
|
+
const current = await (0, promises_1.stat)(logPath);
|
|
147
|
+
if (current.size < DIAGNOSTIC_LOG_MAX_BYTES)
|
|
148
|
+
return;
|
|
149
|
+
const rotatedPath = `${logPath}.1`;
|
|
150
|
+
await (0, promises_1.rm)(rotatedPath, { force: true });
|
|
151
|
+
await (0, promises_1.rename)(logPath, rotatedPath);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// missing log or rotation failure must never affect runtime behavior
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function normalizeLogValue(value) {
|
|
158
|
+
if (value instanceof Error) {
|
|
159
|
+
return {
|
|
160
|
+
name: value.name,
|
|
161
|
+
message: value.message,
|
|
162
|
+
stack: value.stack,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
function writePluginLog(state, level, message, extra = {}) {
|
|
168
|
+
void writeDiagnosticLog(state, `log:${level}`, {
|
|
169
|
+
message,
|
|
170
|
+
...extra,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function writePluginLogForDirectory(directory, level, message, extra = {}) {
|
|
174
|
+
const diagnosticsLogPath = (0, path_1.join)(directory, ".opencode", "state", "opencode-immune-debug.log");
|
|
175
|
+
void (async () => {
|
|
176
|
+
try {
|
|
177
|
+
await (0, promises_1.mkdir)((0, path_1.dirname)(diagnosticsLogPath), { recursive: true });
|
|
178
|
+
await rotateDiagnosticLogIfNeeded(diagnosticsLogPath);
|
|
179
|
+
const line = JSON.stringify({
|
|
180
|
+
ts: new Date().toISOString(),
|
|
181
|
+
event: `log:${level}`,
|
|
182
|
+
message,
|
|
183
|
+
...extra,
|
|
184
|
+
});
|
|
185
|
+
await (0, promises_1.appendFile)(diagnosticsLogPath, `${line}\n`, "utf-8");
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// file logging must never affect runtime behavior
|
|
189
|
+
}
|
|
190
|
+
})();
|
|
191
|
+
}
|
|
192
|
+
function writePluginLogFromArgs(level, values) {
|
|
193
|
+
if (!activeLogDirectory)
|
|
194
|
+
return;
|
|
195
|
+
const [first, ...rest] = values;
|
|
196
|
+
const message = typeof first === "string" ? first : JSON.stringify(normalizeLogValue(first));
|
|
197
|
+
writePluginLogForDirectory(activeLogDirectory, level, message, {
|
|
198
|
+
args: rest.map(normalizeLogValue),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const pluginLog = {
|
|
202
|
+
info: (...values) => writePluginLogFromArgs("info", values),
|
|
203
|
+
warn: (...values) => writePluginLogFromArgs("warn", values),
|
|
204
|
+
error: (...values) => writePluginLogFromArgs("error", values),
|
|
205
|
+
};
|
|
140
206
|
// ── Ultrawork Marker File ──
|
|
141
207
|
async function writeUltraworkMarker(state) {
|
|
142
208
|
try {
|
|
@@ -192,6 +258,12 @@ async function addManagedChildSession(state, sessionID, parentSessionID, timesta
|
|
|
192
258
|
const parent = state.managedUltraworkSessions.get(parentSessionID);
|
|
193
259
|
if (!parent)
|
|
194
260
|
return;
|
|
261
|
+
cancelProviderRetryWatchdog(state, parent.rootSessionID, `child session ${sessionID} created`);
|
|
262
|
+
await writeDiagnosticLog(state, "provider-retry-watchdog:cancel-child-created", {
|
|
263
|
+
sessionID,
|
|
264
|
+
parentSessionID,
|
|
265
|
+
rootSessionID: parent.rootSessionID,
|
|
266
|
+
});
|
|
195
267
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
196
268
|
state.managedUltraworkSessions.set(sessionID, {
|
|
197
269
|
kind: "child",
|
|
@@ -208,7 +280,7 @@ function cancelPendingSessionRetry(state, sessionID, reason) {
|
|
|
208
280
|
return;
|
|
209
281
|
clearTimeout(timer);
|
|
210
282
|
state.sessionRetryTimers.delete(sessionID);
|
|
211
|
-
|
|
283
|
+
writePluginLog(state, "info", `[opencode-immune] Cancelled pending retry for session ${sessionID}: ${reason}`);
|
|
212
284
|
}
|
|
213
285
|
function cancelProviderRetryWatchdog(state, sessionID, reason) {
|
|
214
286
|
const timer = state.providerRetryWatchdogs.get(sessionID);
|
|
@@ -216,7 +288,7 @@ function cancelProviderRetryWatchdog(state, sessionID, reason) {
|
|
|
216
288
|
return;
|
|
217
289
|
clearTimeout(timer);
|
|
218
290
|
state.providerRetryWatchdogs.delete(sessionID);
|
|
219
|
-
|
|
291
|
+
writePluginLog(state, "info", `[opencode-immune] Cancelled provider retry watchdog for session ${sessionID}: ${reason}`);
|
|
220
292
|
}
|
|
221
293
|
async function removeManagedUltraworkSession(state, sessionID, reason) {
|
|
222
294
|
cancelPendingSessionRetry(state, sessionID, reason);
|
|
@@ -225,7 +297,7 @@ async function removeManagedUltraworkSession(state, sessionID, reason) {
|
|
|
225
297
|
const existed = state.managedUltraworkSessions.delete(sessionID);
|
|
226
298
|
if (!existed)
|
|
227
299
|
return;
|
|
228
|
-
|
|
300
|
+
writePluginLog(state, "info", `[opencode-immune] Removed managed ultrawork session ${sessionID}: ${reason}`);
|
|
229
301
|
}
|
|
230
302
|
async function updateManagedSessionAgent(state, sessionID, agent) {
|
|
231
303
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
@@ -487,7 +559,7 @@ async function sendManagedSessionRetryPrompt(state, sessionID, reason, options =
|
|
|
487
559
|
},
|
|
488
560
|
path: { id: sessionID },
|
|
489
561
|
});
|
|
490
|
-
|
|
562
|
+
writePluginLog(state, "info", `[opencode-immune] Retry prompt sent to session ${sessionID} (${reason})` +
|
|
491
563
|
(fallbackModel
|
|
492
564
|
? ` using fallback model ${fallbackModel.providerID}/${fallbackModel.modelID}`
|
|
493
565
|
: ""));
|
|
@@ -504,11 +576,11 @@ function scheduleManagedSessionRetry(state, sessionID, options) {
|
|
|
504
576
|
return false;
|
|
505
577
|
}
|
|
506
578
|
if (state.sessionRetryTimers.has(sessionID)) {
|
|
507
|
-
|
|
579
|
+
writePluginLog(state, "info", `[opencode-immune] Retry already pending for session ${sessionID}, skipping duplicate.`);
|
|
508
580
|
return false;
|
|
509
581
|
}
|
|
510
582
|
const attemptInfo = options.attemptLabel ? ` (${options.attemptLabel})` : "";
|
|
511
|
-
|
|
583
|
+
writePluginLog(state, "info", `[opencode-immune] Scheduling retry for session ${sessionID}${attemptInfo}. ` +
|
|
512
584
|
`Waiting ${options.delayMs / 1000}s before retry...`);
|
|
513
585
|
const timer = setTimeout(async () => {
|
|
514
586
|
state.sessionRetryTimers.delete(sessionID);
|
|
@@ -524,7 +596,7 @@ function scheduleManagedSessionRetry(state, sessionID, options) {
|
|
|
524
596
|
if (options.countAgainstBudget) {
|
|
525
597
|
state.sessionErrorRetryCount.set(sessionID, Math.max((state.sessionErrorRetryCount.get(sessionID) ?? 1) - 1, 0));
|
|
526
598
|
}
|
|
527
|
-
|
|
599
|
+
writePluginLog(state, "warn", `[opencode-immune] Retry prompt failed for session ${sessionID}. ` +
|
|
528
600
|
`Will wait for the next retry signal.`);
|
|
529
601
|
}
|
|
530
602
|
}, options.delayMs);
|
|
@@ -538,13 +610,17 @@ function scheduleManagedSessionRetry(state, sessionID, options) {
|
|
|
538
610
|
* Wraps a hook handler in a try/catch to prevent any single hook failure
|
|
539
611
|
* from crashing the entire agent session.
|
|
540
612
|
*/
|
|
541
|
-
function withErrorBoundary(hookName, handler) {
|
|
613
|
+
function withErrorBoundary(state, hookName, handler) {
|
|
542
614
|
return (async (...args) => {
|
|
543
615
|
try {
|
|
544
616
|
return await handler(...args);
|
|
545
617
|
}
|
|
546
618
|
catch (err) {
|
|
547
|
-
|
|
619
|
+
const hookInput = args.find((arg) => !!arg && typeof arg === "object" && "sessionID" in arg);
|
|
620
|
+
writePluginLog(state, "error", `[opencode-immune] Hook "${hookName}" error.`, {
|
|
621
|
+
error: normalizeLogValue(err),
|
|
622
|
+
sessionID: hookInput?.sessionID,
|
|
623
|
+
});
|
|
548
624
|
// Error is swallowed — hook failure must not crash agent session
|
|
549
625
|
}
|
|
550
626
|
});
|
|
@@ -708,7 +784,7 @@ async function resolveEnvValue(directory, key) {
|
|
|
708
784
|
* Fetch latest release info from the harness GitHub repo.
|
|
709
785
|
* Returns null if token is missing, network fails, or no release found.
|
|
710
786
|
*/
|
|
711
|
-
async function fetchLatestHarnessRelease(repo, token) {
|
|
787
|
+
async function fetchLatestHarnessRelease(directory, repo, token) {
|
|
712
788
|
try {
|
|
713
789
|
const url = `https://api.github.com/repos/${repo}/releases/latest`;
|
|
714
790
|
const resp = await fetch(url, {
|
|
@@ -720,10 +796,10 @@ async function fetchLatestHarnessRelease(repo, token) {
|
|
|
720
796
|
});
|
|
721
797
|
if (!resp.ok) {
|
|
722
798
|
if (resp.status === 404) {
|
|
723
|
-
|
|
799
|
+
writePluginLogForDirectory(directory, "info", `[opencode-immune] Harness sync: no releases found in ${repo}`);
|
|
724
800
|
}
|
|
725
801
|
else {
|
|
726
|
-
|
|
802
|
+
writePluginLogForDirectory(directory, "warn", `[opencode-immune] Harness sync: GitHub API returned ${resp.status} ${resp.statusText}`);
|
|
727
803
|
}
|
|
728
804
|
return null;
|
|
729
805
|
}
|
|
@@ -736,7 +812,7 @@ async function fetchLatestHarnessRelease(repo, token) {
|
|
|
736
812
|
const assetName = isWindows ? "harness-windows.tar.gz" : "harness.tar.gz";
|
|
737
813
|
const asset = data.assets?.find((a) => a.name === assetName);
|
|
738
814
|
if (!asset) {
|
|
739
|
-
|
|
815
|
+
writePluginLogForDirectory(directory, "warn", `[opencode-immune] Harness sync: release ${tagName} has no ${assetName} asset`);
|
|
740
816
|
return null;
|
|
741
817
|
}
|
|
742
818
|
return {
|
|
@@ -746,7 +822,7 @@ async function fetchLatestHarnessRelease(repo, token) {
|
|
|
746
822
|
};
|
|
747
823
|
}
|
|
748
824
|
catch (err) {
|
|
749
|
-
|
|
825
|
+
writePluginLogForDirectory(directory, "warn", `[opencode-immune] Harness sync: failed to fetch release info.`, { error: normalizeLogValue(err) });
|
|
750
826
|
return null;
|
|
751
827
|
}
|
|
752
828
|
}
|
|
@@ -811,7 +887,7 @@ async function copyDirRecursive(src, dest, skipRootFiles, rootDest) {
|
|
|
811
887
|
for (const entry of entries) {
|
|
812
888
|
// Skip files only at the root destination level
|
|
813
889
|
if (skipRootFiles && dest === effectiveRoot && entry.name === ".gitignore") {
|
|
814
|
-
|
|
890
|
+
pluginLog.info(`[opencode-immune] Harness sync: skipping root .gitignore`);
|
|
815
891
|
continue;
|
|
816
892
|
}
|
|
817
893
|
const srcPath = (0, path_1.join)(src, entry.name);
|
|
@@ -862,16 +938,16 @@ async function syncHarness(state) {
|
|
|
862
938
|
|| DEFAULT_HARNESS_REPO;
|
|
863
939
|
try {
|
|
864
940
|
// 1. Fetch latest release
|
|
865
|
-
const release = await fetchLatestHarnessRelease(repo, token);
|
|
941
|
+
const release = await fetchLatestHarnessRelease(state.input.directory, repo, token);
|
|
866
942
|
if (!release)
|
|
867
943
|
return;
|
|
868
944
|
// 2. Compare versions
|
|
869
945
|
const localVersion = await readLocalHarnessVersion(state.input.directory);
|
|
870
946
|
if (localVersion === release.tagName) {
|
|
871
|
-
|
|
947
|
+
pluginLog.info(`[opencode-immune] Harness sync: already up to date (${release.tagName})`);
|
|
872
948
|
return;
|
|
873
949
|
}
|
|
874
|
-
|
|
950
|
+
pluginLog.info(`[opencode-immune] Harness sync: updating ${localVersion ?? "(none)"} → ${release.tagName}`);
|
|
875
951
|
// 3. Hash opencode.json before update
|
|
876
952
|
const configPath = (0, path_1.join)(state.input.directory, "opencode.json");
|
|
877
953
|
const hashBefore = await fileHash(configPath);
|
|
@@ -893,10 +969,10 @@ async function syncHarness(state) {
|
|
|
893
969
|
// 8. Check if opencode.json changed
|
|
894
970
|
const hashAfter = await fileHash(configPath);
|
|
895
971
|
if (hashBefore && hashAfter && hashBefore !== hashAfter) {
|
|
896
|
-
|
|
972
|
+
pluginLog.warn(`[opencode-immune] ⚠ Harness sync: opencode.json was updated. ` +
|
|
897
973
|
`Please restart opencode for the new agent configuration to take effect.`);
|
|
898
974
|
}
|
|
899
|
-
|
|
975
|
+
pluginLog.info(`[opencode-immune] Harness sync: successfully updated to ${release.tagName}`);
|
|
900
976
|
await writeDiagnosticLog(state, "harness-sync:success", {
|
|
901
977
|
from: localVersion,
|
|
902
978
|
to: release.tagName,
|
|
@@ -917,7 +993,7 @@ async function syncHarness(state) {
|
|
|
917
993
|
}
|
|
918
994
|
catch (err) {
|
|
919
995
|
// Sync failure must never prevent plugin from working
|
|
920
|
-
|
|
996
|
+
pluginLog.warn(`[opencode-immune] Harness sync failed:`, err instanceof Error ? err.message : String(err));
|
|
921
997
|
await writeDiagnosticLog(state, "harness-sync:error", {
|
|
922
998
|
error: err instanceof Error ? err.message : String(err),
|
|
923
999
|
});
|
|
@@ -960,7 +1036,7 @@ function createTodoEnforcerChatMessage(state) {
|
|
|
960
1036
|
// On user message, check previous assistant turn's counters
|
|
961
1037
|
// then reset for next turn
|
|
962
1038
|
if (state.toolCallCount > 3 && !state.todoWriteUsed) {
|
|
963
|
-
|
|
1039
|
+
pluginLog.warn(`[opencode-immune] Todo Enforcer: ${state.toolCallCount} tool calls without TodoWrite. ` +
|
|
964
1040
|
`Consider using todo list for multi-step tasks.`);
|
|
965
1041
|
}
|
|
966
1042
|
// Reset per-message counters for the next assistant turn
|
|
@@ -1002,20 +1078,20 @@ function createSessionRecoveryEvent(state) {
|
|
|
1002
1078
|
}
|
|
1003
1079
|
const markerActive = await isUltraworkMarkerActive(state);
|
|
1004
1080
|
if (!markerActive) {
|
|
1005
|
-
|
|
1081
|
+
pluginLog.info(`[opencode-immune] Root session created (${sessionID}), no ultrawork marker — skipping auto-resume.`);
|
|
1006
1082
|
return;
|
|
1007
1083
|
}
|
|
1008
|
-
|
|
1084
|
+
pluginLog.info(`[opencode-immune] Root session created (${sessionID}), ultrawork marker active — checking tasks.md...`);
|
|
1009
1085
|
const recovery = await parseTasksFile(state.input.directory);
|
|
1010
1086
|
if (recovery) {
|
|
1011
1087
|
state.recoveryContext = recovery;
|
|
1012
|
-
|
|
1088
|
+
pluginLog.info(`[opencode-immune] Active task found: "${recovery.task}" (Level ${recovery.level}, Phase: ${recovery.phase})`);
|
|
1013
1089
|
if (recovery.phase !== "ARCHIVE: DONE") {
|
|
1014
1090
|
// Register this root session as managed so retry/recovery works
|
|
1015
1091
|
await addManagedUltraworkSession(state, sessionID);
|
|
1016
1092
|
// Skip sending AUTO-RESUME if already sent from plugin init
|
|
1017
1093
|
if (state.autoResumeAttempted) {
|
|
1018
|
-
|
|
1094
|
+
pluginLog.info(`[opencode-immune] Auto-resume already sent from plugin init, skipping duplicate for session ${sessionID}.`);
|
|
1019
1095
|
return;
|
|
1020
1096
|
}
|
|
1021
1097
|
setTimeout(async () => {
|
|
@@ -1032,17 +1108,17 @@ function createSessionRecoveryEvent(state) {
|
|
|
1032
1108
|
},
|
|
1033
1109
|
path: { id: sessionID },
|
|
1034
1110
|
});
|
|
1035
|
-
|
|
1111
|
+
pluginLog.info(`[opencode-immune] Auto-resume prompt sent to managed ultrawork session ${sessionID}`);
|
|
1036
1112
|
}
|
|
1037
1113
|
catch (err) {
|
|
1038
|
-
|
|
1114
|
+
pluginLog.info(`[opencode-immune] Auto-resume failed (session may have been taken over):`, err);
|
|
1039
1115
|
}
|
|
1040
1116
|
}, 3_000);
|
|
1041
1117
|
}
|
|
1042
1118
|
}
|
|
1043
1119
|
else {
|
|
1044
1120
|
state.recoveryContext = null;
|
|
1045
|
-
|
|
1121
|
+
pluginLog.info("[opencode-immune] No active task found.");
|
|
1046
1122
|
}
|
|
1047
1123
|
}
|
|
1048
1124
|
};
|
|
@@ -1124,7 +1200,7 @@ function createRalphLoopToolAfter(state) {
|
|
|
1124
1200
|
newString: input.args?.newString ?? "",
|
|
1125
1201
|
timestamp: Date.now(),
|
|
1126
1202
|
};
|
|
1127
|
-
|
|
1203
|
+
pluginLog.warn(`[opencode-immune] Ralph Loop: Edit failed for "${state.lastEditAttempt.filePath}". ` +
|
|
1128
1204
|
`Recovery hint will be injected in next system transform.`);
|
|
1129
1205
|
}
|
|
1130
1206
|
else {
|
|
@@ -1151,7 +1227,7 @@ function createContextMonitorChatMessage(state) {
|
|
|
1151
1227
|
if (state.approximateTokens >
|
|
1152
1228
|
ESTIMATED_CONTEXT_LIMIT * CONTEXT_WARNING_THRESHOLD) {
|
|
1153
1229
|
const pct = Math.round((state.approximateTokens / ESTIMATED_CONTEXT_LIMIT) * 100);
|
|
1154
|
-
|
|
1230
|
+
pluginLog.warn(`[opencode-immune] Context Monitor: ~${state.approximateTokens} tokens estimated (${pct}% of ~${ESTIMATED_CONTEXT_LIMIT}). ` +
|
|
1155
1231
|
`Consider compacting the session.`);
|
|
1156
1232
|
}
|
|
1157
1233
|
}
|
|
@@ -1174,7 +1250,7 @@ function createCompactionHandler(state) {
|
|
|
1174
1250
|
"After compaction, the agent should still be able to resume the current phase without re-reading all Memory Bank files.");
|
|
1175
1251
|
// Reset token counter after compaction
|
|
1176
1252
|
state.approximateTokens = 0;
|
|
1177
|
-
|
|
1253
|
+
pluginLog.info("[opencode-immune] Context Monitor: Compaction triggered, token counter reset.");
|
|
1178
1254
|
};
|
|
1179
1255
|
}
|
|
1180
1256
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1198,13 +1274,13 @@ function createCommentCheckerToolAfter(state) {
|
|
|
1198
1274
|
return;
|
|
1199
1275
|
// Check for emoji
|
|
1200
1276
|
if (EMOJI_PATTERN.test(content)) {
|
|
1201
|
-
|
|
1277
|
+
pluginLog.warn(`[opencode-immune] Comment Checker: Emoji detected in ${input.tool} operation. ` +
|
|
1202
1278
|
`Avoid emojis in code unless the user explicitly requested them.`);
|
|
1203
1279
|
}
|
|
1204
1280
|
// Check for TODO/FIXME/HACK
|
|
1205
1281
|
const todoMatch = content.match(TODO_PATTERN);
|
|
1206
1282
|
if (todoMatch) {
|
|
1207
|
-
|
|
1283
|
+
pluginLog.warn(`[opencode-immune] Comment Checker: "${todoMatch[0]}" comment found in ${input.tool} operation. ` +
|
|
1208
1284
|
`Consider resolving it or tracking it in the todo list.`);
|
|
1209
1285
|
}
|
|
1210
1286
|
};
|
|
@@ -1235,11 +1311,11 @@ function createKeywordDetectorChatMessage(_state) {
|
|
|
1235
1311
|
if (!messageContent)
|
|
1236
1312
|
return;
|
|
1237
1313
|
if (ERROR_KEYWORDS.test(messageContent)) {
|
|
1238
|
-
|
|
1314
|
+
pluginLog.info(`[opencode-immune] Keyword Detector: Error-related keywords found. ` +
|
|
1239
1315
|
`Consider using 1-van to analyze the issue systematically.`);
|
|
1240
1316
|
}
|
|
1241
1317
|
if (DEPLOY_KEYWORDS.test(messageContent)) {
|
|
1242
|
-
|
|
1318
|
+
pluginLog.info(`[opencode-immune] Keyword Detector: Deploy/release keywords found. ` +
|
|
1243
1319
|
`Consider running 5-reflect first to verify implementation quality.`);
|
|
1244
1320
|
}
|
|
1245
1321
|
};
|
|
@@ -1268,7 +1344,7 @@ function createFallbackModels(state) {
|
|
|
1268
1344
|
const recovery = await parseTasksFile(state.input.directory);
|
|
1269
1345
|
if (recovery && recovery.phase !== "ARCHIVE: DONE") {
|
|
1270
1346
|
state.recoveryContext = recovery;
|
|
1271
|
-
|
|
1347
|
+
pluginLog.info(`[opencode-immune] Auto-recovery on existing session: ` +
|
|
1272
1348
|
`task="${recovery.task}", level=${recovery.level}, phase=${recovery.phase}. ` +
|
|
1273
1349
|
`Sending AUTO-RESUME prompt in 3s...`);
|
|
1274
1350
|
const sid = input.sessionID;
|
|
@@ -1286,10 +1362,10 @@ function createFallbackModels(state) {
|
|
|
1286
1362
|
},
|
|
1287
1363
|
path: { id: sid },
|
|
1288
1364
|
});
|
|
1289
|
-
|
|
1365
|
+
pluginLog.info(`[opencode-immune] Auto-resume prompt sent to session ${sid}`);
|
|
1290
1366
|
}
|
|
1291
1367
|
catch (err) {
|
|
1292
|
-
|
|
1368
|
+
pluginLog.info(`[opencode-immune] Auto-resume prompt failed:`, err);
|
|
1293
1369
|
}
|
|
1294
1370
|
}, 3_000);
|
|
1295
1371
|
}
|
|
@@ -1308,7 +1384,7 @@ function createFallbackModels(state) {
|
|
|
1308
1384
|
const providerId = input.provider?.info && "id" in input.provider.info
|
|
1309
1385
|
? input.provider.info.id
|
|
1310
1386
|
: "unknown";
|
|
1311
|
-
|
|
1387
|
+
pluginLog.info(`[opencode-immune] Model Observer: agent="${input.agent}", ` +
|
|
1312
1388
|
`model="${modelId}", provider="${providerId}"`);
|
|
1313
1389
|
};
|
|
1314
1390
|
}
|
|
@@ -1346,7 +1422,7 @@ function createEventHandler(state) {
|
|
|
1346
1422
|
if (count < MAX_RETRIES && !state.sessionRetryTimers.has(fallbackSessionID)) {
|
|
1347
1423
|
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, count), MAX_DELAY_MS);
|
|
1348
1424
|
state.sessionErrorRetryCount.set(fallbackSessionID, count + 1);
|
|
1349
|
-
|
|
1425
|
+
pluginLog.warn(`[opencode-immune] session.error without sessionID matched retryable error. ` +
|
|
1350
1426
|
`Retrying sole managed root session ${fallbackSessionID}.`);
|
|
1351
1427
|
scheduleManagedSessionRetry(state, fallbackSessionID, {
|
|
1352
1428
|
delayMs: delay,
|
|
@@ -1371,7 +1447,7 @@ function createEventHandler(state) {
|
|
|
1371
1447
|
return;
|
|
1372
1448
|
}
|
|
1373
1449
|
if (state.sessionRetryTimers.has(sessionID)) {
|
|
1374
|
-
|
|
1450
|
+
pluginLog.info(`[opencode-immune] Retry already pending for ${isChild ? "child" : "root"} session ${sessionID}, skipping duplicate.`);
|
|
1375
1451
|
return;
|
|
1376
1452
|
}
|
|
1377
1453
|
const count = state.sessionErrorRetryCount.get(sessionID) ?? 0;
|
|
@@ -1382,7 +1458,7 @@ function createEventHandler(state) {
|
|
|
1382
1458
|
// child after the router advances can create two writers in one pipeline.
|
|
1383
1459
|
if (isChild) {
|
|
1384
1460
|
recordChildFallbackRequest(state, managedSession, sessionID, error);
|
|
1385
|
-
|
|
1461
|
+
pluginLog.info(`[opencode-immune] Child session ${sessionID}: retryable error detected. ` +
|
|
1386
1462
|
`Recorded router-owned fallback request and skipped plugin auto-retry.`);
|
|
1387
1463
|
state.sessionErrorRetryCount.set(sessionID, count);
|
|
1388
1464
|
return;
|
|
@@ -1390,7 +1466,7 @@ function createEventHandler(state) {
|
|
|
1390
1466
|
else if (isRoot && (isRateLimitApiError(error) || isCertificateApiError(error))) {
|
|
1391
1467
|
await setSessionFallbackModel(state, sessionID, RATE_LIMIT_FALLBACK_MODEL);
|
|
1392
1468
|
const errorType = isCertificateApiError(error) ? "certificate error" : "rate limit";
|
|
1393
|
-
|
|
1469
|
+
pluginLog.info(`[opencode-immune] ${errorType} detected for root session ${sessionID}. ` +
|
|
1394
1470
|
`Retry will use fallback model ${RATE_LIMIT_FALLBACK_MODEL.providerID}/${RATE_LIMIT_FALLBACK_MODEL.modelID}.`);
|
|
1395
1471
|
}
|
|
1396
1472
|
const scheduled = scheduleManagedSessionRetry(state, sessionID, {
|
|
@@ -1404,17 +1480,22 @@ function createEventHandler(state) {
|
|
|
1404
1480
|
}
|
|
1405
1481
|
}
|
|
1406
1482
|
else {
|
|
1407
|
-
|
|
1483
|
+
pluginLog.info(`[opencode-immune] Max retries (${MAX_RETRIES}) reached for ${isChild ? "child" : "root"} session ${sessionID}. Not retrying.`);
|
|
1408
1484
|
}
|
|
1409
1485
|
}
|
|
1410
1486
|
// Reset retry counter on successful activity
|
|
1411
1487
|
if (eventType === "session.updated" && sessionID) {
|
|
1488
|
+
const managedSession = getManagedSession(state, sessionID);
|
|
1412
1489
|
cancelPendingSessionRetry(state, sessionID, "session updated");
|
|
1413
1490
|
cancelProviderRetryWatchdog(state, sessionID, "session updated");
|
|
1414
1491
|
state.sessionErrorRetryCount.delete(sessionID);
|
|
1415
1492
|
if (markUltraworkSessionActive(state, sessionID)) {
|
|
1416
1493
|
// session activity tracked in-memory only
|
|
1417
1494
|
}
|
|
1495
|
+
if (managedSession?.kind === "child") {
|
|
1496
|
+
cancelProviderRetryWatchdog(state, managedSession.rootSessionID, `child session ${sessionID} updated`);
|
|
1497
|
+
markUltraworkSessionActive(state, managedSession.rootSessionID);
|
|
1498
|
+
}
|
|
1418
1499
|
}
|
|
1419
1500
|
if (eventType === "session.deleted" && sessionID) {
|
|
1420
1501
|
await removeManagedUltraworkSession(state, sessionID, "session deleted");
|
|
@@ -1429,7 +1510,7 @@ function createEventHandler(state) {
|
|
|
1429
1510
|
"file.edited",
|
|
1430
1511
|
];
|
|
1431
1512
|
if (significantEvents.includes(eventType)) {
|
|
1432
|
-
|
|
1513
|
+
pluginLog.info(`[opencode-immune] Event: ${eventType}`);
|
|
1433
1514
|
}
|
|
1434
1515
|
};
|
|
1435
1516
|
}
|
|
@@ -1455,7 +1536,7 @@ async function archiveProgress(directory) {
|
|
|
1455
1536
|
const content = await (0, promises_1.readFile)(progressPath, "utf-8");
|
|
1456
1537
|
// Skip if empty or trivially empty
|
|
1457
1538
|
if (!content.trim() || content.trim() === "# Progress") {
|
|
1458
|
-
|
|
1539
|
+
pluginLog.info("[opencode-immune] Archive progress: nothing to archive (empty).");
|
|
1459
1540
|
return;
|
|
1460
1541
|
}
|
|
1461
1542
|
const archiveDir = (0, path_1.join)(directory, "memory-bank", "archive");
|
|
@@ -1466,13 +1547,13 @@ async function archiveProgress(directory) {
|
|
|
1466
1547
|
const archiveName = `progress-${dateStr}-${ts}.md`;
|
|
1467
1548
|
const archivePath = (0, path_1.join)(archiveDir, archiveName);
|
|
1468
1549
|
await (0, promises_1.rename)(progressPath, archivePath);
|
|
1469
|
-
|
|
1550
|
+
pluginLog.info(`[opencode-immune] Archive progress: moved to ${archiveName}`);
|
|
1470
1551
|
}
|
|
1471
1552
|
catch (err) {
|
|
1472
1553
|
// File doesn't exist or move failed — not critical
|
|
1473
1554
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1474
1555
|
if (!msg.includes("ENOENT")) {
|
|
1475
|
-
|
|
1556
|
+
pluginLog.warn("[opencode-immune] Archive progress failed:", msg);
|
|
1476
1557
|
}
|
|
1477
1558
|
}
|
|
1478
1559
|
}
|
|
@@ -1518,7 +1599,7 @@ function runGitCommit(directory) {
|
|
|
1518
1599
|
// Stage all changes
|
|
1519
1600
|
(0, child_process_1.execFile)("git", ["add", "-A"], { cwd: directory }, (addErr) => {
|
|
1520
1601
|
if (addErr) {
|
|
1521
|
-
|
|
1602
|
+
pluginLog.error("[opencode-immune] git add failed:", addErr.message);
|
|
1522
1603
|
resolve(false);
|
|
1523
1604
|
return;
|
|
1524
1605
|
}
|
|
@@ -1529,15 +1610,15 @@ function runGitCommit(directory) {
|
|
|
1529
1610
|
(0, child_process_1.execFile)("git", ["commit", "-m", message], { cwd: directory }, (commitErr, stdout, stderr) => {
|
|
1530
1611
|
if (commitErr) {
|
|
1531
1612
|
if (stderr?.includes("nothing to commit") || stdout?.includes("nothing to commit")) {
|
|
1532
|
-
|
|
1613
|
+
pluginLog.info("[opencode-immune] git commit: nothing to commit (clean tree).");
|
|
1533
1614
|
resolve(true);
|
|
1534
1615
|
return;
|
|
1535
1616
|
}
|
|
1536
|
-
|
|
1617
|
+
pluginLog.error("[opencode-immune] git commit failed:", commitErr.message, stderr);
|
|
1537
1618
|
resolve(false);
|
|
1538
1619
|
return;
|
|
1539
1620
|
}
|
|
1540
|
-
|
|
1621
|
+
pluginLog.info("[opencode-immune] git commit succeeded:", stdout?.trim());
|
|
1541
1622
|
resolve(true);
|
|
1542
1623
|
});
|
|
1543
1624
|
});
|
|
@@ -1572,25 +1653,25 @@ function createTextCompleteHandler(state) {
|
|
|
1572
1653
|
countAgainstBudget: false,
|
|
1573
1654
|
abortBeforePrompt: true,
|
|
1574
1655
|
});
|
|
1575
|
-
|
|
1656
|
+
pluginLog.info(`[opencode-immune] Provider retry banner detected for session ${sessionID}. ` +
|
|
1576
1657
|
`Fallback model pinned to ${fallbackModel.providerID}/${fallbackModel.modelID}.`);
|
|
1577
1658
|
}
|
|
1578
1659
|
// ── ALL_CYCLES_COMPLETE: clear ultrawork marker ──
|
|
1579
1660
|
if (text.includes(ALL_CYCLES_COMPLETE_MARKER)) {
|
|
1580
1661
|
await clearUltraworkMarker(state);
|
|
1581
|
-
|
|
1662
|
+
pluginLog.info("[opencode-immune] Multi-Cycle: ALL_CYCLES_COMPLETE detected, marker cleared.");
|
|
1582
1663
|
return;
|
|
1583
1664
|
}
|
|
1584
1665
|
// ── PRE_COMMIT only (without CYCLE_COMPLETE in same part): run commit ──
|
|
1585
1666
|
if (text.includes(PRE_COMMIT_MARKER) && !text.includes(CYCLE_COMPLETE_MARKER)) {
|
|
1586
1667
|
if (!state.commitPending) {
|
|
1587
1668
|
state.commitPending = true;
|
|
1588
|
-
|
|
1669
|
+
pluginLog.info("[opencode-immune] Multi-Cycle: PRE_COMMIT detected (standalone), running git commit...");
|
|
1589
1670
|
try {
|
|
1590
1671
|
await runGitCommit(state.input.directory);
|
|
1591
1672
|
}
|
|
1592
1673
|
catch (err) {
|
|
1593
|
-
|
|
1674
|
+
pluginLog.error("[opencode-immune] Multi-Cycle: git commit failed (standalone):", err);
|
|
1594
1675
|
}
|
|
1595
1676
|
finally {
|
|
1596
1677
|
state.commitPending = false;
|
|
@@ -1605,18 +1686,18 @@ function createTextCompleteHandler(state) {
|
|
|
1605
1686
|
await archiveProgress(state.input.directory);
|
|
1606
1687
|
}
|
|
1607
1688
|
catch (err) {
|
|
1608
|
-
|
|
1689
|
+
pluginLog.warn("[opencode-immune] Multi-Cycle: archive progress failed:", err);
|
|
1609
1690
|
}
|
|
1610
1691
|
// Step 1: Always commit first
|
|
1611
1692
|
if (!state.commitPending) {
|
|
1612
1693
|
state.commitPending = true;
|
|
1613
|
-
|
|
1694
|
+
pluginLog.info("[opencode-immune] Multi-Cycle: CYCLE_COMPLETE detected, running git commit first...");
|
|
1614
1695
|
try {
|
|
1615
1696
|
await runGitCommit(state.input.directory);
|
|
1616
|
-
|
|
1697
|
+
pluginLog.info("[opencode-immune] Multi-Cycle: git commit completed before new cycle.");
|
|
1617
1698
|
}
|
|
1618
1699
|
catch (err) {
|
|
1619
|
-
|
|
1700
|
+
pluginLog.error("[opencode-immune] Multi-Cycle: git commit failed (continuing anyway):", err);
|
|
1620
1701
|
}
|
|
1621
1702
|
finally {
|
|
1622
1703
|
state.commitPending = false;
|
|
@@ -1625,13 +1706,13 @@ function createTextCompleteHandler(state) {
|
|
|
1625
1706
|
// Step 2: Create new session
|
|
1626
1707
|
state.cycleCount++;
|
|
1627
1708
|
if (state.cycleCount >= MAX_CYCLES) {
|
|
1628
|
-
|
|
1709
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle: MAX_CYCLES (${MAX_CYCLES}) reached. Not creating new session.`);
|
|
1629
1710
|
await clearUltraworkMarker(state);
|
|
1630
1711
|
return;
|
|
1631
1712
|
}
|
|
1632
1713
|
const taskMatch = text.match(NEXT_TASK_PATTERN);
|
|
1633
1714
|
const nextTask = taskMatch?.[1]?.trim() ?? "Continue processing task backlog";
|
|
1634
|
-
|
|
1715
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle: Creating new session (cycle ${state.cycleCount}/${MAX_CYCLES}) for: "${nextTask}"`);
|
|
1635
1716
|
try {
|
|
1636
1717
|
const createResult = await state.input.client.session.create({
|
|
1637
1718
|
body: {
|
|
@@ -1641,10 +1722,10 @@ function createTextCompleteHandler(state) {
|
|
|
1641
1722
|
const newSessionData = createResult?.data;
|
|
1642
1723
|
const newSessionID = newSessionData?.id;
|
|
1643
1724
|
if (!newSessionID) {
|
|
1644
|
-
|
|
1725
|
+
pluginLog.error("[opencode-immune] Multi-Cycle: Failed to create new session — no ID returned.");
|
|
1645
1726
|
return;
|
|
1646
1727
|
}
|
|
1647
|
-
|
|
1728
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle: New session created: ${newSessionID}`);
|
|
1648
1729
|
await addManagedUltraworkSession(state, newSessionID);
|
|
1649
1730
|
await state.input.client.session.promptAsync({
|
|
1650
1731
|
body: {
|
|
@@ -1658,10 +1739,10 @@ function createTextCompleteHandler(state) {
|
|
|
1658
1739
|
},
|
|
1659
1740
|
path: { id: newSessionID },
|
|
1660
1741
|
});
|
|
1661
|
-
|
|
1742
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle: Bootstrap prompt sent to ${newSessionID}`);
|
|
1662
1743
|
}
|
|
1663
1744
|
catch (err) {
|
|
1664
|
-
|
|
1745
|
+
pluginLog.error("[opencode-immune] Multi-Cycle: Failed to create session or send prompt:", err);
|
|
1665
1746
|
}
|
|
1666
1747
|
}
|
|
1667
1748
|
};
|
|
@@ -1694,7 +1775,7 @@ function createMultiCycleHandler(state) {
|
|
|
1694
1775
|
RATE_LIMIT_MESSAGE_PATTERN.test(messageContent)) {
|
|
1695
1776
|
if (managedSession && !managedSession.fallbackModel) {
|
|
1696
1777
|
await setSessionFallbackModel(state, sessionID, RATE_LIMIT_FALLBACK_MODEL);
|
|
1697
|
-
|
|
1778
|
+
pluginLog.info(`[opencode-immune] Rate limit message detected in chat output for session ${sessionID}. ` +
|
|
1698
1779
|
`Fallback model pinned to ${RATE_LIMIT_FALLBACK_MODEL.providerID}/${RATE_LIMIT_FALLBACK_MODEL.modelID}.`);
|
|
1699
1780
|
}
|
|
1700
1781
|
if (managedSession) {
|
|
@@ -1719,7 +1800,7 @@ async function server(input) {
|
|
|
1719
1800
|
// Runs in background so it doesn't delay plugin initialization.
|
|
1720
1801
|
// If sync fails, plugin continues normally with existing config.
|
|
1721
1802
|
syncHarness(state).catch((err) => {
|
|
1722
|
-
|
|
1803
|
+
pluginLog.warn(`[opencode-immune] Harness sync background error:`, err instanceof Error ? err.message : String(err));
|
|
1723
1804
|
});
|
|
1724
1805
|
// Eagerly load recovery context at plugin init so it's available
|
|
1725
1806
|
// for the very first system.transform call (before chat.params fires).
|
|
@@ -1730,7 +1811,7 @@ async function server(input) {
|
|
|
1730
1811
|
// Active task exists with incomplete phases — resume it
|
|
1731
1812
|
state.recoveryContext = recovery;
|
|
1732
1813
|
state.autoResumeAttempted = true;
|
|
1733
|
-
|
|
1814
|
+
pluginLog.info(`[opencode-immune] Plugin init: ultrawork marker active, recovery context loaded: ` +
|
|
1734
1815
|
`task="${recovery.task}", level=${recovery.level}, phase=${recovery.phase}. ` +
|
|
1735
1816
|
`Will create new session and send AUTO-RESUME.`);
|
|
1736
1817
|
// Create a new session and send AUTO-RESUME prompt (same pattern as CYCLE_COMPLETE).
|
|
@@ -1745,10 +1826,10 @@ async function server(input) {
|
|
|
1745
1826
|
const newSessionData = createResult?.data;
|
|
1746
1827
|
const newSessionID = newSessionData?.id;
|
|
1747
1828
|
if (!newSessionID) {
|
|
1748
|
-
|
|
1829
|
+
pluginLog.error("[opencode-immune] Auto-resume: Failed to create session — no session ID returned.");
|
|
1749
1830
|
return;
|
|
1750
1831
|
}
|
|
1751
|
-
|
|
1832
|
+
pluginLog.info(`[opencode-immune] Auto-resume: New session created: ${newSessionID}`);
|
|
1752
1833
|
await addManagedUltraworkSession(state, newSessionID);
|
|
1753
1834
|
await state.input.client.session.promptAsync({
|
|
1754
1835
|
body: {
|
|
@@ -1762,10 +1843,10 @@ async function server(input) {
|
|
|
1762
1843
|
},
|
|
1763
1844
|
path: { id: newSessionID },
|
|
1764
1845
|
});
|
|
1765
|
-
|
|
1846
|
+
pluginLog.info(`[opencode-immune] Auto-resume prompt sent to new session ${newSessionID}`);
|
|
1766
1847
|
}
|
|
1767
1848
|
catch (err) {
|
|
1768
|
-
|
|
1849
|
+
pluginLog.error("[opencode-immune] Auto-resume: Failed to create session or send prompt:", err);
|
|
1769
1850
|
}
|
|
1770
1851
|
}, 5_000);
|
|
1771
1852
|
}
|
|
@@ -1777,7 +1858,7 @@ async function server(input) {
|
|
|
1777
1858
|
const hasPendingTasks = /- \[ \]/.test(backlogContent);
|
|
1778
1859
|
if (hasPendingTasks) {
|
|
1779
1860
|
state.autoResumeAttempted = true;
|
|
1780
|
-
|
|
1861
|
+
pluginLog.info(`[opencode-immune] Plugin init: no active task but backlog has pending items. ` +
|
|
1781
1862
|
`Will create new session to start next cycle.`);
|
|
1782
1863
|
setTimeout(async () => {
|
|
1783
1864
|
try {
|
|
@@ -1789,10 +1870,10 @@ async function server(input) {
|
|
|
1789
1870
|
const newSessionData = createResult?.data;
|
|
1790
1871
|
const newSessionID = newSessionData?.id;
|
|
1791
1872
|
if (!newSessionID) {
|
|
1792
|
-
|
|
1873
|
+
pluginLog.error("[opencode-immune] Auto-cycle: Failed to create session — no session ID returned.");
|
|
1793
1874
|
return;
|
|
1794
1875
|
}
|
|
1795
|
-
|
|
1876
|
+
pluginLog.info(`[opencode-immune] Auto-cycle: New session created: ${newSessionID}`);
|
|
1796
1877
|
await addManagedUltraworkSession(state, newSessionID);
|
|
1797
1878
|
await state.input.client.session.promptAsync({
|
|
1798
1879
|
body: {
|
|
@@ -1806,27 +1887,27 @@ async function server(input) {
|
|
|
1806
1887
|
},
|
|
1807
1888
|
path: { id: newSessionID },
|
|
1808
1889
|
});
|
|
1809
|
-
|
|
1890
|
+
pluginLog.info(`[opencode-immune] Auto-cycle prompt sent to new session ${newSessionID}`);
|
|
1810
1891
|
}
|
|
1811
1892
|
catch (err) {
|
|
1812
|
-
|
|
1893
|
+
pluginLog.error("[opencode-immune] Auto-cycle: Failed to create session or send prompt:", err);
|
|
1813
1894
|
}
|
|
1814
1895
|
}, 5_000);
|
|
1815
1896
|
}
|
|
1816
1897
|
else {
|
|
1817
1898
|
// No active task and no pending backlog — clear marker
|
|
1818
1899
|
await clearUltraworkMarker(state);
|
|
1819
|
-
|
|
1900
|
+
pluginLog.info(`[opencode-immune] Plugin init: no active task and no pending backlog. Marker cleared.`);
|
|
1820
1901
|
}
|
|
1821
1902
|
}
|
|
1822
1903
|
catch {
|
|
1823
1904
|
// backlog.md doesn't exist or can't be read — clear marker
|
|
1824
1905
|
await clearUltraworkMarker(state);
|
|
1825
|
-
|
|
1906
|
+
pluginLog.info(`[opencode-immune] Plugin init: no active task, backlog unreadable. Marker cleared.`);
|
|
1826
1907
|
}
|
|
1827
1908
|
}
|
|
1828
1909
|
}
|
|
1829
|
-
|
|
1910
|
+
pluginLog.info(`[opencode-immune] Plugin initialized. Directory: ${input.directory}`);
|
|
1830
1911
|
// Compose tool.execute.after handlers:
|
|
1831
1912
|
// Todo Enforcer (counter) + Ralph Loop (edit error) + Comment Checker
|
|
1832
1913
|
const toolAfterHandlers = [
|
|
@@ -1843,13 +1924,13 @@ async function server(input) {
|
|
|
1843
1924
|
createMultiCycleHandler(state),
|
|
1844
1925
|
];
|
|
1845
1926
|
return {
|
|
1846
|
-
event: withErrorBoundary("event", createEventHandler(state)),
|
|
1847
|
-
"chat.message": withErrorBoundary("chat.message", compositeChatMessage(chatMessageHandlers)),
|
|
1848
|
-
"chat.params": withErrorBoundary("chat.params", createFallbackModels(state)),
|
|
1849
|
-
"tool.execute.after": withErrorBoundary("tool.execute.after", compositeToolAfter(toolAfterHandlers)),
|
|
1850
|
-
"experimental.chat.system.transform": withErrorBoundary("experimental.chat.system.transform", createSystemTransform(state)),
|
|
1851
|
-
"experimental.session.compacting": withErrorBoundary("experimental.session.compacting", createCompactionHandler(state)),
|
|
1852
|
-
"experimental.text.complete": withErrorBoundary("experimental.text.complete", createTextCompleteHandler(state)),
|
|
1927
|
+
event: withErrorBoundary(state, "event", createEventHandler(state)),
|
|
1928
|
+
"chat.message": withErrorBoundary(state, "chat.message", compositeChatMessage(chatMessageHandlers)),
|
|
1929
|
+
"chat.params": withErrorBoundary(state, "chat.params", createFallbackModels(state)),
|
|
1930
|
+
"tool.execute.after": withErrorBoundary(state, "tool.execute.after", compositeToolAfter(toolAfterHandlers)),
|
|
1931
|
+
"experimental.chat.system.transform": withErrorBoundary(state, "experimental.chat.system.transform", createSystemTransform(state)),
|
|
1932
|
+
"experimental.session.compacting": withErrorBoundary(state, "experimental.session.compacting", createCompactionHandler(state)),
|
|
1933
|
+
"experimental.text.complete": withErrorBoundary(state, "experimental.text.complete", createTextCompleteHandler(state)),
|
|
1853
1934
|
};
|
|
1854
1935
|
}
|
|
1855
1936
|
exports.default = {
|