@teamvibe/poller 0.1.10 → 0.1.12
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/claude-spawner.d.ts +3 -1
- package/dist/claude-spawner.js +8 -0
- package/dist/index.js +5 -1
- package/dist/session-store.d.ts +1 -0
- package/dist/session-store.js +33 -18
- package/package.json +1 -1
package/dist/claude-spawner.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface SpawnResult {
|
|
|
6
6
|
exitCode: number | null;
|
|
7
7
|
error?: string;
|
|
8
8
|
}
|
|
9
|
-
export declare function spawnClaudeCode(msg: TeamVibeQueueMessage, sessionLog: SessionLogger, cwd: string, sessionId?: string, isFirstMessage?: boolean, lastMessageTs?: string, onMessageSent?: () => void): Promise<SpawnResult
|
|
9
|
+
export declare function spawnClaudeCode(msg: TeamVibeQueueMessage, sessionLog: SessionLogger, cwd: string, sessionId?: string, isFirstMessage?: boolean, lastMessageTs?: string, onMessageSent?: () => void): Promise<SpawnResult & {
|
|
10
|
+
newSessionId?: string;
|
|
11
|
+
}>;
|
|
10
12
|
export declare function getActiveProcessCount(): number;
|
|
11
13
|
export declare function isAtCapacity(): boolean;
|
package/dist/claude-spawner.js
CHANGED
|
@@ -275,6 +275,14 @@ async function runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = t
|
|
|
275
275
|
}
|
|
276
276
|
export async function spawnClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage = true, lastMessageTs, onMessageSent) {
|
|
277
277
|
const result = await runClaudeCode(msg, sessionLog, cwd, sessionId, isFirstMessage, lastMessageTs, onMessageSent);
|
|
278
|
+
// If session ID is "already in use" (stale lock file), retry with no session ID (let Claude generate a new one)
|
|
279
|
+
if (!result.success && sessionId && result.error?.includes('already in use')) {
|
|
280
|
+
sessionLog.info('Session ID already in use (stale lock), retrying without session ID');
|
|
281
|
+
const { randomUUID } = await import('crypto');
|
|
282
|
+
const freshId = randomUUID();
|
|
283
|
+
const retryResult = await runClaudeCode(msg, sessionLog, cwd, freshId, true, lastMessageTs, onMessageSent);
|
|
284
|
+
return { ...retryResult, newSessionId: freshId };
|
|
285
|
+
}
|
|
278
286
|
// If --resume failed, retry as a fresh session (session files may have been lost on container restart)
|
|
279
287
|
if (!result.success && sessionId && !isFirstMessage) {
|
|
280
288
|
sessionLog.info('Resume failed, retrying as fresh session (session files may have been lost)');
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { logger } from './logger.js';
|
|
|
4
4
|
import { pollMessages, deleteMessage, extendVisibility, } from './sqs-poller.js';
|
|
5
5
|
import { spawnClaudeCode, isAtCapacity, getActiveProcessCount } from './claude-spawner.js';
|
|
6
6
|
import { sendSlackError, addReaction, getUserInfo, startTypingIndicator } from './slack-client.js';
|
|
7
|
-
import { acquireSessionLock, releaseSessionLock } from './session-store.js';
|
|
7
|
+
import { acquireSessionLock, releaseSessionLock, updateSessionId } from './session-store.js';
|
|
8
8
|
import { getBrainPath, ensureDirectories, ensureBaseBrain, pushBrainChanges } from './brain-manager.js';
|
|
9
9
|
import { initAuth, stopRefresh } from './auth-provider.js';
|
|
10
10
|
logger.info('TeamVibe Poller starting...');
|
|
@@ -135,6 +135,10 @@ async function processMessage(received) {
|
|
|
135
135
|
try {
|
|
136
136
|
const result = await spawnClaudeCode(queueMessage, sessionLog, kbPath, session.session_id || undefined, isFirstMessage, session.last_message_ts, () => stopTyping?.());
|
|
137
137
|
stopTyping?.();
|
|
138
|
+
// If Claude generated a new session ID (stale lock recovery), persist it
|
|
139
|
+
if (result.newSessionId && lockToken) {
|
|
140
|
+
await updateSessionId(threadId, lockToken, result.newSessionId);
|
|
141
|
+
}
|
|
138
142
|
if (result.success) {
|
|
139
143
|
sessionLog.info('Claude Code completed successfully');
|
|
140
144
|
// Push any changes in the channel brain repo
|
package/dist/session-store.d.ts
CHANGED
|
@@ -7,5 +7,6 @@ export interface AcquireLockResult {
|
|
|
7
7
|
lockToken: string | null;
|
|
8
8
|
}
|
|
9
9
|
export declare function acquireSessionLock(threadId: string, workspacePath: string): Promise<AcquireLockResult>;
|
|
10
|
+
export declare function updateSessionId(threadId: string, lockToken: string, newSessionId: string): Promise<void>;
|
|
10
11
|
export declare function releaseSessionLock(threadId: string, lockToken: string, newStatus?: SessionStatus, lastMessageTs?: string): Promise<void>;
|
|
11
12
|
export declare function forceReleaseStalelock(threadId: string, maxAgeMs: number): Promise<boolean>;
|
package/dist/session-store.js
CHANGED
|
@@ -106,33 +106,26 @@ export async function acquireSessionLock(threadId, workspacePath) {
|
|
|
106
106
|
// Session is idle/waiting - try to acquire lock
|
|
107
107
|
const newLockToken = generateLockToken();
|
|
108
108
|
const now = Date.now();
|
|
109
|
-
const hasOldToken = existingSession.lock_token != null;
|
|
110
|
-
const conditionExpression = hasOldToken
|
|
111
|
-
? '#status IN (:idle, :waiting) AND (lock_token = :oldToken OR attribute_not_exists(lock_token))'
|
|
112
|
-
: '#status IN (:idle, :waiting) AND attribute_not_exists(lock_token)';
|
|
113
|
-
const expressionValues = {
|
|
114
|
-
':processing': 'processing',
|
|
115
|
-
':idle': 'idle',
|
|
116
|
-
':waiting': 'waiting_for_input',
|
|
117
|
-
':newToken': newLockToken,
|
|
118
|
-
':now': now,
|
|
119
|
-
':one': 1,
|
|
120
|
-
':ttl': calculateTTL(),
|
|
121
|
-
};
|
|
122
|
-
if (hasOldToken) {
|
|
123
|
-
expressionValues[':oldToken'] = existingSession.lock_token;
|
|
124
|
-
}
|
|
125
109
|
try {
|
|
126
110
|
await createDocClient().send(new UpdateCommand({
|
|
127
111
|
TableName: getTableName(),
|
|
128
112
|
Key: buildKey(threadId),
|
|
129
113
|
UpdateExpression: 'SET #status = :processing, lock_token = :newToken, last_activity = :now, message_count = message_count + :one, #ttl = :ttl',
|
|
130
|
-
ConditionExpression:
|
|
114
|
+
ConditionExpression: '#status IN (:idle, :waiting) AND (lock_token = :oldToken OR attribute_not_exists(lock_token))',
|
|
131
115
|
ExpressionAttributeNames: {
|
|
132
116
|
'#status': 'status',
|
|
133
117
|
'#ttl': 'ttl',
|
|
134
118
|
},
|
|
135
|
-
ExpressionAttributeValues:
|
|
119
|
+
ExpressionAttributeValues: {
|
|
120
|
+
':processing': 'processing',
|
|
121
|
+
':idle': 'idle',
|
|
122
|
+
':waiting': 'waiting_for_input',
|
|
123
|
+
':newToken': newLockToken,
|
|
124
|
+
':oldToken': existingSession.lock_token,
|
|
125
|
+
':now': now,
|
|
126
|
+
':one': 1,
|
|
127
|
+
':ttl': calculateTTL(),
|
|
128
|
+
},
|
|
136
129
|
}));
|
|
137
130
|
const updatedSession = {
|
|
138
131
|
...existingSession,
|
|
@@ -154,6 +147,28 @@ export async function acquireSessionLock(threadId, workspacePath) {
|
|
|
154
147
|
throw error;
|
|
155
148
|
}
|
|
156
149
|
}
|
|
150
|
+
export async function updateSessionId(threadId, lockToken, newSessionId) {
|
|
151
|
+
try {
|
|
152
|
+
await createDocClient().send(new UpdateCommand({
|
|
153
|
+
TableName: getTableName(),
|
|
154
|
+
Key: buildKey(threadId),
|
|
155
|
+
UpdateExpression: 'SET session_id = :newSessionId',
|
|
156
|
+
ConditionExpression: 'lock_token = :lockToken',
|
|
157
|
+
ExpressionAttributeValues: {
|
|
158
|
+
':newSessionId': newSessionId,
|
|
159
|
+
':lockToken': lockToken,
|
|
160
|
+
},
|
|
161
|
+
}));
|
|
162
|
+
logger.info(`Updated session_id for thread ${threadId} to ${newSessionId}`);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error.name === 'ConditionalCheckFailedException') {
|
|
166
|
+
logger.warn(`Lock token mismatch when updating session_id for ${threadId}`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
157
172
|
export async function releaseSessionLock(threadId, lockToken, newStatus = 'idle', lastMessageTs) {
|
|
158
173
|
const now = Date.now();
|
|
159
174
|
const updateExpression = lastMessageTs
|