beecork 1.4.7 → 1.4.9
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/session/manager.js +28 -13
- package/dist/tasks/scheduler.js +3 -2
- package/dist/tasks/store.js +7 -0
- package/dist/types.d.ts +5 -4
- package/package.json +1 -1
package/dist/session/manager.js
CHANGED
|
@@ -146,7 +146,7 @@ export class TabManager {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
async executeMessage(tab, prompt, resume, onTextChunk, onToolUse, compactionDepth) {
|
|
149
|
+
async executeMessage(tab, prompt, resume, onTextChunk, onToolUse, compactionDepth, forceFresh = false, retryDepth = 0) {
|
|
150
150
|
const db = getDb();
|
|
151
151
|
logActivity('task_started', 'Processing message', { tabName: tab.name, details: prompt.slice(0, 500) });
|
|
152
152
|
// Budget check before spawning
|
|
@@ -177,9 +177,10 @@ export class TabManager {
|
|
|
177
177
|
const breaker = new CircuitBreaker(tab.name);
|
|
178
178
|
this.circuitBreakers.set(tab.name, breaker);
|
|
179
179
|
const contextMonitor = new ContextMonitor(tab.name);
|
|
180
|
-
// Resume if: explicitly requested or DB has prior successful responses for this tab
|
|
181
|
-
|
|
182
|
-
const
|
|
180
|
+
// Resume if: explicitly requested or DB has prior successful responses for this tab.
|
|
181
|
+
// forceFresh overrides both — used after a stale-session retry to guarantee --session-id, not --resume.
|
|
182
|
+
const hasDbHistory = db.prepare('SELECT COUNT(*) as count FROM messages WHERE tab_id = ? AND role = ? AND content != ?').get(tab.id, 'assistant', '');
|
|
183
|
+
const shouldResume = !forceFresh && (resume || hasDbHistory.count > 0);
|
|
183
184
|
return new Promise((resolve, reject) => {
|
|
184
185
|
let resultText = '';
|
|
185
186
|
let resultEvent = null;
|
|
@@ -245,21 +246,35 @@ export class TabManager {
|
|
|
245
246
|
sessionId: subprocess.sessionId,
|
|
246
247
|
error: resultEvent?.is_error ?? (code !== 0),
|
|
247
248
|
};
|
|
248
|
-
// Handle resume failure (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
// Handle resume failure (Claude Code session cache rotated / never existed / expired).
|
|
250
|
+
// Detection covers both legacy text-based errors and the modern error_during_execution
|
|
251
|
+
// event shape ({"subtype":"error_during_execution","errors":["No conversation found..."]}).
|
|
252
|
+
// retryDepth guards against any pathological loop.
|
|
253
|
+
const staleSession = result.error && shouldResume && retryDepth === 0 && (result.text.match(/session (not found|expired|invalid)/i) !== null ||
|
|
254
|
+
(resultEvent?.subtype === 'error_during_execution' &&
|
|
255
|
+
resultEvent.errors?.some(e => /no conversation found|session.*not found|session.*expired|session.*invalid/i.test(e))));
|
|
256
|
+
if (staleSession) {
|
|
257
|
+
const detail = resultEvent?.errors?.[0] ?? result.text.split('\n')[0];
|
|
258
|
+
logger.warn(`[${tab.name}] Resume session ${tab.sessionId} unavailable in Claude Code cache (${detail}). Retrying with fresh session.`);
|
|
259
|
+
const recentMsgs = db.prepare("SELECT role, content FROM messages WHERE tab_id = ? AND content != '' ORDER BY created_at DESC LIMIT 5").all(tab.id);
|
|
252
260
|
const context = recentMsgs.reverse().map(m => `${m.role}: ${m.content.slice(0, 200)}`).join('\n');
|
|
253
|
-
const contextPrompt =
|
|
254
|
-
|
|
261
|
+
const contextPrompt = context
|
|
262
|
+
? `[Previous conversation context:\n${context}\n]\n\n${enrichedPrompt}`
|
|
263
|
+
: enrichedPrompt;
|
|
264
|
+
// Reset session ID for fresh start. Use --session-id (forceFresh) to bypass the
|
|
265
|
+
// hasDbHistory shouldResume override that would otherwise --resume the new UUID
|
|
266
|
+
// against an equally-empty Claude Code cache.
|
|
255
267
|
const newSessionId = uuidv4();
|
|
256
268
|
db.prepare('UPDATE tabs SET session_id = ?, status = ? WHERE id = ?').run(newSessionId, 'idle', tab.id);
|
|
257
|
-
this.executeMessage({ ...tab, sessionId: newSessionId }, contextPrompt, false, onTextChunk)
|
|
269
|
+
this.executeMessage({ ...tab, sessionId: newSessionId }, contextPrompt, false, onTextChunk, onToolUse, compactionDepth, true, retryDepth + 1)
|
|
258
270
|
.then(resolve).catch(reject);
|
|
259
271
|
return;
|
|
260
272
|
}
|
|
261
|
-
// Store assistant response
|
|
262
|
-
|
|
273
|
+
// Store assistant response. Skip empty content (typically failed/error runs) so
|
|
274
|
+
// it doesn't trigger the hasDbHistory shouldResume override on future calls.
|
|
275
|
+
if (result.text.trim() !== '') {
|
|
276
|
+
db.prepare('INSERT INTO messages (tab_id, role, content, cost_usd, tokens_in, tokens_out) VALUES (?, ?, ?, ?, ?, ?)').run(tab.id, 'assistant', result.text, result.costUsd, resultEvent?.usage?.input_tokens ?? null, resultEvent?.usage?.output_tokens ?? null);
|
|
277
|
+
}
|
|
263
278
|
// Update tab
|
|
264
279
|
db.prepare('UPDATE tabs SET status = ?, last_activity_at = ?, pid = NULL WHERE name = ?')
|
|
265
280
|
.run('idle', new Date().toISOString(), tab.name);
|
package/dist/tasks/scheduler.js
CHANGED
|
@@ -166,8 +166,9 @@ export class TaskScheduler {
|
|
|
166
166
|
const result = await this.tabManager.sendMessage(job.tabName, job.message);
|
|
167
167
|
this.store.update(job.id, { lastRunAt: new Date().toISOString() });
|
|
168
168
|
const firstLine = result.text.split('\n')[0]?.slice(0, 200) || '(no output)';
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
const status = result.error ? 'ERROR' : 'SUCCESS';
|
|
170
|
+
// Log result (status reflects subprocess exit / is_error, not just completion)
|
|
171
|
+
await fs.promises.appendFile(logFile, `[${new Date().toISOString()}] ${status}: ${firstLine}\n`);
|
|
171
172
|
// Notify (separate try/catch -- notification failure shouldn't be reported as job failure)
|
|
172
173
|
try {
|
|
173
174
|
if (this.onNotify) {
|
package/dist/tasks/store.js
CHANGED
|
@@ -35,7 +35,14 @@ export class TaskStore {
|
|
|
35
35
|
const existing = this.get(id);
|
|
36
36
|
if (!existing)
|
|
37
37
|
return false;
|
|
38
|
+
// If the schedule changes, the stored next_run_at is no longer valid —
|
|
39
|
+
// null it out so the scheduler recomputes from the new expression on
|
|
40
|
+
// next load. Caller can override by explicitly passing nextRunAt.
|
|
41
|
+
const scheduleChanged = (updates.schedule !== undefined && updates.schedule !== existing.schedule) ||
|
|
42
|
+
(updates.scheduleType !== undefined && updates.scheduleType !== existing.scheduleType);
|
|
38
43
|
const merged = { ...existing, ...updates };
|
|
44
|
+
if (scheduleChanged && updates.nextRunAt === undefined)
|
|
45
|
+
merged.nextRunAt = null;
|
|
39
46
|
db.prepare(`UPDATE tasks SET name=?, schedule_type=?, schedule=?, tab_name=?, message=?, enabled=?, last_run_at=?, next_run_at=? WHERE id=?`).run(merged.name, merged.scheduleType, merged.schedule, merged.tabName, merged.message, merged.enabled ? 1 : 0, merged.lastRunAt, merged.nextRunAt, id);
|
|
40
47
|
return true;
|
|
41
48
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -132,13 +132,14 @@ export interface StreamAssistant {
|
|
|
132
132
|
}
|
|
133
133
|
export interface StreamResult {
|
|
134
134
|
type: 'result';
|
|
135
|
-
subtype: 'success' | 'error';
|
|
135
|
+
subtype: 'success' | 'error' | 'error_during_execution';
|
|
136
136
|
is_error: boolean;
|
|
137
137
|
duration_ms: number;
|
|
138
|
-
result
|
|
138
|
+
result?: string;
|
|
139
|
+
errors?: string[];
|
|
139
140
|
session_id: string;
|
|
140
|
-
total_cost_usd
|
|
141
|
-
usage
|
|
141
|
+
total_cost_usd?: number;
|
|
142
|
+
usage?: StreamUsage;
|
|
142
143
|
}
|
|
143
144
|
export interface StreamUsage {
|
|
144
145
|
input_tokens: number;
|