gibi-bot 1.0.0 → 1.1.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/.context.json +47 -3
- package/.github/workflows/npm-publish.yml +33 -0
- package/.github/workflows/release.yml +45 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +2 -0
- package/.prettierignore +3 -0
- package/.prettierrc +7 -0
- package/CHANGELOG.md +45 -0
- package/DISTRIBUTION.md +9 -1
- package/GEMINI.md +28 -9
- package/README.md +55 -28
- package/commitlint.config.js +3 -0
- package/conductor/code_styleguides/general.md +6 -1
- package/conductor/code_styleguides/ts.md +42 -35
- package/conductor/product-guidelines.md +16 -12
- package/conductor/product.md +12 -7
- package/conductor/setup_state.json +1 -1
- package/conductor/tech-stack.md +4 -1
- package/conductor/tracks/slack_bot_20260107/metadata.json +1 -1
- package/conductor/tracks/slack_bot_20260107/plan.md +6 -1
- package/conductor/tracks/slack_bot_20260107/spec.md +9 -6
- package/conductor/tracks.md +2 -1
- package/conductor/workflow.md +74 -53
- package/dist/agents.js +7 -10
- package/dist/agents.test.js +17 -12
- package/dist/app.js +212 -135
- package/dist/config.js +5 -5
- package/dist/context.js +4 -3
- package/dist/context.test.js +2 -4
- package/eslint.config.mjs +17 -0
- package/jest.config.js +4 -3
- package/nodemon.json +5 -9
- package/package.json +34 -4
- package/release.config.js +22 -0
- package/src/agents.test.ts +62 -57
- package/src/agents.ts +94 -82
- package/src/app.d.ts +1 -1
- package/src/app.ts +298 -178
- package/src/config.ts +48 -48
- package/src/context.test.ts +54 -56
- package/src/context.ts +123 -114
- package/test_gemini.js +13 -9
- package/test_gemini_approval.js +13 -9
- package/test_gemini_write.js +19 -9
- package/tests/context.test.ts +145 -0
- package/tsconfig.json +1 -1
- package/src/app.js +0 -55
- package/src/app.js.map +0 -1
package/dist/app.js
CHANGED
|
@@ -57,7 +57,7 @@ const start = async () => {
|
|
|
57
57
|
token: process.env.SLACK_BOT_TOKEN || '',
|
|
58
58
|
signingSecret: process.env.SLACK_SIGNING_SECRET || '',
|
|
59
59
|
socketMode: true,
|
|
60
|
-
appToken: process.env.SLACK_APP_TOKEN || ''
|
|
60
|
+
appToken: process.env.SLACK_APP_TOKEN || '',
|
|
61
61
|
});
|
|
62
62
|
// Fetch Bot User ID on startup
|
|
63
63
|
(async () => {
|
|
@@ -101,23 +101,23 @@ const start = async () => {
|
|
|
101
101
|
type: 'section',
|
|
102
102
|
text: {
|
|
103
103
|
type: 'mrkdwn',
|
|
104
|
-
text: `_Thinking..._ (using \`${agentName}\`)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
104
|
+
text: `_Thinking..._ (using \`${agentName}\`)`,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
107
|
];
|
|
108
108
|
if (processingMsgTs) {
|
|
109
109
|
await client.chat.update({
|
|
110
110
|
channel: channelId,
|
|
111
111
|
ts: processingMsgTs,
|
|
112
112
|
text: 'Thinking...',
|
|
113
|
-
blocks: thinkingBlocks
|
|
113
|
+
blocks: thinkingBlocks,
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
else {
|
|
117
117
|
const processingMsg = await say({
|
|
118
118
|
blocks: thinkingBlocks,
|
|
119
119
|
text: 'Thinking...',
|
|
120
|
-
thread_ts: threadTs
|
|
120
|
+
thread_ts: threadTs,
|
|
121
121
|
});
|
|
122
122
|
processingMsgTs = processingMsg.ts;
|
|
123
123
|
}
|
|
@@ -128,7 +128,7 @@ const start = async () => {
|
|
|
128
128
|
}
|
|
129
129
|
const responseText = await agent.run(context.messages, {
|
|
130
130
|
model: context.data.model,
|
|
131
|
-
mode: context.data.mode
|
|
131
|
+
mode: context.data.mode,
|
|
132
132
|
});
|
|
133
133
|
if (responseText) {
|
|
134
134
|
// Append Assistant Response
|
|
@@ -139,17 +139,17 @@ const start = async () => {
|
|
|
139
139
|
type: 'section',
|
|
140
140
|
text: {
|
|
141
141
|
type: 'mrkdwn',
|
|
142
|
-
text: responseText
|
|
143
|
-
}
|
|
142
|
+
text: responseText,
|
|
143
|
+
},
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
type: 'context',
|
|
147
147
|
elements: [
|
|
148
148
|
{
|
|
149
149
|
type: 'mrkdwn',
|
|
150
|
-
text: `🤖 *Agent:* \`${agentName}\` | 🧠 *Model:* \`${context.data.model || 'default'}\` | 📂 *CWD:* \`${context.data.cwd || 'root'}
|
|
151
|
-
}
|
|
152
|
-
]
|
|
150
|
+
text: `🤖 *Agent:* \`${agentName}\` | 🧠 *Model:* \`${context.data.model || 'default'}\` | 📂 *CWD:* \`${context.data.cwd || 'root'}\``,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'actions',
|
|
@@ -157,15 +157,15 @@ const start = async () => {
|
|
|
157
157
|
{
|
|
158
158
|
type: 'button',
|
|
159
159
|
text: { type: 'plain_text', text: '🔄 Retry', emoji: true },
|
|
160
|
-
action_id: 'retry_turn'
|
|
160
|
+
action_id: 'retry_turn',
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
type: 'button',
|
|
164
164
|
text: { type: 'plain_text', text: '↩️ Revert', emoji: true },
|
|
165
|
-
action_id: 'revert_turn'
|
|
166
|
-
}
|
|
167
|
-
]
|
|
168
|
-
}
|
|
165
|
+
action_id: 'revert_turn',
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
169
|
];
|
|
170
170
|
// Update the processing message with the actual response
|
|
171
171
|
if (processingMsgTs) {
|
|
@@ -173,14 +173,14 @@ const start = async () => {
|
|
|
173
173
|
channel: channelId,
|
|
174
174
|
ts: processingMsgTs,
|
|
175
175
|
text: responseText,
|
|
176
|
-
blocks: responseBlocks
|
|
176
|
+
blocks: responseBlocks,
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
179
|
else {
|
|
180
180
|
await say({
|
|
181
181
|
text: responseText,
|
|
182
182
|
blocks: responseBlocks,
|
|
183
|
-
thread_ts: threadTs
|
|
183
|
+
thread_ts: threadTs,
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
186
|
}
|
|
@@ -189,7 +189,7 @@ const start = async () => {
|
|
|
189
189
|
await client.chat.update({
|
|
190
190
|
channel: channelId,
|
|
191
191
|
ts: processingMsgTs,
|
|
192
|
-
text: `Received empty response from ${agentName}
|
|
192
|
+
text: `Received empty response from ${agentName}.`,
|
|
193
193
|
});
|
|
194
194
|
}
|
|
195
195
|
}
|
|
@@ -200,13 +200,13 @@ const start = async () => {
|
|
|
200
200
|
await client.chat.update({
|
|
201
201
|
channel: channelId,
|
|
202
202
|
ts: processingMsgTs,
|
|
203
|
-
text: errorMessage
|
|
203
|
+
text: errorMessage,
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
206
|
else {
|
|
207
207
|
await say({
|
|
208
208
|
text: errorMessage,
|
|
209
|
-
thread_ts: threadTs
|
|
209
|
+
thread_ts: threadTs,
|
|
210
210
|
});
|
|
211
211
|
}
|
|
212
212
|
}
|
|
@@ -246,7 +246,7 @@ const start = async () => {
|
|
|
246
246
|
if (BOT_ID)
|
|
247
247
|
text = text.replace(new RegExp(`<@${BOT_ID}>`, 'g'), '').trim();
|
|
248
248
|
const replyThreadTs = msg.thread_ts || msg.ts;
|
|
249
|
-
const targetContextId = msg.thread_ts || msg.ts;
|
|
249
|
+
const targetContextId = msg.thread_ts || msg.ts || msg.channel;
|
|
250
250
|
await processAgentTurn(targetContextId, msg.channel, replyThreadTs, text, say, client);
|
|
251
251
|
});
|
|
252
252
|
// Helper for CD command (shared between Slash command and Message)
|
|
@@ -284,9 +284,9 @@ const start = async () => {
|
|
|
284
284
|
type: 'section',
|
|
285
285
|
text: {
|
|
286
286
|
type: 'mrkdwn',
|
|
287
|
-
text: `📂 *Directory:* \`${newPath}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
287
|
+
text: `📂 *Directory:* \`${newPath}\``,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
290
|
];
|
|
291
291
|
// Add "Up" button if not root
|
|
292
292
|
const parentPath = path.dirname(newPath);
|
|
@@ -299,28 +299,29 @@ const start = async () => {
|
|
|
299
299
|
text: {
|
|
300
300
|
type: 'plain_text',
|
|
301
301
|
text: '⬆️ Up one level',
|
|
302
|
-
emoji: true
|
|
302
|
+
emoji: true,
|
|
303
303
|
},
|
|
304
304
|
value: parentPath,
|
|
305
|
-
action_id: 'navigate_dir'
|
|
306
|
-
}
|
|
307
|
-
]
|
|
305
|
+
action_id: 'navigate_dir',
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
308
|
});
|
|
309
309
|
}
|
|
310
310
|
const dirButtons = [];
|
|
311
311
|
const fileNames = [];
|
|
312
|
-
files.forEach(file => {
|
|
312
|
+
files.forEach((file) => {
|
|
313
313
|
if (file.isDirectory()) {
|
|
314
|
-
if (dirButtons.length < 10) {
|
|
314
|
+
if (dirButtons.length < 10) {
|
|
315
|
+
// Limit buttons to avoid clutter
|
|
315
316
|
dirButtons.push({
|
|
316
317
|
type: 'button',
|
|
317
318
|
text: {
|
|
318
319
|
type: 'plain_text',
|
|
319
320
|
text: `📁 ${file.name}`,
|
|
320
|
-
emoji: true
|
|
321
|
+
emoji: true,
|
|
321
322
|
},
|
|
322
323
|
value: path.join(newPath, file.name),
|
|
323
|
-
action_id: 'navigate_dir'
|
|
324
|
+
action_id: 'navigate_dir',
|
|
324
325
|
});
|
|
325
326
|
}
|
|
326
327
|
else {
|
|
@@ -336,7 +337,7 @@ const start = async () => {
|
|
|
336
337
|
for (let i = 0; i < dirButtons.length; i += 5) {
|
|
337
338
|
blocks.push({
|
|
338
339
|
type: 'actions',
|
|
339
|
-
elements: dirButtons.slice(i, i + 5)
|
|
340
|
+
elements: dirButtons.slice(i, i + 5),
|
|
340
341
|
});
|
|
341
342
|
}
|
|
342
343
|
}
|
|
@@ -349,13 +350,13 @@ const start = async () => {
|
|
|
349
350
|
type: 'section',
|
|
350
351
|
text: {
|
|
351
352
|
type: 'mrkdwn',
|
|
352
|
-
text: fileListText
|
|
353
|
-
}
|
|
353
|
+
text: fileListText,
|
|
354
|
+
},
|
|
354
355
|
});
|
|
355
356
|
}
|
|
356
357
|
return {
|
|
357
358
|
text: `Directory: ${newPath}`,
|
|
358
|
-
blocks: blocks
|
|
359
|
+
blocks: blocks,
|
|
359
360
|
};
|
|
360
361
|
}
|
|
361
362
|
catch (error) {
|
|
@@ -367,28 +368,38 @@ const start = async () => {
|
|
|
367
368
|
await ack();
|
|
368
369
|
const action = body.actions[0];
|
|
369
370
|
const contextId = body.container.thread_ts || body.container.channel_id;
|
|
370
|
-
const output = await executeChangeDirectory(contextId, action.value);
|
|
371
|
+
const output = await executeChangeDirectory(contextId, action.value || '');
|
|
371
372
|
await respond({
|
|
372
373
|
...output,
|
|
373
|
-
replace_original: true
|
|
374
|
+
replace_original: true,
|
|
374
375
|
});
|
|
375
376
|
});
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
await ack();
|
|
379
|
-
const contextId = command.channel_id;
|
|
380
|
-
const goal = command.text.trim();
|
|
377
|
+
// Helper for Plan Mode
|
|
378
|
+
const startPlanMode = async (contextId, goal, say, client) => {
|
|
381
379
|
// Switch to Plan Mode
|
|
382
380
|
contextManager.setContextData(contextId, { mode: 'plan' });
|
|
383
381
|
// Notify initiation
|
|
384
382
|
await say({
|
|
385
|
-
text: `📝 *Entering Plan Mode*\nTarget: ${goal ||
|
|
386
|
-
channel:
|
|
383
|
+
text: `📝 *Entering Plan Mode*\nTarget: ${goal || 'No specific goal provided. What would you like to plan?'}`,
|
|
384
|
+
channel: contextId,
|
|
387
385
|
});
|
|
388
386
|
// If goal provided, process it as the first message
|
|
389
387
|
if (goal) {
|
|
390
|
-
await processAgentTurn(contextId,
|
|
388
|
+
await processAgentTurn(contextId, contextId, undefined, goal, say, client);
|
|
391
389
|
}
|
|
390
|
+
};
|
|
391
|
+
// Handle /plan command
|
|
392
|
+
app.command('/plan', async ({ command, ack, say, client }) => {
|
|
393
|
+
await ack();
|
|
394
|
+
await startPlanMode(command.channel_id, command.text.trim(), say, client);
|
|
395
|
+
});
|
|
396
|
+
// Handle "/plan" messages
|
|
397
|
+
app.message(/^\/plan\s*(.*)/, async ({ message, context, say, client }) => {
|
|
398
|
+
const msg = message;
|
|
399
|
+
const goal = context.matches[1].trim();
|
|
400
|
+
const contextId = msg.channel; // Plan mode usually tied to channel for now, or thread if used there.
|
|
401
|
+
// Using msg.channel generally for /plan.
|
|
402
|
+
await startPlanMode(contextId, goal, say, client);
|
|
392
403
|
});
|
|
393
404
|
// Handle /cd command
|
|
394
405
|
app.command('/cd', async ({ command, ack, respond }) => {
|
|
@@ -405,20 +416,30 @@ const start = async () => {
|
|
|
405
416
|
const contextId = msg.thread_ts || msg.channel;
|
|
406
417
|
const output = await executeChangeDirectory(contextId, targetPath);
|
|
407
418
|
await say({
|
|
408
|
-
text: output,
|
|
409
|
-
|
|
419
|
+
text: output.text,
|
|
420
|
+
blocks: output.blocks,
|
|
421
|
+
...(msg.thread_ts ? { thread_ts: msg.thread_ts } : {}), // Reply in thread if it was a thread message
|
|
410
422
|
});
|
|
411
423
|
});
|
|
412
|
-
// Handle /
|
|
413
|
-
app.
|
|
414
|
-
|
|
415
|
-
const
|
|
416
|
-
const contextId =
|
|
424
|
+
// Handle "/cd [path]" or "/cd" messages
|
|
425
|
+
app.message(/^\/cd\s*(.*)/, async ({ message, context, say }) => {
|
|
426
|
+
const msg = message;
|
|
427
|
+
const targetPath = context.matches[1].trim();
|
|
428
|
+
const contextId = msg.thread_ts || msg.channel;
|
|
429
|
+
const output = await executeChangeDirectory(contextId, targetPath);
|
|
430
|
+
await say({
|
|
431
|
+
text: output.text,
|
|
432
|
+
blocks: output.blocks,
|
|
433
|
+
...(msg.thread_ts ? { thread_ts: msg.thread_ts } : {}),
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
// Helper for Model Switch
|
|
437
|
+
const switchModel = async (contextId, input, triggerId, client) => {
|
|
417
438
|
const context = contextManager.getContext(contextId);
|
|
418
|
-
// Show modal if no input
|
|
419
|
-
if (!input) {
|
|
439
|
+
// Show modal if no input AND triggerId is available (Slash command)
|
|
440
|
+
if (!input && triggerId) {
|
|
420
441
|
await client.views.open({
|
|
421
|
-
trigger_id:
|
|
442
|
+
trigger_id: triggerId,
|
|
422
443
|
view: {
|
|
423
444
|
type: 'modal',
|
|
424
445
|
callback_id: 'model_select_modal',
|
|
@@ -432,43 +453,62 @@ const start = async () => {
|
|
|
432
453
|
element: {
|
|
433
454
|
type: 'static_select',
|
|
434
455
|
action_id: 'model_select',
|
|
435
|
-
initial_option: AVAILABLE_MODELS.find(m => m.id === (context.data.model || 'gemini-1.5-pro'))
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
456
|
+
initial_option: AVAILABLE_MODELS.find((m) => m.id === (context.data.model || 'gemini-1.5-pro'))
|
|
457
|
+
? {
|
|
458
|
+
text: { type: 'plain_text', text: context.data.model || 'gemini-1.5-pro' },
|
|
459
|
+
value: context.data.model || 'gemini-1.5-pro',
|
|
460
|
+
}
|
|
461
|
+
: undefined,
|
|
462
|
+
options: AVAILABLE_MODELS.map((m) => ({
|
|
440
463
|
text: { type: 'plain_text', text: m.id },
|
|
441
464
|
value: m.id,
|
|
442
|
-
description: { type: 'plain_text', text: m.desc.substring(0, 75) }
|
|
443
|
-
}))
|
|
444
|
-
}
|
|
445
|
-
}
|
|
465
|
+
description: { type: 'plain_text', text: m.desc.substring(0, 75) },
|
|
466
|
+
})),
|
|
467
|
+
},
|
|
468
|
+
},
|
|
446
469
|
],
|
|
447
|
-
submit: { type: 'plain_text', text: 'Update' }
|
|
448
|
-
}
|
|
470
|
+
submit: { type: 'plain_text', text: 'Update' },
|
|
471
|
+
},
|
|
449
472
|
});
|
|
450
|
-
return;
|
|
473
|
+
return null; // Handled by modal
|
|
451
474
|
}
|
|
452
|
-
if (input === 'list' || input === 'help') {
|
|
475
|
+
if (input === 'list' || input === 'help' || (!input && !triggerId)) {
|
|
453
476
|
const currentModel = context.data.model || 'default';
|
|
454
|
-
const modelList = AVAILABLE_MODELS.map(m => `• \`${m.id}\`: ${m.desc}`).join('\n');
|
|
455
|
-
|
|
477
|
+
const modelList = AVAILABLE_MODELS.map((m) => `• \`${m.id}\`: ${m.desc}`).join('\n');
|
|
478
|
+
return (`Current model: \`${currentModel}\`\n\n` +
|
|
456
479
|
`*Available Models:*\n${modelList}\n\n` +
|
|
457
480
|
`To switch, use \`/model <model_name>\``);
|
|
458
|
-
return;
|
|
459
481
|
}
|
|
460
482
|
contextManager.setContextData(contextId, { model: input });
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
// Handle /
|
|
464
|
-
app.command('/
|
|
483
|
+
return `Switched model to: \`${input}\``;
|
|
484
|
+
};
|
|
485
|
+
// Handle /model command
|
|
486
|
+
app.command('/model', async ({ command, ack, client, respond }) => {
|
|
465
487
|
await ack();
|
|
466
|
-
const
|
|
467
|
-
|
|
488
|
+
const result = await switchModel(command.channel_id, command.text.trim(), command.trigger_id, client);
|
|
489
|
+
if (result)
|
|
490
|
+
await respond(result);
|
|
491
|
+
});
|
|
492
|
+
// Handle "/model" messages
|
|
493
|
+
app.message(/^\/model\s*(.*)/, async ({ message, context, say, client }) => {
|
|
494
|
+
const msg = message;
|
|
495
|
+
const input = context.matches[1].trim();
|
|
496
|
+
const contextId = msg.thread_ts || msg.channel;
|
|
497
|
+
// Pass undefined for triggerId so it doesn't try to open a modal (which requires trigger_id)
|
|
498
|
+
const result = await switchModel(contextId, input, undefined, client);
|
|
499
|
+
if (result) {
|
|
500
|
+
await say({
|
|
501
|
+
text: result,
|
|
502
|
+
thread_ts: msg.thread_ts,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
// Helper for Agent Switch
|
|
507
|
+
const switchAgent = async (contextId, input, triggerId, client) => {
|
|
468
508
|
const context = contextManager.getContext(contextId);
|
|
469
|
-
if (!input) {
|
|
509
|
+
if (!input && triggerId) {
|
|
470
510
|
await client.views.open({
|
|
471
|
-
trigger_id:
|
|
511
|
+
trigger_id: triggerId,
|
|
472
512
|
view: {
|
|
473
513
|
type: 'modal',
|
|
474
514
|
callback_id: 'agent_select_modal',
|
|
@@ -484,35 +524,55 @@ const start = async () => {
|
|
|
484
524
|
action_id: 'agent_select',
|
|
485
525
|
initial_option: {
|
|
486
526
|
text: { type: 'plain_text', text: context.data.agent || 'gemini' },
|
|
487
|
-
value: context.data.agent || 'gemini'
|
|
527
|
+
value: context.data.agent || 'gemini',
|
|
488
528
|
},
|
|
489
|
-
options: Object.keys(agents_1.agentRegistry).map(k => ({
|
|
529
|
+
options: Object.keys(agents_1.agentRegistry).map((k) => ({
|
|
490
530
|
text: { type: 'plain_text', text: k },
|
|
491
|
-
value: k
|
|
492
|
-
}))
|
|
493
|
-
}
|
|
494
|
-
}
|
|
531
|
+
value: k,
|
|
532
|
+
})),
|
|
533
|
+
},
|
|
534
|
+
},
|
|
495
535
|
],
|
|
496
|
-
submit: { type: 'plain_text', text: 'Update' }
|
|
497
|
-
}
|
|
536
|
+
submit: { type: 'plain_text', text: 'Update' },
|
|
537
|
+
},
|
|
498
538
|
});
|
|
499
|
-
return;
|
|
539
|
+
return null;
|
|
500
540
|
}
|
|
501
|
-
if (input === 'list' || input === 'help') {
|
|
541
|
+
if (input === 'list' || input === 'help' || (!input && !triggerId)) {
|
|
502
542
|
const currentAgent = context.data.agent || 'gemini';
|
|
503
|
-
const agentList = Object.keys(agents_1.agentRegistry)
|
|
504
|
-
|
|
543
|
+
const agentList = Object.keys(agents_1.agentRegistry)
|
|
544
|
+
.map((k) => `• \`${k}\``)
|
|
545
|
+
.join('\n');
|
|
546
|
+
return (`Current agent: \`${currentAgent}\`\n\n` +
|
|
505
547
|
`*Available Agents:*\n${agentList}\n\n` +
|
|
506
548
|
`To switch, use \`/agent <agent_name>\``);
|
|
507
|
-
return;
|
|
508
549
|
}
|
|
509
550
|
const agentName = input.toLowerCase();
|
|
510
551
|
if (!agents_1.agentRegistry[agentName]) {
|
|
511
|
-
|
|
512
|
-
return;
|
|
552
|
+
return `❌ Unknown agent: \`${agentName}\`. Available agents: ${Object.keys(agents_1.agentRegistry).join(', ')}`;
|
|
513
553
|
}
|
|
514
554
|
contextManager.setContextData(contextId, { agent: agentName });
|
|
515
|
-
|
|
555
|
+
return `Switched agent to: \`${agentName}\``;
|
|
556
|
+
};
|
|
557
|
+
// Handle /agent command
|
|
558
|
+
app.command('/agent', async ({ command, ack, client, respond }) => {
|
|
559
|
+
await ack();
|
|
560
|
+
const result = await switchAgent(command.channel_id, command.text.trim(), command.trigger_id, client);
|
|
561
|
+
if (result)
|
|
562
|
+
await respond(result);
|
|
563
|
+
});
|
|
564
|
+
// Handle "/agent" messages
|
|
565
|
+
app.message(/^\/agent\s*(.*)/, async ({ message, context, say, client }) => {
|
|
566
|
+
const msg = message;
|
|
567
|
+
const input = context.matches[1].trim();
|
|
568
|
+
const contextId = msg.thread_ts || msg.channel;
|
|
569
|
+
const result = await switchAgent(contextId, input, undefined, client);
|
|
570
|
+
if (result) {
|
|
571
|
+
await say({
|
|
572
|
+
text: result,
|
|
573
|
+
thread_ts: msg.thread_ts,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
516
576
|
});
|
|
517
577
|
// Handle Modal Submissions
|
|
518
578
|
app.view('model_select_modal', async ({ ack, view, client }) => {
|
|
@@ -523,7 +583,7 @@ const start = async () => {
|
|
|
523
583
|
contextManager.setContextData(contextId, { model: selectedModel });
|
|
524
584
|
await client.chat.postMessage({
|
|
525
585
|
channel: contextId,
|
|
526
|
-
text: `🔄 Model updated to \`${selectedModel}
|
|
586
|
+
text: `🔄 Model updated to \`${selectedModel}\``,
|
|
527
587
|
});
|
|
528
588
|
}
|
|
529
589
|
});
|
|
@@ -535,7 +595,7 @@ const start = async () => {
|
|
|
535
595
|
contextManager.setContextData(contextId, { agent: selectedAgent });
|
|
536
596
|
await client.chat.postMessage({
|
|
537
597
|
channel: contextId,
|
|
538
|
-
text: `🔄 Agent updated to \`${selectedAgent}
|
|
598
|
+
text: `🔄 Agent updated to \`${selectedAgent}\``,
|
|
539
599
|
});
|
|
540
600
|
}
|
|
541
601
|
});
|
|
@@ -543,17 +603,19 @@ const start = async () => {
|
|
|
543
603
|
app.action('retry_turn', async ({ ack, body, client }) => {
|
|
544
604
|
await ack();
|
|
545
605
|
const action = body;
|
|
546
|
-
const contextId = action.container
|
|
606
|
+
const contextId = action.container?.thread_ts || action.container?.channel_id;
|
|
547
607
|
const channelId = action.container.channel_id;
|
|
548
608
|
const messageTs = action.container.message_ts;
|
|
549
609
|
const threadTs = action.container.thread_ts;
|
|
550
610
|
const context = contextManager.getContext(contextId);
|
|
551
611
|
// Remove last assistant message
|
|
552
|
-
if (context.messages.length > 0 &&
|
|
612
|
+
if (context.messages.length > 0 &&
|
|
613
|
+
context.messages[context.messages.length - 1].role === 'assistant') {
|
|
553
614
|
context.messages.pop();
|
|
554
615
|
}
|
|
555
616
|
// Add a note to the last user message if not already present
|
|
556
|
-
if (context.messages.length > 0 &&
|
|
617
|
+
if (context.messages.length > 0 &&
|
|
618
|
+
context.messages[context.messages.length - 1].role === 'user') {
|
|
557
619
|
const lastMsg = context.messages[context.messages.length - 1];
|
|
558
620
|
if (!lastMsg.content.includes('[System: The user requested a retry')) {
|
|
559
621
|
lastMsg.content += '\n\n[System: The user requested a retry of this instruction.]';
|
|
@@ -565,7 +627,8 @@ const start = async () => {
|
|
|
565
627
|
const msg = typeof args === 'string' ? { text: args } : args;
|
|
566
628
|
return await client.chat.postMessage({
|
|
567
629
|
channel: channelId,
|
|
568
|
-
|
|
630
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
631
|
+
...msg,
|
|
569
632
|
});
|
|
570
633
|
};
|
|
571
634
|
// Re-run agent turn (without new user text, using existing message to update)
|
|
@@ -575,7 +638,7 @@ const start = async () => {
|
|
|
575
638
|
app.action('revert_turn', async ({ ack, body, client }) => {
|
|
576
639
|
await ack();
|
|
577
640
|
const action = body;
|
|
578
|
-
const contextId = action.container
|
|
641
|
+
const contextId = action.container?.thread_ts || action.container?.channel_id;
|
|
579
642
|
const channelId = action.container.channel_id;
|
|
580
643
|
const threadTs = action.container.thread_ts;
|
|
581
644
|
const context = contextManager.getContext(contextId);
|
|
@@ -592,7 +655,8 @@ const start = async () => {
|
|
|
592
655
|
const msg = typeof args === 'string' ? { text: args } : args;
|
|
593
656
|
return await client.chat.postMessage({
|
|
594
657
|
channel: channelId,
|
|
595
|
-
|
|
658
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
659
|
+
...msg,
|
|
596
660
|
});
|
|
597
661
|
};
|
|
598
662
|
const revertPrompt = `The user wants to revert the actions taken for the following request: "${lastUserText}". Please undo any file modifications or state changes made in that turn.`;
|
|
@@ -600,10 +664,9 @@ const start = async () => {
|
|
|
600
664
|
// We do NOT pop messages here, we want the agent to see what it did and undo it.
|
|
601
665
|
await processAgentTurn(contextId, channelId, threadTs, revertPrompt, say, client);
|
|
602
666
|
});
|
|
603
|
-
//
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const helpMessage = `
|
|
667
|
+
// Helper for Help
|
|
668
|
+
const getHelpMessage = () => {
|
|
669
|
+
return `
|
|
607
670
|
*Available Commands:*
|
|
608
671
|
|
|
609
672
|
- \`/plan [goal]\`: Enter plan mode to generate and execute coding plans.
|
|
@@ -612,7 +675,19 @@ const start = async () => {
|
|
|
612
675
|
- \`/agent [name]\`: Switch the backing agent (e.g., \`gemini\`, \`claude\`, \`cursor\`).
|
|
613
676
|
- \`/help\`: Show this help message.
|
|
614
677
|
`;
|
|
615
|
-
|
|
678
|
+
};
|
|
679
|
+
// Handle /help command
|
|
680
|
+
app.command('/help', async ({ ack, respond }) => {
|
|
681
|
+
await ack();
|
|
682
|
+
await respond(getHelpMessage());
|
|
683
|
+
});
|
|
684
|
+
// Handle "/help" messages
|
|
685
|
+
app.message(/^\/help/, async ({ message, say }) => {
|
|
686
|
+
const msg = message;
|
|
687
|
+
await say({
|
|
688
|
+
text: getHelpMessage(),
|
|
689
|
+
thread_ts: msg.thread_ts,
|
|
690
|
+
});
|
|
616
691
|
});
|
|
617
692
|
// Handle App Home Tab
|
|
618
693
|
app.event('app_home_opened', async ({ event, client, logger }) => {
|
|
@@ -620,12 +695,12 @@ const start = async () => {
|
|
|
620
695
|
const contexts = contextManager.getAllContexts();
|
|
621
696
|
// Sort by last active
|
|
622
697
|
contexts.sort((a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime());
|
|
623
|
-
const contextBlocks = contexts.slice(0, 5).map(ctx => ({
|
|
698
|
+
const contextBlocks = contexts.slice(0, 5).map((ctx) => ({
|
|
624
699
|
type: 'section',
|
|
625
700
|
text: {
|
|
626
701
|
type: 'mrkdwn',
|
|
627
|
-
text: `*Context:* \`${ctx.id}\`\n*Last Active:* ${ctx.lastActiveAt.toLocaleString()}\n*Agent:* \`${ctx.data.agent || 'gemini'}\` | *Model:* \`${ctx.data.model || 'default'}\` | *CWD:* \`${ctx.data.cwd || 'root'}
|
|
628
|
-
}
|
|
702
|
+
text: `*Context:* \`${ctx.id}\`\n*Last Active:* ${ctx.lastActiveAt.toLocaleString()}\n*Agent:* \`${ctx.data.agent || 'gemini'}\` | *Model:* \`${ctx.data.model || 'default'}\` | *CWD:* \`${ctx.data.cwd || 'root'}\``,
|
|
703
|
+
},
|
|
629
704
|
}));
|
|
630
705
|
const blocks = [
|
|
631
706
|
{
|
|
@@ -633,54 +708,56 @@ const start = async () => {
|
|
|
633
708
|
text: {
|
|
634
709
|
type: 'plain_text',
|
|
635
710
|
text: '🏠 Gibi Dashboard',
|
|
636
|
-
emoji: true
|
|
637
|
-
}
|
|
711
|
+
emoji: true,
|
|
712
|
+
},
|
|
638
713
|
},
|
|
639
714
|
{
|
|
640
715
|
type: 'section',
|
|
641
716
|
text: {
|
|
642
717
|
type: 'mrkdwn',
|
|
643
|
-
text: `Welcome, <@${event.user}>! Here is a summary of your Gibi instance status
|
|
644
|
-
}
|
|
718
|
+
text: `Welcome, <@${event.user}>! Here is a summary of your Gibi instance status.`,
|
|
719
|
+
},
|
|
645
720
|
},
|
|
646
721
|
{
|
|
647
|
-
type: 'divider'
|
|
722
|
+
type: 'divider',
|
|
648
723
|
},
|
|
649
724
|
{
|
|
650
725
|
type: 'section',
|
|
651
726
|
fields: [
|
|
652
727
|
{
|
|
653
728
|
type: 'mrkdwn',
|
|
654
|
-
text: `*Default Agent:*\n\`gemini
|
|
729
|
+
text: `*Default Agent:*\n\`gemini\``,
|
|
655
730
|
},
|
|
656
731
|
{
|
|
657
732
|
type: 'mrkdwn',
|
|
658
|
-
text: `*Available Agents:*\n${Object.keys(agents_1.agentRegistry)
|
|
659
|
-
|
|
660
|
-
|
|
733
|
+
text: `*Available Agents:*\n${Object.keys(agents_1.agentRegistry)
|
|
734
|
+
.map((k) => `\`${k}\``)
|
|
735
|
+
.join(', ')}`,
|
|
736
|
+
},
|
|
737
|
+
],
|
|
661
738
|
},
|
|
662
739
|
{
|
|
663
|
-
type: 'divider'
|
|
740
|
+
type: 'divider',
|
|
664
741
|
},
|
|
665
742
|
{
|
|
666
743
|
type: 'header',
|
|
667
744
|
text: {
|
|
668
745
|
type: 'plain_text',
|
|
669
746
|
text: '🕒 Recent Activity',
|
|
670
|
-
emoji: true
|
|
671
|
-
}
|
|
672
|
-
}
|
|
747
|
+
emoji: true,
|
|
748
|
+
},
|
|
749
|
+
},
|
|
673
750
|
];
|
|
674
751
|
if (contextBlocks.length > 0) {
|
|
675
|
-
blocks.push(...contextBlocks.flatMap(b => [b, { type: 'divider' }]));
|
|
752
|
+
blocks.push(...contextBlocks.flatMap((b) => [b, { type: 'divider' }]));
|
|
676
753
|
}
|
|
677
754
|
else {
|
|
678
755
|
blocks.push({
|
|
679
756
|
type: 'section',
|
|
680
757
|
text: {
|
|
681
758
|
type: 'mrkdwn',
|
|
682
|
-
text: '_No active contexts found. Send a message to Gibi in any channel to start._'
|
|
683
|
-
}
|
|
759
|
+
text: '_No active contexts found. Send a message to Gibi in any channel to start._',
|
|
760
|
+
},
|
|
684
761
|
});
|
|
685
762
|
}
|
|
686
763
|
blocks.push({
|
|
@@ -688,16 +765,16 @@ const start = async () => {
|
|
|
688
765
|
elements: [
|
|
689
766
|
{
|
|
690
767
|
type: 'mrkdwn',
|
|
691
|
-
text: `Last updated: ${new Date().toLocaleString()}
|
|
692
|
-
}
|
|
693
|
-
]
|
|
768
|
+
text: `Last updated: ${new Date().toLocaleString()}`,
|
|
769
|
+
},
|
|
770
|
+
],
|
|
694
771
|
});
|
|
695
772
|
await client.views.publish({
|
|
696
773
|
user_id: event.user,
|
|
697
774
|
view: {
|
|
698
775
|
type: 'home',
|
|
699
|
-
blocks: blocks
|
|
700
|
-
}
|
|
776
|
+
blocks: blocks,
|
|
777
|
+
},
|
|
701
778
|
});
|
|
702
779
|
}
|
|
703
780
|
catch (error) {
|