Revert "5427 translations (#5429)"

This reverts commit 4172751858.
This commit is contained in:
Timothy Carambat
2026-04-14 13:40:03 -07:00
parent 4172751858
commit b7b380ac51
48 changed files with 60 additions and 6336 deletions

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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);

View File

@@ -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.

View File

@@ -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,
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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);
}
},
});
},
};
},
};

View File

@@ -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(