instar 0.24.34 → 0.25.1
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +215 -23
- package/dist/commands/server.js.map +1 -1
- package/dist/core/SessionManager.d.ts +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +35 -1
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +182 -6
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/monitoring/PresenceProxy.d.ts +5 -0
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.js +73 -0
- package/dist/monitoring/PresenceProxy.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +0 -4
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/0.25.0.md +34 -0
- package/upgrades/0.25.1.md +24 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0PH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA0PH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsnCD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAu2GtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
|
package/dist/commands/server.js
CHANGED
|
@@ -1019,6 +1019,24 @@ async function ensureSlackAttentionChannel(slack, state) {
|
|
|
1019
1019
|
console.error(` Failed to create Slack Attention channel: ${err}`);
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Ensure a Slack updates channel exists — for version updates and feature announcements.
|
|
1024
|
+
*/
|
|
1025
|
+
async function ensureSlackUpdatesChannel(slack, state) {
|
|
1026
|
+
const existingChannelId = state.get('slack-updates-channel');
|
|
1027
|
+
if (existingChannelId)
|
|
1028
|
+
return;
|
|
1029
|
+
try {
|
|
1030
|
+
const agentName = slack.config?.workspaceName?.replace(/-agent$/, '') || 'agent';
|
|
1031
|
+
const channelId = await slack.createChannel(`${agentName}-sys-updates`);
|
|
1032
|
+
state.set('slack-updates-channel', channelId);
|
|
1033
|
+
await slack.sendToChannel(channelId, `Updates channel active. Version updates, new features, and system announcements will appear here.`);
|
|
1034
|
+
console.log(` Created Slack Updates channel: ${channelId}`);
|
|
1035
|
+
}
|
|
1036
|
+
catch (err) {
|
|
1037
|
+
console.error(` Failed to create Slack Updates channel: ${err}`);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1022
1040
|
/**
|
|
1023
1041
|
* Ensure the Agent Updates topic exists — for version updates, feature announcements, etc.
|
|
1024
1042
|
* Separates informational updates from critical attention items.
|
|
@@ -1259,8 +1277,8 @@ export async function startServer(options) {
|
|
|
1259
1277
|
topicId: resolvedTopicId,
|
|
1260
1278
|
}).catch(() => { });
|
|
1261
1279
|
}
|
|
1262
|
-
// Slack: send
|
|
1263
|
-
if (
|
|
1280
|
+
// Slack: send all notification tiers to attention channel
|
|
1281
|
+
if (_slackAdapter) {
|
|
1264
1282
|
const slackAttentionChannel = _notifyState?.get('slack-attention-channel');
|
|
1265
1283
|
if (slackAttentionChannel) {
|
|
1266
1284
|
_slackAdapter.sendToChannel(slackAttentionChannel, message).catch(() => { });
|
|
@@ -2218,8 +2236,46 @@ export async function startServer(options) {
|
|
|
2218
2236
|
const channelId = message.channel.identifier;
|
|
2219
2237
|
const isDM = message.metadata?.isDM;
|
|
2220
2238
|
const senderName = message.metadata?.senderName || 'User';
|
|
2221
|
-
//
|
|
2222
|
-
|
|
2239
|
+
// Sentinel intercept — classify message for emergency stop/pause
|
|
2240
|
+
if (sentinel) {
|
|
2241
|
+
try {
|
|
2242
|
+
const classification = await sentinel.classify(message.content);
|
|
2243
|
+
if (classification.category === 'emergency-stop') {
|
|
2244
|
+
// Kill all sessions
|
|
2245
|
+
const sessions = sessionManager.listRunningSessions();
|
|
2246
|
+
for (const s of sessions) {
|
|
2247
|
+
try {
|
|
2248
|
+
sessionManager.killSession(s.id);
|
|
2249
|
+
}
|
|
2250
|
+
catch { /* ok */ }
|
|
2251
|
+
}
|
|
2252
|
+
slackAdapter.sendToChannel(channelId, '🛑 Emergency stop — all sessions killed.').catch(() => { });
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
else if (classification.category === 'pause') {
|
|
2256
|
+
const existingSession = slackAdapter.getSessionForChannel(channelId);
|
|
2257
|
+
if (existingSession) {
|
|
2258
|
+
sessionManager.sendKey(existingSession, 'Escape');
|
|
2259
|
+
slackAdapter.sendToChannel(channelId, '⏸️ Session paused.').catch(() => { });
|
|
2260
|
+
}
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
catch { /* fail-open — if Sentinel errors, process message normally */ }
|
|
2265
|
+
}
|
|
2266
|
+
// Build injection tag with sender info (matches Telegram's buildInjectionTag pattern)
|
|
2267
|
+
const slackUserId = message.metadata?.slackUserId;
|
|
2268
|
+
// Sanitize sender name at injection boundary (prevents injection attacks)
|
|
2269
|
+
const safeSenderName = senderName
|
|
2270
|
+
? senderName.replace(/[\x00-\x1f\x7f]/g, '').replace(/\s+/g, ' ').replace(/["\[\]]/g, '').trim().slice(0, 64) || 'User'
|
|
2271
|
+
: undefined;
|
|
2272
|
+
let prefix = `[slack:${channelId}]`;
|
|
2273
|
+
if (safeSenderName && slackUserId) {
|
|
2274
|
+
prefix = `[slack:${channelId} from ${safeSenderName} (uid:${slackUserId})]`;
|
|
2275
|
+
}
|
|
2276
|
+
else if (safeSenderName) {
|
|
2277
|
+
prefix = `[slack:${channelId} from ${safeSenderName}]`;
|
|
2278
|
+
}
|
|
2223
2279
|
// Write context file for the session
|
|
2224
2280
|
const tmpDir = '/tmp/instar-slack';
|
|
2225
2281
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -2267,7 +2323,18 @@ export async function startServer(options) {
|
|
|
2267
2323
|
}
|
|
2268
2324
|
return `[User sent a file — it has been saved to ${docPath}. Read the file to view its contents]`;
|
|
2269
2325
|
});
|
|
2270
|
-
const
|
|
2326
|
+
const FILE_THRESHOLD = 500;
|
|
2327
|
+
const fullMessage = `${prefix} ${transformedContent} (IMPORTANT: Read ${ctxPath} for thread history and Slack relay instructions — you MUST relay your response back.)`;
|
|
2328
|
+
// Long messages: write to temp file and inject reference (matches Telegram pattern)
|
|
2329
|
+
let bootstrapMessage;
|
|
2330
|
+
if (fullMessage.length > FILE_THRESHOLD) {
|
|
2331
|
+
const msgFilePath = path.join(tmpDir, `msg-${channelId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}.txt`);
|
|
2332
|
+
fs.writeFileSync(msgFilePath, fullMessage);
|
|
2333
|
+
bootstrapMessage = `${prefix} [Long message saved to ${msgFilePath} — read it to see the full message]`;
|
|
2334
|
+
}
|
|
2335
|
+
else {
|
|
2336
|
+
bootstrapMessage = fullMessage;
|
|
2337
|
+
}
|
|
2271
2338
|
// Check for existing session bound to this channel
|
|
2272
2339
|
const existingSession = slackAdapter.getSessionForChannel(channelId);
|
|
2273
2340
|
if (existingSession) {
|
|
@@ -2292,13 +2359,13 @@ export async function startServer(options) {
|
|
|
2292
2359
|
// Fall through to respawn below — registerChannelSession will be called with new session name
|
|
2293
2360
|
}
|
|
2294
2361
|
else {
|
|
2295
|
-
// Session is ready — inject
|
|
2362
|
+
// Session is ready — inject via SessionManager (handles idle timer reset + bracketed paste)
|
|
2296
2363
|
try {
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2364
|
+
sessionManager.injectMessage(existingSession, bootstrapMessage);
|
|
2365
|
+
// Track for stall detection
|
|
2366
|
+
slackAdapter.trackMessageInjection(channelId, existingSession, message.content);
|
|
2367
|
+
// Delivery confirmation
|
|
2368
|
+
slackAdapter.sendToChannel(channelId, '✓ Delivered').catch(() => { });
|
|
2302
2369
|
}
|
|
2303
2370
|
catch (injectErr) {
|
|
2304
2371
|
console.error(`[slack→session] Injection failed: ${injectErr instanceof Error ? injectErr.message : injectErr}`);
|
|
@@ -2320,6 +2387,7 @@ export async function startServer(options) {
|
|
|
2320
2387
|
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, { resumeSessionId });
|
|
2321
2388
|
if (newSessionName) {
|
|
2322
2389
|
slackAdapter.registerChannelSession(channelId, newSessionName);
|
|
2390
|
+
slackAdapter.trackMessageInjection(channelId, newSessionName, message.content);
|
|
2323
2391
|
console.log(`[slack→session] ${resumeSessionId ? 'Resumed' : 'Spawned'} "${newSessionName}" for channel ${channelId}`);
|
|
2324
2392
|
}
|
|
2325
2393
|
}
|
|
@@ -2330,17 +2398,100 @@ export async function startServer(options) {
|
|
|
2330
2398
|
await slackAdapter.start();
|
|
2331
2399
|
_slackAdapter = slackAdapter;
|
|
2332
2400
|
console.log(pc.green(` Slack connected (workspace: ${slackConfig.config.workspaceName || 'unknown'})`));
|
|
2333
|
-
// Ensure Slack
|
|
2401
|
+
// Ensure Slack system channels exist
|
|
2334
2402
|
ensureSlackAttentionChannel(slackAdapter, state).catch(err => {
|
|
2335
2403
|
console.error(`[server] Failed to ensure Slack Attention channel: ${err}`);
|
|
2336
2404
|
});
|
|
2405
|
+
ensureSlackUpdatesChannel(slackAdapter, state).catch(err => {
|
|
2406
|
+
console.error(`[server] Failed to ensure Slack Updates channel: ${err}`);
|
|
2407
|
+
});
|
|
2337
2408
|
// Wire stall detection — route stall alerts to Slack attention channel
|
|
2338
|
-
slackAdapter.onStallDetected = (channelId, sessionName, messageText) => {
|
|
2409
|
+
slackAdapter.onStallDetected = (channelId, sessionName, messageText, injectedAt) => {
|
|
2339
2410
|
const slackAttentionChannel = state.get('slack-attention-channel');
|
|
2340
2411
|
if (slackAttentionChannel) {
|
|
2341
|
-
slackAdapter.sendToChannel(slackAttentionChannel,
|
|
2412
|
+
slackAdapter.sendToChannel(slackAttentionChannel, `⚠️ Stall detected in session "${sessionName}" (channel ${channelId}). Message may not have been answered: "${messageText.slice(0, 100)}"`).catch(() => { });
|
|
2413
|
+
}
|
|
2414
|
+
// Also notify in the originating channel
|
|
2415
|
+
slackAdapter.sendToChannel(channelId, `⚠️ The session appears to have stalled. Use \`!restart\` to restart or \`!interrupt\` to nudge it.`).catch(() => { });
|
|
2416
|
+
};
|
|
2417
|
+
// Wire voice transcription (reuses Telegram's provider resolution: Groq → OpenAI)
|
|
2418
|
+
slackAdapter.transcribeVoice = async (filePath) => {
|
|
2419
|
+
const providers = {
|
|
2420
|
+
groq: { envKey: 'GROQ_API_KEY', baseUrl: 'https://api.groq.com/openai/v1', model: 'whisper-large-v3' },
|
|
2421
|
+
openai: { envKey: 'OPENAI_API_KEY', baseUrl: 'https://api.openai.com/v1', model: 'whisper-1' },
|
|
2422
|
+
};
|
|
2423
|
+
let provider = null;
|
|
2424
|
+
const explicit = slackConfig.config.audioTranscriptionProvider;
|
|
2425
|
+
if (explicit && providers[explicit.toLowerCase()]) {
|
|
2426
|
+
const p = providers[explicit.toLowerCase()];
|
|
2427
|
+
const apiKey = process.env[p.envKey];
|
|
2428
|
+
if (apiKey)
|
|
2429
|
+
provider = { apiKey, baseUrl: p.baseUrl, model: p.model };
|
|
2430
|
+
}
|
|
2431
|
+
if (!provider) {
|
|
2432
|
+
for (const [, p] of Object.entries(providers)) {
|
|
2433
|
+
const apiKey = process.env[p.envKey];
|
|
2434
|
+
if (apiKey) {
|
|
2435
|
+
provider = { apiKey, baseUrl: p.baseUrl, model: p.model };
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
if (!provider)
|
|
2441
|
+
throw new Error('No voice transcription provider. Set GROQ_API_KEY or OPENAI_API_KEY.');
|
|
2442
|
+
const formData = new FormData();
|
|
2443
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
2444
|
+
const blob = new Blob([fileBuffer], { type: 'audio/ogg' });
|
|
2445
|
+
formData.append('file', blob, path.basename(filePath));
|
|
2446
|
+
formData.append('model', provider.model);
|
|
2447
|
+
const controller = new AbortController();
|
|
2448
|
+
const timer = setTimeout(() => controller.abort(), 60_000);
|
|
2449
|
+
try {
|
|
2450
|
+
const response = await fetch(`${provider.baseUrl}/audio/transcriptions`, {
|
|
2451
|
+
method: 'POST',
|
|
2452
|
+
headers: { Authorization: `Bearer ${provider.apiKey}` },
|
|
2453
|
+
body: formData,
|
|
2454
|
+
signal: controller.signal,
|
|
2455
|
+
});
|
|
2456
|
+
if (!response.ok)
|
|
2457
|
+
throw new Error(`Transcription API error (${response.status}): ${await response.text()}`);
|
|
2458
|
+
const data = await response.json();
|
|
2459
|
+
return data.text;
|
|
2342
2460
|
}
|
|
2461
|
+
finally {
|
|
2462
|
+
clearTimeout(timer);
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
// Start stall detection timer
|
|
2466
|
+
const stallTimeout = slackConfig.config.stallTimeoutMinutes ?? 5;
|
|
2467
|
+
if (stallTimeout > 0) {
|
|
2468
|
+
slackAdapter.startStallDetection(stallTimeout * 60 * 1000);
|
|
2469
|
+
}
|
|
2470
|
+
// Wire session management callbacks for slash commands
|
|
2471
|
+
slackAdapter.onInterruptSession = async (sessionName) => {
|
|
2472
|
+
return sessionManager.sendKey(sessionName, 'Escape');
|
|
2473
|
+
};
|
|
2474
|
+
slackAdapter.onRestartSession = async (sessionName, channelId) => {
|
|
2475
|
+
try {
|
|
2476
|
+
const stuckSession = sessionManager.listRunningSessions().find(s => s.tmuxSession === sessionName);
|
|
2477
|
+
if (stuckSession) {
|
|
2478
|
+
sessionManager.killSession(stuckSession.id);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
catch { /* ok if already dead */ }
|
|
2482
|
+
};
|
|
2483
|
+
slackAdapter.onListSessions = () => {
|
|
2484
|
+
return sessionManager.listRunningSessions().map(s => ({
|
|
2485
|
+
name: s.name,
|
|
2486
|
+
tmuxSession: s.tmuxSession,
|
|
2487
|
+
status: s.status,
|
|
2488
|
+
alive: sessionManager.isSessionAlive(s.tmuxSession),
|
|
2489
|
+
}));
|
|
2343
2490
|
};
|
|
2491
|
+
slackAdapter.onIsSessionAlive = (tmuxSession) => {
|
|
2492
|
+
return sessionManager.isSessionAlive(tmuxSession);
|
|
2493
|
+
};
|
|
2494
|
+
// Standby commands will be wired after PresenceProxy is initialized (below)
|
|
2344
2495
|
}
|
|
2345
2496
|
catch (err) {
|
|
2346
2497
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -2453,14 +2604,25 @@ export async function startServer(options) {
|
|
|
2453
2604
|
if (_topicResumeMap && telegram) {
|
|
2454
2605
|
sessionManager.on('beforeSessionKill', (session) => {
|
|
2455
2606
|
try {
|
|
2607
|
+
// Save Telegram topic resume UUID
|
|
2456
2608
|
const topicId = telegram.getTopicForSession(session.tmuxSession);
|
|
2457
|
-
if (
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2609
|
+
if (topicId) {
|
|
2610
|
+
const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession, session.claudeSessionId ?? undefined);
|
|
2611
|
+
if (uuid) {
|
|
2612
|
+
_topicResumeMap.save(topicId, uuid, session.tmuxSession);
|
|
2613
|
+
console.log(`[beforeSessionKill] Saved resume UUID ${uuid} for topic ${topicId} (session: ${session.name}, source: ${session.claudeSessionId ? 'hook' : 'mtime'})`);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
// Save Slack channel resume UUID
|
|
2617
|
+
if (_slackAdapter) {
|
|
2618
|
+
const channelId = _slackAdapter.getChannelForSession(session.tmuxSession);
|
|
2619
|
+
if (channelId) {
|
|
2620
|
+
const uuid = _topicResumeMap.findUuidForSession(session.tmuxSession, session.claudeSessionId ?? undefined);
|
|
2621
|
+
if (uuid) {
|
|
2622
|
+
_slackAdapter.saveChannelResume(channelId, uuid, session.tmuxSession);
|
|
2623
|
+
console.log(`[beforeSessionKill] Saved Slack resume UUID ${uuid} for channel ${channelId} (session: ${session.name})`);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2464
2626
|
}
|
|
2465
2627
|
}
|
|
2466
2628
|
catch (err) {
|
|
@@ -3095,6 +3257,14 @@ export async function startServer(options) {
|
|
|
3095
3257
|
slackProxyChannelMap.set(syntheticId, channelId);
|
|
3096
3258
|
return syntheticId;
|
|
3097
3259
|
}
|
|
3260
|
+
// Pre-populate the Slack proxy channel map from existing channel registry
|
|
3261
|
+
// so PresenceProxy state recovery can resolve synthetic IDs on restart
|
|
3262
|
+
if (_slackAdapter) {
|
|
3263
|
+
const registry = _slackAdapter.getChannelRegistry();
|
|
3264
|
+
for (const channelId of Object.keys(registry)) {
|
|
3265
|
+
slackChannelToSyntheticId(channelId);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3098
3268
|
let presenceProxy;
|
|
3099
3269
|
if (sharedIntelligence && telegram) {
|
|
3100
3270
|
try {
|
|
@@ -3224,18 +3394,35 @@ export async function startServer(options) {
|
|
|
3224
3394
|
});
|
|
3225
3395
|
};
|
|
3226
3396
|
presenceProxy.start();
|
|
3227
|
-
// Wire Slack's onMessageLogged into PresenceProxy (if Slack is active)
|
|
3397
|
+
// Wire Slack's onMessageLogged into PresenceProxy + TopicMemory (if Slack is active)
|
|
3228
3398
|
if (_slackAdapter) {
|
|
3229
3399
|
const existingSlackCallback = _slackAdapter.onMessageLogged;
|
|
3230
3400
|
_slackAdapter.onMessageLogged = (entry) => {
|
|
3231
3401
|
if (existingSlackCallback) {
|
|
3232
3402
|
existingSlackCallback(entry);
|
|
3233
3403
|
}
|
|
3404
|
+
// TopicMemory dual-write (matches Telegram's insertMessage pattern)
|
|
3405
|
+
// TopicMemory uses numeric topicId; for Slack we use the synthetic hash
|
|
3406
|
+
if (entry.channelId && topicMemory) {
|
|
3407
|
+
const synId = slackChannelToSyntheticId(String(entry.channelId));
|
|
3408
|
+
topicMemory.insertMessage({
|
|
3409
|
+
messageId: typeof entry.messageId === 'number' ? entry.messageId : 0,
|
|
3410
|
+
topicId: synId,
|
|
3411
|
+
text: entry.text,
|
|
3412
|
+
fromUser: entry.fromUser,
|
|
3413
|
+
timestamp: entry.timestamp,
|
|
3414
|
+
sessionName: entry.sessionName,
|
|
3415
|
+
senderName: entry.senderName,
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
// Clear stall tracking when agent responds in this channel
|
|
3419
|
+
if (!entry.fromUser && entry.channelId) {
|
|
3420
|
+
_slackAdapter.clearStallTracking(String(entry.channelId));
|
|
3421
|
+
}
|
|
3234
3422
|
// Convert Slack channelId to synthetic numeric ID for PresenceProxy
|
|
3235
3423
|
if (!entry.channelId)
|
|
3236
3424
|
return;
|
|
3237
3425
|
const syntheticId = slackChannelToSyntheticId(String(entry.channelId));
|
|
3238
|
-
console.log(`[slack-proxy] onMessageLogged: channel=${entry.channelId} syntheticId=${syntheticId} fromUser=${entry.fromUser} text="${(entry.text || '').slice(0, 40)}"`);
|
|
3239
3426
|
presenceProxy.onMessageLogged({
|
|
3240
3427
|
messageId: typeof entry.messageId === 'number' ? entry.messageId : parseInt(String(entry.messageId), 10) || 0,
|
|
3241
3428
|
channelId: syntheticId.toString(),
|
|
@@ -3248,6 +3435,11 @@ export async function startServer(options) {
|
|
|
3248
3435
|
});
|
|
3249
3436
|
};
|
|
3250
3437
|
console.log(pc.green(' Presence Proxy wired to Slack'));
|
|
3438
|
+
// Wire standby commands for Slack (unstick, quiet, resume, restart)
|
|
3439
|
+
_slackAdapter.onStandbyCommand = async (channelId, command, userId) => {
|
|
3440
|
+
const syntheticId = slackChannelToSyntheticId(channelId);
|
|
3441
|
+
return presenceProxy.handleCommand(syntheticId, command, parseInt(userId, 10) || 0);
|
|
3442
|
+
};
|
|
3251
3443
|
}
|
|
3252
3444
|
console.log(pc.green(' Presence Proxy enabled (🔭 [Standby])'));
|
|
3253
3445
|
}
|