@teamvibe/poller 0.8.8 → 0.8.9-rc.2
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/README.md +40 -0
- package/bin/tv-git-credential.mjs +11 -6
- package/dist/brain-inventory-emitter.d.ts +13 -0
- package/dist/brain-inventory-emitter.js +57 -0
- package/dist/brain-inventory.d.ts +28 -0
- package/dist/brain-inventory.js +209 -0
- package/dist/brain-manager.js +4 -4
- package/dist/claude-spawner.d.ts +2 -2
- package/dist/claude-spawner.js +43 -29
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/connections-client.d.ts +16 -4
- package/dist/connections-client.js +42 -11
- package/dist/end-of-turn-sentinel.d.ts +2 -0
- package/dist/end-of-turn-sentinel.js +2 -0
- package/dist/git-credential-bindings.d.ts +2 -0
- package/dist/git-credential-bindings.js +4 -0
- package/dist/git-credential-env.d.ts +2 -2
- package/dist/git-credential-env.js +22 -9
- package/dist/git-credential-session.d.ts +6 -6
- package/dist/git-credential-session.js +24 -4
- package/dist/inbox-watcher.d.ts +2 -2
- package/dist/inbox-watcher.js +20 -13
- package/dist/poller.d.ts +4 -3
- package/dist/poller.js +43 -8
- package/dist/redact-text.js +1 -1
- package/dist/slack-client.d.ts +3 -1
- package/dist/slack-client.js +84 -12
- package/dist/thread-routing.d.ts +7 -0
- package/dist/thread-routing.js +7 -0
- package/npm-shrinkwrap.json +15 -2
- package/package.json +2 -1
package/dist/slack-client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WebClient } from '@slack/web-api';
|
|
1
|
+
import { LogLevel, WebClient } from '@slack/web-api';
|
|
2
2
|
import { logger } from './logger.js';
|
|
3
3
|
import { emitError } from './event-queue.js';
|
|
4
4
|
import { config } from './config.js';
|
|
@@ -6,6 +6,20 @@ import { config } from './config.js';
|
|
|
6
6
|
const userCache = new Map();
|
|
7
7
|
// Per-token Slack client cache
|
|
8
8
|
const slackClients = new Map();
|
|
9
|
+
const slackStatusClients = new Map();
|
|
10
|
+
// tv#589 review: the SDK's own logger prints raw `response_metadata.messages`,
|
|
11
|
+
// which can echo the status text (agent narration) on an API error. The
|
|
12
|
+
// status client therefore gets a silent logger; application code owns the
|
|
13
|
+
// redacted warnings in requestThreadStatus below.
|
|
14
|
+
const silentSlackLogger = {
|
|
15
|
+
debug: () => { },
|
|
16
|
+
info: () => { },
|
|
17
|
+
warn: () => { },
|
|
18
|
+
error: () => { },
|
|
19
|
+
setLevel: () => { },
|
|
20
|
+
getLevel: () => LogLevel.ERROR,
|
|
21
|
+
setName: () => { },
|
|
22
|
+
};
|
|
9
23
|
// Slack: "must be less than 51 characters [json-pointer:/loading_messages/0]"
|
|
10
24
|
const SLACK_LOADING_MESSAGE_MAX = 50;
|
|
11
25
|
function getSlackClient(botToken) {
|
|
@@ -16,6 +30,21 @@ function getSlackClient(botToken) {
|
|
|
16
30
|
}
|
|
17
31
|
return client;
|
|
18
32
|
}
|
|
33
|
+
function getSlackStatusClient(botToken) {
|
|
34
|
+
let client = slackStatusClients.get(botToken);
|
|
35
|
+
if (!client) {
|
|
36
|
+
client = new WebClient(botToken, {
|
|
37
|
+
// The SDK passes timeout to Axios, which aborts the underlying HTTP request.
|
|
38
|
+
timeout: config.TYPING_STATUS_REQUEST_TIMEOUT_MS,
|
|
39
|
+
// Let the indicator recover immediately and own the single clear retry.
|
|
40
|
+
retryConfig: { retries: 0 },
|
|
41
|
+
rejectRateLimitedCalls: true,
|
|
42
|
+
logger: silentSlackLogger, // Never let the SDK echo status text; warnings are redacted below.
|
|
43
|
+
});
|
|
44
|
+
slackStatusClients.set(botToken, client);
|
|
45
|
+
}
|
|
46
|
+
return client;
|
|
47
|
+
}
|
|
19
48
|
export async function getUserInfo(botToken, userId) {
|
|
20
49
|
const cached = userCache.get(userId);
|
|
21
50
|
if (cached)
|
|
@@ -111,16 +140,29 @@ export async function addReaction(msg, emoji) {
|
|
|
111
140
|
}
|
|
112
141
|
}
|
|
113
142
|
export async function setThreadStatus(msg, status) {
|
|
143
|
+
// Standalone callers retain best-effort delivery; indicators handle retries.
|
|
144
|
+
await requestThreadStatus(msg, status).catch(() => { });
|
|
145
|
+
}
|
|
146
|
+
async function requestThreadStatus(msg, status, onTimeout = () => logger.warn('Slack thread status request timed out')) {
|
|
114
147
|
if (!msg.response_context.slack)
|
|
115
148
|
return;
|
|
116
149
|
const { channel, thread_ts } = msg.response_context.slack;
|
|
117
|
-
const slack =
|
|
150
|
+
const slack = getSlackStatusClient(msg.teamvibe.botToken);
|
|
118
151
|
const safeStatus = neutralizeSlackMarkup(status);
|
|
119
152
|
const payload = {
|
|
120
153
|
channel_id: channel,
|
|
121
154
|
thread_ts,
|
|
122
155
|
status: safeStatus,
|
|
123
156
|
};
|
|
157
|
+
const reportFailure = (error) => {
|
|
158
|
+
// WebClient wraps Axios errors in a WebAPIRequestError with the original attached.
|
|
159
|
+
const code = error?.original?.code ?? error?.code;
|
|
160
|
+
if (code === 'ECONNABORTED' || code === 'ETIMEDOUT')
|
|
161
|
+
onTimeout();
|
|
162
|
+
else
|
|
163
|
+
logger.warn('Failed to set thread status');
|
|
164
|
+
emitError('slack', 'Failed to set thread status', { severity: 'warn' });
|
|
165
|
+
};
|
|
124
166
|
try {
|
|
125
167
|
await slack.apiCall('assistant.threads.setStatus', {
|
|
126
168
|
...payload,
|
|
@@ -137,14 +179,14 @@ export async function setThreadStatus(msg, status) {
|
|
|
137
179
|
await slack.apiCall('assistant.threads.setStatus', payload);
|
|
138
180
|
return;
|
|
139
181
|
}
|
|
140
|
-
catch {
|
|
141
|
-
|
|
142
|
-
|
|
182
|
+
catch (retryError) {
|
|
183
|
+
reportFailure(retryError);
|
|
184
|
+
throw retryError;
|
|
143
185
|
}
|
|
144
186
|
}
|
|
145
187
|
// API errors may echo request fields; status can contain private narration.
|
|
146
|
-
|
|
147
|
-
|
|
188
|
+
reportFailure(error);
|
|
189
|
+
throw error;
|
|
148
190
|
}
|
|
149
191
|
}
|
|
150
192
|
function truncateLoadingMessage(text) {
|
|
@@ -176,9 +218,36 @@ export function startTypingIndicator(msg, statusText = 'Thinking...') {
|
|
|
176
218
|
else
|
|
177
219
|
logger.info(`[Typing] status -> ${source} (${status.length} chars)`);
|
|
178
220
|
};
|
|
221
|
+
// Serialize all writes so an older request cannot land after a newer status.
|
|
222
|
+
let statusChain = Promise.resolve();
|
|
223
|
+
let pendingRequests = 0;
|
|
224
|
+
let timeoutWarned = false;
|
|
225
|
+
const warnTimeout = () => {
|
|
226
|
+
if (timeoutWarned)
|
|
227
|
+
return;
|
|
228
|
+
timeoutWarned = true;
|
|
229
|
+
logger.warn('Slack thread status request timed out');
|
|
230
|
+
};
|
|
231
|
+
const enqueueStatus = (text, retryClear = false) => {
|
|
232
|
+
pendingRequests++;
|
|
233
|
+
const request = statusChain.then(async () => {
|
|
234
|
+
try {
|
|
235
|
+
await requestThreadStatus(msg, text, warnTimeout);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (!retryClear)
|
|
239
|
+
throw error;
|
|
240
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
241
|
+
await requestThreadStatus(msg, text, warnTimeout);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
// Recover the chain so a failed write never blocks later writes or the clear.
|
|
245
|
+
statusChain = request.catch(() => { }).finally(() => { pendingRequests--; });
|
|
246
|
+
return request;
|
|
247
|
+
};
|
|
179
248
|
// Set initial status
|
|
180
249
|
logStatus(currentStatus, currentSource);
|
|
181
|
-
|
|
250
|
+
void enqueueStatus(currentStatus).catch(() => { });
|
|
182
251
|
const cancelHold = () => {
|
|
183
252
|
if (holdTimer !== undefined)
|
|
184
253
|
clearTimeout(holdTimer);
|
|
@@ -190,17 +259,19 @@ export function startTypingIndicator(msg, statusText = 'Thinking...') {
|
|
|
190
259
|
currentStatus = text;
|
|
191
260
|
currentSource = text ? source : 'default';
|
|
192
261
|
if (text && source === 'comment')
|
|
193
|
-
commentShownAt = Date.now();
|
|
262
|
+
commentShownAt = Date.now(); // Pre-existing enqueue-time hold, before successful display (#589 review P2-2).
|
|
194
263
|
if (changed) {
|
|
195
264
|
logStatus(text, source);
|
|
196
|
-
|
|
265
|
+
void enqueueStatus(text).catch(() => { });
|
|
197
266
|
}
|
|
198
267
|
};
|
|
199
268
|
// Keepalive every 3 seconds (Slack clears after ~2 min timeout)
|
|
200
269
|
let failures = 0;
|
|
201
270
|
const interval = setInterval(async () => {
|
|
271
|
+
if (stopped || pendingRequests > 0)
|
|
272
|
+
return;
|
|
202
273
|
try {
|
|
203
|
-
await
|
|
274
|
+
await enqueueStatus(currentStatus);
|
|
204
275
|
failures = 0;
|
|
205
276
|
}
|
|
206
277
|
catch {
|
|
@@ -211,6 +282,7 @@ export function startTypingIndicator(msg, statusText = 'Thinking...') {
|
|
|
211
282
|
}
|
|
212
283
|
}, 3000);
|
|
213
284
|
return {
|
|
285
|
+
__flushForTest: () => statusChain,
|
|
214
286
|
updateStatus: (newStatus, source = 'comment') => {
|
|
215
287
|
if (stopped)
|
|
216
288
|
return;
|
|
@@ -244,7 +316,7 @@ export function startTypingIndicator(msg, statusText = 'Thinking...') {
|
|
|
244
316
|
clearInterval(interval);
|
|
245
317
|
if (currentStatus)
|
|
246
318
|
logStatus('', 'default');
|
|
247
|
-
|
|
319
|
+
void enqueueStatus('', true).catch(() => { });
|
|
248
320
|
},
|
|
249
321
|
};
|
|
250
322
|
}
|
package/dist/thread-routing.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TeamVibeQueueMessage } from './types.js';
|
|
1
2
|
export interface ThreadRouting {
|
|
2
3
|
workspaceId: string;
|
|
3
4
|
channelId: string;
|
|
@@ -6,6 +7,12 @@ export interface ThreadRouting {
|
|
|
6
7
|
directory: string;
|
|
7
8
|
threadId?: string;
|
|
8
9
|
processedDropIds?: string[];
|
|
10
|
+
/** Original queue source; recovery must preserve its worktree isolation policy. */
|
|
11
|
+
source?: TeamVibeQueueMessage['source'];
|
|
12
|
+
/** Captured only from trusted queue input, never inbox drops or context files. */
|
|
13
|
+
responseContext?: {
|
|
14
|
+
slack: NonNullable<TeamVibeQueueMessage['response_context']['slack']>;
|
|
15
|
+
};
|
|
9
16
|
}
|
|
10
17
|
/** Poller state survives inbox pruning and process restarts; contains no credentials. */
|
|
11
18
|
export declare function saveThreadRouting(threadId: string, routing: ThreadRouting): void;
|
package/dist/thread-routing.js
CHANGED
|
@@ -25,6 +25,13 @@ export function readThreadRouting(threadId) {
|
|
|
25
25
|
return undefined;
|
|
26
26
|
if (routing.processedDropIds !== undefined && (!Array.isArray(routing.processedDropIds) || !routing.processedDropIds.every((id) => typeof id === 'string')))
|
|
27
27
|
return undefined;
|
|
28
|
+
if (routing.source !== undefined && !['slack', 'email', 'cron', 'maintenance', 'webhook'].includes(routing.source))
|
|
29
|
+
return undefined;
|
|
30
|
+
if (routing.responseContext !== undefined) {
|
|
31
|
+
const slack = routing.responseContext?.slack;
|
|
32
|
+
if (!slack || typeof slack.channel !== 'string' || !slack.channel || typeof slack.thread_ts !== 'string' || typeof slack.message_ts !== 'string')
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
28
35
|
return routing;
|
|
29
36
|
}
|
|
30
37
|
catch {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamvibe/poller",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.9-rc.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@teamvibe/poller",
|
|
9
|
-
"version": "0.8.
|
|
9
|
+
"version": "0.8.9-rc.2",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@aws-sdk/client-dynamodb": "3.1108.0",
|
|
12
12
|
"@aws-sdk/client-sqs": "3.1108.0",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"@slack/web-api": "^7.0.0",
|
|
16
16
|
"croner": "^10.0.1",
|
|
17
17
|
"dotenv": "^17.2.3",
|
|
18
|
+
"yaml": "2.8.1",
|
|
18
19
|
"zod": "^3.22.0"
|
|
19
20
|
},
|
|
20
21
|
"bin": {
|
|
@@ -1594,6 +1595,18 @@
|
|
|
1594
1595
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
|
1595
1596
|
"license": "MIT"
|
|
1596
1597
|
},
|
|
1598
|
+
"node_modules/yaml": {
|
|
1599
|
+
"version": "2.8.1",
|
|
1600
|
+
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
|
1601
|
+
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
|
1602
|
+
"license": "ISC",
|
|
1603
|
+
"bin": {
|
|
1604
|
+
"yaml": "bin.mjs"
|
|
1605
|
+
},
|
|
1606
|
+
"engines": {
|
|
1607
|
+
"node": ">= 14.6"
|
|
1608
|
+
}
|
|
1609
|
+
},
|
|
1597
1610
|
"node_modules/zod": {
|
|
1598
1611
|
"version": "3.25.76",
|
|
1599
1612
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamvibe/poller",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.9-rc.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"@slack/web-api": "^7.0.0",
|
|
35
35
|
"croner": "^10.0.1",
|
|
36
36
|
"dotenv": "^17.2.3",
|
|
37
|
+
"yaml": "2.8.1",
|
|
37
38
|
"zod": "^3.22.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|