opencode-immune 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.js +74 -14
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -31,6 +31,13 @@ const RATE_LIMIT_FALLBACK_MODEL = {
|
|
|
31
31
|
function isManagedUltraworkSession(state, sessionID) {
|
|
32
32
|
return !!sessionID && state.managedUltraworkSessions.has(sessionID);
|
|
33
33
|
}
|
|
34
|
+
function getManagedSession(state, sessionID) {
|
|
35
|
+
return sessionID ? state.managedUltraworkSessions.get(sessionID) : undefined;
|
|
36
|
+
}
|
|
37
|
+
function isManagedRootUltraworkSession(state, sessionID) {
|
|
38
|
+
const record = getManagedSession(state, sessionID);
|
|
39
|
+
return !!record && record.kind === "root";
|
|
40
|
+
}
|
|
34
41
|
function pruneExpiredManagedSessions(state, now = Date.now()) {
|
|
35
42
|
let removed = 0;
|
|
36
43
|
for (const [sessionID, record] of state.managedUltraworkSessions.entries()) {
|
|
@@ -68,12 +75,23 @@ async function loadManagedSessionsCache(state) {
|
|
|
68
75
|
return;
|
|
69
76
|
}
|
|
70
77
|
for (const [sessionID, record] of Object.entries(parsed.sessions)) {
|
|
71
|
-
if (!record
|
|
78
|
+
if (!record)
|
|
72
79
|
continue;
|
|
73
80
|
state.managedUltraworkSessions.set(sessionID, {
|
|
74
|
-
|
|
81
|
+
kind: record.kind === "child" ? "child" : "root",
|
|
82
|
+
agent: typeof record.agent === "string" && record.agent.length > 0
|
|
83
|
+
? record.agent
|
|
84
|
+
: ULTRAWORK_AGENT,
|
|
85
|
+
rootSessionID: typeof record.rootSessionID === "string" && record.rootSessionID.length > 0
|
|
86
|
+
? record.rootSessionID
|
|
87
|
+
: sessionID,
|
|
75
88
|
createdAt: typeof record.createdAt === "number" ? record.createdAt : Date.now(),
|
|
76
89
|
updatedAt: typeof record.updatedAt === "number" ? record.updatedAt : Date.now(),
|
|
90
|
+
fallbackModel: record.fallbackModel &&
|
|
91
|
+
typeof record.fallbackModel.providerID === "string" &&
|
|
92
|
+
typeof record.fallbackModel.modelID === "string"
|
|
93
|
+
? record.fallbackModel
|
|
94
|
+
: undefined,
|
|
77
95
|
});
|
|
78
96
|
}
|
|
79
97
|
const removed = pruneExpiredManagedSessions(state);
|
|
@@ -93,7 +111,9 @@ async function loadManagedSessionsCache(state) {
|
|
|
93
111
|
async function addManagedUltraworkSession(state, sessionID, timestamp = Date.now()) {
|
|
94
112
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
95
113
|
const nextRecord = {
|
|
114
|
+
kind: existing?.kind ?? "root",
|
|
96
115
|
agent: ULTRAWORK_AGENT,
|
|
116
|
+
rootSessionID: existing?.rootSessionID ?? sessionID,
|
|
97
117
|
createdAt: existing?.createdAt ?? timestamp,
|
|
98
118
|
updatedAt: timestamp,
|
|
99
119
|
fallbackModel: existing?.fallbackModel,
|
|
@@ -107,6 +127,21 @@ async function addManagedUltraworkSession(state, sessionID, timestamp = Date.now
|
|
|
107
127
|
state.managedUltraworkSessions.set(sessionID, nextRecord);
|
|
108
128
|
await writeManagedSessionsCache(state);
|
|
109
129
|
}
|
|
130
|
+
async function addManagedChildSession(state, sessionID, parentSessionID, timestamp = Date.now()) {
|
|
131
|
+
const parent = state.managedUltraworkSessions.get(parentSessionID);
|
|
132
|
+
if (!parent)
|
|
133
|
+
return;
|
|
134
|
+
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
135
|
+
state.managedUltraworkSessions.set(sessionID, {
|
|
136
|
+
kind: "child",
|
|
137
|
+
agent: existing?.agent ?? "unknown",
|
|
138
|
+
rootSessionID: parent.rootSessionID,
|
|
139
|
+
createdAt: existing?.createdAt ?? timestamp,
|
|
140
|
+
updatedAt: timestamp,
|
|
141
|
+
fallbackModel: existing?.fallbackModel ?? parent.fallbackModel,
|
|
142
|
+
});
|
|
143
|
+
await writeManagedSessionsCache(state);
|
|
144
|
+
}
|
|
110
145
|
function cancelRetry(state, sessionID, reason) {
|
|
111
146
|
const timer = state.retryTimers.get(sessionID);
|
|
112
147
|
if (!timer)
|
|
@@ -124,6 +159,17 @@ async function removeManagedUltraworkSession(state, sessionID, reason) {
|
|
|
124
159
|
await writeManagedSessionsCache(state);
|
|
125
160
|
console.log(`[opencode-immune] Removed managed ultrawork session ${sessionID}: ${reason}`);
|
|
126
161
|
}
|
|
162
|
+
async function updateManagedSessionAgent(state, sessionID, agent) {
|
|
163
|
+
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
164
|
+
if (!existing || existing.agent === agent)
|
|
165
|
+
return;
|
|
166
|
+
state.managedUltraworkSessions.set(sessionID, {
|
|
167
|
+
...existing,
|
|
168
|
+
agent,
|
|
169
|
+
updatedAt: Date.now(),
|
|
170
|
+
});
|
|
171
|
+
await writeManagedSessionsCache(state);
|
|
172
|
+
}
|
|
127
173
|
function markUltraworkSessionActive(state, sessionID) {
|
|
128
174
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
129
175
|
if (!existing)
|
|
@@ -151,9 +197,7 @@ function isRateLimitApiError(error) {
|
|
|
151
197
|
const maybeError = error;
|
|
152
198
|
const message = `${maybeError.message ?? ""} ${maybeError.data?.message ?? ""}`.toLowerCase();
|
|
153
199
|
const type = `${maybeError.data?.type ?? ""}`.toLowerCase();
|
|
154
|
-
return (
|
|
155
|
-
maybeError.data?.isRetryable === true &&
|
|
156
|
-
(type.includes("rate_limit") || message.includes("too many requests") || message.includes("rate limit")));
|
|
200
|
+
return ((type.includes("rate_limit") || message.includes("too many requests") || message.includes("rate limit")));
|
|
157
201
|
}
|
|
158
202
|
async function setSessionFallbackModel(state, sessionID, model) {
|
|
159
203
|
const existing = state.managedUltraworkSessions.get(sessionID);
|
|
@@ -302,12 +346,16 @@ function createTodoEnforcerChatMessage(state) {
|
|
|
302
346
|
return async (input, _output) => {
|
|
303
347
|
const sessionID = input.sessionID;
|
|
304
348
|
const agent = input.agent;
|
|
349
|
+
const record = getManagedSession(state, sessionID);
|
|
305
350
|
if (sessionID && agent === ULTRAWORK_AGENT) {
|
|
306
351
|
await addManagedUltraworkSession(state, sessionID);
|
|
307
352
|
}
|
|
308
|
-
else if (sessionID && agent &&
|
|
353
|
+
else if (sessionID && agent && record?.kind === "root") {
|
|
309
354
|
await removeManagedUltraworkSession(state, sessionID, `session taken over by agent \"${agent}\"`);
|
|
310
355
|
}
|
|
356
|
+
else if (sessionID && agent && record?.kind === "child") {
|
|
357
|
+
await updateManagedSessionAgent(state, sessionID, agent);
|
|
358
|
+
}
|
|
311
359
|
// On user message, check previous assistant turn's counters
|
|
312
360
|
// then reset for next turn
|
|
313
361
|
if (state.toolCallCount > 3 && !state.todoWriteUsed) {
|
|
@@ -335,7 +383,11 @@ function createSessionRecoveryEvent(state) {
|
|
|
335
383
|
state.sessionActive = true;
|
|
336
384
|
const sessionInfo = event.properties?.info;
|
|
337
385
|
const sessionID = sessionInfo?.id ?? event.properties?.sessionID;
|
|
338
|
-
|
|
386
|
+
const parentID = sessionInfo?.parentID;
|
|
387
|
+
if (sessionID && parentID && isManagedUltraworkSession(state, parentID)) {
|
|
388
|
+
await addManagedChildSession(state, sessionID, parentID);
|
|
389
|
+
}
|
|
390
|
+
if (!isManagedRootUltraworkSession(state, sessionID)) {
|
|
339
391
|
return;
|
|
340
392
|
}
|
|
341
393
|
console.log(`[opencode-immune] Managed ultrawork session created, checking for active task...`);
|
|
@@ -346,7 +398,7 @@ function createSessionRecoveryEvent(state) {
|
|
|
346
398
|
if (sessionID && recovery.phase !== "ARCHIVE: DONE") {
|
|
347
399
|
setTimeout(async () => {
|
|
348
400
|
try {
|
|
349
|
-
if (!
|
|
401
|
+
if (!isManagedRootUltraworkSession(state, sessionID)) {
|
|
350
402
|
return;
|
|
351
403
|
}
|
|
352
404
|
await state.input.client.session.promptAsync({
|
|
@@ -383,7 +435,7 @@ function createSessionRecoveryEvent(state) {
|
|
|
383
435
|
function createSystemTransform(state) {
|
|
384
436
|
return async (input, output) => {
|
|
385
437
|
// Session Recovery injection
|
|
386
|
-
if (state.recoveryContext &&
|
|
438
|
+
if (state.recoveryContext && isManagedRootUltraworkSession(state, input.sessionID)) {
|
|
387
439
|
const ctx = state.recoveryContext;
|
|
388
440
|
const intentInfo = ctx.intent ? `, Intent: ${ctx.intent}` : "";
|
|
389
441
|
const categoryInfo = ctx.category ? `, Category: ${ctx.category}` : "";
|
|
@@ -559,9 +611,12 @@ function createFallbackModels(state) {
|
|
|
559
611
|
if (input.agent === ULTRAWORK_AGENT) {
|
|
560
612
|
await addManagedUltraworkSession(state, input.sessionID);
|
|
561
613
|
}
|
|
562
|
-
else if (
|
|
614
|
+
else if (getManagedSession(state, input.sessionID)?.kind === "root") {
|
|
563
615
|
await removeManagedUltraworkSession(state, input.sessionID, `session switched to agent \"${input.agent}\"`);
|
|
564
616
|
}
|
|
617
|
+
else if (getManagedSession(state, input.sessionID)?.kind === "child") {
|
|
618
|
+
await updateManagedSessionAgent(state, input.sessionID, input.agent);
|
|
619
|
+
}
|
|
565
620
|
// Log model and agent for observability
|
|
566
621
|
const modelId = input.model && "id" in input.model
|
|
567
622
|
? input.model.id
|
|
@@ -617,7 +672,12 @@ function createEventHandler(state) {
|
|
|
617
672
|
console.log(`[opencode-immune] Rate limit detected for session ${sessionID}. ` +
|
|
618
673
|
`Retry will use fallback model ${RATE_LIMIT_FALLBACK_MODEL.providerID}/${RATE_LIMIT_FALLBACK_MODEL.modelID}.`);
|
|
619
674
|
}
|
|
620
|
-
const
|
|
675
|
+
const managedSession = state.managedUltraworkSessions.get(sessionID);
|
|
676
|
+
const fallbackModel = managedSession?.fallbackModel;
|
|
677
|
+
const retryAgent = managedSession?.agent || ULTRAWORK_AGENT;
|
|
678
|
+
const retryText = retryAgent === ULTRAWORK_AGENT
|
|
679
|
+
? "[SYSTEM: Previous API call failed with a transient error. Re-read memory-bank/tasks.md, check the Phase Status block, and continue the pipeline. Use the exact neutral prompt from your Step 5 table for the next router call. Do NOT analyze or evaluate file contents.]"
|
|
680
|
+
: `[SYSTEM: Previous API call failed with a transient error. Resume the current task in your current role as ${retryAgent}. Continue from the existing session state. Do not restart from scratch unless the current session state is missing.]`;
|
|
621
681
|
console.log(`[opencode-immune] Session error detected (attempt ${count + 1}/${MAX_RETRIES}). ` +
|
|
622
682
|
`Waiting ${delay / 1000}s before retry...`);
|
|
623
683
|
const timer = setTimeout(async () => {
|
|
@@ -629,11 +689,11 @@ function createEventHandler(state) {
|
|
|
629
689
|
await state.input.client.session.promptAsync({
|
|
630
690
|
body: {
|
|
631
691
|
...(fallbackModel ? { model: fallbackModel } : {}),
|
|
632
|
-
agent:
|
|
692
|
+
agent: retryAgent,
|
|
633
693
|
parts: [
|
|
634
694
|
{
|
|
635
695
|
type: "text",
|
|
636
|
-
text:
|
|
696
|
+
text: retryText,
|
|
637
697
|
},
|
|
638
698
|
],
|
|
639
699
|
},
|
|
@@ -696,7 +756,7 @@ const NEXT_TASK_PATTERN = /Next task:\s*(.+)/;
|
|
|
696
756
|
function createMultiCycleHandler(state) {
|
|
697
757
|
return async (input, output) => {
|
|
698
758
|
const sessionID = input.sessionID;
|
|
699
|
-
if (!
|
|
759
|
+
if (!isManagedRootUltraworkSession(state, sessionID))
|
|
700
760
|
return;
|
|
701
761
|
// Extract text content from parts
|
|
702
762
|
const parts = output.parts ?? [];
|