mirror of
https://github.com/Mintplex-Labs/anything-llm
synced 2026-04-25 17:15:37 +02:00
@@ -412,9 +412,6 @@ function adminEndpoints(app) {
|
||||
case "disabled_gmail_skills":
|
||||
requestedSettings[label] = safeJsonParse(setting?.value, []);
|
||||
break;
|
||||
case "disabled_outlook_skills":
|
||||
requestedSettings[label] = safeJsonParse(setting?.value, []);
|
||||
break;
|
||||
case "gmail_deployment_id":
|
||||
requestedSettings[label] = setting?.value || null;
|
||||
break;
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
const { reqBody } = require("../../utils/http");
|
||||
const {
|
||||
isSingleUserMode,
|
||||
} = require("../../utils/middleware/multiUserProtected");
|
||||
const { validatedRequest } = require("../../utils/middleware/validatedRequest");
|
||||
|
||||
/**
|
||||
* Constructs the OAuth redirect URI from the request headers.
|
||||
* @param {Object} request - Express request object
|
||||
* @returns {string} The redirect URI for OAuth callback
|
||||
*/
|
||||
function getOutlookRedirectUri(request) {
|
||||
const protocol = request.headers["x-forwarded-proto"] || request.protocol;
|
||||
const host = request.headers["x-forwarded-host"] || request.get("host");
|
||||
return `${protocol}://${host}/api/agent-skills/outlook/auth-callback`;
|
||||
}
|
||||
|
||||
function outlookAgentEndpoints(app) {
|
||||
if (!app) return;
|
||||
|
||||
app.post(
|
||||
"/admin/agent-skills/outlook/auth-url",
|
||||
[validatedRequest, isSingleUserMode],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { clientId, tenantId, clientSecret, authType } = reqBody(request);
|
||||
|
||||
if (!clientId || !clientSecret) {
|
||||
return response.status(400).json({
|
||||
success: false,
|
||||
error: "Client ID and Client Secret are required.",
|
||||
});
|
||||
}
|
||||
|
||||
const outlookLib = require("../../utils/agents/aibitat/plugins/outlook/lib");
|
||||
const { AUTH_TYPES } = outlookLib;
|
||||
const validAuthType = Object.values(AUTH_TYPES).includes(authType)
|
||||
? authType
|
||||
: AUTH_TYPES.common;
|
||||
|
||||
if (validAuthType === AUTH_TYPES.organization && !tenantId) {
|
||||
return response.status(400).json({
|
||||
success: false,
|
||||
error:
|
||||
"Tenant ID is required for organization-only authentication.",
|
||||
});
|
||||
}
|
||||
|
||||
const existingConfig = await outlookLib.OutlookBridge.getConfig();
|
||||
const configUpdate = {
|
||||
...existingConfig,
|
||||
clientId: clientId.trim(),
|
||||
tenantId: tenantId?.trim() || "",
|
||||
authType: validAuthType,
|
||||
};
|
||||
|
||||
if (!/^\*+$/.test(clientSecret))
|
||||
configUpdate.clientSecret = clientSecret.trim();
|
||||
|
||||
// If auth type changed, clear tokens as they won't work with different authority
|
||||
if (
|
||||
existingConfig.authType &&
|
||||
existingConfig.authType !== validAuthType
|
||||
) {
|
||||
delete configUpdate.accessToken;
|
||||
delete configUpdate.refreshToken;
|
||||
delete configUpdate.tokenExpiry;
|
||||
}
|
||||
|
||||
await outlookLib.OutlookBridge.updateConfig(configUpdate);
|
||||
outlookLib.reset();
|
||||
|
||||
const redirectUri = getOutlookRedirectUri(request);
|
||||
const result = await outlookLib.getAuthUrl(redirectUri);
|
||||
if (!result.success) {
|
||||
return response
|
||||
.status(400)
|
||||
.json({ success: false, error: result.error });
|
||||
}
|
||||
|
||||
return response.status(200).json({ success: true, url: result.url });
|
||||
} catch (e) {
|
||||
console.error("Outlook auth URL error:", e);
|
||||
response.status(500).json({ success: false, error: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/agent-skills/outlook/auth-callback",
|
||||
[validatedRequest, isSingleUserMode],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { code, error, error_description } = request.query;
|
||||
|
||||
if (error) {
|
||||
console.error("Outlook OAuth error:", error, error_description);
|
||||
return response.redirect(
|
||||
`/?outlook_auth=error&message=${encodeURIComponent(error_description || error)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!code) {
|
||||
return response.redirect(
|
||||
"/?outlook_auth=error&message=No authorization code received"
|
||||
);
|
||||
}
|
||||
|
||||
const outlookLib = require("../../utils/agents/aibitat/plugins/outlook/lib");
|
||||
const redirectUri = getOutlookRedirectUri(request);
|
||||
const result = await outlookLib.exchangeCodeForToken(code, redirectUri);
|
||||
|
||||
const frontendUrl =
|
||||
process.env.NODE_ENV === "development" ? "http://localhost:3000" : "";
|
||||
|
||||
if (!result.success) {
|
||||
return response.redirect(
|
||||
`${frontendUrl}/settings/agents?outlook_auth=error&message=${encodeURIComponent(result.error)}`
|
||||
);
|
||||
}
|
||||
|
||||
return response.redirect(
|
||||
`${frontendUrl}/settings/agents?outlook_auth=success`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Outlook OAuth callback error:", e);
|
||||
response.redirect(
|
||||
`/?outlook_auth=error&message=${encodeURIComponent(e.message)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/admin/agent-skills/outlook/status",
|
||||
[validatedRequest, isSingleUserMode],
|
||||
async (_request, response) => {
|
||||
try {
|
||||
const outlookLib = require("../../utils/agents/aibitat/plugins/outlook/lib");
|
||||
const { AUTH_TYPES, normalizeTokenExpiry } = outlookLib;
|
||||
const config = await outlookLib.OutlookBridge.getConfig();
|
||||
const isConfigured = await outlookLib.OutlookBridge.isToolAvailable();
|
||||
|
||||
const authType = config.authType || AUTH_TYPES.common;
|
||||
let hasCredentials = !!(config.clientId && config.clientSecret);
|
||||
if (authType === AUTH_TYPES.organization) {
|
||||
hasCredentials = hasCredentials && !!config.tenantId;
|
||||
}
|
||||
|
||||
const safeConfig = {
|
||||
clientId: config.clientId || "",
|
||||
tenantId: config.tenantId || "",
|
||||
clientSecret: config.clientSecret ? "********" : "",
|
||||
authType: config.authType || AUTH_TYPES.common,
|
||||
};
|
||||
|
||||
return response.status(200).json({
|
||||
success: true,
|
||||
isConfigured,
|
||||
hasCredentials,
|
||||
isAuthenticated: !!config.accessToken,
|
||||
tokenExpiry: normalizeTokenExpiry(config.tokenExpiry),
|
||||
config: safeConfig,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Outlook status error:", e);
|
||||
response.status(500).json({ success: false, error: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/admin/agent-skills/outlook/revoke",
|
||||
[validatedRequest, isSingleUserMode],
|
||||
async (_request, response) => {
|
||||
try {
|
||||
const outlookLib = require("../../utils/agents/aibitat/plugins/outlook/lib");
|
||||
const { SystemSettings } = require("../../models/systemSettings");
|
||||
await SystemSettings.delete({ label: "outlook_agent_config" });
|
||||
outlookLib.reset();
|
||||
return response.status(200).json({ success: true });
|
||||
} catch (e) {
|
||||
console.error("Outlook revoke error:", e);
|
||||
response.status(500).json({ success: false, error: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { outlookAgentEndpoints };
|
||||
@@ -35,9 +35,6 @@ const { mcpServersEndpoints } = require("./endpoints/mcpServers");
|
||||
const { mobileEndpoints } = require("./endpoints/mobile");
|
||||
const { webPushEndpoints } = require("./endpoints/webPush");
|
||||
const { telegramEndpoints } = require("./endpoints/telegram");
|
||||
const {
|
||||
outlookAgentEndpoints,
|
||||
} = require("./endpoints/utils/outlookAgentUtils");
|
||||
const { httpLogger } = require("./middleware/httpLogger");
|
||||
const app = express();
|
||||
const apiRouter = express.Router();
|
||||
@@ -92,7 +89,6 @@ mcpServersEndpoints(apiRouter);
|
||||
mobileEndpoints(apiRouter);
|
||||
webPushEndpoints(apiRouter);
|
||||
telegramEndpoints(apiRouter);
|
||||
outlookAgentEndpoints(apiRouter);
|
||||
// Externally facing embedder endpoints
|
||||
embeddedEndpoints(apiRouter);
|
||||
|
||||
|
||||
@@ -18,21 +18,6 @@ function isNullOrNaN(value) {
|
||||
return isNaN(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges a string field from source to target if it passes validation.
|
||||
* @param {Object} target - The target object to merge into
|
||||
* @param {Object} source - The source object to read from
|
||||
* @param {string} fieldName - The field name to merge
|
||||
* @param {Function|null} validator - Optional validator function that returns false to reject the value
|
||||
*/
|
||||
function mergeStringField(target, source, fieldName, validator = null) {
|
||||
const value = source[fieldName];
|
||||
if (value && typeof value === "string" && value.trim()) {
|
||||
if (validator && !validator(value)) return;
|
||||
target[fieldName] = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
const SystemSettings = {
|
||||
/** A default system prompt that is used when no other system prompt is set or available to the function caller. */
|
||||
saneDefaultSystemPrompt:
|
||||
@@ -53,8 +38,6 @@ const SystemSettings = {
|
||||
"disabled_gmail_skills",
|
||||
"gmail_deployment_id",
|
||||
"gmail_api_key",
|
||||
"disabled_outlook_skills",
|
||||
"outlook_agent_config",
|
||||
"imported_agent_skills",
|
||||
"custom_app_name",
|
||||
"feature_flags",
|
||||
@@ -77,8 +60,6 @@ const SystemSettings = {
|
||||
"disabled_gmail_skills",
|
||||
"gmail_deployment_id",
|
||||
"gmail_api_key",
|
||||
"disabled_outlook_skills",
|
||||
"outlook_agent_config",
|
||||
"agent_sql_connections",
|
||||
"custom_app_name",
|
||||
"default_system_prompt",
|
||||
@@ -226,56 +207,6 @@ const SystemSettings = {
|
||||
GmailBridge.reset();
|
||||
}
|
||||
},
|
||||
disabled_outlook_skills: (updates) => {
|
||||
try {
|
||||
const skills = updates.split(",").filter((skill) => !!skill);
|
||||
return JSON.stringify(skills);
|
||||
} catch {
|
||||
console.error(`Could not validate disabled outlook skills.`);
|
||||
return JSON.stringify([]);
|
||||
}
|
||||
},
|
||||
outlook_agent_config: async (update) => {
|
||||
const OutlookBridge = require("../utils/agents/aibitat/plugins/outlook/lib");
|
||||
try {
|
||||
if (!update) return JSON.stringify({});
|
||||
|
||||
const newConfig =
|
||||
typeof update === "string" ? safeJsonParse(update, {}) : update;
|
||||
const existingConfig = safeJsonParse(
|
||||
(await SystemSettings.get({ label: "outlook_agent_config" }))?.value,
|
||||
{}
|
||||
);
|
||||
|
||||
const mergedConfig = { ...existingConfig };
|
||||
|
||||
mergeStringField(mergedConfig, newConfig, "clientId");
|
||||
mergeStringField(mergedConfig, newConfig, "tenantId");
|
||||
mergeStringField(
|
||||
mergedConfig,
|
||||
newConfig,
|
||||
"clientSecret",
|
||||
(v) => !v.match(/^\*+$/)
|
||||
);
|
||||
|
||||
if (newConfig.accessToken !== undefined) {
|
||||
mergedConfig.accessToken = newConfig.accessToken;
|
||||
}
|
||||
if (newConfig.refreshToken !== undefined) {
|
||||
mergedConfig.refreshToken = newConfig.refreshToken;
|
||||
}
|
||||
if (newConfig.tokenExpiry !== undefined) {
|
||||
mergedConfig.tokenExpiry = newConfig.tokenExpiry;
|
||||
}
|
||||
|
||||
return JSON.stringify(mergedConfig);
|
||||
} catch (e) {
|
||||
console.error(`Could not validate outlook agent config:`, e.message);
|
||||
return JSON.stringify({});
|
||||
} finally {
|
||||
OutlookBridge.reset();
|
||||
}
|
||||
},
|
||||
agent_sql_connections: async (updates) => {
|
||||
const existingConnections = safeJsonParse(
|
||||
(await SystemSettings.get({ label: "agent_sql_connections" }))?.value,
|
||||
@@ -503,18 +434,6 @@ const SystemSettings = {
|
||||
return this._updateSettings(updates);
|
||||
},
|
||||
|
||||
delete: async function (clause = {}) {
|
||||
try {
|
||||
if (!Object.keys(clause).length)
|
||||
throw new Error("Clause cannot be empty");
|
||||
await prisma.system_settings.deleteMany({ where: clause });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(error.message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Explicit update of settings + key validations.
|
||||
// Only use this method when directly setting a key value
|
||||
// that takes no user input for the keys being modified.
|
||||
|
||||
@@ -9,7 +9,6 @@ const { sqlAgent } = require("./sql-agent/index.js");
|
||||
const { filesystemAgent } = require("./filesystem/index.js");
|
||||
const { createFilesAgent } = require("./create-files/index.js");
|
||||
const { gmailAgent } = require("./gmail/index.js");
|
||||
const { outlookAgent } = require("./outlook/index.js");
|
||||
|
||||
module.exports = {
|
||||
webScraping,
|
||||
@@ -23,7 +22,6 @@ module.exports = {
|
||||
filesystemAgent,
|
||||
createFilesAgent,
|
||||
gmailAgent,
|
||||
outlookAgent,
|
||||
|
||||
// Plugin name aliases so they can be pulled by slug as well.
|
||||
[webScraping.name]: webScraping,
|
||||
@@ -37,5 +35,4 @@ module.exports = {
|
||||
[filesystemAgent.name]: filesystemAgent,
|
||||
[createFilesAgent.name]: createFilesAgent,
|
||||
[gmailAgent.name]: gmailAgent,
|
||||
[outlookAgent.name]: outlookAgent,
|
||||
};
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookGetMailboxStats = {
|
||||
name: "outlook-get-mailbox-stats",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-get-mailbox-stats",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Get Outlook mailbox statistics including folder counts and user profile information. " +
|
||||
"Returns the total and unread counts for inbox, drafts, sent items, and deleted items.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "How many unread emails do I have?",
|
||||
call: JSON.stringify({}),
|
||||
},
|
||||
{
|
||||
prompt: "Show me my mailbox statistics",
|
||||
call: JSON.stringify({}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {},
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function () {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the outlook-get-mailbox-stats tool.`
|
||||
);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Getting Outlook mailbox statistics...`
|
||||
);
|
||||
|
||||
const result = await outlookLib.getMailboxStats();
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to get mailbox stats - ${result.error}`
|
||||
);
|
||||
return `Error getting mailbox statistics: ${result.error}`;
|
||||
}
|
||||
|
||||
const { email, displayName, folders } = result.data;
|
||||
this.super.introspect(
|
||||
`${this.caller}: Successfully retrieved mailbox statistics`
|
||||
);
|
||||
|
||||
let folderStats = "";
|
||||
if (folders.inbox)
|
||||
folderStats += `\nInbox: ${folders.inbox.total} total, ${folders.inbox.unread} unread`;
|
||||
if (folders.drafts)
|
||||
folderStats += `\nDrafts: ${folders.drafts.total} total`;
|
||||
if (folders.sentitems)
|
||||
folderStats += `\nSent Items: ${folders.sentitems.total} total`;
|
||||
if (folders.deleteditems)
|
||||
folderStats += `\nDeleted Items: ${folders.deleteditems.total} total`;
|
||||
|
||||
return (
|
||||
`Outlook Mailbox Statistics:\n` +
|
||||
`\nAccount: ${displayName} (${email})\n` +
|
||||
`\nFolder Statistics:${folderStats}`
|
||||
);
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-get-mailbox-stats", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,232 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { prepareAttachmentsWithValidation, handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookCreateDraft = {
|
||||
name: "outlook-create-draft",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-create-draft",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Create a new draft email in Outlook. The draft will be saved but not sent. " +
|
||||
"Can also create a draft reply to an existing message by providing replyToMessageId. " +
|
||||
"You can optionally include CC, BCC recipients, HTML body content, and file attachments.",
|
||||
examples: [
|
||||
{
|
||||
prompt:
|
||||
"Create a draft email to john@example.com about the meeting",
|
||||
call: JSON.stringify({
|
||||
to: "john@example.com",
|
||||
subject: "Meeting Tomorrow",
|
||||
body: "Hi John,\n\nJust wanted to confirm our meeting tomorrow at 2pm.\n\nBest regards",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Create a draft reply to message AAMkAGI2...",
|
||||
call: JSON.stringify({
|
||||
replyToMessageId: "AAMkAGI2...",
|
||||
body: "Thanks for the update. I'll review and get back to you.",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Create a draft reply-all to message AAMkAGI2...",
|
||||
call: JSON.stringify({
|
||||
replyToMessageId: "AAMkAGI2...",
|
||||
body: "Thanks everyone for your input.",
|
||||
replyAll: true,
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
replyToMessageId: {
|
||||
type: "string",
|
||||
description:
|
||||
"Message ID to reply to. If provided, creates a draft reply instead of a new draft. " +
|
||||
"When replying, 'to' and 'subject' are optional (inherited from original).",
|
||||
},
|
||||
replyAll: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"When replying, whether to reply to all recipients. Defaults to false.",
|
||||
default: false,
|
||||
},
|
||||
to: {
|
||||
type: "string",
|
||||
description:
|
||||
"Recipient email address(es). Required for new drafts, optional for replies.",
|
||||
},
|
||||
subject: {
|
||||
type: "string",
|
||||
description:
|
||||
"Email subject line. Required for new drafts, optional for replies.",
|
||||
},
|
||||
body: {
|
||||
type: "string",
|
||||
description: "Email body content.",
|
||||
},
|
||||
cc: {
|
||||
type: "string",
|
||||
description: "CC recipient email address(es). Optional.",
|
||||
},
|
||||
bcc: {
|
||||
type: "string",
|
||||
description: "BCC recipient email address(es). Optional.",
|
||||
},
|
||||
isHtml: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Whether the body is HTML content. Defaults to false.",
|
||||
default: false,
|
||||
},
|
||||
attachments: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description:
|
||||
"Array of absolute file paths to attach to the draft.",
|
||||
},
|
||||
},
|
||||
required: ["body"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({
|
||||
replyToMessageId,
|
||||
replyAll = false,
|
||||
to,
|
||||
subject,
|
||||
body,
|
||||
cc,
|
||||
bcc,
|
||||
isHtml,
|
||||
attachments,
|
||||
}) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the outlook-create-draft tool.`
|
||||
);
|
||||
|
||||
const isReply = !!replyToMessageId;
|
||||
|
||||
if (!isReply && (!to || !subject)) {
|
||||
return "Error: 'to' and 'subject' are required for new drafts. For draft replies, provide 'replyToMessageId'.";
|
||||
}
|
||||
|
||||
if (!body) {
|
||||
return "Error: 'body' is required.";
|
||||
}
|
||||
|
||||
const attachmentResult = await prepareAttachmentsWithValidation(
|
||||
this,
|
||||
attachments
|
||||
);
|
||||
if (!attachmentResult.success) {
|
||||
return `Error with attachment: ${attachmentResult.error}`;
|
||||
}
|
||||
const {
|
||||
attachments: preparedAttachments,
|
||||
summaries: attachmentSummaries,
|
||||
} = attachmentResult;
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const attachmentNote =
|
||||
preparedAttachments.length > 0
|
||||
? ` with ${preparedAttachments.length} attachment(s): ${attachmentSummaries.join(", ")}`
|
||||
: "";
|
||||
const description = isReply
|
||||
? `Create draft ${replyAll ? "reply-all" : "reply"} to message ${replyToMessageId}${attachmentNote}`
|
||||
: `Create Outlook draft to "${to}" with subject "${subject}"${attachmentNote}`;
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: isReply
|
||||
? { replyToMessageId, replyAll }
|
||||
: {
|
||||
to,
|
||||
subject,
|
||||
attachmentCount: preparedAttachments.length,
|
||||
},
|
||||
description,
|
||||
});
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
let result;
|
||||
if (isReply) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Creating draft ${replyAll ? "reply-all" : "reply"} to message...`
|
||||
);
|
||||
result = await outlookLib.createDraftReply(
|
||||
replyToMessageId,
|
||||
body,
|
||||
replyAll
|
||||
);
|
||||
} else {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Creating Outlook draft to ${to}${preparedAttachments.length > 0 ? ` with ${preparedAttachments.length} attachment(s)` : ""}`
|
||||
);
|
||||
|
||||
const options = { isHtml };
|
||||
if (cc) options.cc = cc;
|
||||
if (bcc) options.bcc = bcc;
|
||||
if (preparedAttachments.length > 0) {
|
||||
options.attachments = preparedAttachments;
|
||||
}
|
||||
|
||||
result = await outlookLib.createDraft(
|
||||
to,
|
||||
subject,
|
||||
body,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to create draft - ${result.error}`
|
||||
);
|
||||
return `Error creating draft: ${result.error}`;
|
||||
}
|
||||
|
||||
const draft = result.data;
|
||||
this.super.introspect(
|
||||
`${this.caller}: Successfully created draft (ID: ${draft.draftId})`
|
||||
);
|
||||
|
||||
if (isReply) {
|
||||
return (
|
||||
`Successfully created draft ${replyAll ? "reply-all" : "reply"}:\n` +
|
||||
`Draft ID: ${draft.draftId}\n` +
|
||||
`Subject: ${draft.subject}\n` +
|
||||
`Type: ${replyAll ? "Reply All" : "Reply"}\n` +
|
||||
`\nThe draft reply has been saved and can be edited or sent later.`
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
`Successfully created Outlook draft:\n` +
|
||||
`Draft ID: ${draft.draftId}\n` +
|
||||
`To: ${to}\n` +
|
||||
`Subject: ${subject}\n` +
|
||||
(preparedAttachments.length > 0
|
||||
? `Attachments: ${attachmentSummaries.join(", ")}\n`
|
||||
: "") +
|
||||
`\nThe draft has been saved and can be edited or sent later.`
|
||||
);
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-create-draft", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,86 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookDeleteDraft = {
|
||||
name: "outlook-delete-draft",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-delete-draft",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Delete a draft email from Outlook. " +
|
||||
"This permanently removes the draft and cannot be undone.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Delete the draft with ID AAMkAGI2...",
|
||||
call: JSON.stringify({
|
||||
draftId: "AAMkAGI2...",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
draftId: {
|
||||
type: "string",
|
||||
description: "The ID of the draft to delete.",
|
||||
},
|
||||
},
|
||||
required: ["draftId"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ draftId }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the outlook-delete-draft tool.`
|
||||
);
|
||||
|
||||
if (!draftId) {
|
||||
return "Error: 'draftId' is required.";
|
||||
}
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: { draftId },
|
||||
description: `Delete draft ${draftId}? This cannot be undone.`,
|
||||
});
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Deleting draft ${draftId}...`
|
||||
);
|
||||
|
||||
const result = await outlookLib.deleteDraft(draftId);
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to delete draft - ${result.error}`
|
||||
);
|
||||
return `Error deleting draft: ${result.error}`;
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Successfully deleted draft`
|
||||
);
|
||||
|
||||
return `Successfully deleted draft (ID: ${draftId}).`;
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-delete-draft", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,80 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookListDrafts = {
|
||||
name: "outlook-list-drafts",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-list-drafts",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"List all draft emails in Outlook. " +
|
||||
"Returns a summary of each draft including ID, subject, recipients, and last modified date.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Show me my draft emails",
|
||||
call: JSON.stringify({ limit: 25 }),
|
||||
},
|
||||
{
|
||||
prompt: "List drafts",
|
||||
call: JSON.stringify({ limit: 10 }),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: {
|
||||
type: "number",
|
||||
description:
|
||||
"Maximum number of drafts to return (1-50). Defaults to 25.",
|
||||
default: 25,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ limit = 25 }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the outlook-list-drafts tool.`
|
||||
);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Listing Outlook drafts...`
|
||||
);
|
||||
|
||||
const result = await outlookLib.listDrafts(limit);
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to list drafts - ${result.error}`
|
||||
);
|
||||
return `Error listing drafts: ${result.error}`;
|
||||
}
|
||||
|
||||
const { drafts, count } = result.data;
|
||||
this.super.introspect(`${this.caller}: Found ${count} drafts`);
|
||||
|
||||
if (count === 0) {
|
||||
return "No drafts found.";
|
||||
}
|
||||
|
||||
const summary = drafts
|
||||
.map(
|
||||
(d, i) =>
|
||||
`${i + 1}. "${d.subject || "(No Subject)"}" to ${d.to || "(No Recipients)"}\n ID: ${d.id}\n Last Modified: ${new Date(d.lastModified).toLocaleString()}\n Preview: ${d.preview?.substring(0, 100) || "(No preview)"}...`
|
||||
)
|
||||
.join("\n\n");
|
||||
|
||||
return `Found ${count} drafts:\n\n${summary}\n\nUse the draft ID with outlook-get-draft to see full content, outlook-update-draft to modify, or outlook-send-draft to send.`;
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-list-drafts", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,82 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookSendDraft = {
|
||||
name: "outlook-send-draft",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-send-draft",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Send an existing draft email from Outlook. " +
|
||||
"This action sends the email immediately and cannot be undone.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Send the draft with ID AAMkAGI2...",
|
||||
call: JSON.stringify({
|
||||
draftId: "AAMkAGI2...",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
draftId: {
|
||||
type: "string",
|
||||
description: "The ID of the draft to send.",
|
||||
},
|
||||
},
|
||||
required: ["draftId"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ draftId }) {
|
||||
try {
|
||||
this.super.handlerProps.log(`Using the outlook-send-draft tool.`);
|
||||
|
||||
if (!draftId) {
|
||||
return "Error: 'draftId' is required.";
|
||||
}
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: { draftId },
|
||||
description: `Send draft ${draftId}? This will send the email immediately.`,
|
||||
});
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Sending draft ${draftId}...`
|
||||
);
|
||||
|
||||
const result = await outlookLib.sendDraft(draftId);
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to send draft - ${result.error}`
|
||||
);
|
||||
return `Error sending draft: ${result.error}`;
|
||||
}
|
||||
|
||||
this.super.introspect(`${this.caller}: Successfully sent draft`);
|
||||
|
||||
return `Successfully sent draft (ID: ${draftId}). The email has been delivered.`;
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-send-draft", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookUpdateDraft = {
|
||||
name: "outlook-update-draft",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-update-draft",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Update an existing draft email in Outlook. " +
|
||||
"You can modify the recipients, subject, or body of the draft.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Update the draft subject",
|
||||
call: JSON.stringify({
|
||||
draftId: "AAMkAGI2...",
|
||||
subject: "Updated Meeting - Tomorrow at 3pm",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Change the draft body and add CC",
|
||||
call: JSON.stringify({
|
||||
draftId: "AAMkAGI2...",
|
||||
body: "Updated content here.",
|
||||
cc: "manager@example.com",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
draftId: {
|
||||
type: "string",
|
||||
description: "The ID of the draft to update.",
|
||||
},
|
||||
to: {
|
||||
type: "string",
|
||||
description: "New recipient email address(es). Optional.",
|
||||
},
|
||||
subject: {
|
||||
type: "string",
|
||||
description: "New email subject. Optional.",
|
||||
},
|
||||
body: {
|
||||
type: "string",
|
||||
description: "New email body content. Optional.",
|
||||
},
|
||||
cc: {
|
||||
type: "string",
|
||||
description: "New CC recipient email address(es). Optional.",
|
||||
},
|
||||
isHtml: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Whether the body is HTML content. Defaults to false.",
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
required: ["draftId"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ draftId, to, subject, body, cc, isHtml }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the outlook-update-draft tool.`
|
||||
);
|
||||
|
||||
if (!draftId) {
|
||||
return "Error: 'draftId' is required.";
|
||||
}
|
||||
|
||||
if (!to && !subject && !body && !cc) {
|
||||
return "Error: At least one field to update (to, subject, body, cc) is required.";
|
||||
}
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: { draftId, hasChanges: { to, subject, body, cc } },
|
||||
description: `Update draft ${draftId}`,
|
||||
});
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Updating draft ${draftId}...`
|
||||
);
|
||||
|
||||
const updates = { isHtml };
|
||||
if (to) updates.to = to;
|
||||
if (subject) updates.subject = subject;
|
||||
if (body) updates.body = body;
|
||||
if (cc) updates.cc = cc;
|
||||
|
||||
const result = await outlookLib.updateDraft(draftId, updates);
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to update draft - ${result.error}`
|
||||
);
|
||||
return `Error updating draft: ${result.error}`;
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Successfully updated draft`
|
||||
);
|
||||
|
||||
return (
|
||||
`Successfully updated draft:\n` +
|
||||
`Draft ID: ${result.data.draftId}\n` +
|
||||
`Subject: ${result.data.subject}\n` +
|
||||
`\nThe draft has been updated.`
|
||||
);
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-update-draft", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
const { OutlookGetInbox } = require("./search/outlook-get-inbox.js");
|
||||
const { OutlookSearch } = require("./search/outlook-search.js");
|
||||
const { OutlookReadThread } = require("./search/outlook-read-thread.js");
|
||||
|
||||
const { OutlookCreateDraft } = require("./drafts/outlook-create-draft.js");
|
||||
const { OutlookUpdateDraft } = require("./drafts/outlook-update-draft.js");
|
||||
const { OutlookListDrafts } = require("./drafts/outlook-list-drafts.js");
|
||||
const { OutlookDeleteDraft } = require("./drafts/outlook-delete-draft.js");
|
||||
const { OutlookSendDraft } = require("./drafts/outlook-send-draft.js");
|
||||
|
||||
const { OutlookSendEmail } = require("./send/outlook-send-email.js");
|
||||
|
||||
const {
|
||||
OutlookGetMailboxStats,
|
||||
} = require("./account/outlook-get-mailbox-stats.js");
|
||||
|
||||
const outlookAgent = {
|
||||
name: "outlook-agent",
|
||||
startupConfig: {
|
||||
params: {},
|
||||
},
|
||||
plugin: [
|
||||
// Inbox & Search (read-only)
|
||||
OutlookGetInbox,
|
||||
OutlookSearch,
|
||||
OutlookReadThread,
|
||||
|
||||
// Drafts (create-draft also supports replies via replyToMessageId)
|
||||
OutlookCreateDraft,
|
||||
OutlookUpdateDraft,
|
||||
OutlookListDrafts,
|
||||
OutlookDeleteDraft,
|
||||
OutlookSendDraft,
|
||||
|
||||
// Send (send-email also supports replies via replyToMessageId)
|
||||
OutlookSendEmail,
|
||||
|
||||
// Account (read-only)
|
||||
OutlookGetMailboxStats,
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
outlookAgent,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,75 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { formatMessageSummary, handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookGetInbox = {
|
||||
name: "outlook-get-inbox",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-get-inbox",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Get recent emails from the Outlook inbox. " +
|
||||
"Returns a list of recent messages with subject, sender, date, and read status. " +
|
||||
"Use this to quickly see what's in the inbox.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Show me my recent emails",
|
||||
call: JSON.stringify({ limit: 10 }),
|
||||
},
|
||||
{
|
||||
prompt: "What's in my inbox?",
|
||||
call: JSON.stringify({ limit: 25 }),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
limit: {
|
||||
type: "number",
|
||||
description:
|
||||
"Maximum number of messages to return (1-50). Defaults to 25.",
|
||||
default: 25,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ limit = 25 }) {
|
||||
try {
|
||||
this.super.handlerProps.log(`Using the outlook-get-inbox tool.`);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Fetching Outlook inbox...`
|
||||
);
|
||||
|
||||
const result = await outlookLib.getInbox(limit);
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to get inbox - ${result.error}`
|
||||
);
|
||||
return `Error getting Outlook inbox: ${result.error}`;
|
||||
}
|
||||
|
||||
const { messages, resultCount } = result.data;
|
||||
this.super.introspect(
|
||||
`${this.caller}: Found ${resultCount} messages in inbox`
|
||||
);
|
||||
|
||||
if (resultCount === 0) {
|
||||
return "No messages found in inbox.";
|
||||
}
|
||||
|
||||
const summary = formatMessageSummary(messages);
|
||||
return `Found ${resultCount} messages in inbox:\n\n${summary}\n\nUse the conversation ID with outlook-read-thread to read the full conversation.`;
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-get-inbox", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,114 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { handleAttachments, handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookReadThread = {
|
||||
name: "outlook-read-thread",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-read-thread",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Read a full email conversation thread by its conversation ID. " +
|
||||
"Returns all messages in the thread including sender, recipients, subject, body, date, and attachment information. " +
|
||||
"Use this after searching to read the full conversation.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Read the email thread with conversation ID AAQkAGI2...",
|
||||
call: JSON.stringify({
|
||||
conversationId: "AAQkAGI2...",
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
conversationId: {
|
||||
type: "string",
|
||||
description: "The Outlook conversation ID to read.",
|
||||
},
|
||||
},
|
||||
required: ["conversationId"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ conversationId }) {
|
||||
try {
|
||||
this.super.handlerProps.log(
|
||||
`Using the outlook-read-thread tool.`
|
||||
);
|
||||
|
||||
if (!conversationId) {
|
||||
return "Error: conversationId is required.";
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Reading Outlook conversation...`
|
||||
);
|
||||
|
||||
const result = await outlookLib.readThread(conversationId);
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to read thread - ${result.error}`
|
||||
);
|
||||
return `Error reading Outlook thread: ${result.error}`;
|
||||
}
|
||||
|
||||
const thread = result.data;
|
||||
|
||||
const { allAttachments, parsedContent: parsedAttachmentContent } =
|
||||
await handleAttachments(this, thread.messages);
|
||||
|
||||
const messagesFormatted = thread.messages
|
||||
.map((msg, i) => {
|
||||
let attachmentInfo = "";
|
||||
if (msg.attachments?.length > 0) {
|
||||
attachmentInfo = `\n Attachments: ${msg.attachments.map((a) => `${a.name} (${a.contentType}, ${(a.size / 1024).toFixed(1)}KB)`).join(", ")}`;
|
||||
}
|
||||
|
||||
const bodyContent =
|
||||
msg.bodyType === "html"
|
||||
? msg.body
|
||||
.replace(/<[^>]*>/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
: msg.body;
|
||||
|
||||
return (
|
||||
`--- Message ${i + 1} ---\n` +
|
||||
`From: ${msg.fromName} <${msg.from}>\n` +
|
||||
`To: ${msg.to}\n` +
|
||||
(msg.cc ? `CC: ${msg.cc}\n` : "") +
|
||||
`Date: ${new Date(msg.date).toLocaleString()}\n` +
|
||||
`Subject: ${msg.subject}\n` +
|
||||
`Status: ${msg.isRead ? "READ" : "UNREAD"}\n` +
|
||||
`\n${bodyContent}` +
|
||||
attachmentInfo
|
||||
);
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Successfully read thread with ${thread.messageCount} messages`
|
||||
);
|
||||
|
||||
return (
|
||||
`Thread: "${thread.subject}"\n` +
|
||||
`Conversation ID: ${thread.conversationId}\n` +
|
||||
`Messages: ${thread.messageCount}\n` +
|
||||
`Total Attachments: ${allAttachments.length}\n\n` +
|
||||
messagesFormatted +
|
||||
parsedAttachmentContent
|
||||
);
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-read-thread", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,101 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { formatMessageSummary, handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookSearch = {
|
||||
name: "outlook-search",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-search",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Search emails in Outlook using Microsoft Search syntax. " +
|
||||
"Supports searching by keywords, sender, subject, and more. " +
|
||||
"Common search terms: 'from:email', 'subject:word', 'hasAttachments:true'. " +
|
||||
"Returns message summaries with ID, subject, date, and read status.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Search for emails about the project",
|
||||
call: JSON.stringify({
|
||||
query: "project update",
|
||||
limit: 10,
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Find emails from john@example.com",
|
||||
call: JSON.stringify({
|
||||
query: "from:john@example.com",
|
||||
limit: 20,
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Search for emails with attachments about invoices",
|
||||
call: JSON.stringify({
|
||||
query: "hasAttachments:true invoice",
|
||||
limit: 15,
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description:
|
||||
"Search query. Use Microsoft Search syntax like 'from:email', 'subject:keyword', etc.",
|
||||
},
|
||||
limit: {
|
||||
type: "number",
|
||||
description:
|
||||
"Maximum number of results to return (1-50). Defaults to 10.",
|
||||
default: 10,
|
||||
},
|
||||
skip: {
|
||||
type: "number",
|
||||
description:
|
||||
"Number of results to skip for pagination. Defaults to 0.",
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({ query, limit = 10, skip = 0 }) {
|
||||
try {
|
||||
this.super.handlerProps.log(`Using the outlook-search tool.`);
|
||||
this.super.introspect(
|
||||
`${this.caller}: Searching Outlook with query "${query}"`
|
||||
);
|
||||
|
||||
const result = await outlookLib.search(query, limit, skip);
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Outlook search failed - ${result.error}`
|
||||
);
|
||||
return `Error searching Outlook: ${result.error}`;
|
||||
}
|
||||
|
||||
const { messages, resultCount } = result.data;
|
||||
this.super.introspect(
|
||||
`${this.caller}: Found ${resultCount} messages matching query`
|
||||
);
|
||||
|
||||
if (resultCount === 0) {
|
||||
return `No emails found matching query "${query}".`;
|
||||
}
|
||||
|
||||
const summary = formatMessageSummary(messages);
|
||||
return `Found ${resultCount} messages:\n\n${summary}\n\nUse the conversation ID with outlook-read-thread to read the full conversation.`;
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-search", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,222 +0,0 @@
|
||||
const outlookLib = require("../lib.js");
|
||||
const { prepareAttachmentsWithValidation, handleSkillError } = outlookLib;
|
||||
|
||||
module.exports.OutlookSendEmail = {
|
||||
name: "outlook-send-email",
|
||||
plugin: function () {
|
||||
return {
|
||||
name: "outlook-send-email",
|
||||
setup(aibitat) {
|
||||
aibitat.function({
|
||||
super: aibitat,
|
||||
name: this.name,
|
||||
description:
|
||||
"Send an email immediately through Outlook. " +
|
||||
"This action sends the email right away and cannot be undone. " +
|
||||
"Can also reply to an existing message by providing replyToMessageId. " +
|
||||
"For composing emails that need review before sending, use outlook-create-draft instead.",
|
||||
examples: [
|
||||
{
|
||||
prompt: "Send an email to john@example.com about the project",
|
||||
call: JSON.stringify({
|
||||
to: "john@example.com",
|
||||
subject: "Project Update",
|
||||
body: "Hi John,\n\nHere's the latest update on the project.\n\nBest regards",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Reply to message AAMkAGI2...",
|
||||
call: JSON.stringify({
|
||||
replyToMessageId: "AAMkAGI2...",
|
||||
body: "Thanks for the update. I'll review and get back to you.",
|
||||
}),
|
||||
},
|
||||
{
|
||||
prompt: "Reply all to message AAMkAGI2...",
|
||||
call: JSON.stringify({
|
||||
replyToMessageId: "AAMkAGI2...",
|
||||
body: "Thanks everyone for your input.",
|
||||
replyAll: true,
|
||||
}),
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
$schema: "http://json-schema.org/draft-07/schema#",
|
||||
type: "object",
|
||||
properties: {
|
||||
replyToMessageId: {
|
||||
type: "string",
|
||||
description:
|
||||
"Message ID to reply to. If provided, sends a reply instead of a new email. " +
|
||||
"When replying, 'to' and 'subject' are optional (inherited from original).",
|
||||
},
|
||||
replyAll: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"When replying, whether to reply to all recipients. Defaults to false.",
|
||||
default: false,
|
||||
},
|
||||
to: {
|
||||
type: "string",
|
||||
description:
|
||||
"Recipient email address(es). Required for new emails, optional for replies.",
|
||||
},
|
||||
subject: {
|
||||
type: "string",
|
||||
description:
|
||||
"Email subject line. Required for new emails, optional for replies.",
|
||||
},
|
||||
body: {
|
||||
type: "string",
|
||||
description: "Email body content.",
|
||||
},
|
||||
cc: {
|
||||
type: "string",
|
||||
description: "CC recipient email address(es). Optional.",
|
||||
},
|
||||
bcc: {
|
||||
type: "string",
|
||||
description: "BCC recipient email address(es). Optional.",
|
||||
},
|
||||
isHtml: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Whether the body is HTML content. Defaults to false.",
|
||||
default: false,
|
||||
},
|
||||
attachments: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description:
|
||||
"Array of absolute file paths to attach to the email.",
|
||||
},
|
||||
},
|
||||
required: ["body"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
handler: async function ({
|
||||
replyToMessageId,
|
||||
replyAll = false,
|
||||
to,
|
||||
subject,
|
||||
body,
|
||||
cc,
|
||||
bcc,
|
||||
isHtml,
|
||||
attachments,
|
||||
}) {
|
||||
try {
|
||||
this.super.handlerProps.log(`Using the outlook-send-email tool.`);
|
||||
|
||||
const isReply = !!replyToMessageId;
|
||||
|
||||
if (!isReply && (!to || !subject)) {
|
||||
return "Error: 'to' and 'subject' are required for new emails. For replies, provide 'replyToMessageId'.";
|
||||
}
|
||||
|
||||
if (!body) {
|
||||
return "Error: 'body' is required.";
|
||||
}
|
||||
|
||||
const attachmentResult = await prepareAttachmentsWithValidation(
|
||||
this,
|
||||
attachments,
|
||||
{ requireApprovalPerFile: true, recipientInfo: to }
|
||||
);
|
||||
if (!attachmentResult.success) {
|
||||
return `Error with attachment: ${attachmentResult.error}`;
|
||||
}
|
||||
const {
|
||||
attachments: preparedAttachments,
|
||||
summaries: attachmentSummaries,
|
||||
} = attachmentResult;
|
||||
|
||||
if (this.super.requestToolApproval) {
|
||||
const attachmentNote =
|
||||
preparedAttachments.length > 0
|
||||
? ` with ${preparedAttachments.length} attachment(s): ${attachmentSummaries.join(", ")}`
|
||||
: "";
|
||||
const description = isReply
|
||||
? `Send ${replyAll ? "reply-all" : "reply"} to message ${replyToMessageId}${attachmentNote}? This will send immediately.`
|
||||
: `Send email to "${to}" with subject "${subject}"${attachmentNote} - This will send immediately`;
|
||||
const approval = await this.super.requestToolApproval({
|
||||
skillName: this.name,
|
||||
payload: isReply
|
||||
? { replyToMessageId, replyAll }
|
||||
: {
|
||||
to,
|
||||
subject,
|
||||
attachmentCount: preparedAttachments.length,
|
||||
},
|
||||
description,
|
||||
});
|
||||
if (!approval.approved) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: User rejected the ${this.name} request.`
|
||||
);
|
||||
return approval.message;
|
||||
}
|
||||
}
|
||||
|
||||
let result;
|
||||
if (isReply) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Sending ${replyAll ? "reply-all" : "reply"} to message...`
|
||||
);
|
||||
result = await outlookLib.replyToMessage(
|
||||
replyToMessageId,
|
||||
body,
|
||||
replyAll
|
||||
);
|
||||
} else {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Sending email to ${to}${preparedAttachments.length > 0 ? ` with ${preparedAttachments.length} attachment(s)` : ""}`
|
||||
);
|
||||
|
||||
const options = { isHtml };
|
||||
if (cc) options.cc = cc;
|
||||
if (bcc) options.bcc = bcc;
|
||||
if (preparedAttachments.length > 0) {
|
||||
options.attachments = preparedAttachments;
|
||||
}
|
||||
|
||||
result = await outlookLib.sendEmail(to, subject, body, options);
|
||||
}
|
||||
|
||||
if (!result.success) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Failed to send - ${result.error}`
|
||||
);
|
||||
return `Error sending: ${result.error}`;
|
||||
}
|
||||
|
||||
if (isReply) {
|
||||
this.super.introspect(
|
||||
`${this.caller}: Successfully sent ${replyAll ? "reply-all" : "reply"}`
|
||||
);
|
||||
return `Successfully sent ${replyAll ? "reply-all" : "reply"} to message (ID: ${replyToMessageId}). The reply has been delivered.`;
|
||||
}
|
||||
|
||||
this.super.introspect(
|
||||
`${this.caller}: Successfully sent email to ${to}`
|
||||
);
|
||||
|
||||
return (
|
||||
`Successfully sent email:\n` +
|
||||
`To: ${to}\n` +
|
||||
`Subject: ${subject}\n` +
|
||||
(cc ? `CC: ${cc}\n` : "") +
|
||||
(preparedAttachments.length > 0
|
||||
? `Attachments: ${attachmentSummaries.join(", ")}\n`
|
||||
: "") +
|
||||
`\nThe email has been sent.`
|
||||
);
|
||||
} catch (e) {
|
||||
return handleSkillError(this, "outlook-send-email", e);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -13,33 +13,6 @@ const DEFAULT_SKILLS = [
|
||||
AgentPlugins.webScraping.name,
|
||||
];
|
||||
|
||||
/**
|
||||
* Configuration for agent skills that require availability checks and disabled sub-skill lists.
|
||||
* Each entry maps a skill name to its availability checker and disabled skills list key.
|
||||
*/
|
||||
const SKILL_FILTER_CONFIG = {
|
||||
"filesystem-agent": {
|
||||
getAvailability: () =>
|
||||
require("./aibitat/plugins/filesystem/lib").isToolAvailable(),
|
||||
disabledSettingKey: "disabled_filesystem_skills",
|
||||
},
|
||||
"create-files-agent": {
|
||||
getAvailability: () =>
|
||||
require("./aibitat/plugins/create-files/lib").isToolAvailable(),
|
||||
disabledSettingKey: "disabled_create_files_skills",
|
||||
},
|
||||
"gmail-agent": {
|
||||
getAvailability: async () =>
|
||||
require("./aibitat/plugins/gmail/lib").GmailBridge.isToolAvailable(),
|
||||
disabledSettingKey: "disabled_gmail_skills",
|
||||
},
|
||||
"outlook-agent": {
|
||||
getAvailability: async () =>
|
||||
require("./aibitat/plugins/outlook/lib").OutlookBridge.isToolAvailable(),
|
||||
disabledSettingKey: "disabled_outlook_skills",
|
||||
},
|
||||
};
|
||||
|
||||
const USER_AGENT = {
|
||||
name: "USER",
|
||||
getDefinition: () => {
|
||||
@@ -99,6 +72,33 @@ async function agentSkillsFromSystemSettings() {
|
||||
systemFunctions.push(AgentPlugins[skill].name);
|
||||
});
|
||||
|
||||
// Load disabled filesystem sub-skills
|
||||
const _disabledFilesystemSkills = safeJsonParse(
|
||||
await SystemSettings.getValueOrFallback(
|
||||
{ label: "disabled_filesystem_skills" },
|
||||
"[]"
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// Load disabled create-files sub-skills
|
||||
const _disabledCreateFilesSkills = safeJsonParse(
|
||||
await SystemSettings.getValueOrFallback(
|
||||
{ label: "disabled_create_files_skills" },
|
||||
"[]"
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// Load disabled gmail sub-skills
|
||||
const _disabledGmailSkills = safeJsonParse(
|
||||
await SystemSettings.getValueOrFallback(
|
||||
{ label: "disabled_gmail_skills" },
|
||||
"[]"
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
// Load non-imported built-in skills that are configurable.
|
||||
const _setting = safeJsonParse(
|
||||
await SystemSettings.getValueOrFallback(
|
||||
@@ -108,21 +108,11 @@ async function agentSkillsFromSystemSettings() {
|
||||
[]
|
||||
);
|
||||
|
||||
// Pre-load disabled sub-skills and availability for configured skills
|
||||
const skillFilterState = {};
|
||||
for (const skillName of Object.keys(SKILL_FILTER_CONFIG)) {
|
||||
if (!_setting.includes(skillName)) continue;
|
||||
const config = SKILL_FILTER_CONFIG[skillName];
|
||||
skillFilterState[skillName] = {
|
||||
available: await config.getAvailability(),
|
||||
disabledSubSkills: safeJsonParse(
|
||||
await SystemSettings.getValueOrFallback(
|
||||
{ label: config.disabledSettingKey },
|
||||
"[]"
|
||||
),
|
||||
[]
|
||||
),
|
||||
};
|
||||
// Pre-check gmail availability once (async) to avoid await inside loop
|
||||
let gmailAvailable = false;
|
||||
if (_setting.includes("gmail-agent")) {
|
||||
const gmailTool = require("./aibitat/plugins/gmail/lib");
|
||||
gmailAvailable = await gmailTool.GmailBridge.isToolAvailable();
|
||||
}
|
||||
|
||||
for (const skillName of _setting) {
|
||||
@@ -132,11 +122,34 @@ async function agentSkillsFromSystemSettings() {
|
||||
// need to be named via `${parent}#${child}` naming convention
|
||||
if (Array.isArray(AgentPlugins[skillName].plugin)) {
|
||||
for (const subPlugin of AgentPlugins[skillName].plugin) {
|
||||
// Check if this skill has filter configuration
|
||||
const filterState = skillFilterState[skillName];
|
||||
if (filterState) {
|
||||
if (!filterState.available) continue;
|
||||
if (filterState.disabledSubSkills.includes(subPlugin.name)) continue;
|
||||
/**
|
||||
* If the filesystem tool is not available, or the sub-skill is explicitly disabled, skip it
|
||||
* This is a docker specific skill so it cannot be used in other environments.
|
||||
*/
|
||||
if (skillName === "filesystem-agent") {
|
||||
const filesystemTool = require("./aibitat/plugins/filesystem/lib");
|
||||
if (!filesystemTool.isToolAvailable()) continue;
|
||||
if (_disabledFilesystemSkills.includes(subPlugin.name)) continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the create-files tool is not available, or the sub-skill is explicitly disabled, skip it
|
||||
* This is a docker specific skill so it cannot be used in other environments.
|
||||
*/
|
||||
if (skillName === "create-files-agent") {
|
||||
const createFilesTool = require("./aibitat/plugins/create-files/lib");
|
||||
if (!createFilesTool.isToolAvailable()) continue;
|
||||
if (_disabledCreateFilesSkills.includes(subPlugin.name)) continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the gmail tool is not available (multi-user mode or missing config),
|
||||
* or the sub-skill is explicitly disabled, skip it.
|
||||
* Gmail integration is only available in single-user mode for security reasons.
|
||||
*/
|
||||
if (skillName === "gmail-agent") {
|
||||
if (!gmailAvailable) continue;
|
||||
if (_disabledGmailSkills.includes(subPlugin.name)) continue;
|
||||
}
|
||||
|
||||
systemFunctions.push(
|
||||
|
||||
Reference in New Issue
Block a user