@integrity-labs/agt-cli 0.21.9 → 0.22.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/bin/agt.js +29 -27
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-3ZO3LKA2.js → chunk-7AOVUCPO.js} +68 -3145
- package/dist/chunk-7AOVUCPO.js.map +1 -0
- package/dist/{chunk-QO2FOMWJ.js → chunk-KTPMEVEO.js} +84 -4
- package/dist/chunk-KTPMEVEO.js.map +1 -0
- package/dist/chunk-ZNRQSPPM.js +3151 -0
- package/dist/chunk-ZNRQSPPM.js.map +1 -0
- package/dist/{claude-pair-runtime-KXWTU5YT.js → claude-pair-runtime-L35F6K5E.js} +2 -2
- package/dist/lib/manager-worker.js +210 -84
- package/dist/lib/manager-worker.js.map +1 -1
- package/{mcp → dist/mcp}/index.js +0 -0
- package/{mcp → dist/mcp}/slack-channel.js +0 -0
- package/{mcp → dist/mcp}/telegram-channel.js +672 -14
- package/dist/{persistent-session-3LVAPC3T.js → persistent-session-XG4QK2U5.js} +11 -2
- package/dist/responsiveness-probe-LIXEVHUJ.js +32 -0
- package/dist/responsiveness-probe-LIXEVHUJ.js.map +1 -0
- package/package.json +3 -4
- package/dist/chunk-3ZO3LKA2.js.map +0 -1
- package/dist/chunk-QO2FOMWJ.js.map +0 -1
- /package/dist/{claude-pair-runtime-KXWTU5YT.js.map → claude-pair-runtime-L35F6K5E.js.map} +0 -0
- /package/{mcp → dist/mcp}/direct-chat-channel.js +0 -0
- /package/dist/{persistent-session-3LVAPC3T.js.map → persistent-session-XG4QK2U5.js.map} +0 -0
|
@@ -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,433 @@ var require_dist = __commonJS({
|
|
|
6798
6801
|
}
|
|
6799
6802
|
});
|
|
6800
6803
|
|
|
6804
|
+
// src/ask-user-runtime.ts
|
|
6805
|
+
var ask_user_runtime_exports = {};
|
|
6806
|
+
__export(ask_user_runtime_exports, {
|
|
6807
|
+
DEFAULT_ASK_USER_LIMITS: () => DEFAULT_ASK_USER_LIMITS,
|
|
6808
|
+
apiCall: () => apiCall,
|
|
6809
|
+
createPendingInteraction: () => createPendingInteraction,
|
|
6810
|
+
generateOptionToken: () => generateOptionToken,
|
|
6811
|
+
updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
|
|
6812
|
+
validateAskUserOptions: () => validateAskUserOptions,
|
|
6813
|
+
waitForResolution: () => waitForResolution
|
|
6814
|
+
});
|
|
6815
|
+
async function getAuthToken(cfg) {
|
|
6816
|
+
if (auth.token && Date.now() < auth.expiresAtEpochMs) return auth.token;
|
|
6817
|
+
const res = await fetch(`${cfg.apiHost}/host/exchange`, {
|
|
6818
|
+
method: "POST",
|
|
6819
|
+
headers: { "Content-Type": "application/json" },
|
|
6820
|
+
body: JSON.stringify({ host_key: cfg.apiKey }),
|
|
6821
|
+
signal: AbortSignal.timeout(API_FETCH_TIMEOUT_MS)
|
|
6822
|
+
});
|
|
6823
|
+
if (!res.ok) {
|
|
6824
|
+
const body = await res.text().catch(() => "");
|
|
6825
|
+
throw new Error(`AGT auth exchange failed (${res.status}): ${body.slice(0, 200)}`);
|
|
6826
|
+
}
|
|
6827
|
+
const data = await res.json();
|
|
6828
|
+
auth.token = data.token;
|
|
6829
|
+
auth.expiresAtEpochMs = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
|
|
6830
|
+
return auth.token;
|
|
6831
|
+
}
|
|
6832
|
+
async function apiCall(cfg, method, path, body) {
|
|
6833
|
+
const token = await getAuthToken(cfg);
|
|
6834
|
+
const buildInit = (authToken) => ({
|
|
6835
|
+
method,
|
|
6836
|
+
headers: {
|
|
6837
|
+
"Content-Type": "application/json",
|
|
6838
|
+
Authorization: `Bearer ${authToken}`
|
|
6839
|
+
},
|
|
6840
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
6841
|
+
signal: AbortSignal.timeout(API_FETCH_TIMEOUT_MS)
|
|
6842
|
+
});
|
|
6843
|
+
const res = await fetch(`${cfg.apiHost}${path}`, buildInit(token));
|
|
6844
|
+
if (res.status !== 401) return res;
|
|
6845
|
+
auth.token = null;
|
|
6846
|
+
auth.expiresAtEpochMs = 0;
|
|
6847
|
+
const fresh = await getAuthToken(cfg);
|
|
6848
|
+
return fetch(`${cfg.apiHost}${path}`, buildInit(fresh));
|
|
6849
|
+
}
|
|
6850
|
+
async function createPendingInteraction(cfg, input) {
|
|
6851
|
+
const res = await apiCall(cfg, "POST", "/host/pending-interactions", {
|
|
6852
|
+
agent_id: cfg.agentId,
|
|
6853
|
+
callback_id: input.callbackId,
|
|
6854
|
+
channel_id: input.channelId,
|
|
6855
|
+
message_ts: input.messageTs ?? "",
|
|
6856
|
+
thread_ts: input.threadTs,
|
|
6857
|
+
options: input.options,
|
|
6858
|
+
expires_at: input.expiresAt.toISOString(),
|
|
6859
|
+
kind: input.kind,
|
|
6860
|
+
kind_data: input.kindData
|
|
6861
|
+
});
|
|
6862
|
+
if (!res.ok) {
|
|
6863
|
+
const body = await res.text().catch(() => "");
|
|
6864
|
+
throw new Error(`createPendingInteraction failed (${res.status}): ${body.slice(0, 200)}`);
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
async function updatePendingInteractionMessageTs(cfg, callbackId, messageTs) {
|
|
6868
|
+
const res = await apiCall(
|
|
6869
|
+
cfg,
|
|
6870
|
+
"PATCH",
|
|
6871
|
+
`/host/pending-interactions/${encodeURIComponent(callbackId)}`,
|
|
6872
|
+
{ agent_id: cfg.agentId, message_ts: messageTs }
|
|
6873
|
+
);
|
|
6874
|
+
if (!res.ok) {
|
|
6875
|
+
const body = await res.text().catch(() => "");
|
|
6876
|
+
throw new Error(
|
|
6877
|
+
`updatePendingInteractionMessageTs failed (${res.status}): ${body.slice(0, 200)}`
|
|
6878
|
+
);
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
async function waitForResolution(cfg, callbackId, opts) {
|
|
6882
|
+
const interval = opts.pollIntervalMs ?? 500;
|
|
6883
|
+
const deadlineMs = opts.deadline.getTime();
|
|
6884
|
+
let firstPass = true;
|
|
6885
|
+
while (firstPass || Date.now() < deadlineMs) {
|
|
6886
|
+
firstPass = false;
|
|
6887
|
+
try {
|
|
6888
|
+
const res = await apiCall(
|
|
6889
|
+
cfg,
|
|
6890
|
+
"GET",
|
|
6891
|
+
`/host/pending-interactions/${encodeURIComponent(callbackId)}?agent_id=${encodeURIComponent(cfg.agentId)}`
|
|
6892
|
+
);
|
|
6893
|
+
if (res.ok) {
|
|
6894
|
+
const row = await res.json();
|
|
6895
|
+
if (row.status === "resolved") {
|
|
6896
|
+
return {
|
|
6897
|
+
kind: "resolved",
|
|
6898
|
+
value: row.value ?? "",
|
|
6899
|
+
respondedByUser: row.responded_by_user,
|
|
6900
|
+
respondedAt: row.responded_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
6901
|
+
};
|
|
6902
|
+
}
|
|
6903
|
+
if (row.status === "timeout") {
|
|
6904
|
+
return { kind: "timeout" };
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6907
|
+
} catch {
|
|
6908
|
+
}
|
|
6909
|
+
await sleep(interval);
|
|
6910
|
+
}
|
|
6911
|
+
return { kind: "timeout" };
|
|
6912
|
+
}
|
|
6913
|
+
function sleep(ms) {
|
|
6914
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
6915
|
+
}
|
|
6916
|
+
function generateOptionToken() {
|
|
6917
|
+
const bytes = new Uint8Array(12);
|
|
6918
|
+
const webCrypto = globalThis.crypto;
|
|
6919
|
+
if (webCrypto && typeof webCrypto.getRandomValues === "function") {
|
|
6920
|
+
webCrypto.getRandomValues(bytes);
|
|
6921
|
+
} else {
|
|
6922
|
+
throw new Error(
|
|
6923
|
+
"generateOptionToken: no crypto.getRandomValues implementation available. Need Node 20+ or a Web Crypto polyfill."
|
|
6924
|
+
);
|
|
6925
|
+
}
|
|
6926
|
+
const out = Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
6927
|
+
return out;
|
|
6928
|
+
}
|
|
6929
|
+
function validateAskUserOptions(options, limits = DEFAULT_ASK_USER_LIMITS) {
|
|
6930
|
+
const errors = [];
|
|
6931
|
+
if (!Array.isArray(options)) {
|
|
6932
|
+
return { ok: false, errors: [{ path: "options", message: "must be an array" }] };
|
|
6933
|
+
}
|
|
6934
|
+
if (options.length === 0) {
|
|
6935
|
+
errors.push({ path: "options", message: "must contain at least one option" });
|
|
6936
|
+
}
|
|
6937
|
+
if (options.length > limits.maxOptions) {
|
|
6938
|
+
errors.push({
|
|
6939
|
+
path: "options",
|
|
6940
|
+
message: `max ${limits.maxOptions} options`
|
|
6941
|
+
});
|
|
6942
|
+
}
|
|
6943
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
6944
|
+
const seenValues = /* @__PURE__ */ new Set();
|
|
6945
|
+
options.forEach((opt, i) => {
|
|
6946
|
+
const o = opt;
|
|
6947
|
+
const path = `options[${i}]`;
|
|
6948
|
+
if (typeof o?.label !== "string" || o.label.length === 0) {
|
|
6949
|
+
errors.push({ path: `${path}.label`, message: "required string" });
|
|
6950
|
+
} else {
|
|
6951
|
+
if (o.label.length > limits.maxLabelChars) {
|
|
6952
|
+
errors.push({ path: `${path}.label`, message: `max ${limits.maxLabelChars} chars` });
|
|
6953
|
+
}
|
|
6954
|
+
if (seenLabels.has(o.label)) errors.push({ path: `${path}.label`, message: "duplicate label" });
|
|
6955
|
+
seenLabels.add(o.label);
|
|
6956
|
+
}
|
|
6957
|
+
if (typeof o?.value !== "string" || o.value.length === 0) {
|
|
6958
|
+
errors.push({ path: `${path}.value`, message: "required string" });
|
|
6959
|
+
} else {
|
|
6960
|
+
if (o.value.length > limits.maxValueChars) {
|
|
6961
|
+
errors.push({ path: `${path}.value`, message: `max ${limits.maxValueChars} chars` });
|
|
6962
|
+
}
|
|
6963
|
+
if (seenValues.has(o.value)) errors.push({ path: `${path}.value`, message: "duplicate value" });
|
|
6964
|
+
seenValues.add(o.value);
|
|
6965
|
+
}
|
|
6966
|
+
});
|
|
6967
|
+
return { ok: errors.length === 0, errors };
|
|
6968
|
+
}
|
|
6969
|
+
var auth, API_FETCH_TIMEOUT_MS, DEFAULT_ASK_USER_LIMITS;
|
|
6970
|
+
var init_ask_user_runtime = __esm({
|
|
6971
|
+
"src/ask-user-runtime.ts"() {
|
|
6972
|
+
"use strict";
|
|
6973
|
+
auth = { token: null, expiresAtEpochMs: 0 };
|
|
6974
|
+
API_FETCH_TIMEOUT_MS = 1e4;
|
|
6975
|
+
DEFAULT_ASK_USER_LIMITS = {
|
|
6976
|
+
maxOptions: 5,
|
|
6977
|
+
maxLabelChars: 75,
|
|
6978
|
+
maxValueChars: 2e3
|
|
6979
|
+
};
|
|
6980
|
+
}
|
|
6981
|
+
});
|
|
6982
|
+
|
|
6983
|
+
// src/slack-block-kit-runtime.ts
|
|
6984
|
+
var slack_block_kit_runtime_exports = {};
|
|
6985
|
+
__export(slack_block_kit_runtime_exports, {
|
|
6986
|
+
DEFAULT_ASK_USER_LIMITS: () => DEFAULT_ASK_USER_LIMITS,
|
|
6987
|
+
apiCall: () => apiCall,
|
|
6988
|
+
buildAskUserBlocks: () => buildAskUserBlocks,
|
|
6989
|
+
buildRatingActionsBlock: () => buildRatingActionsBlock,
|
|
6990
|
+
createPendingInteraction: () => createPendingInteraction,
|
|
6991
|
+
decodeActionId: () => decodeActionId,
|
|
6992
|
+
defaultRatingLabels: () => defaultRatingLabels,
|
|
6993
|
+
encodeActionId: () => encodeActionId,
|
|
6994
|
+
generateOptionToken: () => generateOptionToken,
|
|
6995
|
+
ratingValuesForScale: () => ratingValuesForScale,
|
|
6996
|
+
recordSlackDelivery: () => recordSlackDelivery,
|
|
6997
|
+
resolveInteractive: () => resolveInteractive,
|
|
6998
|
+
updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
|
|
6999
|
+
validateAskUserOptions: () => validateAskUserOptions,
|
|
7000
|
+
validateSlackBlocks: () => validateSlackBlocks,
|
|
7001
|
+
waitForResolution: () => waitForResolution
|
|
7002
|
+
});
|
|
7003
|
+
function validateSlackBlocks(blocks) {
|
|
7004
|
+
const errors = [];
|
|
7005
|
+
const cap = 10;
|
|
7006
|
+
const push = (path, message) => {
|
|
7007
|
+
if (errors.length < cap) errors.push({ path, message });
|
|
7008
|
+
};
|
|
7009
|
+
if (!Array.isArray(blocks)) {
|
|
7010
|
+
return { ok: false, errors: [{ path: "blocks", message: "must be an array" }] };
|
|
7011
|
+
}
|
|
7012
|
+
if (blocks.length === 0) {
|
|
7013
|
+
return { ok: false, errors: [{ path: "blocks", message: "must contain at least one block" }] };
|
|
7014
|
+
}
|
|
7015
|
+
if (blocks.length > SLACK_LIMITS.blocksPerMessage) {
|
|
7016
|
+
push("blocks", `max ${SLACK_LIMITS.blocksPerMessage} blocks per message (got ${blocks.length})`);
|
|
7017
|
+
}
|
|
7018
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
7019
|
+
const block = blocks[i];
|
|
7020
|
+
const path = `blocks[${i}]`;
|
|
7021
|
+
const type = block?.type;
|
|
7022
|
+
if (typeof type !== "string") {
|
|
7023
|
+
push(path, "`type` is required and must be a string");
|
|
7024
|
+
continue;
|
|
7025
|
+
}
|
|
7026
|
+
if (!SUPPORTED_BLOCK_TYPES.includes(type)) {
|
|
7027
|
+
push(path, `unsupported block type "${type}" \u2014 supported: ${SUPPORTED_BLOCK_TYPES.join(", ")}`);
|
|
7028
|
+
continue;
|
|
7029
|
+
}
|
|
7030
|
+
if ("block_id" in block && typeof block.block_id === "string" && block.block_id.length > SLACK_LIMITS.identifierChars) {
|
|
7031
|
+
push(`${path}.block_id`, `max ${SLACK_LIMITS.identifierChars} chars`);
|
|
7032
|
+
}
|
|
7033
|
+
if (type === "header") validateHeaderBlock(block, path, push);
|
|
7034
|
+
else if (type === "section") validateSectionBlock(block, path, push);
|
|
7035
|
+
else if (type === "context") validateContextBlock(block, path, push);
|
|
7036
|
+
else if (type === "actions") validateActionsBlock(block, path, push);
|
|
7037
|
+
}
|
|
7038
|
+
return { ok: errors.length === 0, errors };
|
|
7039
|
+
}
|
|
7040
|
+
function validateHeaderBlock(b, path, push) {
|
|
7041
|
+
const text = b.text;
|
|
7042
|
+
if (!text) {
|
|
7043
|
+
push(`${path}.text`, "header requires a text object");
|
|
7044
|
+
return;
|
|
7045
|
+
}
|
|
7046
|
+
validateTextObject(text, `${path}.text`, SLACK_LIMITS.headerTextChars, ["plain_text"], push);
|
|
7047
|
+
}
|
|
7048
|
+
function validateSectionBlock(b, path, push) {
|
|
7049
|
+
const text = b.text;
|
|
7050
|
+
const fields = b.fields;
|
|
7051
|
+
if (!text && !fields) {
|
|
7052
|
+
push(path, "section must have at least one of `text` or `fields`");
|
|
7053
|
+
return;
|
|
7054
|
+
}
|
|
7055
|
+
if (text) {
|
|
7056
|
+
validateTextObject(text, `${path}.text`, SLACK_LIMITS.sectionTextChars, ["plain_text", "mrkdwn"], push);
|
|
7057
|
+
}
|
|
7058
|
+
if (fields) {
|
|
7059
|
+
if (!Array.isArray(fields)) {
|
|
7060
|
+
push(`${path}.fields`, "`fields` must be an array");
|
|
7061
|
+
} else if (fields.length > SLACK_LIMITS.sectionFields) {
|
|
7062
|
+
push(`${path}.fields`, `max ${SLACK_LIMITS.sectionFields} fields`);
|
|
7063
|
+
} else {
|
|
7064
|
+
fields.forEach((f, i) => {
|
|
7065
|
+
validateTextObject(f, `${path}.fields[${i}]`, SLACK_LIMITS.sectionFieldChars, ["plain_text", "mrkdwn"], push);
|
|
7066
|
+
});
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
function validateContextBlock(b, path, push) {
|
|
7071
|
+
const elements = b.elements;
|
|
7072
|
+
if (!Array.isArray(elements) || elements.length === 0) {
|
|
7073
|
+
push(`${path}.elements`, "context requires non-empty `elements` array");
|
|
7074
|
+
return;
|
|
7075
|
+
}
|
|
7076
|
+
elements.forEach((el, i) => {
|
|
7077
|
+
validateTextObject(el, `${path}.elements[${i}]`, SLACK_LIMITS.sectionTextChars, ["plain_text", "mrkdwn"], push);
|
|
7078
|
+
});
|
|
7079
|
+
}
|
|
7080
|
+
function validateActionsBlock(b, path, push) {
|
|
7081
|
+
const elements = b.elements;
|
|
7082
|
+
if (!Array.isArray(elements) || elements.length === 0) {
|
|
7083
|
+
push(`${path}.elements`, "actions requires non-empty `elements` array");
|
|
7084
|
+
return;
|
|
7085
|
+
}
|
|
7086
|
+
if (elements.length > SLACK_LIMITS.actionElements) {
|
|
7087
|
+
push(`${path}.elements`, `max ${SLACK_LIMITS.actionElements} elements`);
|
|
7088
|
+
}
|
|
7089
|
+
elements.forEach((el, i) => {
|
|
7090
|
+
const type = el?.type;
|
|
7091
|
+
if (typeof type !== "string") {
|
|
7092
|
+
push(`${path}.elements[${i}]`, "each element must have a `type`");
|
|
7093
|
+
return;
|
|
7094
|
+
}
|
|
7095
|
+
if (!SUPPORTED_ACTION_ELEMENTS.includes(type)) {
|
|
7096
|
+
push(`${path}.elements[${i}]`, `unsupported action element "${type}" \u2014 supported: ${SUPPORTED_ACTION_ELEMENTS.join(", ")}`);
|
|
7097
|
+
return;
|
|
7098
|
+
}
|
|
7099
|
+
if (type === "button") {
|
|
7100
|
+
const text = el.text;
|
|
7101
|
+
if (!text) {
|
|
7102
|
+
push(`${path}.elements[${i}].text`, "button requires a text object");
|
|
7103
|
+
} else {
|
|
7104
|
+
validateTextObject(text, `${path}.elements[${i}].text`, SLACK_LIMITS.buttonLabelChars, ["plain_text"], push);
|
|
7105
|
+
}
|
|
7106
|
+
const actionId = el.action_id;
|
|
7107
|
+
if (typeof actionId !== "string" || actionId.length === 0) {
|
|
7108
|
+
push(`${path}.elements[${i}].action_id`, "button requires a non-empty `action_id`");
|
|
7109
|
+
} else if (actionId.length > SLACK_LIMITS.identifierChars) {
|
|
7110
|
+
push(`${path}.elements[${i}].action_id`, `max ${SLACK_LIMITS.identifierChars} chars`);
|
|
7111
|
+
}
|
|
7112
|
+
const value = el.value;
|
|
7113
|
+
if (value !== void 0) {
|
|
7114
|
+
if (typeof value !== "string") {
|
|
7115
|
+
push(`${path}.elements[${i}].value`, "`value` must be a string");
|
|
7116
|
+
} else if (value.length > SLACK_LIMITS.optionValueChars) {
|
|
7117
|
+
push(`${path}.elements[${i}].value`, `max ${SLACK_LIMITS.optionValueChars} chars`);
|
|
7118
|
+
}
|
|
7119
|
+
}
|
|
7120
|
+
}
|
|
7121
|
+
});
|
|
7122
|
+
}
|
|
7123
|
+
function validateTextObject(text, path, maxChars, allowedTypes, push) {
|
|
7124
|
+
if (!text || typeof text !== "object") {
|
|
7125
|
+
push(path, "must be an object with `type` and `text`");
|
|
7126
|
+
return;
|
|
7127
|
+
}
|
|
7128
|
+
const t = text;
|
|
7129
|
+
if (typeof t.type !== "string" || !allowedTypes.includes(t.type)) {
|
|
7130
|
+
push(`${path}.type`, `must be one of: ${allowedTypes.join(", ")}`);
|
|
7131
|
+
}
|
|
7132
|
+
if (typeof t.text !== "string" || t.text.length === 0) {
|
|
7133
|
+
push(`${path}.text`, "required non-empty string");
|
|
7134
|
+
} else if (t.text.length > maxChars) {
|
|
7135
|
+
push(`${path}.text`, `max ${maxChars} chars`);
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
7138
|
+
function encodeActionId(callbackId, token) {
|
|
7139
|
+
return `aug:${callbackId}:${token}`;
|
|
7140
|
+
}
|
|
7141
|
+
function decodeActionId(actionId) {
|
|
7142
|
+
const m = ACTION_ID_RE.exec(actionId);
|
|
7143
|
+
if (!m) return null;
|
|
7144
|
+
return { callbackId: m[1], token: m[2] };
|
|
7145
|
+
}
|
|
7146
|
+
function buildAskUserBlocks(opts) {
|
|
7147
|
+
return [
|
|
7148
|
+
{
|
|
7149
|
+
type: "section",
|
|
7150
|
+
text: { type: "mrkdwn", text: opts.question }
|
|
7151
|
+
},
|
|
7152
|
+
{
|
|
7153
|
+
type: "actions",
|
|
7154
|
+
elements: opts.options.map(({ label, token }) => ({
|
|
7155
|
+
type: "button",
|
|
7156
|
+
text: { type: "plain_text", text: label },
|
|
7157
|
+
action_id: encodeActionId(opts.callbackId, token)
|
|
7158
|
+
}))
|
|
7159
|
+
}
|
|
7160
|
+
];
|
|
7161
|
+
}
|
|
7162
|
+
function ratingValuesForScale(scale) {
|
|
7163
|
+
return scale === "1-5" ? [1, 2, 3, 4, 5] : [-1, 0, 1];
|
|
7164
|
+
}
|
|
7165
|
+
function defaultRatingLabels(scale) {
|
|
7166
|
+
return scale === "1-5" ? ["1", "2", "3", "4", "5"] : ["\u{1F44E}", "\u{1F914}", "\u{1F44D}"];
|
|
7167
|
+
}
|
|
7168
|
+
function buildRatingActionsBlock(opts) {
|
|
7169
|
+
return {
|
|
7170
|
+
type: "actions",
|
|
7171
|
+
elements: opts.options.map(({ label, token }) => ({
|
|
7172
|
+
type: "button",
|
|
7173
|
+
text: { type: "plain_text", text: label },
|
|
7174
|
+
action_id: encodeActionId(opts.callbackId, token)
|
|
7175
|
+
}))
|
|
7176
|
+
};
|
|
7177
|
+
}
|
|
7178
|
+
async function resolveInteractive(cfg, input) {
|
|
7179
|
+
const res = await apiCall(cfg, "POST", "/host/interactive/resolve", {
|
|
7180
|
+
agent_id: cfg.agentId,
|
|
7181
|
+
callback_id: input.callbackId,
|
|
7182
|
+
token: input.token,
|
|
7183
|
+
responded_by_user: input.respondedByUser,
|
|
7184
|
+
response_url: input.responseUrl,
|
|
7185
|
+
original_blocks: input.originalBlocks
|
|
7186
|
+
});
|
|
7187
|
+
if (!res.ok) {
|
|
7188
|
+
const body = await res.text().catch(() => "");
|
|
7189
|
+
throw new Error(`resolveInteractive failed (${res.status}): ${body.slice(0, 200)}`);
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
async function recordSlackDelivery(cfg, input) {
|
|
7193
|
+
const res = await apiCall(
|
|
7194
|
+
cfg,
|
|
7195
|
+
"POST",
|
|
7196
|
+
`/host/agents/${encodeURIComponent(cfg.agentId)}/slack-delivery`,
|
|
7197
|
+
{
|
|
7198
|
+
channel: input.channel,
|
|
7199
|
+
message_ts: input.messageTs,
|
|
7200
|
+
thread_ts: input.threadTs
|
|
7201
|
+
}
|
|
7202
|
+
);
|
|
7203
|
+
if (!res.ok) {
|
|
7204
|
+
const body = await res.text().catch(() => "");
|
|
7205
|
+
throw new Error(`recordSlackDelivery failed (${res.status}): ${body.slice(0, 200)}`);
|
|
7206
|
+
}
|
|
7207
|
+
}
|
|
7208
|
+
var SLACK_LIMITS, SUPPORTED_BLOCK_TYPES, SUPPORTED_ACTION_ELEMENTS, ACTION_ID_RE;
|
|
7209
|
+
var init_slack_block_kit_runtime = __esm({
|
|
7210
|
+
"src/slack-block-kit-runtime.ts"() {
|
|
7211
|
+
"use strict";
|
|
7212
|
+
init_ask_user_runtime();
|
|
7213
|
+
init_ask_user_runtime();
|
|
7214
|
+
SLACK_LIMITS = {
|
|
7215
|
+
blocksPerMessage: 50,
|
|
7216
|
+
sectionTextChars: 3e3,
|
|
7217
|
+
sectionFields: 10,
|
|
7218
|
+
sectionFieldChars: 2e3,
|
|
7219
|
+
actionElements: 5,
|
|
7220
|
+
buttonLabelChars: 75,
|
|
7221
|
+
identifierChars: 255,
|
|
7222
|
+
optionValueChars: 2e3,
|
|
7223
|
+
headerTextChars: 150
|
|
7224
|
+
};
|
|
7225
|
+
SUPPORTED_BLOCK_TYPES = ["header", "section", "divider", "context", "actions"];
|
|
7226
|
+
SUPPORTED_ACTION_ELEMENTS = ["button"];
|
|
7227
|
+
ACTION_ID_RE = /^aug:([0-9a-f-]{36}):([A-Za-z0-9_-]{1,200})$/;
|
|
7228
|
+
}
|
|
7229
|
+
});
|
|
7230
|
+
|
|
6801
7231
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/core.js
|
|
6802
7232
|
var NEVER = Object.freeze({
|
|
6803
7233
|
status: "aborted"
|
|
@@ -15276,6 +15706,7 @@ var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
|
|
15276
15706
|
var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
|
|
15277
15707
|
var AGT_HOST = process.env.AGT_HOST ?? null;
|
|
15278
15708
|
var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
|
|
15709
|
+
var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
15279
15710
|
var ALLOWED_CHATS = new Set(
|
|
15280
15711
|
(process.env.TELEGRAM_ALLOWED_CHATS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
15281
15712
|
);
|
|
@@ -16081,7 +16512,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16081
16512
|
...progressRegistry.getDefinitions(),
|
|
16082
16513
|
{
|
|
16083
16514
|
name: "channel_request_input",
|
|
16084
|
-
description: "Generic Yes/No or multi-choice picker.
|
|
16515
|
+
description: "Generic Yes/No or multi-choice picker. Renders as a Telegram inline keyboard; the tool blocks until the user taps an option (or the timeout elapses) and returns the resolved value. Use this when you need a structured pick rather than freeform text \u2014 e.g. confirming a draft action or disambiguating an ambiguous fork.",
|
|
16085
16516
|
inputSchema: {
|
|
16086
16517
|
type: "object",
|
|
16087
16518
|
properties: {
|
|
@@ -16197,15 +16628,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16197
16628
|
const progressResult = await progressRegistry.handle(name, args ?? {});
|
|
16198
16629
|
if (progressResult !== void 0) return progressResult;
|
|
16199
16630
|
if (name === "channel_request_input") {
|
|
16200
|
-
return {
|
|
16201
|
-
content: [
|
|
16202
|
-
{
|
|
16203
|
-
type: "text",
|
|
16204
|
-
text: "NotImplemented: channel_request_input is not yet available on Telegram. Sibling follow-up to ENG-5392 will add the Telegram inline-keyboard renderer. For now, send a freeform telegram.reply asking the question and parse the reply text."
|
|
16205
|
-
}
|
|
16206
|
-
],
|
|
16207
|
-
isError: true
|
|
16208
|
-
};
|
|
16631
|
+
return handleChannelRequestInput(args ?? {});
|
|
16209
16632
|
}
|
|
16210
16633
|
if (name === "telegram.reply" || name === "telegram.send_message") {
|
|
16211
16634
|
const { chat_id, text, reply_to_message_id } = args;
|
|
@@ -16400,6 +16823,228 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16400
16823
|
}
|
|
16401
16824
|
throw new Error(`Unknown tool: ${name}`);
|
|
16402
16825
|
});
|
|
16826
|
+
function channelRequestInputAvailable() {
|
|
16827
|
+
return Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
|
|
16828
|
+
}
|
|
16829
|
+
function errResult2(text) {
|
|
16830
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
16831
|
+
}
|
|
16832
|
+
async function handleChannelRequestInput(args) {
|
|
16833
|
+
if (!channelRequestInputAvailable()) {
|
|
16834
|
+
return errResult2(
|
|
16835
|
+
"channel_request_input is disabled \u2014 missing AGT_HOST / AGT_API_KEY / AGT_AGENT_ID env wiring."
|
|
16836
|
+
);
|
|
16837
|
+
}
|
|
16838
|
+
const runtime = await Promise.resolve().then(() => (init_ask_user_runtime(), ask_user_runtime_exports));
|
|
16839
|
+
const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
|
|
16840
|
+
const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
|
|
16841
|
+
if (typeof thread_id !== "string" || !thread_id) {
|
|
16842
|
+
return errResult2(
|
|
16843
|
+
"thread_id is required (Telegram shape: `<chat_id>` or `<chat_id>:<reply_to_message_id>`)"
|
|
16844
|
+
);
|
|
16845
|
+
}
|
|
16846
|
+
if (typeof prompt_text !== "string" || !prompt_text) {
|
|
16847
|
+
return errResult2("prompt_text is required");
|
|
16848
|
+
}
|
|
16849
|
+
const optsValidation = validateAskUserOptions2(options, {
|
|
16850
|
+
maxOptions: 4,
|
|
16851
|
+
maxLabelChars: 75,
|
|
16852
|
+
maxValueChars: 2e3
|
|
16853
|
+
});
|
|
16854
|
+
if (!optsValidation.ok) {
|
|
16855
|
+
return errResult2(
|
|
16856
|
+
`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
16857
|
+
);
|
|
16858
|
+
}
|
|
16859
|
+
const optionsArr = options;
|
|
16860
|
+
if (optionsArr.length < 2) {
|
|
16861
|
+
return errResult2(
|
|
16862
|
+
"options must contain at least 2 entries (this is a picker, not a notification)"
|
|
16863
|
+
);
|
|
16864
|
+
}
|
|
16865
|
+
if (!schema || typeof schema.type !== "string") {
|
|
16866
|
+
return errResult2("schema is required with shape { type: 'yes_no' | 'choice' }");
|
|
16867
|
+
}
|
|
16868
|
+
if (schema.type !== "yes_no" && schema.type !== "choice") {
|
|
16869
|
+
return errResult2("schema.type must be 'yes_no' or 'choice'");
|
|
16870
|
+
}
|
|
16871
|
+
if (schema.type === "yes_no" && optionsArr.length !== 2) {
|
|
16872
|
+
return errResult2("schema.type='yes_no' requires exactly 2 options");
|
|
16873
|
+
}
|
|
16874
|
+
if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
|
|
16875
|
+
return errResult2("request_id must be a non-empty string when provided");
|
|
16876
|
+
}
|
|
16877
|
+
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
16878
|
+
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
16879
|
+
const cfg = {
|
|
16880
|
+
apiHost: AGT_HOST,
|
|
16881
|
+
apiKey: AGT_API_KEY,
|
|
16882
|
+
agentId: AGT_AGENT_ID
|
|
16883
|
+
};
|
|
16884
|
+
let dispatchPayload;
|
|
16885
|
+
try {
|
|
16886
|
+
const res = await apiCall2(cfg, "POST", "/host/channel/request-input", {
|
|
16887
|
+
agent_id: cfg.agentId,
|
|
16888
|
+
thread_id,
|
|
16889
|
+
prompt_text,
|
|
16890
|
+
options: optionsArr,
|
|
16891
|
+
schema,
|
|
16892
|
+
...request_id ? { request_id } : {}
|
|
16893
|
+
});
|
|
16894
|
+
dispatchPayload = await res.json().catch(() => ({}));
|
|
16895
|
+
if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
|
|
16896
|
+
const reason = dispatchPayload.reason ?? `http_${res.status}`;
|
|
16897
|
+
return errResult2(`channel_request_input dispatch failed: ${reason}`);
|
|
16898
|
+
}
|
|
16899
|
+
} catch (err) {
|
|
16900
|
+
return errResult2(
|
|
16901
|
+
`channel_request_input dispatch failed: ${err.message}`
|
|
16902
|
+
);
|
|
16903
|
+
}
|
|
16904
|
+
const callbackId = dispatchPayload.callback_id;
|
|
16905
|
+
const deadline = new Date(Date.now() + timeoutSec * 1e3);
|
|
16906
|
+
const resolution = await waitForResolution2(cfg, callbackId, { deadline });
|
|
16907
|
+
if (resolution.kind === "timeout") {
|
|
16908
|
+
return {
|
|
16909
|
+
content: [
|
|
16910
|
+
{
|
|
16911
|
+
type: "text",
|
|
16912
|
+
text: JSON.stringify({
|
|
16913
|
+
value: null,
|
|
16914
|
+
clicked_by_user: null,
|
|
16915
|
+
timed_out: true,
|
|
16916
|
+
responded_at: null,
|
|
16917
|
+
callback_id: callbackId
|
|
16918
|
+
})
|
|
16919
|
+
}
|
|
16920
|
+
]
|
|
16921
|
+
};
|
|
16922
|
+
}
|
|
16923
|
+
return {
|
|
16924
|
+
content: [
|
|
16925
|
+
{
|
|
16926
|
+
type: "text",
|
|
16927
|
+
text: JSON.stringify({
|
|
16928
|
+
value: resolution.value,
|
|
16929
|
+
clicked_by_user: resolution.respondedByUser,
|
|
16930
|
+
timed_out: false,
|
|
16931
|
+
responded_at: resolution.respondedAt,
|
|
16932
|
+
callback_id: callbackId
|
|
16933
|
+
})
|
|
16934
|
+
}
|
|
16935
|
+
]
|
|
16936
|
+
};
|
|
16937
|
+
}
|
|
16938
|
+
async function handleCallbackQuery(cb) {
|
|
16939
|
+
if (!channelRequestInputAvailable()) {
|
|
16940
|
+
await ackCallbackQuery(cb.id, "Not configured").catch(() => {
|
|
16941
|
+
});
|
|
16942
|
+
process.stderr.write(
|
|
16943
|
+
`telegram-channel(${AGENT_CODE_NAME}): callback_query received but host wiring missing \u2014 dropping
|
|
16944
|
+
`
|
|
16945
|
+
);
|
|
16946
|
+
return;
|
|
16947
|
+
}
|
|
16948
|
+
if (typeof cb.data !== "string" || !cb.data) {
|
|
16949
|
+
await ackCallbackQuery(cb.id).catch(() => {
|
|
16950
|
+
});
|
|
16951
|
+
return;
|
|
16952
|
+
}
|
|
16953
|
+
const runtime = await Promise.resolve().then(() => (init_slack_block_kit_runtime(), slack_block_kit_runtime_exports));
|
|
16954
|
+
const decoded = runtime.decodeActionId(cb.data);
|
|
16955
|
+
if (!decoded) {
|
|
16956
|
+
await ackCallbackQuery(cb.id).catch(() => {
|
|
16957
|
+
});
|
|
16958
|
+
return;
|
|
16959
|
+
}
|
|
16960
|
+
await ackCallbackQuery(cb.id, "Got it").catch(() => {
|
|
16961
|
+
});
|
|
16962
|
+
const apiCallRuntime = await Promise.resolve().then(() => (init_ask_user_runtime(), ask_user_runtime_exports));
|
|
16963
|
+
const cfg = {
|
|
16964
|
+
apiHost: AGT_HOST,
|
|
16965
|
+
apiKey: AGT_API_KEY,
|
|
16966
|
+
agentId: AGT_AGENT_ID
|
|
16967
|
+
};
|
|
16968
|
+
let resolveResult;
|
|
16969
|
+
try {
|
|
16970
|
+
const res = await apiCallRuntime.apiCall(cfg, "POST", "/host/interactive/resolve", {
|
|
16971
|
+
agent_id: cfg.agentId,
|
|
16972
|
+
callback_id: decoded.callbackId,
|
|
16973
|
+
token: decoded.token,
|
|
16974
|
+
responded_by_user: String(cb.from.id)
|
|
16975
|
+
});
|
|
16976
|
+
resolveResult = await res.json().catch(() => ({}));
|
|
16977
|
+
if (!res.ok || !resolveResult.ok) {
|
|
16978
|
+
process.stderr.write(
|
|
16979
|
+
`telegram-channel(${AGENT_CODE_NAME}): /host/interactive/resolve returned non-ok (callback_id=${decoded.callbackId}, http=${res.status}, reason=${resolveResult.reason ?? "unknown"})
|
|
16980
|
+
`
|
|
16981
|
+
);
|
|
16982
|
+
await ackCallbackQuery(cb.id, "Could not record your choice").catch(() => {
|
|
16983
|
+
});
|
|
16984
|
+
return;
|
|
16985
|
+
}
|
|
16986
|
+
} catch (err) {
|
|
16987
|
+
process.stderr.write(
|
|
16988
|
+
`telegram-channel(${AGENT_CODE_NAME}): /host/interactive/resolve threw (callback_id=${decoded.callbackId}): ${err.message}
|
|
16989
|
+
`
|
|
16990
|
+
);
|
|
16991
|
+
await ackCallbackQuery(cb.id, "Could not record your choice").catch(() => {
|
|
16992
|
+
});
|
|
16993
|
+
return;
|
|
16994
|
+
}
|
|
16995
|
+
const confirmationLabel = "\u2713";
|
|
16996
|
+
if (cb.message) {
|
|
16997
|
+
try {
|
|
16998
|
+
const resp = await telegramApiCall(
|
|
16999
|
+
"editMessageReplyMarkup",
|
|
17000
|
+
{
|
|
17001
|
+
chat_id: cb.message.chat.id,
|
|
17002
|
+
message_id: cb.message.message_id,
|
|
17003
|
+
// Empty inline_keyboard removes the buttons but leaves the
|
|
17004
|
+
// original prompt text intact. Less destructive than
|
|
17005
|
+
// editMessageText, and the prompt remains scrollable history.
|
|
17006
|
+
reply_markup: { inline_keyboard: [] }
|
|
17007
|
+
},
|
|
17008
|
+
5e3
|
|
17009
|
+
);
|
|
17010
|
+
if (!resp.ok) {
|
|
17011
|
+
process.stderr.write(
|
|
17012
|
+
`telegram-channel(${AGENT_CODE_NAME}): editMessageReplyMarkup failed (callback_id=${decoded.callbackId}): ${resp.description ?? "unknown"}
|
|
17013
|
+
`
|
|
17014
|
+
);
|
|
17015
|
+
}
|
|
17016
|
+
} catch (err) {
|
|
17017
|
+
process.stderr.write(
|
|
17018
|
+
`telegram-channel(${AGENT_CODE_NAME}): editMessageReplyMarkup threw (callback_id=${decoded.callbackId}): ${err.message}
|
|
17019
|
+
`
|
|
17020
|
+
);
|
|
17021
|
+
}
|
|
17022
|
+
void confirmationLabel;
|
|
17023
|
+
}
|
|
17024
|
+
}
|
|
17025
|
+
async function ackCallbackQuery(callbackQueryId, text) {
|
|
17026
|
+
try {
|
|
17027
|
+
const resp = await telegramApiCall(
|
|
17028
|
+
"answerCallbackQuery",
|
|
17029
|
+
{
|
|
17030
|
+
callback_query_id: callbackQueryId,
|
|
17031
|
+
...text ? { text } : {}
|
|
17032
|
+
},
|
|
17033
|
+
5e3
|
|
17034
|
+
);
|
|
17035
|
+
if (!resp.ok) {
|
|
17036
|
+
process.stderr.write(
|
|
17037
|
+
`telegram-channel(${AGENT_CODE_NAME}): answerCallbackQuery rejected (id=${callbackQueryId}): ${resp.description ?? "unknown"}
|
|
17038
|
+
`
|
|
17039
|
+
);
|
|
17040
|
+
}
|
|
17041
|
+
} catch (err) {
|
|
17042
|
+
process.stderr.write(
|
|
17043
|
+
`telegram-channel(${AGENT_CODE_NAME}): answerCallbackQuery failed (id=${callbackQueryId}): ${err.message}
|
|
17044
|
+
`
|
|
17045
|
+
);
|
|
17046
|
+
}
|
|
17047
|
+
}
|
|
16403
17048
|
await mcp.connect(new StdioServerTransport());
|
|
16404
17049
|
var LONG_POLL_SECONDS = 50;
|
|
16405
17050
|
var POLL_HTTP_TIMEOUT_MS = (LONG_POLL_SECONDS + 10) * 1e3;
|
|
@@ -16493,7 +17138,11 @@ async function pollLoop() {
|
|
|
16493
17138
|
{
|
|
16494
17139
|
offset: nextOffset,
|
|
16495
17140
|
timeout: LONG_POLL_SECONDS,
|
|
16496
|
-
|
|
17141
|
+
// ENG-5398: `callback_query` carries inline-keyboard taps from
|
|
17142
|
+
// `channel_request_input`. Without it the buttons render fine
|
|
17143
|
+
// but Telegram silently drops the tap and the agent's polling
|
|
17144
|
+
// loop times out as if no-one ever pressed anything.
|
|
17145
|
+
allowed_updates: ["message", "callback_query"]
|
|
16497
17146
|
},
|
|
16498
17147
|
POLL_HTTP_TIMEOUT_MS
|
|
16499
17148
|
);
|
|
@@ -16503,11 +17152,20 @@ async function pollLoop() {
|
|
|
16503
17152
|
`telegram-channel(${AGENT_CODE_NAME}): getUpdates failed: ${resp.description ?? "unknown"}
|
|
16504
17153
|
`
|
|
16505
17154
|
);
|
|
16506
|
-
await
|
|
17155
|
+
await sleep2(5e3);
|
|
16507
17156
|
continue;
|
|
16508
17157
|
}
|
|
16509
17158
|
for (const update of resp.result ?? []) {
|
|
16510
17159
|
nextOffset = Math.max(nextOffset, update.update_id + 1);
|
|
17160
|
+
if (update.callback_query) {
|
|
17161
|
+
await handleCallbackQuery(update.callback_query).catch((err) => {
|
|
17162
|
+
process.stderr.write(
|
|
17163
|
+
`telegram-channel(${AGENT_CODE_NAME}): callback_query handler crashed: ${err.message}
|
|
17164
|
+
`
|
|
17165
|
+
);
|
|
17166
|
+
});
|
|
17167
|
+
continue;
|
|
17168
|
+
}
|
|
16511
17169
|
const msg = update.message;
|
|
16512
17170
|
if (!msg) continue;
|
|
16513
17171
|
const classifiedAttachments = classifyTelegramAttachments(msg);
|
|
@@ -16721,11 +17379,11 @@ async function pollLoop() {
|
|
|
16721
17379
|
`telegram-channel(${AGENT_CODE_NAME}): poll error: ${err.message}
|
|
16722
17380
|
`
|
|
16723
17381
|
);
|
|
16724
|
-
await
|
|
17382
|
+
await sleep2(5e3);
|
|
16725
17383
|
}
|
|
16726
17384
|
}
|
|
16727
17385
|
}
|
|
16728
|
-
function
|
|
17386
|
+
function sleep2(ms) {
|
|
16729
17387
|
return new Promise((resolve2) => setTimeout(resolve2, ms).unref());
|
|
16730
17388
|
}
|
|
16731
17389
|
function shutdown(reason) {
|