loopengine 0.1.79 → 0.1.80

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.
@@ -80,11 +80,13 @@ export class SlackNotifier {
80
80
  }
81
81
  notifyPendingQuestion(question, options, agent, sessionId) {
82
82
  const pendingId = randomUUID();
83
- const optionButtons = (options ?? []).map((option) => ({
83
+ // Slack rejects a block whose buttons share an action_id
84
+ // (invalid_blocks), so each option gets its own: answer_option_<n>.
85
+ const optionButtons = (options ?? []).map((option, i) => ({
84
86
  type: 'button',
85
87
  text: { type: 'plain_text', text: option },
86
88
  value: JSON.stringify({ pendingId, answer: option }),
87
- action_id: 'answer_option',
89
+ action_id: `answer_option_${i}`,
88
90
  }));
89
91
  this.postMessage(`Question from ${agent}: ${question}`, [
90
92
  { type: 'section', text: { type: 'mrkdwn', text: `*Question from ${agent}${sessionId ? ` (session ${sessionId})` : ''}:*\n${question}${this.decideLine(pendingId, 'question')}` } },
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // Same button contract SlackNotifier (core/http-notify-triggers/slack.ts)
9
9
  // sends: action_id approve|deny with the pendingId as value,
10
- // answer_option with {pendingId, answer}, open_answer_modal with the
10
+ // answer_option_<n> with {pendingId, answer}, open_answer_modal with the
11
11
  // pendingId, and the modal's answer under ANSWER_BLOCK_ID/ANSWER_ACTION_ID.
12
12
  import { createHmac, timingSafeEqual } from 'node:crypto';
13
13
  export const SLACK_ANSWER_BLOCK_ID = 'answer_block';
@@ -48,10 +48,13 @@ export function parseSlackInteraction(rawBody) {
48
48
  const action = payload.actions?.[0];
49
49
  if (!action?.value)
50
50
  return { type: 'ignore' };
51
- switch (action.action_id) {
51
+ // answer_option_<n> (one per option); a plain answer_option is from
52
+ // messages posted before each button got its own id.
53
+ const actionId = action.action_id?.startsWith('answer_option') ? 'answer_option' : action.action_id;
54
+ switch (actionId) {
52
55
  case 'approve':
53
56
  case 'deny':
54
- return { type: 'decide', pendingId: action.value, decision: action.action_id, responseUrl: payload.response_url, user };
57
+ return { type: 'decide', pendingId: action.value, decision: actionId, responseUrl: payload.response_url, user };
55
58
  case 'answer_option': {
56
59
  try {
57
60
  const { pendingId, answer } = JSON.parse(action.value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.79",
3
+ "version": "0.1.80",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",