opencode-immune 1.0.34 → 1.0.36
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 +69 -6
- package/package.json +3 -3
package/dist/plugin.js
CHANGED
|
@@ -263,6 +263,15 @@ function isRetryableApiError(error) {
|
|
|
263
263
|
const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""}`.toLowerCase();
|
|
264
264
|
if (message.includes("не разрешен") ||
|
|
265
265
|
message.includes("not allowed") ||
|
|
266
|
+
message.includes("expected") ||
|
|
267
|
+
message.includes("to be a string") ||
|
|
268
|
+
message.includes("invalid_type") ||
|
|
269
|
+
message.includes("validation") ||
|
|
270
|
+
message.includes("schema") ||
|
|
271
|
+
message.includes("zod") ||
|
|
272
|
+
message.includes("parse error") ||
|
|
273
|
+
message.includes("invalid id") ||
|
|
274
|
+
message.includes("expected id") ||
|
|
266
275
|
message.includes("model not available") ||
|
|
267
276
|
message.includes("model_not_found") ||
|
|
268
277
|
message.includes("access denied") ||
|
|
@@ -1207,9 +1216,33 @@ function createEventHandler(state) {
|
|
|
1207
1216
|
const eventType = event.type ?? "unknown";
|
|
1208
1217
|
const info = event.properties?.info;
|
|
1209
1218
|
const sessionID = event.properties?.sessionID ?? info?.id;
|
|
1219
|
+
const error = event.properties?.error;
|
|
1220
|
+
// Fallback: some SDK/schema errors can arrive without a valid sessionID
|
|
1221
|
+
// (for example: "Expected 'id' to be a string."). If there is exactly one
|
|
1222
|
+
// active managed root session, retry it as a best-effort recovery path.
|
|
1223
|
+
if (eventType === "session.error" && !sessionID && isRetryableApiError(error)) {
|
|
1224
|
+
const rootCandidates = Array.from(state.managedUltraworkSessions.entries())
|
|
1225
|
+
.filter(([, record]) => record.kind === "root")
|
|
1226
|
+
.map(([id]) => id);
|
|
1227
|
+
if (rootCandidates.length === 1) {
|
|
1228
|
+
const fallbackSessionID = rootCandidates[0];
|
|
1229
|
+
const count = state.sessionErrorRetryCount.get(fallbackSessionID) ?? 0;
|
|
1230
|
+
if (count < MAX_RETRIES && !state.sessionRetryTimers.has(fallbackSessionID)) {
|
|
1231
|
+
const delay = Math.min(BASE_DELAY_MS * Math.pow(2, count), MAX_DELAY_MS);
|
|
1232
|
+
state.sessionErrorRetryCount.set(fallbackSessionID, count + 1);
|
|
1233
|
+
console.warn(`[opencode-immune] session.error without sessionID matched retryable error. ` +
|
|
1234
|
+
`Retrying sole managed root session ${fallbackSessionID}.`);
|
|
1235
|
+
scheduleManagedSessionRetry(state, fallbackSessionID, {
|
|
1236
|
+
delayMs: delay,
|
|
1237
|
+
reason: "session.error without sessionID",
|
|
1238
|
+
attemptLabel: `fallback attempt ${count + 1}/${MAX_RETRIES}`,
|
|
1239
|
+
countAgainstBudget: true,
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1210
1244
|
// ── Auto-retry on retryable API error for managed ultrawork sessions ──
|
|
1211
1245
|
if (eventType === "session.error" && sessionID) {
|
|
1212
|
-
const error = event.properties?.error;
|
|
1213
1246
|
const managedSession = getManagedSession(state, sessionID);
|
|
1214
1247
|
const isRoot = managedSession?.kind === "root";
|
|
1215
1248
|
const isChild = managedSession?.kind === "child";
|
|
@@ -1323,10 +1356,41 @@ async function archiveProgress(directory) {
|
|
|
1323
1356
|
}
|
|
1324
1357
|
}
|
|
1325
1358
|
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Build a descriptive commit message from tasks.md context + diff stat.
|
|
1361
|
+
* Format: "feat(ultrawork): <task-name-short> [L<level>]"
|
|
1362
|
+
* Body: diff stat + phase info
|
|
1363
|
+
*/
|
|
1364
|
+
async function buildCommitMessage(directory, diffStat) {
|
|
1365
|
+
try {
|
|
1366
|
+
const ctx = await parseTasksFile(directory);
|
|
1367
|
+
if (ctx) {
|
|
1368
|
+
// Truncate task name to ~45 chars for subject line
|
|
1369
|
+
let taskShort = ctx.task;
|
|
1370
|
+
if (taskShort.length > 45) {
|
|
1371
|
+
taskShort = taskShort.slice(0, 42) + "...";
|
|
1372
|
+
}
|
|
1373
|
+
const subject = `feat(ultrawork): ${taskShort}`;
|
|
1374
|
+
const parts = [];
|
|
1375
|
+
parts.push(`- Level: ${ctx.level}`);
|
|
1376
|
+
if (ctx.intent)
|
|
1377
|
+
parts.push(`- Intent: ${ctx.intent}`);
|
|
1378
|
+
if (ctx.category)
|
|
1379
|
+
parts.push(`- Category: ${ctx.category}`);
|
|
1380
|
+
parts.push(`- Phase: ${ctx.phase}`);
|
|
1381
|
+
if (diffStat)
|
|
1382
|
+
parts.push("", diffStat);
|
|
1383
|
+
return `${subject}\n\n${parts.join("\n")}`;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
catch { /* fallback below */ }
|
|
1387
|
+
// Fallback: no tasks.md or parse failed
|
|
1388
|
+
const body = diffStat ? `\n\n${diffStat}` : "";
|
|
1389
|
+
return `chore(ultrawork): cycle auto-commit${body}`;
|
|
1390
|
+
}
|
|
1326
1391
|
/**
|
|
1327
1392
|
* Helper: run git add + diff-stat + commit with descriptive message.
|
|
1328
|
-
*
|
|
1329
|
-
* is not an opaque "auto-commit" but shows what changed.
|
|
1393
|
+
* Reads tasks.md to build a meaningful commit subject from task context.
|
|
1330
1394
|
* Returns true if commit succeeded (or nothing to commit), false on error.
|
|
1331
1395
|
*/
|
|
1332
1396
|
function runGitCommit(directory) {
|
|
@@ -1339,10 +1403,9 @@ function runGitCommit(directory) {
|
|
|
1339
1403
|
return;
|
|
1340
1404
|
}
|
|
1341
1405
|
// Get diff stat for commit body
|
|
1342
|
-
(0, child_process_1.execFile)("git", ["diff", "--cached", "--stat"], { cwd: directory }, (_diffErr, diffOut) => {
|
|
1406
|
+
(0, child_process_1.execFile)("git", ["diff", "--cached", "--stat"], { cwd: directory }, async (_diffErr, diffOut) => {
|
|
1343
1407
|
const stat = (diffOut ?? "").trim();
|
|
1344
|
-
const
|
|
1345
|
-
const message = `chore: ultrawork cycle auto-commit${body}`;
|
|
1408
|
+
const message = await buildCommitMessage(directory, stat);
|
|
1346
1409
|
(0, child_process_1.execFile)("git", ["commit", "-m", message], { cwd: directory }, (commitErr, stdout, stderr) => {
|
|
1347
1410
|
if (commitErr) {
|
|
1348
1411
|
if (stderr?.includes("nothing to commit") || stdout?.includes("nothing to commit")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-immune",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.36",
|
|
4
4
|
"description": "OpenCode plugin: session recovery, auto-retry, multi-cycle automation, context monitoring",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./server": "./dist/plugin.js"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"prepublishOnly": "npm run build"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@opencode-ai/plugin": "1.4.
|
|
17
|
+
"@opencode-ai/plugin": "1.4.7"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/node": "^25.5.2",
|
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
"retry"
|
|
30
30
|
],
|
|
31
31
|
"license": "MIT"
|
|
32
|
-
}
|
|
32
|
+
}
|