@teamvibe/poller 0.8.2-rc.1 → 0.8.2-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/dist/claude-spawner.js +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/slack-client.js +81 -10
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/claude-spawner.js
CHANGED
|
@@ -1137,7 +1137,7 @@ async function runClaudeCode(msg, sessionLog, cwd, brainPath, sessionId, isFirst
|
|
|
1137
1137
|
});
|
|
1138
1138
|
// Surface a visible status so the user isn't left with a stale/generic
|
|
1139
1139
|
// indicator during long silent stretches (e.g. context compaction).
|
|
1140
|
-
typingIndicator?.updateStatus('Still working on a long response...');
|
|
1140
|
+
typingIndicator?.updateStatus('Still working on a long response...', 'default');
|
|
1141
1141
|
idleWarned = true;
|
|
1142
1142
|
}
|
|
1143
1143
|
}, 30000); // Check every 30 seconds
|
package/dist/config.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
18
18
|
IDLE_WATCHDOG_COMPACTION_GRACE_MS: z.ZodDefault<z.ZodNumber>;
|
|
19
19
|
NETWORK_ERROR_KILL_MS: z.ZodDefault<z.ZodNumber>;
|
|
20
20
|
MAX_CONSECUTIVE_NET_FAILURES: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
TYPING_COMMENT_MIN_HOLD_MS: z.ZodDefault<z.ZodNumber>;
|
|
21
22
|
TEAMVIBE_DATA_DIR: z.ZodDefault<z.ZodString>;
|
|
22
23
|
BRAINS_PATH: z.ZodDefault<z.ZodString>;
|
|
23
24
|
DEFAULT_BRAIN_PATH: z.ZodDefault<z.ZodString>;
|
|
@@ -60,6 +61,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
60
61
|
IDLE_WATCHDOG_COMPACTION_GRACE_MS: number;
|
|
61
62
|
NETWORK_ERROR_KILL_MS: number;
|
|
62
63
|
MAX_CONSECUTIVE_NET_FAILURES: number;
|
|
64
|
+
TYPING_COMMENT_MIN_HOLD_MS: number;
|
|
63
65
|
TEAMVIBE_DATA_DIR: string;
|
|
64
66
|
BRAINS_PATH: string;
|
|
65
67
|
DEFAULT_BRAIN_PATH: string;
|
|
@@ -108,6 +110,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
108
110
|
IDLE_WATCHDOG_COMPACTION_GRACE_MS?: number | undefined;
|
|
109
111
|
NETWORK_ERROR_KILL_MS?: number | undefined;
|
|
110
112
|
MAX_CONSECUTIVE_NET_FAILURES?: number | undefined;
|
|
113
|
+
TYPING_COMMENT_MIN_HOLD_MS?: number | undefined;
|
|
111
114
|
TEAMVIBE_DATA_DIR?: string | undefined;
|
|
112
115
|
BRAINS_PATH?: string | undefined;
|
|
113
116
|
DEFAULT_BRAIN_PATH?: string | undefined;
|
|
@@ -153,6 +156,7 @@ export declare const config: {
|
|
|
153
156
|
IDLE_WATCHDOG_COMPACTION_GRACE_MS: number;
|
|
154
157
|
NETWORK_ERROR_KILL_MS: number;
|
|
155
158
|
MAX_CONSECUTIVE_NET_FAILURES: number;
|
|
159
|
+
TYPING_COMMENT_MIN_HOLD_MS: number;
|
|
156
160
|
TEAMVIBE_DATA_DIR: string;
|
|
157
161
|
BRAINS_PATH: string;
|
|
158
162
|
DEFAULT_BRAIN_PATH: string;
|
package/dist/config.js
CHANGED
|
@@ -27,6 +27,7 @@ const configSchema = z.object({
|
|
|
27
27
|
IDLE_WATCHDOG_COMPACTION_GRACE_MS: z.coerce.number().default(300000), // 5 minutes — fixed grace from compact_boundary arrival, not a large speculative ceiling (compact_boundary only proves compaction STARTED, never that it finishes in bounded time)
|
|
28
28
|
NETWORK_ERROR_KILL_MS: z.coerce.number().default(120000), // Per-session: kill stuck session after 2 min of network errors
|
|
29
29
|
MAX_CONSECUTIVE_NET_FAILURES: z.coerce.number().default(5), // Poll loop: DNS flush at N, exit(1) at 2×N consecutive failures
|
|
30
|
+
TYPING_COMMENT_MIN_HOLD_MS: z.coerce.number().default(4500), // Minimum time to display agent narration before a canned status
|
|
30
31
|
// Paths
|
|
31
32
|
TEAMVIBE_DATA_DIR: z.string().default(DEFAULT_DATA_DIR),
|
|
32
33
|
BRAINS_PATH: z.string().default(''),
|
package/dist/slack-client.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { WebClient } from '@slack/web-api';
|
|
2
2
|
import { logger } from './logger.js';
|
|
3
3
|
import { emitError } from './event-queue.js';
|
|
4
|
+
import { config } from './config.js';
|
|
4
5
|
// Cache user info to avoid repeated API calls
|
|
5
6
|
const userCache = new Map();
|
|
6
7
|
// Per-token Slack client cache
|
|
7
8
|
const slackClients = new Map();
|
|
9
|
+
// Slack: "must be less than 51 characters [json-pointer:/loading_messages/0]"
|
|
10
|
+
const SLACK_LOADING_MESSAGE_MAX = 50;
|
|
8
11
|
function getSlackClient(botToken) {
|
|
9
12
|
let client = slackClients.get(botToken);
|
|
10
13
|
if (!client) {
|
|
@@ -95,25 +98,59 @@ export async function setThreadStatus(msg, status) {
|
|
|
95
98
|
const { channel, thread_ts } = msg.response_context.slack;
|
|
96
99
|
const slack = getSlackClient(msg.teamvibe.botToken);
|
|
97
100
|
const safeStatus = neutralizeSlackMarkup(status);
|
|
101
|
+
const payload = {
|
|
102
|
+
channel_id: channel,
|
|
103
|
+
thread_ts,
|
|
104
|
+
status: safeStatus,
|
|
105
|
+
};
|
|
98
106
|
try {
|
|
99
107
|
await slack.apiCall('assistant.threads.setStatus', {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
status: safeStatus,
|
|
103
|
-
...(safeStatus ? { loading_messages: [safeStatus] } : {}),
|
|
108
|
+
...payload,
|
|
109
|
+
...(safeStatus ? { loading_messages: [truncateLoadingMessage(safeStatus)] } : {}),
|
|
104
110
|
});
|
|
105
111
|
}
|
|
106
|
-
catch {
|
|
112
|
+
catch (error) {
|
|
113
|
+
const messages = error?.data?.response_metadata?.messages;
|
|
114
|
+
const details = [error?.message, ...(Array.isArray(messages) ? messages : [])].join(' ');
|
|
115
|
+
if (safeStatus && error?.data?.error === 'invalid_arguments' && details.includes('loading_messages')) {
|
|
116
|
+
// API errors may echo request fields; never log the error or status.
|
|
117
|
+
logger.warn('Slack rejected loading messages; retrying thread status');
|
|
118
|
+
try {
|
|
119
|
+
await slack.apiCall('assistant.threads.setStatus', payload);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
emitError('slack', 'Failed to set thread status', { severity: 'warn' });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
107
127
|
// API errors may echo request fields; status can contain private narration.
|
|
108
128
|
logger.warn('Failed to set thread status');
|
|
109
129
|
emitError('slack', `Failed to set thread status`, { severity: 'warn' });
|
|
110
130
|
}
|
|
111
131
|
}
|
|
132
|
+
function truncateLoadingMessage(text) {
|
|
133
|
+
// Treat escaped markup as whole tokens so truncation never leaves a partial entity.
|
|
134
|
+
const tokens = text.match(/&(?:lt|gt|amp);|[\s\S]/gu) ?? [];
|
|
135
|
+
if (text.length <= SLACK_LOADING_MESSAGE_MAX)
|
|
136
|
+
return text;
|
|
137
|
+
let result = '';
|
|
138
|
+
for (const token of tokens) {
|
|
139
|
+
if (result.length + token.length >= SLACK_LOADING_MESSAGE_MAX)
|
|
140
|
+
break;
|
|
141
|
+
result += token;
|
|
142
|
+
}
|
|
143
|
+
return result + '…';
|
|
144
|
+
}
|
|
112
145
|
function neutralizeSlackMarkup(text) {
|
|
113
146
|
return text.replace(/[<>&]/g, (character) => ({ '<': '<', '>': '>', '&': '&' })[character]);
|
|
114
147
|
}
|
|
115
148
|
export function startTypingIndicator(msg, statusText = 'Thinking...') {
|
|
116
149
|
let currentStatus = statusText;
|
|
150
|
+
let currentSource = statusText === 'Thinking...' ? 'default' : 'comment';
|
|
151
|
+
let commentShownAt = currentSource === 'comment' ? Date.now() : 0;
|
|
152
|
+
let pendingCanned = null;
|
|
153
|
+
let holdTimer;
|
|
117
154
|
let stopped = false;
|
|
118
155
|
const logStatus = (status, source) => {
|
|
119
156
|
if (!status)
|
|
@@ -122,8 +159,25 @@ export function startTypingIndicator(msg, statusText = 'Thinking...') {
|
|
|
122
159
|
logger.info(`[Typing] status -> ${source} (${status.length} chars)`);
|
|
123
160
|
};
|
|
124
161
|
// Set initial status
|
|
125
|
-
logStatus(currentStatus,
|
|
162
|
+
logStatus(currentStatus, currentSource);
|
|
126
163
|
setThreadStatus(msg, currentStatus);
|
|
164
|
+
const cancelHold = () => {
|
|
165
|
+
if (holdTimer !== undefined)
|
|
166
|
+
clearTimeout(holdTimer);
|
|
167
|
+
holdTimer = undefined;
|
|
168
|
+
pendingCanned = null;
|
|
169
|
+
};
|
|
170
|
+
const showStatus = (text, source) => {
|
|
171
|
+
const changed = text !== currentStatus;
|
|
172
|
+
currentStatus = text;
|
|
173
|
+
currentSource = text ? source : 'default';
|
|
174
|
+
if (text && source === 'comment')
|
|
175
|
+
commentShownAt = Date.now();
|
|
176
|
+
if (changed) {
|
|
177
|
+
logStatus(text, source);
|
|
178
|
+
setThreadStatus(msg, text);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
127
181
|
// Keepalive every 3 seconds (Slack clears after ~2 min timeout)
|
|
128
182
|
let failures = 0;
|
|
129
183
|
const interval = setInterval(async () => {
|
|
@@ -142,16 +196,33 @@ export function startTypingIndicator(msg, statusText = 'Thinking...') {
|
|
|
142
196
|
updateStatus: (newStatus, source = 'comment') => {
|
|
143
197
|
if (stopped)
|
|
144
198
|
return;
|
|
145
|
-
if (newStatus
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
if (!newStatus || source === 'comment') {
|
|
200
|
+
cancelHold();
|
|
201
|
+
showStatus(newStatus, source);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const remainingHold = config.TYPING_COMMENT_MIN_HOLD_MS - (Date.now() - commentShownAt);
|
|
205
|
+
if (currentSource === 'comment' && remainingHold > 0) {
|
|
206
|
+
pendingCanned = { text: newStatus, source };
|
|
207
|
+
if (holdTimer === undefined) {
|
|
208
|
+
holdTimer = setTimeout(() => {
|
|
209
|
+
holdTimer = undefined;
|
|
210
|
+
const pending = pendingCanned;
|
|
211
|
+
pendingCanned = null;
|
|
212
|
+
if (!stopped && pending)
|
|
213
|
+
showStatus(pending.text, pending.source);
|
|
214
|
+
}, remainingHold);
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
149
217
|
}
|
|
218
|
+
cancelHold();
|
|
219
|
+
showStatus(newStatus, source);
|
|
150
220
|
},
|
|
151
221
|
stop: () => {
|
|
152
222
|
if (stopped)
|
|
153
223
|
return;
|
|
154
224
|
stopped = true;
|
|
225
|
+
cancelHold();
|
|
155
226
|
clearInterval(interval);
|
|
156
227
|
if (currentStatus)
|
|
157
228
|
logStatus('', 'default');
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamvibe/poller",
|
|
3
|
-
"version": "0.8.2-rc.
|
|
3
|
+
"version": "0.8.2-rc.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@teamvibe/poller",
|
|
9
|
-
"version": "0.8.2-rc.
|
|
9
|
+
"version": "0.8.2-rc.2",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@aws-sdk/client-dynamodb": "3.1108.0",
|
|
12
12
|
"@aws-sdk/client-sqs": "3.1108.0",
|