@integrity-labs/agt-cli 0.15.10 → 0.15.11
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/bin/agt.js +3 -3
- package/dist/{chunk-EC6FSHOW.js → chunk-LYWDRDJZ.js} +41 -5
- package/dist/chunk-LYWDRDJZ.js.map +1 -0
- package/dist/claude-pair-runtime-WGIKIPJV.js +187 -0
- package/dist/claude-pair-runtime-WGIKIPJV.js.map +1 -0
- package/dist/lib/manager-worker.js +101 -38
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/slack-channel.js +822 -0
- package/package.json +1 -1
- package/dist/chunk-EC6FSHOW.js.map +0 -1
package/mcp/slack-channel.js
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __commonJS = (cb, mod) => function __require() {
|
|
9
12
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
13
|
};
|
|
@@ -6798,6 +6801,402 @@ var require_dist = __commonJS({
|
|
|
6798
6801
|
}
|
|
6799
6802
|
});
|
|
6800
6803
|
|
|
6804
|
+
// src/slack-block-kit-runtime.ts
|
|
6805
|
+
var slack_block_kit_runtime_exports = {};
|
|
6806
|
+
__export(slack_block_kit_runtime_exports, {
|
|
6807
|
+
buildAskUserBlocks: () => buildAskUserBlocks,
|
|
6808
|
+
buildRatingActionsBlock: () => buildRatingActionsBlock,
|
|
6809
|
+
createPendingInteraction: () => createPendingInteraction,
|
|
6810
|
+
decodeActionId: () => decodeActionId,
|
|
6811
|
+
defaultRatingLabels: () => defaultRatingLabels,
|
|
6812
|
+
encodeActionId: () => encodeActionId,
|
|
6813
|
+
generateOptionToken: () => generateOptionToken,
|
|
6814
|
+
ratingValuesForScale: () => ratingValuesForScale,
|
|
6815
|
+
recordSlackDelivery: () => recordSlackDelivery,
|
|
6816
|
+
resolveInteractive: () => resolveInteractive,
|
|
6817
|
+
updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
|
|
6818
|
+
validateAskUserOptions: () => validateAskUserOptions,
|
|
6819
|
+
validateSlackBlocks: () => validateSlackBlocks,
|
|
6820
|
+
waitForResolution: () => waitForResolution
|
|
6821
|
+
});
|
|
6822
|
+
async function getAuthToken(cfg) {
|
|
6823
|
+
if (auth.token && Date.now() < auth.expiresAtEpochMs) return auth.token;
|
|
6824
|
+
const res = await fetch(`${cfg.apiHost}/host/exchange`, {
|
|
6825
|
+
method: "POST",
|
|
6826
|
+
headers: { "Content-Type": "application/json" },
|
|
6827
|
+
body: JSON.stringify({ host_key: cfg.apiKey }),
|
|
6828
|
+
signal: AbortSignal.timeout(API_FETCH_TIMEOUT_MS)
|
|
6829
|
+
});
|
|
6830
|
+
if (!res.ok) {
|
|
6831
|
+
const body = await res.text().catch(() => "");
|
|
6832
|
+
throw new Error(`AGT auth exchange failed (${res.status}): ${body.slice(0, 200)}`);
|
|
6833
|
+
}
|
|
6834
|
+
const data = await res.json();
|
|
6835
|
+
auth.token = data.token;
|
|
6836
|
+
auth.expiresAtEpochMs = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
|
|
6837
|
+
return auth.token;
|
|
6838
|
+
}
|
|
6839
|
+
async function apiCall(cfg, method, path, body) {
|
|
6840
|
+
const token = await getAuthToken(cfg);
|
|
6841
|
+
const buildInit = (authToken) => ({
|
|
6842
|
+
method,
|
|
6843
|
+
headers: {
|
|
6844
|
+
"Content-Type": "application/json",
|
|
6845
|
+
Authorization: `Bearer ${authToken}`
|
|
6846
|
+
},
|
|
6847
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
6848
|
+
signal: AbortSignal.timeout(API_FETCH_TIMEOUT_MS)
|
|
6849
|
+
});
|
|
6850
|
+
const res = await fetch(`${cfg.apiHost}${path}`, buildInit(token));
|
|
6851
|
+
if (res.status !== 401) return res;
|
|
6852
|
+
auth.token = null;
|
|
6853
|
+
auth.expiresAtEpochMs = 0;
|
|
6854
|
+
const fresh = await getAuthToken(cfg);
|
|
6855
|
+
return fetch(`${cfg.apiHost}${path}`, buildInit(fresh));
|
|
6856
|
+
}
|
|
6857
|
+
async function createPendingInteraction(cfg, input) {
|
|
6858
|
+
const res = await apiCall(cfg, "POST", "/host/pending-interactions", {
|
|
6859
|
+
agent_id: cfg.agentId,
|
|
6860
|
+
callback_id: input.callbackId,
|
|
6861
|
+
channel_id: input.channelId,
|
|
6862
|
+
message_ts: input.messageTs ?? "",
|
|
6863
|
+
thread_ts: input.threadTs,
|
|
6864
|
+
options: input.options,
|
|
6865
|
+
expires_at: input.expiresAt.toISOString(),
|
|
6866
|
+
kind: input.kind,
|
|
6867
|
+
kind_data: input.kindData
|
|
6868
|
+
});
|
|
6869
|
+
if (!res.ok) {
|
|
6870
|
+
const body = await res.text().catch(() => "");
|
|
6871
|
+
throw new Error(`createPendingInteraction failed (${res.status}): ${body.slice(0, 200)}`);
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
async function updatePendingInteractionMessageTs(cfg, callbackId, messageTs) {
|
|
6875
|
+
const res = await apiCall(cfg, "PATCH", `/host/pending-interactions/${encodeURIComponent(callbackId)}`, {
|
|
6876
|
+
agent_id: cfg.agentId,
|
|
6877
|
+
message_ts: messageTs
|
|
6878
|
+
});
|
|
6879
|
+
if (!res.ok) {
|
|
6880
|
+
const body = await res.text().catch(() => "");
|
|
6881
|
+
throw new Error(`updatePendingInteractionMessageTs failed (${res.status}): ${body.slice(0, 200)}`);
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6884
|
+
async function waitForResolution(cfg, callbackId, opts) {
|
|
6885
|
+
const interval = opts.pollIntervalMs ?? 500;
|
|
6886
|
+
const deadlineMs = opts.deadline.getTime();
|
|
6887
|
+
let firstPass = true;
|
|
6888
|
+
while (firstPass || Date.now() < deadlineMs) {
|
|
6889
|
+
firstPass = false;
|
|
6890
|
+
try {
|
|
6891
|
+
const res = await apiCall(
|
|
6892
|
+
cfg,
|
|
6893
|
+
"GET",
|
|
6894
|
+
`/host/pending-interactions/${encodeURIComponent(callbackId)}?agent_id=${encodeURIComponent(cfg.agentId)}`
|
|
6895
|
+
);
|
|
6896
|
+
if (res.ok) {
|
|
6897
|
+
const row = await res.json();
|
|
6898
|
+
if (row.status === "resolved") {
|
|
6899
|
+
return {
|
|
6900
|
+
kind: "resolved",
|
|
6901
|
+
value: row.value ?? "",
|
|
6902
|
+
respondedByUser: row.responded_by_user,
|
|
6903
|
+
respondedAt: row.responded_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6904
|
+
};
|
|
6905
|
+
}
|
|
6906
|
+
if (row.status === "timeout") {
|
|
6907
|
+
return { kind: "timeout" };
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6910
|
+
} catch {
|
|
6911
|
+
}
|
|
6912
|
+
await sleep(interval);
|
|
6913
|
+
}
|
|
6914
|
+
return { kind: "timeout" };
|
|
6915
|
+
}
|
|
6916
|
+
function sleep(ms) {
|
|
6917
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
6918
|
+
}
|
|
6919
|
+
function generateOptionToken() {
|
|
6920
|
+
const bytes = new Uint8Array(12);
|
|
6921
|
+
const webCrypto = globalThis.crypto;
|
|
6922
|
+
if (webCrypto && typeof webCrypto.getRandomValues === "function") {
|
|
6923
|
+
webCrypto.getRandomValues(bytes);
|
|
6924
|
+
} else {
|
|
6925
|
+
throw new Error(
|
|
6926
|
+
"generateOptionToken: no crypto.getRandomValues implementation available. Need Node 20+ or a Web Crypto polyfill."
|
|
6927
|
+
);
|
|
6928
|
+
}
|
|
6929
|
+
const out = Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
6930
|
+
return out;
|
|
6931
|
+
}
|
|
6932
|
+
function validateSlackBlocks(blocks) {
|
|
6933
|
+
const errors = [];
|
|
6934
|
+
const cap = 10;
|
|
6935
|
+
const push = (path, message) => {
|
|
6936
|
+
if (errors.length < cap) errors.push({ path, message });
|
|
6937
|
+
};
|
|
6938
|
+
if (!Array.isArray(blocks)) {
|
|
6939
|
+
return { ok: false, errors: [{ path: "blocks", message: "must be an array" }] };
|
|
6940
|
+
}
|
|
6941
|
+
if (blocks.length === 0) {
|
|
6942
|
+
return { ok: false, errors: [{ path: "blocks", message: "must contain at least one block" }] };
|
|
6943
|
+
}
|
|
6944
|
+
if (blocks.length > SLACK_LIMITS.blocksPerMessage) {
|
|
6945
|
+
push("blocks", `max ${SLACK_LIMITS.blocksPerMessage} blocks per message (got ${blocks.length})`);
|
|
6946
|
+
}
|
|
6947
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
6948
|
+
const block = blocks[i];
|
|
6949
|
+
const path = `blocks[${i}]`;
|
|
6950
|
+
const type = block?.type;
|
|
6951
|
+
if (typeof type !== "string") {
|
|
6952
|
+
push(path, "block.type is required");
|
|
6953
|
+
continue;
|
|
6954
|
+
}
|
|
6955
|
+
if (!SUPPORTED_BLOCK_TYPES.includes(type)) {
|
|
6956
|
+
push(path, `block type '${type}' is not supported. Use one of: ${SUPPORTED_BLOCK_TYPES.join(", ")}`);
|
|
6957
|
+
continue;
|
|
6958
|
+
}
|
|
6959
|
+
if (typeof block.block_id === "string" && block.block_id.length > SLACK_LIMITS.identifierChars) {
|
|
6960
|
+
push(`${path}.block_id`, `max ${SLACK_LIMITS.identifierChars} chars`);
|
|
6961
|
+
}
|
|
6962
|
+
switch (type) {
|
|
6963
|
+
case "header":
|
|
6964
|
+
validateHeaderBlock(block, path, push);
|
|
6965
|
+
break;
|
|
6966
|
+
case "section":
|
|
6967
|
+
validateSectionBlock(block, path, push);
|
|
6968
|
+
break;
|
|
6969
|
+
case "context":
|
|
6970
|
+
validateContextBlock(block, path, push);
|
|
6971
|
+
break;
|
|
6972
|
+
case "actions":
|
|
6973
|
+
validateActionsBlock(block, path, push);
|
|
6974
|
+
break;
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
return { ok: errors.length === 0, errors };
|
|
6978
|
+
}
|
|
6979
|
+
function validateHeaderBlock(b, path, push) {
|
|
6980
|
+
const text = b.text;
|
|
6981
|
+
if (!text || text.type !== "plain_text" || typeof text.text !== "string") {
|
|
6982
|
+
push(`${path}.text`, "header.text must be { type: 'plain_text', text: string }");
|
|
6983
|
+
return;
|
|
6984
|
+
}
|
|
6985
|
+
if (text.text.length > SLACK_LIMITS.headerTextChars) {
|
|
6986
|
+
push(`${path}.text.text`, `max ${SLACK_LIMITS.headerTextChars} chars`);
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
function validateSectionBlock(b, path, push) {
|
|
6990
|
+
const hasText = "text" in b;
|
|
6991
|
+
const hasFields = "fields" in b;
|
|
6992
|
+
if (!hasText && !hasFields) {
|
|
6993
|
+
push(path, "section block requires text or fields");
|
|
6994
|
+
return;
|
|
6995
|
+
}
|
|
6996
|
+
if (hasText) validateTextObject(b.text, `${path}.text`, push, SLACK_LIMITS.sectionTextChars);
|
|
6997
|
+
if (hasFields) {
|
|
6998
|
+
if (!Array.isArray(b.fields)) {
|
|
6999
|
+
push(`${path}.fields`, "must be an array");
|
|
7000
|
+
return;
|
|
7001
|
+
}
|
|
7002
|
+
if (b.fields.length > SLACK_LIMITS.sectionFields) {
|
|
7003
|
+
push(`${path}.fields`, `max ${SLACK_LIMITS.sectionFields} fields (got ${b.fields.length})`);
|
|
7004
|
+
}
|
|
7005
|
+
b.fields.forEach(
|
|
7006
|
+
(f, i) => validateTextObject(f, `${path}.fields[${i}]`, push, SLACK_LIMITS.sectionFieldChars)
|
|
7007
|
+
);
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
7010
|
+
function validateContextBlock(b, path, push) {
|
|
7011
|
+
if (!Array.isArray(b.elements) || b.elements.length === 0) {
|
|
7012
|
+
push(`${path}.elements`, "context block requires non-empty elements array");
|
|
7013
|
+
return;
|
|
7014
|
+
}
|
|
7015
|
+
b.elements.forEach(
|
|
7016
|
+
(el, i) => validateTextObject(el, `${path}.elements[${i}]`, push, SLACK_LIMITS.sectionFieldChars)
|
|
7017
|
+
);
|
|
7018
|
+
}
|
|
7019
|
+
function validateActionsBlock(b, path, push) {
|
|
7020
|
+
if (!Array.isArray(b.elements) || b.elements.length === 0) {
|
|
7021
|
+
push(`${path}.elements`, "actions block requires non-empty elements array");
|
|
7022
|
+
return;
|
|
7023
|
+
}
|
|
7024
|
+
if (b.elements.length > SLACK_LIMITS.actionElements) {
|
|
7025
|
+
push(`${path}.elements`, `max ${SLACK_LIMITS.actionElements} action elements (got ${b.elements.length})`);
|
|
7026
|
+
}
|
|
7027
|
+
b.elements.forEach((el, i) => {
|
|
7028
|
+
const elPath = `${path}.elements[${i}]`;
|
|
7029
|
+
const e = el;
|
|
7030
|
+
if (typeof e?.type !== "string") {
|
|
7031
|
+
push(elPath, "element.type is required");
|
|
7032
|
+
return;
|
|
7033
|
+
}
|
|
7034
|
+
if (!SUPPORTED_ACTION_ELEMENTS.includes(e.type)) {
|
|
7035
|
+
push(elPath, `element type '${e.type}' is not supported. Use one of: ${SUPPORTED_ACTION_ELEMENTS.join(", ")}`);
|
|
7036
|
+
return;
|
|
7037
|
+
}
|
|
7038
|
+
if (typeof e.action_id !== "string" || e.action_id.length === 0) {
|
|
7039
|
+
push(`${elPath}.action_id`, "required");
|
|
7040
|
+
} else if (e.action_id.length > SLACK_LIMITS.identifierChars) {
|
|
7041
|
+
push(`${elPath}.action_id`, `max ${SLACK_LIMITS.identifierChars} chars`);
|
|
7042
|
+
}
|
|
7043
|
+
if (e.type === "button") {
|
|
7044
|
+
const t = e.text;
|
|
7045
|
+
if (!t || t.type !== "plain_text" || typeof t.text !== "string") {
|
|
7046
|
+
push(`${elPath}.text`, "button.text must be { type: 'plain_text', text: string }");
|
|
7047
|
+
} else if (t.text.length > SLACK_LIMITS.buttonLabelChars) {
|
|
7048
|
+
push(`${elPath}.text.text`, `max ${SLACK_LIMITS.buttonLabelChars} chars`);
|
|
7049
|
+
}
|
|
7050
|
+
}
|
|
7051
|
+
});
|
|
7052
|
+
}
|
|
7053
|
+
function validateTextObject(obj, path, push, maxChars) {
|
|
7054
|
+
if (!obj || typeof obj !== "object") {
|
|
7055
|
+
push(path, "must be a text object");
|
|
7056
|
+
return;
|
|
7057
|
+
}
|
|
7058
|
+
if (obj.type !== "plain_text" && obj.type !== "mrkdwn") {
|
|
7059
|
+
push(`${path}.type`, "must be 'plain_text' or 'mrkdwn'");
|
|
7060
|
+
return;
|
|
7061
|
+
}
|
|
7062
|
+
if (typeof obj.text !== "string") {
|
|
7063
|
+
push(`${path}.text`, "must be a string");
|
|
7064
|
+
return;
|
|
7065
|
+
}
|
|
7066
|
+
if (obj.text.length > maxChars) {
|
|
7067
|
+
push(`${path}.text`, `max ${maxChars} chars (got ${obj.text.length})`);
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
function validateAskUserOptions(options) {
|
|
7071
|
+
const errors = [];
|
|
7072
|
+
if (!Array.isArray(options)) {
|
|
7073
|
+
return { ok: false, errors: [{ path: "options", message: "must be an array" }] };
|
|
7074
|
+
}
|
|
7075
|
+
if (options.length === 0) {
|
|
7076
|
+
errors.push({ path: "options", message: "must contain at least one option" });
|
|
7077
|
+
}
|
|
7078
|
+
if (options.length > SLACK_LIMITS.actionElements) {
|
|
7079
|
+
errors.push({ path: "options", message: `max ${SLACK_LIMITS.actionElements} options (one Slack actions block)` });
|
|
7080
|
+
}
|
|
7081
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
7082
|
+
const seenValues = /* @__PURE__ */ new Set();
|
|
7083
|
+
options.forEach((opt, i) => {
|
|
7084
|
+
const o = opt;
|
|
7085
|
+
const path = `options[${i}]`;
|
|
7086
|
+
if (typeof o?.label !== "string" || o.label.length === 0) {
|
|
7087
|
+
errors.push({ path: `${path}.label`, message: "required string" });
|
|
7088
|
+
} else {
|
|
7089
|
+
if (o.label.length > SLACK_LIMITS.buttonLabelChars) {
|
|
7090
|
+
errors.push({ path: `${path}.label`, message: `max ${SLACK_LIMITS.buttonLabelChars} chars` });
|
|
7091
|
+
}
|
|
7092
|
+
if (seenLabels.has(o.label)) errors.push({ path: `${path}.label`, message: "duplicate label" });
|
|
7093
|
+
seenLabels.add(o.label);
|
|
7094
|
+
}
|
|
7095
|
+
if (typeof o?.value !== "string" || o.value.length === 0) {
|
|
7096
|
+
errors.push({ path: `${path}.value`, message: "required string" });
|
|
7097
|
+
} else {
|
|
7098
|
+
if (o.value.length > SLACK_LIMITS.optionValueChars) {
|
|
7099
|
+
errors.push({ path: `${path}.value`, message: `max ${SLACK_LIMITS.optionValueChars} chars` });
|
|
7100
|
+
}
|
|
7101
|
+
if (seenValues.has(o.value)) errors.push({ path: `${path}.value`, message: "duplicate value" });
|
|
7102
|
+
seenValues.add(o.value);
|
|
7103
|
+
}
|
|
7104
|
+
});
|
|
7105
|
+
return { ok: errors.length === 0, errors };
|
|
7106
|
+
}
|
|
7107
|
+
function encodeActionId(callbackId, token) {
|
|
7108
|
+
return `aug:${callbackId}:${token}`;
|
|
7109
|
+
}
|
|
7110
|
+
function decodeActionId(actionId) {
|
|
7111
|
+
const m = ACTION_ID_RE.exec(actionId);
|
|
7112
|
+
if (!m) return null;
|
|
7113
|
+
return { callbackId: m[1], token: m[2] };
|
|
7114
|
+
}
|
|
7115
|
+
function buildAskUserBlocks(opts) {
|
|
7116
|
+
return [
|
|
7117
|
+
{
|
|
7118
|
+
type: "section",
|
|
7119
|
+
text: { type: "mrkdwn", text: opts.question }
|
|
7120
|
+
},
|
|
7121
|
+
{
|
|
7122
|
+
type: "actions",
|
|
7123
|
+
elements: opts.options.map(({ label, token }) => ({
|
|
7124
|
+
type: "button",
|
|
7125
|
+
text: { type: "plain_text", text: label },
|
|
7126
|
+
action_id: encodeActionId(opts.callbackId, token)
|
|
7127
|
+
}))
|
|
7128
|
+
}
|
|
7129
|
+
];
|
|
7130
|
+
}
|
|
7131
|
+
function ratingValuesForScale(scale) {
|
|
7132
|
+
return scale === "1-5" ? [1, 2, 3, 4, 5] : [-1, 0, 1];
|
|
7133
|
+
}
|
|
7134
|
+
function defaultRatingLabels(scale) {
|
|
7135
|
+
return scale === "1-5" ? ["1", "2", "3", "4", "5"] : ["\u{1F44E}", "\u{1F914}", "\u{1F44D}"];
|
|
7136
|
+
}
|
|
7137
|
+
function buildRatingActionsBlock(opts) {
|
|
7138
|
+
return {
|
|
7139
|
+
type: "actions",
|
|
7140
|
+
elements: opts.options.map(({ label, token }) => ({
|
|
7141
|
+
type: "button",
|
|
7142
|
+
text: { type: "plain_text", text: label },
|
|
7143
|
+
action_id: encodeActionId(opts.callbackId, token)
|
|
7144
|
+
}))
|
|
7145
|
+
};
|
|
7146
|
+
}
|
|
7147
|
+
async function resolveInteractive(cfg, input) {
|
|
7148
|
+
const res = await apiCall(cfg, "POST", "/host/interactive/resolve", {
|
|
7149
|
+
agent_id: cfg.agentId,
|
|
7150
|
+
callback_id: input.callbackId,
|
|
7151
|
+
token: input.token,
|
|
7152
|
+
responded_by_user: input.respondedByUser,
|
|
7153
|
+
response_url: input.responseUrl,
|
|
7154
|
+
original_blocks: input.originalBlocks
|
|
7155
|
+
});
|
|
7156
|
+
if (!res.ok) {
|
|
7157
|
+
const body = await res.text().catch(() => "");
|
|
7158
|
+
throw new Error(`resolveInteractive failed (${res.status}): ${body.slice(0, 200)}`);
|
|
7159
|
+
}
|
|
7160
|
+
}
|
|
7161
|
+
async function recordSlackDelivery(cfg, input) {
|
|
7162
|
+
const res = await apiCall(
|
|
7163
|
+
cfg,
|
|
7164
|
+
"POST",
|
|
7165
|
+
`/host/agents/${encodeURIComponent(cfg.agentId)}/slack-delivery`,
|
|
7166
|
+
{
|
|
7167
|
+
channel: input.channel,
|
|
7168
|
+
message_ts: input.messageTs,
|
|
7169
|
+
thread_ts: input.threadTs
|
|
7170
|
+
}
|
|
7171
|
+
);
|
|
7172
|
+
if (!res.ok) {
|
|
7173
|
+
const body = await res.text().catch(() => "");
|
|
7174
|
+
throw new Error(`recordSlackDelivery failed (${res.status}): ${body.slice(0, 200)}`);
|
|
7175
|
+
}
|
|
7176
|
+
}
|
|
7177
|
+
var auth, API_FETCH_TIMEOUT_MS, SLACK_LIMITS, SUPPORTED_BLOCK_TYPES, SUPPORTED_ACTION_ELEMENTS, ACTION_ID_RE;
|
|
7178
|
+
var init_slack_block_kit_runtime = __esm({
|
|
7179
|
+
"src/slack-block-kit-runtime.ts"() {
|
|
7180
|
+
"use strict";
|
|
7181
|
+
auth = { token: null, expiresAtEpochMs: 0 };
|
|
7182
|
+
API_FETCH_TIMEOUT_MS = 1e4;
|
|
7183
|
+
SLACK_LIMITS = {
|
|
7184
|
+
blocksPerMessage: 50,
|
|
7185
|
+
sectionTextChars: 3e3,
|
|
7186
|
+
sectionFields: 10,
|
|
7187
|
+
sectionFieldChars: 2e3,
|
|
7188
|
+
actionElements: 5,
|
|
7189
|
+
buttonLabelChars: 75,
|
|
7190
|
+
identifierChars: 255,
|
|
7191
|
+
optionValueChars: 2e3,
|
|
7192
|
+
headerTextChars: 150
|
|
7193
|
+
};
|
|
7194
|
+
SUPPORTED_BLOCK_TYPES = ["header", "section", "divider", "context", "actions"];
|
|
7195
|
+
SUPPORTED_ACTION_ELEMENTS = ["button"];
|
|
7196
|
+
ACTION_ID_RE = /^aug:([0-9a-f-]{36}):([A-Za-z0-9_-]{1,200})$/;
|
|
7197
|
+
}
|
|
7198
|
+
});
|
|
7199
|
+
|
|
6801
7200
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/core.js
|
|
6802
7201
|
var NEVER = Object.freeze({
|
|
6803
7202
|
status: "aborted"
|
|
@@ -14164,6 +14563,12 @@ function channelMessageShouldRespond(text, mode) {
|
|
|
14164
14563
|
var BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
|
|
14165
14564
|
var APP_TOKEN = process.env.SLACK_APP_TOKEN;
|
|
14166
14565
|
var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? null;
|
|
14566
|
+
var AGT_HOST = process.env.AGT_HOST ?? null;
|
|
14567
|
+
var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
|
|
14568
|
+
var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
14569
|
+
var BLOCK_KIT_ENABLED = process.env.SLACK_BLOCK_KIT_ENABLED === "true";
|
|
14570
|
+
var BLOCK_KIT_ASK_USER_ENABLED = process.env.SLACK_BLOCK_KIT_ASK_USER_ENABLED === "true";
|
|
14571
|
+
var BLOCK_KIT_DISABLED = process.env.SLACK_BLOCK_KIT_DISABLED === "true";
|
|
14167
14572
|
var ALLOWED_USERS = new Set(
|
|
14168
14573
|
(process.env.SLACK_ALLOWED_USERS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
14169
14574
|
);
|
|
@@ -14448,6 +14853,9 @@ var RESTART_FLAGS_DIR = join2(homedir2(), ".augmented", "restart-flags");
|
|
|
14448
14853
|
function hashChannelId(id) {
|
|
14449
14854
|
return createHash("sha256").update(id).digest("hex").slice(0, 8);
|
|
14450
14855
|
}
|
|
14856
|
+
function hashId(id) {
|
|
14857
|
+
return createHash("sha256").update(id).digest("hex").slice(0, 8);
|
|
14858
|
+
}
|
|
14451
14859
|
async function postSlackMessage(body) {
|
|
14452
14860
|
try {
|
|
14453
14861
|
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
|
@@ -14710,6 +15118,63 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
14710
15118
|
required: ["file_id", "channel"]
|
|
14711
15119
|
}
|
|
14712
15120
|
},
|
|
15121
|
+
// ENG-4573: Block Kit tools — only listed when the team has opted in
|
|
15122
|
+
// and the env wiring is present. The kill-switch (SLACK_BLOCK_KIT_DISABLED)
|
|
15123
|
+
// takes precedence so an operator can pull the tools at runtime
|
|
15124
|
+
// without redeploying.
|
|
15125
|
+
...blockKitToolsAvailable() ? [
|
|
15126
|
+
{
|
|
15127
|
+
name: "slack.send_structured",
|
|
15128
|
+
description: 'Send a Slack message built from Block Kit blocks (structured layout: headers, sections, dividers, context, action buttons). Use when plain text isn\'t enough \u2014 status reports, confirmations with multiple fields, calls-to-action with buttons. Pass a `text` fallback for push notifications. Returns { message_ts, permalink }. Does NOT block waiting for user interactions; for that use slack.ask_user. Pass `interactive: { type: "rate_run", run_id }` to append a 1-5 rating button row that records the score against this scheduled-task run (ENG-4572).',
|
|
15129
|
+
inputSchema: {
|
|
15130
|
+
type: "object",
|
|
15131
|
+
properties: {
|
|
15132
|
+
channel: { type: "string", description: "Slack channel ID" },
|
|
15133
|
+
blocks: {
|
|
15134
|
+
type: "array",
|
|
15135
|
+
description: "Array of Block Kit blocks. Supported block types: header, section, divider, context, actions. Supported interactive elements (inside actions blocks): button. Hard limits: 50 blocks per message, 5 elements per actions block, 3000 chars per section text, 75 chars per button label."
|
|
15136
|
+
},
|
|
15137
|
+
text: { type: "string", description: "Plain-text fallback for push notifications and unfurls. Required." },
|
|
15138
|
+
thread_ts: { type: "string", description: "Thread timestamp for threaded replies (optional)" },
|
|
15139
|
+
interactive: {
|
|
15140
|
+
type: "object",
|
|
15141
|
+
description: `Optional interactive affordance appended to the message. Currently supports type="rate_run" \u2014 appends a 1-5 button row that updates this run's rating when tapped. Use after a scheduled-task delivery so users can rate the firing.`,
|
|
15142
|
+
properties: {
|
|
15143
|
+
type: { type: "string", enum: ["rate_run"], description: 'Affordance kind. Only "rate_run" is supported today.' },
|
|
15144
|
+
run_id: { type: "string", description: "The runs.run_id this rating belongs to (returned by /host/runs/start)." },
|
|
15145
|
+
scale: { type: "string", enum: ["1-5", "sentiment"], description: 'Rating scale. Default "1-5" (1..5 buttons). "sentiment" emits \u{1F44E}/\u{1F914}/\u{1F44D} mapping to -1/0/+1 \u2014 reserved for future Telegram parity.' },
|
|
15146
|
+
expires_in_seconds: { type: "number", description: "How long the rating row stays tappable. Default 86400 (24h). The pending row times out after this; later taps are ignored." }
|
|
15147
|
+
},
|
|
15148
|
+
required: ["type", "run_id"]
|
|
15149
|
+
}
|
|
15150
|
+
},
|
|
15151
|
+
required: ["channel", "blocks", "text"]
|
|
15152
|
+
}
|
|
15153
|
+
}
|
|
15154
|
+
] : [],
|
|
15155
|
+
...askUserToolAvailable() ? [
|
|
15156
|
+
{
|
|
15157
|
+
name: "slack.ask_user",
|
|
15158
|
+
description: "Post a question to Slack with multiple-choice buttons and BLOCK until the user taps one (or the timeout fires). Returns the user's choice as { value, label, responded_by, timed_out, responded_at }. Use sparingly \u2014 this stops the agent for up to 5 minutes by default. Prefer plain slack.reply for asking questions where you don't need a structured answer back.",
|
|
15159
|
+
inputSchema: {
|
|
15160
|
+
type: "object",
|
|
15161
|
+
properties: {
|
|
15162
|
+
channel: { type: "string", description: "Slack channel ID" },
|
|
15163
|
+
question: { type: "string", description: "The prompt shown above the buttons. Markdown is supported." },
|
|
15164
|
+
options: {
|
|
15165
|
+
type: "array",
|
|
15166
|
+
description: "Up to 5 options. Each is { label: string (button text, max 75 chars), value: string (returned to you on tap, max 2000 chars) }. Labels and values must be unique within the array."
|
|
15167
|
+
},
|
|
15168
|
+
timeout_seconds: {
|
|
15169
|
+
type: "number",
|
|
15170
|
+
description: "How long to wait for a response. Min 10, max 3600, default 300 (5 min)."
|
|
15171
|
+
},
|
|
15172
|
+
thread_ts: { type: "string", description: "Optional \u2014 post into a specific thread." }
|
|
15173
|
+
},
|
|
15174
|
+
required: ["channel", "question", "options"]
|
|
15175
|
+
}
|
|
15176
|
+
}
|
|
15177
|
+
] : [],
|
|
14713
15178
|
{
|
|
14714
15179
|
name: "slack.upload_file",
|
|
14715
15180
|
description: "Upload a file from the agent project dir to a Slack channel or thread. Use this for PDFs, images, reports, and any binary deliverables the agent generates. Path must be inside the agent's project directory.",
|
|
@@ -14777,6 +15242,30 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
14777
15242
|
const trackTs = thread_ts ?? data.ts ?? void 0;
|
|
14778
15243
|
rememberThread(channel, trackTs, thread_ts ? "mentioned" : "started");
|
|
14779
15244
|
}
|
|
15245
|
+
if (interactiveHostAvailable() && data.ts) {
|
|
15246
|
+
const cfg = {
|
|
15247
|
+
apiHost: AGT_HOST,
|
|
15248
|
+
apiKey: AGT_API_KEY,
|
|
15249
|
+
agentId: AGT_AGENT_ID
|
|
15250
|
+
};
|
|
15251
|
+
const tsToRecord = data.ts;
|
|
15252
|
+
const threadTsToRecord = thread_ts;
|
|
15253
|
+
void (async () => {
|
|
15254
|
+
try {
|
|
15255
|
+
const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
|
|
15256
|
+
await runtime.recordSlackDelivery(cfg, {
|
|
15257
|
+
channel,
|
|
15258
|
+
messageTs: tsToRecord,
|
|
15259
|
+
threadTs: threadTsToRecord
|
|
15260
|
+
});
|
|
15261
|
+
} catch (err) {
|
|
15262
|
+
process.stderr.write(
|
|
15263
|
+
`slack-channel(${AGENT_CODE_NAME}): recordSlackDelivery failed: ${err.message}
|
|
15264
|
+
`
|
|
15265
|
+
);
|
|
15266
|
+
}
|
|
15267
|
+
})();
|
|
15268
|
+
}
|
|
14780
15269
|
return { content: [{ type: "text", text: "sent" }] };
|
|
14781
15270
|
} catch (err) {
|
|
14782
15271
|
return {
|
|
@@ -15020,8 +15509,303 @@ ${formatted}` : "Thread is empty or not found."
|
|
|
15020
15509
|
};
|
|
15021
15510
|
}
|
|
15022
15511
|
}
|
|
15512
|
+
if (name === "slack.send_structured") {
|
|
15513
|
+
return await handleSendStructured(args);
|
|
15514
|
+
}
|
|
15515
|
+
if (name === "slack.ask_user") {
|
|
15516
|
+
return await handleAskUser(args);
|
|
15517
|
+
}
|
|
15023
15518
|
throw new Error(`Unknown tool: ${name}`);
|
|
15024
15519
|
});
|
|
15520
|
+
function blockKitToolsAvailable() {
|
|
15521
|
+
return BLOCK_KIT_ENABLED && !BLOCK_KIT_DISABLED;
|
|
15522
|
+
}
|
|
15523
|
+
function askUserToolAvailable() {
|
|
15524
|
+
return blockKitToolsAvailable() && BLOCK_KIT_ASK_USER_ENABLED && Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
|
|
15525
|
+
}
|
|
15526
|
+
function interactiveHostAvailable() {
|
|
15527
|
+
return blockKitToolsAvailable() && Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
|
|
15528
|
+
}
|
|
15529
|
+
async function postSlackMessageWithTs(payload) {
|
|
15530
|
+
try {
|
|
15531
|
+
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
|
15532
|
+
method: "POST",
|
|
15533
|
+
headers: {
|
|
15534
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
15535
|
+
Authorization: `Bearer ${BOT_TOKEN}`
|
|
15536
|
+
},
|
|
15537
|
+
body: JSON.stringify(payload),
|
|
15538
|
+
signal: AbortSignal.timeout(SLACK_DOWNLOAD_TIMEOUT_MS)
|
|
15539
|
+
});
|
|
15540
|
+
return await res.json();
|
|
15541
|
+
} catch (err) {
|
|
15542
|
+
const isTimeout = err.name === "TimeoutError" || err.name === "AbortError";
|
|
15543
|
+
return { ok: false, error: isTimeout ? "timeout" : err.message };
|
|
15544
|
+
}
|
|
15545
|
+
}
|
|
15546
|
+
async function getPermalink(channel, ts) {
|
|
15547
|
+
try {
|
|
15548
|
+
const url = `https://slack.com/api/chat.getPermalink?channel=${encodeURIComponent(channel)}&message_ts=${encodeURIComponent(ts)}`;
|
|
15549
|
+
const res = await fetch(url, {
|
|
15550
|
+
headers: { Authorization: `Bearer ${BOT_TOKEN}` },
|
|
15551
|
+
signal: AbortSignal.timeout(SLACK_DOWNLOAD_TIMEOUT_MS)
|
|
15552
|
+
});
|
|
15553
|
+
const data = await res.json();
|
|
15554
|
+
return data.ok && typeof data.permalink === "string" ? data.permalink : null;
|
|
15555
|
+
} catch {
|
|
15556
|
+
return null;
|
|
15557
|
+
}
|
|
15558
|
+
}
|
|
15559
|
+
async function handleSendStructured(args) {
|
|
15560
|
+
if (!blockKitToolsAvailable()) {
|
|
15561
|
+
return {
|
|
15562
|
+
content: [{ type: "text", text: "slack.send_structured is disabled by team configuration." }],
|
|
15563
|
+
isError: true
|
|
15564
|
+
};
|
|
15565
|
+
}
|
|
15566
|
+
const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
|
|
15567
|
+
const { validateSlackBlocks: validateSlackBlocks2 } = runtime;
|
|
15568
|
+
const { channel, blocks, text, thread_ts, interactive } = args;
|
|
15569
|
+
if (typeof channel !== "string" || !channel) {
|
|
15570
|
+
return errResult("channel is required");
|
|
15571
|
+
}
|
|
15572
|
+
if (typeof text !== "string" || !text) {
|
|
15573
|
+
return errResult("text is required (used as fallback for push notifications and unfurls)");
|
|
15574
|
+
}
|
|
15575
|
+
const validation = validateSlackBlocks2(blocks);
|
|
15576
|
+
if (!validation.ok) {
|
|
15577
|
+
return errResult(`Invalid blocks: ${validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
15578
|
+
}
|
|
15579
|
+
let effectiveBlocks = blocks;
|
|
15580
|
+
let ratingCallbackId = null;
|
|
15581
|
+
let ratingExpiresAt = null;
|
|
15582
|
+
let ratingTokenised = [];
|
|
15583
|
+
if (interactive) {
|
|
15584
|
+
if (interactive.type !== "rate_run") {
|
|
15585
|
+
return errResult(`Unsupported interactive.type '${interactive.type}'. Supported: rate_run.`);
|
|
15586
|
+
}
|
|
15587
|
+
if (typeof interactive.run_id !== "string" || !interactive.run_id) {
|
|
15588
|
+
return errResult("interactive.run_id is required when interactive is set");
|
|
15589
|
+
}
|
|
15590
|
+
if (!interactiveHostAvailable()) {
|
|
15591
|
+
return errResult(
|
|
15592
|
+
"interactive=rate_run requires Block Kit plus host MCP wiring (AGT_HOST, AGT_API_KEY, AGT_AGENT_ID)."
|
|
15593
|
+
);
|
|
15594
|
+
}
|
|
15595
|
+
const scale = interactive.scale === "sentiment" ? "sentiment" : "1-5";
|
|
15596
|
+
const requestedTtl = typeof interactive.expires_in_seconds === "number" ? interactive.expires_in_seconds : 86400;
|
|
15597
|
+
const ttl = Math.max(60, Math.min(7 * 86400, requestedTtl));
|
|
15598
|
+
const { randomUUID: rndUUID } = await import("crypto");
|
|
15599
|
+
ratingCallbackId = rndUUID();
|
|
15600
|
+
ratingExpiresAt = new Date(Date.now() + ttl * 1e3);
|
|
15601
|
+
const values = runtime.ratingValuesForScale(scale);
|
|
15602
|
+
const labels = runtime.defaultRatingLabels(scale);
|
|
15603
|
+
ratingTokenised = values.map((v, i) => ({
|
|
15604
|
+
token: runtime.generateOptionToken(),
|
|
15605
|
+
value: String(v),
|
|
15606
|
+
label: labels[i] ?? String(v)
|
|
15607
|
+
}));
|
|
15608
|
+
const ratingBlock = runtime.buildRatingActionsBlock({
|
|
15609
|
+
callbackId: ratingCallbackId,
|
|
15610
|
+
options: ratingTokenised.map(({ label, token }) => ({
|
|
15611
|
+
// The numeric value is carried by token → options mapping in the
|
|
15612
|
+
// pending_interaction row, not by the button itself, so this
|
|
15613
|
+
// cast-back to RatingOption only needs label + token + a placeholder.
|
|
15614
|
+
value: 0,
|
|
15615
|
+
label,
|
|
15616
|
+
token
|
|
15617
|
+
}))
|
|
15618
|
+
});
|
|
15619
|
+
effectiveBlocks = [...blocks, ratingBlock];
|
|
15620
|
+
const finalValidation = runtime.validateSlackBlocks(effectiveBlocks);
|
|
15621
|
+
if (!finalValidation.ok) {
|
|
15622
|
+
return errResult(
|
|
15623
|
+
`Invalid blocks (after appending rating row): ${finalValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
15624
|
+
);
|
|
15625
|
+
}
|
|
15626
|
+
const cfg = {
|
|
15627
|
+
apiHost: AGT_HOST,
|
|
15628
|
+
apiKey: AGT_API_KEY,
|
|
15629
|
+
agentId: AGT_AGENT_ID
|
|
15630
|
+
};
|
|
15631
|
+
try {
|
|
15632
|
+
await runtime.createPendingInteraction(cfg, {
|
|
15633
|
+
callbackId: ratingCallbackId,
|
|
15634
|
+
channelId: channel,
|
|
15635
|
+
messageTs: "",
|
|
15636
|
+
threadTs: thread_ts,
|
|
15637
|
+
options: ratingTokenised,
|
|
15638
|
+
expiresAt: ratingExpiresAt,
|
|
15639
|
+
kind: "rate_run",
|
|
15640
|
+
kindData: { run_id: interactive.run_id, scale }
|
|
15641
|
+
});
|
|
15642
|
+
} catch (err) {
|
|
15643
|
+
return errResult(`Failed to register rating row: ${err.message}`);
|
|
15644
|
+
}
|
|
15645
|
+
}
|
|
15646
|
+
const result = await postSlackMessageWithTs({
|
|
15647
|
+
channel,
|
|
15648
|
+
blocks: effectiveBlocks,
|
|
15649
|
+
text,
|
|
15650
|
+
...thread_ts ? { thread_ts } : {}
|
|
15651
|
+
});
|
|
15652
|
+
if (!result.ok || !result.ts) {
|
|
15653
|
+
return errResult(`Slack chat.postMessage failed: ${result.error ?? "unknown"}`);
|
|
15654
|
+
}
|
|
15655
|
+
if (ratingCallbackId) {
|
|
15656
|
+
try {
|
|
15657
|
+
await runtime.updatePendingInteractionMessageTs(
|
|
15658
|
+
{ apiHost: AGT_HOST, apiKey: AGT_API_KEY, agentId: AGT_AGENT_ID },
|
|
15659
|
+
ratingCallbackId,
|
|
15660
|
+
result.ts
|
|
15661
|
+
);
|
|
15662
|
+
} catch (err) {
|
|
15663
|
+
process.stderr.write(
|
|
15664
|
+
`slack-channel(${AGENT_CODE_NAME}): updatePendingInteractionMessageTs failed for rating ${hashId(ratingCallbackId)}: ${err.message}
|
|
15665
|
+
`
|
|
15666
|
+
);
|
|
15667
|
+
}
|
|
15668
|
+
}
|
|
15669
|
+
if (interactiveHostAvailable() && !ratingCallbackId) {
|
|
15670
|
+
const cfg = {
|
|
15671
|
+
apiHost: AGT_HOST,
|
|
15672
|
+
apiKey: AGT_API_KEY,
|
|
15673
|
+
agentId: AGT_AGENT_ID
|
|
15674
|
+
};
|
|
15675
|
+
const tsToRecord = result.ts;
|
|
15676
|
+
const threadTsToRecord = thread_ts;
|
|
15677
|
+
void (async () => {
|
|
15678
|
+
try {
|
|
15679
|
+
await runtime.recordSlackDelivery(cfg, {
|
|
15680
|
+
channel,
|
|
15681
|
+
messageTs: tsToRecord,
|
|
15682
|
+
threadTs: threadTsToRecord
|
|
15683
|
+
});
|
|
15684
|
+
} catch (err) {
|
|
15685
|
+
process.stderr.write(
|
|
15686
|
+
`slack-channel(${AGENT_CODE_NAME}): recordSlackDelivery (send_structured) failed: ${err.message}
|
|
15687
|
+
`
|
|
15688
|
+
);
|
|
15689
|
+
}
|
|
15690
|
+
})();
|
|
15691
|
+
}
|
|
15692
|
+
const permalink = await getPermalink(channel, result.ts);
|
|
15693
|
+
return {
|
|
15694
|
+
content: [
|
|
15695
|
+
{
|
|
15696
|
+
type: "text",
|
|
15697
|
+
text: JSON.stringify({
|
|
15698
|
+
message_ts: result.ts,
|
|
15699
|
+
permalink: permalink ?? "",
|
|
15700
|
+
...ratingCallbackId ? { rating_callback_id: ratingCallbackId } : {}
|
|
15701
|
+
})
|
|
15702
|
+
}
|
|
15703
|
+
]
|
|
15704
|
+
};
|
|
15705
|
+
}
|
|
15706
|
+
async function handleAskUser(args) {
|
|
15707
|
+
if (!askUserToolAvailable()) {
|
|
15708
|
+
return errResult("slack.ask_user is disabled or the host MCP is missing AGT_HOST/AGT_API_KEY/AGT_AGENT_ID env wiring.");
|
|
15709
|
+
}
|
|
15710
|
+
const { randomUUID: rndUUID } = await import("crypto");
|
|
15711
|
+
const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
|
|
15712
|
+
const { validateAskUserOptions: validateAskUserOptions2, buildAskUserBlocks: buildAskUserBlocks2 } = runtime;
|
|
15713
|
+
const { channel, question, options, timeout_seconds, thread_ts } = args;
|
|
15714
|
+
if (typeof channel !== "string" || !channel) return errResult("channel is required");
|
|
15715
|
+
if (typeof question !== "string" || !question) return errResult("question is required");
|
|
15716
|
+
const optsValidation = validateAskUserOptions2(options);
|
|
15717
|
+
if (!optsValidation.ok) {
|
|
15718
|
+
return errResult(`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
15719
|
+
}
|
|
15720
|
+
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
15721
|
+
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
15722
|
+
const callbackId = rndUUID();
|
|
15723
|
+
const optionsArr = options;
|
|
15724
|
+
const tokenised = optionsArr.map(({ label, value }) => ({
|
|
15725
|
+
token: runtime.generateOptionToken(),
|
|
15726
|
+
label,
|
|
15727
|
+
value
|
|
15728
|
+
}));
|
|
15729
|
+
const blocks = buildAskUserBlocks2({
|
|
15730
|
+
callbackId,
|
|
15731
|
+
question,
|
|
15732
|
+
options: tokenised.map(({ label, token }) => ({ label, token }))
|
|
15733
|
+
});
|
|
15734
|
+
const cfg = {
|
|
15735
|
+
apiHost: AGT_HOST,
|
|
15736
|
+
apiKey: AGT_API_KEY,
|
|
15737
|
+
agentId: AGT_AGENT_ID
|
|
15738
|
+
};
|
|
15739
|
+
const expiresAt = new Date(Date.now() + timeoutSec * 1e3);
|
|
15740
|
+
try {
|
|
15741
|
+
await runtime.createPendingInteraction(cfg, {
|
|
15742
|
+
callbackId,
|
|
15743
|
+
channelId: channel,
|
|
15744
|
+
messageTs: "",
|
|
15745
|
+
// placeholder — patched after Slack returns the real ts
|
|
15746
|
+
threadTs: thread_ts,
|
|
15747
|
+
options: tokenised,
|
|
15748
|
+
expiresAt
|
|
15749
|
+
});
|
|
15750
|
+
} catch (err) {
|
|
15751
|
+
return errResult(`Failed to register pending interaction: ${err.message}`);
|
|
15752
|
+
}
|
|
15753
|
+
const slackResult = await postSlackMessageWithTs({
|
|
15754
|
+
channel,
|
|
15755
|
+
blocks,
|
|
15756
|
+
text: question,
|
|
15757
|
+
// fallback for push notifications
|
|
15758
|
+
...thread_ts ? { thread_ts } : {}
|
|
15759
|
+
});
|
|
15760
|
+
if (!slackResult.ok || !slackResult.ts) {
|
|
15761
|
+
return errResult(`Slack chat.postMessage failed: ${slackResult.error ?? "unknown"}`);
|
|
15762
|
+
}
|
|
15763
|
+
try {
|
|
15764
|
+
await runtime.updatePendingInteractionMessageTs(cfg, callbackId, slackResult.ts);
|
|
15765
|
+
} catch (err) {
|
|
15766
|
+
process.stderr.write(
|
|
15767
|
+
`slack-channel(${AGENT_CODE_NAME}): updatePendingInteractionMessageTs failed for ${callbackId}: ${err.message}
|
|
15768
|
+
`
|
|
15769
|
+
);
|
|
15770
|
+
}
|
|
15771
|
+
const resolution = await runtime.waitForResolution(cfg, callbackId, {
|
|
15772
|
+
deadline: expiresAt
|
|
15773
|
+
});
|
|
15774
|
+
if (resolution.kind === "timeout") {
|
|
15775
|
+
return {
|
|
15776
|
+
content: [
|
|
15777
|
+
{
|
|
15778
|
+
type: "text",
|
|
15779
|
+
text: JSON.stringify({
|
|
15780
|
+
value: null,
|
|
15781
|
+
label: null,
|
|
15782
|
+
responded_by: null,
|
|
15783
|
+
responded_at: null,
|
|
15784
|
+
timed_out: true
|
|
15785
|
+
})
|
|
15786
|
+
}
|
|
15787
|
+
]
|
|
15788
|
+
};
|
|
15789
|
+
}
|
|
15790
|
+
const matched = tokenised.find((o) => o.value === resolution.value);
|
|
15791
|
+
return {
|
|
15792
|
+
content: [
|
|
15793
|
+
{
|
|
15794
|
+
type: "text",
|
|
15795
|
+
text: JSON.stringify({
|
|
15796
|
+
value: resolution.value,
|
|
15797
|
+
label: matched?.label ?? resolution.value,
|
|
15798
|
+
responded_by: resolution.respondedByUser,
|
|
15799
|
+
responded_at: resolution.respondedAt,
|
|
15800
|
+
timed_out: false
|
|
15801
|
+
})
|
|
15802
|
+
}
|
|
15803
|
+
]
|
|
15804
|
+
};
|
|
15805
|
+
}
|
|
15806
|
+
function errResult(text) {
|
|
15807
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
15808
|
+
}
|
|
15025
15809
|
var MAX_INBOUND_FILE_BYTES = 25 * 1024 * 1024;
|
|
15026
15810
|
var SLACK_DOWNLOAD_TIMEOUT_MS = 15e3;
|
|
15027
15811
|
var DOWNLOAD_ALLOWLIST_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -15217,6 +16001,44 @@ async function connectSocketMode() {
|
|
|
15217
16001
|
if (msg.envelope_id) {
|
|
15218
16002
|
ws.send(JSON.stringify({ envelope_id: msg.envelope_id }));
|
|
15219
16003
|
}
|
|
16004
|
+
if (msg.type === "interactive" && msg.payload?.type === "block_actions") {
|
|
16005
|
+
if (!interactiveHostAvailable()) {
|
|
16006
|
+
process.stderr.write(
|
|
16007
|
+
`slack-channel(${AGENT_CODE_NAME}): block_actions received but Block Kit + host MCP wiring missing \u2014 dropping
|
|
16008
|
+
`
|
|
16009
|
+
);
|
|
16010
|
+
return;
|
|
16011
|
+
}
|
|
16012
|
+
const action = msg.payload.actions?.[0];
|
|
16013
|
+
if (!action || typeof action.action_id !== "string") return;
|
|
16014
|
+
const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
|
|
16015
|
+
const decoded = runtime.decodeActionId(action.action_id);
|
|
16016
|
+
if (!decoded) {
|
|
16017
|
+
return;
|
|
16018
|
+
}
|
|
16019
|
+
try {
|
|
16020
|
+
await runtime.resolveInteractive(
|
|
16021
|
+
{
|
|
16022
|
+
apiHost: AGT_HOST,
|
|
16023
|
+
apiKey: AGT_API_KEY,
|
|
16024
|
+
agentId: AGT_AGENT_ID
|
|
16025
|
+
},
|
|
16026
|
+
{
|
|
16027
|
+
callbackId: decoded.callbackId,
|
|
16028
|
+
token: decoded.token,
|
|
16029
|
+
respondedByUser: msg.payload.user?.id,
|
|
16030
|
+
originalBlocks: msg.payload.message?.blocks,
|
|
16031
|
+
responseUrl: msg.payload.response_url
|
|
16032
|
+
}
|
|
16033
|
+
);
|
|
16034
|
+
} catch (err) {
|
|
16035
|
+
process.stderr.write(
|
|
16036
|
+
`slack-channel(${AGENT_CODE_NAME}): resolveInteractive failed for ${hashId(decoded.callbackId)}: ${err.message}
|
|
16037
|
+
`
|
|
16038
|
+
);
|
|
16039
|
+
}
|
|
16040
|
+
return;
|
|
16041
|
+
}
|
|
15220
16042
|
if (msg.type !== "events_api" || !msg.payload?.event) return;
|
|
15221
16043
|
const evt = msg.payload.event;
|
|
15222
16044
|
if (evt.user === botUserId) return;
|