opencode-immune 1.0.45 → 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 +161 -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 {
|
|
@@ -214,7 +280,7 @@ function cancelPendingSessionRetry(state, sessionID, reason) {
|
|
|
214
280
|
return;
|
|
215
281
|
clearTimeout(timer);
|
|
216
282
|
state.sessionRetryTimers.delete(sessionID);
|
|
217
|
-
|
|
283
|
+
writePluginLog(state, "info", `[opencode-immune] Cancelled pending retry for session ${sessionID}: ${reason}`);
|
|
218
284
|
}
|
|
219
285
|
function cancelProviderRetryWatchdog(state, sessionID, reason) {
|
|
220
286
|
const timer = state.providerRetryWatchdogs.get(sessionID);
|
|
@@ -222,7 +288,7 @@ function cancelProviderRetryWatchdog(state, sessionID, reason) {
|
|
|
222
288
|
return;
|
|
223
289
|
clearTimeout(timer);
|
|
224
290
|
state.providerRetryWatchdogs.delete(sessionID);
|
|
225
|
-
|
|
291
|
+
writePluginLog(state, "info", `[opencode-immune] Cancelled provider retry watchdog for session ${sessionID}: ${reason}`);
|
|
226
292
|
}
|
|
227
293
|
async function removeManagedUltraworkSession(state, sessionID, reason) {
|
|
228
294
|
cancelPendingSessionRetry(state, sessionID, reason);
|
|
@@ -231,7 +297,7 @@ async function removeManagedUltraworkSession(state, sessionID, reason) {
|
|
|
231
297
|
const existed = state.managedUltraworkSessions.delete(sessionID);
|
|
232
298
|
if (!existed)
|
|
233
299
|
return;
|
|
234
|
-
|
|
300
|
+
writePluginLog(state, "info", `[opencode-immune] Removed managed ultrawork session ${sessionID}: ${reason}`);
|
|
235
301
|
}
|
|
236
302
|
async function updateManagedSessionAgent(state, sessionID, agent) {
|
|
237
303
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
@@ -493,7 +559,7 @@ async function sendManagedSessionRetryPrompt(state, sessionID, reason, options =
|
|
|
493
559
|
},
|
|
494
560
|
path: { id: sessionID },
|
|
495
561
|
});
|
|
496
|
-
|
|
562
|
+
writePluginLog(state, "info", `[opencode-immune] Retry prompt sent to session ${sessionID} (${reason})` +
|
|
497
563
|
(fallbackModel
|
|
498
564
|
? ` using fallback model ${fallbackModel.providerID}/${fallbackModel.modelID}`
|
|
499
565
|
: ""));
|
|
@@ -510,11 +576,11 @@ function scheduleManagedSessionRetry(state, sessionID, options) {
|
|
|
510
576
|
return false;
|
|
511
577
|
}
|
|
512
578
|
if (state.sessionRetryTimers.has(sessionID)) {
|
|
513
|
-
|
|
579
|
+
writePluginLog(state, "info", `[opencode-immune] Retry already pending for session ${sessionID}, skipping duplicate.`);
|
|
514
580
|
return false;
|
|
515
581
|
}
|
|
516
582
|
const attemptInfo = options.attemptLabel ? ` (${options.attemptLabel})` : "";
|
|
517
|
-
|
|
583
|
+
writePluginLog(state, "info", `[opencode-immune] Scheduling retry for session ${sessionID}${attemptInfo}. ` +
|
|
518
584
|
`Waiting ${options.delayMs / 1000}s before retry...`);
|
|
519
585
|
const timer = setTimeout(async () => {
|
|
520
586
|
state.sessionRetryTimers.delete(sessionID);
|
|
@@ -530,7 +596,7 @@ function scheduleManagedSessionRetry(state, sessionID, options) {
|
|
|
530
596
|
if (options.countAgainstBudget) {
|
|
531
597
|
state.sessionErrorRetryCount.set(sessionID, Math.max((state.sessionErrorRetryCount.get(sessionID) ?? 1) - 1, 0));
|
|
532
598
|
}
|
|
533
|
-
|
|
599
|
+
writePluginLog(state, "warn", `[opencode-immune] Retry prompt failed for session ${sessionID}. ` +
|
|
534
600
|
`Will wait for the next retry signal.`);
|
|
535
601
|
}
|
|
536
602
|
}, options.delayMs);
|
|
@@ -544,13 +610,17 @@ function scheduleManagedSessionRetry(state, sessionID, options) {
|
|
|
544
610
|
* Wraps a hook handler in a try/catch to prevent any single hook failure
|
|
545
611
|
* from crashing the entire agent session.
|
|
546
612
|
*/
|
|
547
|
-
function withErrorBoundary(hookName, handler) {
|
|
613
|
+
function withErrorBoundary(state, hookName, handler) {
|
|
548
614
|
return (async (...args) => {
|
|
549
615
|
try {
|
|
550
616
|
return await handler(...args);
|
|
551
617
|
}
|
|
552
618
|
catch (err) {
|
|
553
|
-
|
|
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
|
+
});
|
|
554
624
|
// Error is swallowed — hook failure must not crash agent session
|
|
555
625
|
}
|
|
556
626
|
});
|
|
@@ -714,7 +784,7 @@ async function resolveEnvValue(directory, key) {
|
|
|
714
784
|
* Fetch latest release info from the harness GitHub repo.
|
|
715
785
|
* Returns null if token is missing, network fails, or no release found.
|
|
716
786
|
*/
|
|
717
|
-
async function fetchLatestHarnessRelease(repo, token) {
|
|
787
|
+
async function fetchLatestHarnessRelease(directory, repo, token) {
|
|
718
788
|
try {
|
|
719
789
|
const url = `https://api.github.com/repos/${repo}/releases/latest`;
|
|
720
790
|
const resp = await fetch(url, {
|
|
@@ -726,10 +796,10 @@ async function fetchLatestHarnessRelease(repo, token) {
|
|
|
726
796
|
});
|
|
727
797
|
if (!resp.ok) {
|
|
728
798
|
if (resp.status === 404) {
|
|
729
|
-
|
|
799
|
+
writePluginLogForDirectory(directory, "info", `[opencode-immune] Harness sync: no releases found in ${repo}`);
|
|
730
800
|
}
|
|
731
801
|
else {
|
|
732
|
-
|
|
802
|
+
writePluginLogForDirectory(directory, "warn", `[opencode-immune] Harness sync: GitHub API returned ${resp.status} ${resp.statusText}`);
|
|
733
803
|
}
|
|
734
804
|
return null;
|
|
735
805
|
}
|
|
@@ -742,7 +812,7 @@ async function fetchLatestHarnessRelease(repo, token) {
|
|
|
742
812
|
const assetName = isWindows ? "harness-windows.tar.gz" : "harness.tar.gz";
|
|
743
813
|
const asset = data.assets?.find((a) => a.name === assetName);
|
|
744
814
|
if (!asset) {
|
|
745
|
-
|
|
815
|
+
writePluginLogForDirectory(directory, "warn", `[opencode-immune] Harness sync: release ${tagName} has no ${assetName} asset`);
|
|
746
816
|
return null;
|
|
747
817
|
}
|
|
748
818
|
return {
|
|
@@ -752,7 +822,7 @@ async function fetchLatestHarnessRelease(repo, token) {
|
|
|
752
822
|
};
|
|
753
823
|
}
|
|
754
824
|
catch (err) {
|
|
755
|
-
|
|
825
|
+
writePluginLogForDirectory(directory, "warn", `[opencode-immune] Harness sync: failed to fetch release info.`, { error: normalizeLogValue(err) });
|
|
756
826
|
return null;
|
|
757
827
|
}
|
|
758
828
|
}
|
|
@@ -817,7 +887,7 @@ async function copyDirRecursive(src, dest, skipRootFiles, rootDest) {
|
|
|
817
887
|
for (const entry of entries) {
|
|
818
888
|
// Skip files only at the root destination level
|
|
819
889
|
if (skipRootFiles && dest === effectiveRoot && entry.name === ".gitignore") {
|
|
820
|
-
|
|
890
|
+
pluginLog.info(`[opencode-immune] Harness sync: skipping root .gitignore`);
|
|
821
891
|
continue;
|
|
822
892
|
}
|
|
823
893
|
const srcPath = (0, path_1.join)(src, entry.name);
|
|
@@ -868,16 +938,16 @@ async function syncHarness(state) {
|
|
|
868
938
|
|| DEFAULT_HARNESS_REPO;
|
|
869
939
|
try {
|
|
870
940
|
// 1. Fetch latest release
|
|
871
|
-
const release = await fetchLatestHarnessRelease(repo, token);
|
|
941
|
+
const release = await fetchLatestHarnessRelease(state.input.directory, repo, token);
|
|
872
942
|
if (!release)
|
|
873
943
|
return;
|
|
874
944
|
// 2. Compare versions
|
|
875
945
|
const localVersion = await readLocalHarnessVersion(state.input.directory);
|
|
876
946
|
if (localVersion === release.tagName) {
|
|
877
|
-
|
|
947
|
+
pluginLog.info(`[opencode-immune] Harness sync: already up to date (${release.tagName})`);
|
|
878
948
|
return;
|
|
879
949
|
}
|
|
880
|
-
|
|
950
|
+
pluginLog.info(`[opencode-immune] Harness sync: updating ${localVersion ?? "(none)"} → ${release.tagName}`);
|
|
881
951
|
// 3. Hash opencode.json before update
|
|
882
952
|
const configPath = (0, path_1.join)(state.input.directory, "opencode.json");
|
|
883
953
|
const hashBefore = await fileHash(configPath);
|
|
@@ -899,10 +969,10 @@ async function syncHarness(state) {
|
|
|
899
969
|
// 8. Check if opencode.json changed
|
|
900
970
|
const hashAfter = await fileHash(configPath);
|
|
901
971
|
if (hashBefore && hashAfter && hashBefore !== hashAfter) {
|
|
902
|
-
|
|
972
|
+
pluginLog.warn(`[opencode-immune] ⚠ Harness sync: opencode.json was updated. ` +
|
|
903
973
|
`Please restart opencode for the new agent configuration to take effect.`);
|
|
904
974
|
}
|
|
905
|
-
|
|
975
|
+
pluginLog.info(`[opencode-immune] Harness sync: successfully updated to ${release.tagName}`);
|
|
906
976
|
await writeDiagnosticLog(state, "harness-sync:success", {
|
|
907
977
|
from: localVersion,
|
|
908
978
|
to: release.tagName,
|
|
@@ -923,7 +993,7 @@ async function syncHarness(state) {
|
|
|
923
993
|
}
|
|
924
994
|
catch (err) {
|
|
925
995
|
// Sync failure must never prevent plugin from working
|
|
926
|
-
|
|
996
|
+
pluginLog.warn(`[opencode-immune] Harness sync failed:`, err instanceof Error ? err.message : String(err));
|
|
927
997
|
await writeDiagnosticLog(state, "harness-sync:error", {
|
|
928
998
|
error: err instanceof Error ? err.message : String(err),
|
|
929
999
|
});
|
|
@@ -966,7 +1036,7 @@ function createTodoEnforcerChatMessage(state) {
|
|
|
966
1036
|
// On user message, check previous assistant turn's counters
|
|
967
1037
|
// then reset for next turn
|
|
968
1038
|
if (state.toolCallCount > 3 && !state.todoWriteUsed) {
|
|
969
|
-
|
|
1039
|
+
pluginLog.warn(`[opencode-immune] Todo Enforcer: ${state.toolCallCount} tool calls without TodoWrite. ` +
|
|
970
1040
|
`Consider using todo list for multi-step tasks.`);
|
|
971
1041
|
}
|
|
972
1042
|
// Reset per-message counters for the next assistant turn
|
|
@@ -1008,20 +1078,20 @@ function createSessionRecoveryEvent(state) {
|
|
|
1008
1078
|
}
|
|
1009
1079
|
const markerActive = await isUltraworkMarkerActive(state);
|
|
1010
1080
|
if (!markerActive) {
|
|
1011
|
-
|
|
1081
|
+
pluginLog.info(`[opencode-immune] Root session created (${sessionID}), no ultrawork marker — skipping auto-resume.`);
|
|
1012
1082
|
return;
|
|
1013
1083
|
}
|
|
1014
|
-
|
|
1084
|
+
pluginLog.info(`[opencode-immune] Root session created (${sessionID}), ultrawork marker active — checking tasks.md...`);
|
|
1015
1085
|
const recovery = await parseTasksFile(state.input.directory);
|
|
1016
1086
|
if (recovery) {
|
|
1017
1087
|
state.recoveryContext = recovery;
|
|
1018
|
-
|
|
1088
|
+
pluginLog.info(`[opencode-immune] Active task found: "${recovery.task}" (Level ${recovery.level}, Phase: ${recovery.phase})`);
|
|
1019
1089
|
if (recovery.phase !== "ARCHIVE: DONE") {
|
|
1020
1090
|
// Register this root session as managed so retry/recovery works
|
|
1021
1091
|
await addManagedUltraworkSession(state, sessionID);
|
|
1022
1092
|
// Skip sending AUTO-RESUME if already sent from plugin init
|
|
1023
1093
|
if (state.autoResumeAttempted) {
|
|
1024
|
-
|
|
1094
|
+
pluginLog.info(`[opencode-immune] Auto-resume already sent from plugin init, skipping duplicate for session ${sessionID}.`);
|
|
1025
1095
|
return;
|
|
1026
1096
|
}
|
|
1027
1097
|
setTimeout(async () => {
|
|
@@ -1038,17 +1108,17 @@ function createSessionRecoveryEvent(state) {
|
|
|
1038
1108
|
},
|
|
1039
1109
|
path: { id: sessionID },
|
|
1040
1110
|
});
|
|
1041
|
-
|
|
1111
|
+
pluginLog.info(`[opencode-immune] Auto-resume prompt sent to managed ultrawork session ${sessionID}`);
|
|
1042
1112
|
}
|
|
1043
1113
|
catch (err) {
|
|
1044
|
-
|
|
1114
|
+
pluginLog.info(`[opencode-immune] Auto-resume failed (session may have been taken over):`, err);
|
|
1045
1115
|
}
|
|
1046
1116
|
}, 3_000);
|
|
1047
1117
|
}
|
|
1048
1118
|
}
|
|
1049
1119
|
else {
|
|
1050
1120
|
state.recoveryContext = null;
|
|
1051
|
-
|
|
1121
|
+
pluginLog.info("[opencode-immune] No active task found.");
|
|
1052
1122
|
}
|
|
1053
1123
|
}
|
|
1054
1124
|
};
|
|
@@ -1130,7 +1200,7 @@ function createRalphLoopToolAfter(state) {
|
|
|
1130
1200
|
newString: input.args?.newString ?? "",
|
|
1131
1201
|
timestamp: Date.now(),
|
|
1132
1202
|
};
|
|
1133
|
-
|
|
1203
|
+
pluginLog.warn(`[opencode-immune] Ralph Loop: Edit failed for "${state.lastEditAttempt.filePath}". ` +
|
|
1134
1204
|
`Recovery hint will be injected in next system transform.`);
|
|
1135
1205
|
}
|
|
1136
1206
|
else {
|
|
@@ -1157,7 +1227,7 @@ function createContextMonitorChatMessage(state) {
|
|
|
1157
1227
|
if (state.approximateTokens >
|
|
1158
1228
|
ESTIMATED_CONTEXT_LIMIT * CONTEXT_WARNING_THRESHOLD) {
|
|
1159
1229
|
const pct = Math.round((state.approximateTokens / ESTIMATED_CONTEXT_LIMIT) * 100);
|
|
1160
|
-
|
|
1230
|
+
pluginLog.warn(`[opencode-immune] Context Monitor: ~${state.approximateTokens} tokens estimated (${pct}% of ~${ESTIMATED_CONTEXT_LIMIT}). ` +
|
|
1161
1231
|
`Consider compacting the session.`);
|
|
1162
1232
|
}
|
|
1163
1233
|
}
|
|
@@ -1180,7 +1250,7 @@ function createCompactionHandler(state) {
|
|
|
1180
1250
|
"After compaction, the agent should still be able to resume the current phase without re-reading all Memory Bank files.");
|
|
1181
1251
|
// Reset token counter after compaction
|
|
1182
1252
|
state.approximateTokens = 0;
|
|
1183
|
-
|
|
1253
|
+
pluginLog.info("[opencode-immune] Context Monitor: Compaction triggered, token counter reset.");
|
|
1184
1254
|
};
|
|
1185
1255
|
}
|
|
1186
1256
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1204,13 +1274,13 @@ function createCommentCheckerToolAfter(state) {
|
|
|
1204
1274
|
return;
|
|
1205
1275
|
// Check for emoji
|
|
1206
1276
|
if (EMOJI_PATTERN.test(content)) {
|
|
1207
|
-
|
|
1277
|
+
pluginLog.warn(`[opencode-immune] Comment Checker: Emoji detected in ${input.tool} operation. ` +
|
|
1208
1278
|
`Avoid emojis in code unless the user explicitly requested them.`);
|
|
1209
1279
|
}
|
|
1210
1280
|
// Check for TODO/FIXME/HACK
|
|
1211
1281
|
const todoMatch = content.match(TODO_PATTERN);
|
|
1212
1282
|
if (todoMatch) {
|
|
1213
|
-
|
|
1283
|
+
pluginLog.warn(`[opencode-immune] Comment Checker: "${todoMatch[0]}" comment found in ${input.tool} operation. ` +
|
|
1214
1284
|
`Consider resolving it or tracking it in the todo list.`);
|
|
1215
1285
|
}
|
|
1216
1286
|
};
|
|
@@ -1241,11 +1311,11 @@ function createKeywordDetectorChatMessage(_state) {
|
|
|
1241
1311
|
if (!messageContent)
|
|
1242
1312
|
return;
|
|
1243
1313
|
if (ERROR_KEYWORDS.test(messageContent)) {
|
|
1244
|
-
|
|
1314
|
+
pluginLog.info(`[opencode-immune] Keyword Detector: Error-related keywords found. ` +
|
|
1245
1315
|
`Consider using 1-van to analyze the issue systematically.`);
|
|
1246
1316
|
}
|
|
1247
1317
|
if (DEPLOY_KEYWORDS.test(messageContent)) {
|
|
1248
|
-
|
|
1318
|
+
pluginLog.info(`[opencode-immune] Keyword Detector: Deploy/release keywords found. ` +
|
|
1249
1319
|
`Consider running 5-reflect first to verify implementation quality.`);
|
|
1250
1320
|
}
|
|
1251
1321
|
};
|
|
@@ -1274,7 +1344,7 @@ function createFallbackModels(state) {
|
|
|
1274
1344
|
const recovery = await parseTasksFile(state.input.directory);
|
|
1275
1345
|
if (recovery && recovery.phase !== "ARCHIVE: DONE") {
|
|
1276
1346
|
state.recoveryContext = recovery;
|
|
1277
|
-
|
|
1347
|
+
pluginLog.info(`[opencode-immune] Auto-recovery on existing session: ` +
|
|
1278
1348
|
`task="${recovery.task}", level=${recovery.level}, phase=${recovery.phase}. ` +
|
|
1279
1349
|
`Sending AUTO-RESUME prompt in 3s...`);
|
|
1280
1350
|
const sid = input.sessionID;
|
|
@@ -1292,10 +1362,10 @@ function createFallbackModels(state) {
|
|
|
1292
1362
|
},
|
|
1293
1363
|
path: { id: sid },
|
|
1294
1364
|
});
|
|
1295
|
-
|
|
1365
|
+
pluginLog.info(`[opencode-immune] Auto-resume prompt sent to session ${sid}`);
|
|
1296
1366
|
}
|
|
1297
1367
|
catch (err) {
|
|
1298
|
-
|
|
1368
|
+
pluginLog.info(`[opencode-immune] Auto-resume prompt failed:`, err);
|
|
1299
1369
|
}
|
|
1300
1370
|
}, 3_000);
|
|
1301
1371
|
}
|
|
@@ -1314,7 +1384,7 @@ function createFallbackModels(state) {
|
|
|
1314
1384
|
const providerId = input.provider?.info && "id" in input.provider.info
|
|
1315
1385
|
? input.provider.info.id
|
|
1316
1386
|
: "unknown";
|
|
1317
|
-
|
|
1387
|
+
pluginLog.info(`[opencode-immune] Model Observer: agent="${input.agent}", ` +
|
|
1318
1388
|
`model="${modelId}", provider="${providerId}"`);
|
|
1319
1389
|
};
|
|
1320
1390
|
}
|
|
@@ -1352,7 +1422,7 @@ function createEventHandler(state) {
|
|
|
1352
1422
|
if (count < MAX_RETRIES && !state.sessionRetryTimers.has(fallbackSessionID)) {
|
|
1353
1423
|
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, count), MAX_DELAY_MS);
|
|
1354
1424
|
state.sessionErrorRetryCount.set(fallbackSessionID, count + 1);
|
|
1355
|
-
|
|
1425
|
+
pluginLog.warn(`[opencode-immune] session.error without sessionID matched retryable error. ` +
|
|
1356
1426
|
`Retrying sole managed root session ${fallbackSessionID}.`);
|
|
1357
1427
|
scheduleManagedSessionRetry(state, fallbackSessionID, {
|
|
1358
1428
|
delayMs: delay,
|
|
@@ -1377,7 +1447,7 @@ function createEventHandler(state) {
|
|
|
1377
1447
|
return;
|
|
1378
1448
|
}
|
|
1379
1449
|
if (state.sessionRetryTimers.has(sessionID)) {
|
|
1380
|
-
|
|
1450
|
+
pluginLog.info(`[opencode-immune] Retry already pending for ${isChild ? "child" : "root"} session ${sessionID}, skipping duplicate.`);
|
|
1381
1451
|
return;
|
|
1382
1452
|
}
|
|
1383
1453
|
const count = state.sessionErrorRetryCount.get(sessionID) ?? 0;
|
|
@@ -1388,7 +1458,7 @@ function createEventHandler(state) {
|
|
|
1388
1458
|
// child after the router advances can create two writers in one pipeline.
|
|
1389
1459
|
if (isChild) {
|
|
1390
1460
|
recordChildFallbackRequest(state, managedSession, sessionID, error);
|
|
1391
|
-
|
|
1461
|
+
pluginLog.info(`[opencode-immune] Child session ${sessionID}: retryable error detected. ` +
|
|
1392
1462
|
`Recorded router-owned fallback request and skipped plugin auto-retry.`);
|
|
1393
1463
|
state.sessionErrorRetryCount.set(sessionID, count);
|
|
1394
1464
|
return;
|
|
@@ -1396,7 +1466,7 @@ function createEventHandler(state) {
|
|
|
1396
1466
|
else if (isRoot && (isRateLimitApiError(error) || isCertificateApiError(error))) {
|
|
1397
1467
|
await setSessionFallbackModel(state, sessionID, RATE_LIMIT_FALLBACK_MODEL);
|
|
1398
1468
|
const errorType = isCertificateApiError(error) ? "certificate error" : "rate limit";
|
|
1399
|
-
|
|
1469
|
+
pluginLog.info(`[opencode-immune] ${errorType} detected for root session ${sessionID}. ` +
|
|
1400
1470
|
`Retry will use fallback model ${RATE_LIMIT_FALLBACK_MODEL.providerID}/${RATE_LIMIT_FALLBACK_MODEL.modelID}.`);
|
|
1401
1471
|
}
|
|
1402
1472
|
const scheduled = scheduleManagedSessionRetry(state, sessionID, {
|
|
@@ -1410,7 +1480,7 @@ function createEventHandler(state) {
|
|
|
1410
1480
|
}
|
|
1411
1481
|
}
|
|
1412
1482
|
else {
|
|
1413
|
-
|
|
1483
|
+
pluginLog.info(`[opencode-immune] Max retries (${MAX_RETRIES}) reached for ${isChild ? "child" : "root"} session ${sessionID}. Not retrying.`);
|
|
1414
1484
|
}
|
|
1415
1485
|
}
|
|
1416
1486
|
// Reset retry counter on successful activity
|
|
@@ -1440,7 +1510,7 @@ function createEventHandler(state) {
|
|
|
1440
1510
|
"file.edited",
|
|
1441
1511
|
];
|
|
1442
1512
|
if (significantEvents.includes(eventType)) {
|
|
1443
|
-
|
|
1513
|
+
pluginLog.info(`[opencode-immune] Event: ${eventType}`);
|
|
1444
1514
|
}
|
|
1445
1515
|
};
|
|
1446
1516
|
}
|
|
@@ -1466,7 +1536,7 @@ async function archiveProgress(directory) {
|
|
|
1466
1536
|
const content = await (0, promises_1.readFile)(progressPath, "utf-8");
|
|
1467
1537
|
// Skip if empty or trivially empty
|
|
1468
1538
|
if (!content.trim() || content.trim() === "# Progress") {
|
|
1469
|
-
|
|
1539
|
+
pluginLog.info("[opencode-immune] Archive progress: nothing to archive (empty).");
|
|
1470
1540
|
return;
|
|
1471
1541
|
}
|
|
1472
1542
|
const archiveDir = (0, path_1.join)(directory, "memory-bank", "archive");
|
|
@@ -1477,13 +1547,13 @@ async function archiveProgress(directory) {
|
|
|
1477
1547
|
const archiveName = `progress-${dateStr}-${ts}.md`;
|
|
1478
1548
|
const archivePath = (0, path_1.join)(archiveDir, archiveName);
|
|
1479
1549
|
await (0, promises_1.rename)(progressPath, archivePath);
|
|
1480
|
-
|
|
1550
|
+
pluginLog.info(`[opencode-immune] Archive progress: moved to ${archiveName}`);
|
|
1481
1551
|
}
|
|
1482
1552
|
catch (err) {
|
|
1483
1553
|
// File doesn't exist or move failed — not critical
|
|
1484
1554
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1485
1555
|
if (!msg.includes("ENOENT")) {
|
|
1486
|
-
|
|
1556
|
+
pluginLog.warn("[opencode-immune] Archive progress failed:", msg);
|
|
1487
1557
|
}
|
|
1488
1558
|
}
|
|
1489
1559
|
}
|
|
@@ -1529,7 +1599,7 @@ function runGitCommit(directory) {
|
|
|
1529
1599
|
// Stage all changes
|
|
1530
1600
|
(0, child_process_1.execFile)("git", ["add", "-A"], { cwd: directory }, (addErr) => {
|
|
1531
1601
|
if (addErr) {
|
|
1532
|
-
|
|
1602
|
+
pluginLog.error("[opencode-immune] git add failed:", addErr.message);
|
|
1533
1603
|
resolve(false);
|
|
1534
1604
|
return;
|
|
1535
1605
|
}
|
|
@@ -1540,15 +1610,15 @@ function runGitCommit(directory) {
|
|
|
1540
1610
|
(0, child_process_1.execFile)("git", ["commit", "-m", message], { cwd: directory }, (commitErr, stdout, stderr) => {
|
|
1541
1611
|
if (commitErr) {
|
|
1542
1612
|
if (stderr?.includes("nothing to commit") || stdout?.includes("nothing to commit")) {
|
|
1543
|
-
|
|
1613
|
+
pluginLog.info("[opencode-immune] git commit: nothing to commit (clean tree).");
|
|
1544
1614
|
resolve(true);
|
|
1545
1615
|
return;
|
|
1546
1616
|
}
|
|
1547
|
-
|
|
1617
|
+
pluginLog.error("[opencode-immune] git commit failed:", commitErr.message, stderr);
|
|
1548
1618
|
resolve(false);
|
|
1549
1619
|
return;
|
|
1550
1620
|
}
|
|
1551
|
-
|
|
1621
|
+
pluginLog.info("[opencode-immune] git commit succeeded:", stdout?.trim());
|
|
1552
1622
|
resolve(true);
|
|
1553
1623
|
});
|
|
1554
1624
|
});
|
|
@@ -1583,25 +1653,25 @@ function createTextCompleteHandler(state) {
|
|
|
1583
1653
|
countAgainstBudget: false,
|
|
1584
1654
|
abortBeforePrompt: true,
|
|
1585
1655
|
});
|
|
1586
|
-
|
|
1656
|
+
pluginLog.info(`[opencode-immune] Provider retry banner detected for session ${sessionID}. ` +
|
|
1587
1657
|
`Fallback model pinned to ${fallbackModel.providerID}/${fallbackModel.modelID}.`);
|
|
1588
1658
|
}
|
|
1589
1659
|
// ── ALL_CYCLES_COMPLETE: clear ultrawork marker ──
|
|
1590
1660
|
if (text.includes(ALL_CYCLES_COMPLETE_MARKER)) {
|
|
1591
1661
|
await clearUltraworkMarker(state);
|
|
1592
|
-
|
|
1662
|
+
pluginLog.info("[opencode-immune] Multi-Cycle: ALL_CYCLES_COMPLETE detected, marker cleared.");
|
|
1593
1663
|
return;
|
|
1594
1664
|
}
|
|
1595
1665
|
// ── PRE_COMMIT only (without CYCLE_COMPLETE in same part): run commit ──
|
|
1596
1666
|
if (text.includes(PRE_COMMIT_MARKER) && !text.includes(CYCLE_COMPLETE_MARKER)) {
|
|
1597
1667
|
if (!state.commitPending) {
|
|
1598
1668
|
state.commitPending = true;
|
|
1599
|
-
|
|
1669
|
+
pluginLog.info("[opencode-immune] Multi-Cycle: PRE_COMMIT detected (standalone), running git commit...");
|
|
1600
1670
|
try {
|
|
1601
1671
|
await runGitCommit(state.input.directory);
|
|
1602
1672
|
}
|
|
1603
1673
|
catch (err) {
|
|
1604
|
-
|
|
1674
|
+
pluginLog.error("[opencode-immune] Multi-Cycle: git commit failed (standalone):", err);
|
|
1605
1675
|
}
|
|
1606
1676
|
finally {
|
|
1607
1677
|
state.commitPending = false;
|
|
@@ -1616,18 +1686,18 @@ function createTextCompleteHandler(state) {
|
|
|
1616
1686
|
await archiveProgress(state.input.directory);
|
|
1617
1687
|
}
|
|
1618
1688
|
catch (err) {
|
|
1619
|
-
|
|
1689
|
+
pluginLog.warn("[opencode-immune] Multi-Cycle: archive progress failed:", err);
|
|
1620
1690
|
}
|
|
1621
1691
|
// Step 1: Always commit first
|
|
1622
1692
|
if (!state.commitPending) {
|
|
1623
1693
|
state.commitPending = true;
|
|
1624
|
-
|
|
1694
|
+
pluginLog.info("[opencode-immune] Multi-Cycle: CYCLE_COMPLETE detected, running git commit first...");
|
|
1625
1695
|
try {
|
|
1626
1696
|
await runGitCommit(state.input.directory);
|
|
1627
|
-
|
|
1697
|
+
pluginLog.info("[opencode-immune] Multi-Cycle: git commit completed before new cycle.");
|
|
1628
1698
|
}
|
|
1629
1699
|
catch (err) {
|
|
1630
|
-
|
|
1700
|
+
pluginLog.error("[opencode-immune] Multi-Cycle: git commit failed (continuing anyway):", err);
|
|
1631
1701
|
}
|
|
1632
1702
|
finally {
|
|
1633
1703
|
state.commitPending = false;
|
|
@@ -1636,13 +1706,13 @@ function createTextCompleteHandler(state) {
|
|
|
1636
1706
|
// Step 2: Create new session
|
|
1637
1707
|
state.cycleCount++;
|
|
1638
1708
|
if (state.cycleCount >= MAX_CYCLES) {
|
|
1639
|
-
|
|
1709
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle: MAX_CYCLES (${MAX_CYCLES}) reached. Not creating new session.`);
|
|
1640
1710
|
await clearUltraworkMarker(state);
|
|
1641
1711
|
return;
|
|
1642
1712
|
}
|
|
1643
1713
|
const taskMatch = text.match(NEXT_TASK_PATTERN);
|
|
1644
1714
|
const nextTask = taskMatch?.[1]?.trim() ?? "Continue processing task backlog";
|
|
1645
|
-
|
|
1715
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle: Creating new session (cycle ${state.cycleCount}/${MAX_CYCLES}) for: "${nextTask}"`);
|
|
1646
1716
|
try {
|
|
1647
1717
|
const createResult = await state.input.client.session.create({
|
|
1648
1718
|
body: {
|
|
@@ -1652,10 +1722,10 @@ function createTextCompleteHandler(state) {
|
|
|
1652
1722
|
const newSessionData = createResult?.data;
|
|
1653
1723
|
const newSessionID = newSessionData?.id;
|
|
1654
1724
|
if (!newSessionID) {
|
|
1655
|
-
|
|
1725
|
+
pluginLog.error("[opencode-immune] Multi-Cycle: Failed to create new session — no ID returned.");
|
|
1656
1726
|
return;
|
|
1657
1727
|
}
|
|
1658
|
-
|
|
1728
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle: New session created: ${newSessionID}`);
|
|
1659
1729
|
await addManagedUltraworkSession(state, newSessionID);
|
|
1660
1730
|
await state.input.client.session.promptAsync({
|
|
1661
1731
|
body: {
|
|
@@ -1669,10 +1739,10 @@ function createTextCompleteHandler(state) {
|
|
|
1669
1739
|
},
|
|
1670
1740
|
path: { id: newSessionID },
|
|
1671
1741
|
});
|
|
1672
|
-
|
|
1742
|
+
pluginLog.info(`[opencode-immune] Multi-Cycle: Bootstrap prompt sent to ${newSessionID}`);
|
|
1673
1743
|
}
|
|
1674
1744
|
catch (err) {
|
|
1675
|
-
|
|
1745
|
+
pluginLog.error("[opencode-immune] Multi-Cycle: Failed to create session or send prompt:", err);
|
|
1676
1746
|
}
|
|
1677
1747
|
}
|
|
1678
1748
|
};
|
|
@@ -1705,7 +1775,7 @@ function createMultiCycleHandler(state) {
|
|
|
1705
1775
|
RATE_LIMIT_MESSAGE_PATTERN.test(messageContent)) {
|
|
1706
1776
|
if (managedSession && !managedSession.fallbackModel) {
|
|
1707
1777
|
await setSessionFallbackModel(state, sessionID, RATE_LIMIT_FALLBACK_MODEL);
|
|
1708
|
-
|
|
1778
|
+
pluginLog.info(`[opencode-immune] Rate limit message detected in chat output for session ${sessionID}. ` +
|
|
1709
1779
|
`Fallback model pinned to ${RATE_LIMIT_FALLBACK_MODEL.providerID}/${RATE_LIMIT_FALLBACK_MODEL.modelID}.`);
|
|
1710
1780
|
}
|
|
1711
1781
|
if (managedSession) {
|
|
@@ -1730,7 +1800,7 @@ async function server(input) {
|
|
|
1730
1800
|
// Runs in background so it doesn't delay plugin initialization.
|
|
1731
1801
|
// If sync fails, plugin continues normally with existing config.
|
|
1732
1802
|
syncHarness(state).catch((err) => {
|
|
1733
|
-
|
|
1803
|
+
pluginLog.warn(`[opencode-immune] Harness sync background error:`, err instanceof Error ? err.message : String(err));
|
|
1734
1804
|
});
|
|
1735
1805
|
// Eagerly load recovery context at plugin init so it's available
|
|
1736
1806
|
// for the very first system.transform call (before chat.params fires).
|
|
@@ -1741,7 +1811,7 @@ async function server(input) {
|
|
|
1741
1811
|
// Active task exists with incomplete phases — resume it
|
|
1742
1812
|
state.recoveryContext = recovery;
|
|
1743
1813
|
state.autoResumeAttempted = true;
|
|
1744
|
-
|
|
1814
|
+
pluginLog.info(`[opencode-immune] Plugin init: ultrawork marker active, recovery context loaded: ` +
|
|
1745
1815
|
`task="${recovery.task}", level=${recovery.level}, phase=${recovery.phase}. ` +
|
|
1746
1816
|
`Will create new session and send AUTO-RESUME.`);
|
|
1747
1817
|
// Create a new session and send AUTO-RESUME prompt (same pattern as CYCLE_COMPLETE).
|
|
@@ -1756,10 +1826,10 @@ async function server(input) {
|
|
|
1756
1826
|
const newSessionData = createResult?.data;
|
|
1757
1827
|
const newSessionID = newSessionData?.id;
|
|
1758
1828
|
if (!newSessionID) {
|
|
1759
|
-
|
|
1829
|
+
pluginLog.error("[opencode-immune] Auto-resume: Failed to create session — no session ID returned.");
|
|
1760
1830
|
return;
|
|
1761
1831
|
}
|
|
1762
|
-
|
|
1832
|
+
pluginLog.info(`[opencode-immune] Auto-resume: New session created: ${newSessionID}`);
|
|
1763
1833
|
await addManagedUltraworkSession(state, newSessionID);
|
|
1764
1834
|
await state.input.client.session.promptAsync({
|
|
1765
1835
|
body: {
|
|
@@ -1773,10 +1843,10 @@ async function server(input) {
|
|
|
1773
1843
|
},
|
|
1774
1844
|
path: { id: newSessionID },
|
|
1775
1845
|
});
|
|
1776
|
-
|
|
1846
|
+
pluginLog.info(`[opencode-immune] Auto-resume prompt sent to new session ${newSessionID}`);
|
|
1777
1847
|
}
|
|
1778
1848
|
catch (err) {
|
|
1779
|
-
|
|
1849
|
+
pluginLog.error("[opencode-immune] Auto-resume: Failed to create session or send prompt:", err);
|
|
1780
1850
|
}
|
|
1781
1851
|
}, 5_000);
|
|
1782
1852
|
}
|
|
@@ -1788,7 +1858,7 @@ async function server(input) {
|
|
|
1788
1858
|
const hasPendingTasks = /- \[ \]/.test(backlogContent);
|
|
1789
1859
|
if (hasPendingTasks) {
|
|
1790
1860
|
state.autoResumeAttempted = true;
|
|
1791
|
-
|
|
1861
|
+
pluginLog.info(`[opencode-immune] Plugin init: no active task but backlog has pending items. ` +
|
|
1792
1862
|
`Will create new session to start next cycle.`);
|
|
1793
1863
|
setTimeout(async () => {
|
|
1794
1864
|
try {
|
|
@@ -1800,10 +1870,10 @@ async function server(input) {
|
|
|
1800
1870
|
const newSessionData = createResult?.data;
|
|
1801
1871
|
const newSessionID = newSessionData?.id;
|
|
1802
1872
|
if (!newSessionID) {
|
|
1803
|
-
|
|
1873
|
+
pluginLog.error("[opencode-immune] Auto-cycle: Failed to create session — no session ID returned.");
|
|
1804
1874
|
return;
|
|
1805
1875
|
}
|
|
1806
|
-
|
|
1876
|
+
pluginLog.info(`[opencode-immune] Auto-cycle: New session created: ${newSessionID}`);
|
|
1807
1877
|
await addManagedUltraworkSession(state, newSessionID);
|
|
1808
1878
|
await state.input.client.session.promptAsync({
|
|
1809
1879
|
body: {
|
|
@@ -1817,27 +1887,27 @@ async function server(input) {
|
|
|
1817
1887
|
},
|
|
1818
1888
|
path: { id: newSessionID },
|
|
1819
1889
|
});
|
|
1820
|
-
|
|
1890
|
+
pluginLog.info(`[opencode-immune] Auto-cycle prompt sent to new session ${newSessionID}`);
|
|
1821
1891
|
}
|
|
1822
1892
|
catch (err) {
|
|
1823
|
-
|
|
1893
|
+
pluginLog.error("[opencode-immune] Auto-cycle: Failed to create session or send prompt:", err);
|
|
1824
1894
|
}
|
|
1825
1895
|
}, 5_000);
|
|
1826
1896
|
}
|
|
1827
1897
|
else {
|
|
1828
1898
|
// No active task and no pending backlog — clear marker
|
|
1829
1899
|
await clearUltraworkMarker(state);
|
|
1830
|
-
|
|
1900
|
+
pluginLog.info(`[opencode-immune] Plugin init: no active task and no pending backlog. Marker cleared.`);
|
|
1831
1901
|
}
|
|
1832
1902
|
}
|
|
1833
1903
|
catch {
|
|
1834
1904
|
// backlog.md doesn't exist or can't be read — clear marker
|
|
1835
1905
|
await clearUltraworkMarker(state);
|
|
1836
|
-
|
|
1906
|
+
pluginLog.info(`[opencode-immune] Plugin init: no active task, backlog unreadable. Marker cleared.`);
|
|
1837
1907
|
}
|
|
1838
1908
|
}
|
|
1839
1909
|
}
|
|
1840
|
-
|
|
1910
|
+
pluginLog.info(`[opencode-immune] Plugin initialized. Directory: ${input.directory}`);
|
|
1841
1911
|
// Compose tool.execute.after handlers:
|
|
1842
1912
|
// Todo Enforcer (counter) + Ralph Loop (edit error) + Comment Checker
|
|
1843
1913
|
const toolAfterHandlers = [
|
|
@@ -1854,13 +1924,13 @@ async function server(input) {
|
|
|
1854
1924
|
createMultiCycleHandler(state),
|
|
1855
1925
|
];
|
|
1856
1926
|
return {
|
|
1857
|
-
event: withErrorBoundary("event", createEventHandler(state)),
|
|
1858
|
-
"chat.message": withErrorBoundary("chat.message", compositeChatMessage(chatMessageHandlers)),
|
|
1859
|
-
"chat.params": withErrorBoundary("chat.params", createFallbackModels(state)),
|
|
1860
|
-
"tool.execute.after": withErrorBoundary("tool.execute.after", compositeToolAfter(toolAfterHandlers)),
|
|
1861
|
-
"experimental.chat.system.transform": withErrorBoundary("experimental.chat.system.transform", createSystemTransform(state)),
|
|
1862
|
-
"experimental.session.compacting": withErrorBoundary("experimental.session.compacting", createCompactionHandler(state)),
|
|
1863
|
-
"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)),
|
|
1864
1934
|
};
|
|
1865
1935
|
}
|
|
1866
1936
|
exports.default = {
|