Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,14 @@ jobs:
if: always()
run: pnpm run lint
working-directory: packages/ai-bot
- name: Lint Bot Core
if: always()
run: pnpm run lint
working-directory: packages/bot-core
- name: Lint Submission Bot
if: always()
run: pnpm run lint
working-directory: packages/submission-bot
- name: Lint Workspace Sync CLI
if: always()
run: pnpm run lint
Expand Down
58 changes: 0 additions & 58 deletions packages/ai-bot/lib/shutdown.ts

This file was deleted.

41 changes: 0 additions & 41 deletions packages/ai-bot/lib/signal-handlers.ts

This file was deleted.

116 changes: 36 additions & 80 deletions packages/ai-bot/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import './instrument';
import './setup-logger'; // This should be first
import type { MatrixEvent } from 'matrix-js-sdk';
import { RoomMemberEvent, RoomEvent, createClient } from 'matrix-js-sdk';
import { SlidingSync, type MSC3575List } from 'matrix-js-sdk/lib/sliding-sync';
import { RoomEvent } from 'matrix-js-sdk';
import OpenAI from 'openai';
import {
logger,
Expand All @@ -23,12 +22,17 @@ import {
constructHistory,
} from '@cardstack/runtime-common/ai';
import { validateAICredits } from '@cardstack/billing/ai-billing';
import { APP_BOXEL_CODE_PATCH_CORRECTNESS_MSGTYPE } from '@cardstack/runtime-common/matrix-constants';
import {
SLIDING_SYNC_AI_ROOM_LIST_NAME,
SLIDING_SYNC_LIST_TIMELINE_LIMIT,
SLIDING_SYNC_TIMEOUT,
APP_BOXEL_CODE_PATCH_CORRECTNESS_MSGTYPE,
} from '@cardstack/runtime-common/matrix-constants';
createBotMatrixClient,
acquireRoomLock,
releaseRoomLock,
createShutdownHandler,
setupSignalHandlers,
isShuttingDown,
createSlidingSync,
setupAutoJoinOnInvite,
} from '@cardstack/bot-core';

import { handleDebugCommands } from './lib/debug';
import { Responder } from './lib/responder';
Expand All @@ -45,12 +49,7 @@ import { PgAdapter } from '@cardstack/postgres';
import type { ChatCompletionMessageParam } from 'openai/resources';
import type { OpenAIError } from 'openai/error';
import type { ChatCompletionStream } from 'openai/lib/ChatCompletionStream';
import { acquireRoomLock, releaseRoomLock } from './lib/queries';
import { DebugLogger } from 'matrix-js-sdk/lib/logger';
import { setupSignalHandlers } from './lib/signal-handlers';
import { isShuttingDown, setActiveGenerations } from './lib/shutdown';
import type { MatrixClient } from 'matrix-js-sdk';
import { debug } from 'debug';
import { profEnabled, profTime, profNote } from './lib/profiler';
import { publishCodePatchCorrectnessMessage } from './lib/code-patch-correctness';

Expand Down Expand Up @@ -163,63 +162,34 @@ let assistant: Assistant;

(async () => {
const matrixUrl = process.env.MATRIX_URL || 'http://localhost:8008';
let matrixDebugLogger = !process.env.DISABLE_MATRIX_JS_LOGGING
? new DebugLogger(debug(`matrix-js-sdk:${aiBotUsername}`))
: undefined;
let client = createClient({
baseUrl: matrixUrl,
logger: matrixDebugLogger,
const enableDebugLogging = !process.env.DISABLE_MATRIX_JS_LOGGING;

// Create and authenticate Matrix client using bot-core
const { client, userId: aiBotUserId } = await createBotMatrixClient({
matrixUrl,
username: aiBotUsername,
password: process.env.BOXEL_AIBOT_PASSWORD || 'pass',
enableDebugLogging,
}).catch((e) => {
log.error(e);
process.exit(1);
});
let auth = await client
.loginWithPassword(
aiBotUsername,
process.env.BOXEL_AIBOT_PASSWORD || 'pass',
)
.catch((e) => {
log.error(e);
log.info(`The matrix bot could not login to the server.
Common issues are:
- The server is not running (configured to use ${matrixUrl})
- Check it is reachable at ${matrixUrl}/_matrix/client/versions
- If running in development, check the docker container is running (see the boxel README)
- The bot is not registered on the matrix server
- The bot uses the username ${aiBotUsername}
- The bot is registered but the password is incorrect
- The bot password ${
process.env.BOXEL_AIBOT_PASSWORD
? 'is set in the env var, check it is correct'
: 'is not set in the env var so defaults to "pass"'
}
`);
process.exit(1);
});
let { user_id: aiBotUserId } = auth;

assistant = new Assistant(client, aiBotUserId, aiBotInstanceId);

// Set up signal handlers for graceful shutdown
setupSignalHandlers();

// Share activeGenerations map with shutdown module
setActiveGenerations(activeGenerations);
// Set up signal handlers for graceful shutdown using bot-core
const handleShutdown = createShutdownHandler({
activeWork: activeGenerations,
workLabel: 'active generations',
});
setupSignalHandlers({ onShutdown: handleShutdown, botName: 'ai-bot' });

client.on(RoomMemberEvent.Membership, function (event, member) {
if (event.event.origin_server_ts! < startTime) {
return;
}
if (member.membership === 'invite' && member.userId === aiBotUserId) {
client
.joinRoom(member.roomId)
.then(function () {
log.info('%s auto-joined %s', member.name, member.roomId);
})
.catch(function (err) {
log.info(
'Error joining this room, typically happens when a user invites then leaves before this is joined',
err,
);
});
}
// Set up auto-join on invite using bot-core
setupAutoJoinOnInvite({
client,
botUserId: aiBotUserId,
ignoreEventsBefore: startTime,
botName: 'ai-bot',
});

// TODO: Set this up to use a queue that gets drained (CS-8516)
Expand Down Expand Up @@ -604,22 +574,8 @@ Common issues are:
}
});

let lists: Map<string, MSC3575List> = new Map();
lists.set(SLIDING_SYNC_AI_ROOM_LIST_NAME, {
ranges: [[0, 0]],
filters: {
is_dm: false,
},
timeline_limit: SLIDING_SYNC_LIST_TIMELINE_LIMIT,
required_state: [['*', '*']],
});
let slidingSync = new SlidingSync(
client.baseUrl,
lists,
{ timeline_limit: SLIDING_SYNC_LIST_TIMELINE_LIMIT },
client,
SLIDING_SYNC_TIMEOUT,
);
// Set up sliding sync using bot-core
const slidingSync = createSlidingSync({ client });
await client.startClient({
slidingSync,
});
Expand Down
1 change: 1 addition & 0 deletions packages/ai-bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@cardstack/ai-bot",
"dependencies": {
"@cardstack/billing": "workspace:*",
"@cardstack/bot-core": "workspace:*",
"@cardstack/postgres": "workspace:*",
"@cardstack/runtime-common": "workspace:*",
"@sentry/node": "catalog:",
Expand Down
8 changes: 8 additions & 0 deletions packages/base/command.gts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,14 @@ export class OpenAiAssistantRoomInput extends CardDef {
@field roomId = contains(StringField);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the invite command shud invite any user. This input can be a subclass if neeeded

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

submissionTarget and submissionType are obviously part of submission bot only

// Submission Bot types
export class InviteSubmissionBotInput extends CardDef {
@field submissionTarget = contains(StringField); // What is being submitted (e.g., PR URL, card ID)
@field submissionType = contains(StringField); // 'pull-request' | 'card' | 'other'
@field autoStart = contains(BooleanField); // Whether to start processing immediately
@field metadata = contains(JsonField); // Additional metadata for the submission
}

export class AddFieldToCardDefinitionInput extends CardDef {
@field realm = contains(StringField);
@field path = contains(StringField);
Expand Down
43 changes: 43 additions & 0 deletions packages/bot-core/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

module.exports = {
root: true,
env: {
node: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
legacyDecorators: true,
},
Comment on lines +12 to +14
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The legacyDecorators feature is enabled but this package doesn't use decorators. Since this is a utility package for Matrix bot services without class decorators, this setting can be removed.

Suggested change
ecmaFeatures: {
legacyDecorators: true,
},

Copilot uses AI. Check for mistakes.
},
plugins: ['ember'],
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'ember' plugin is included in the plugins array but this is not an Ember package - it's a pure Node.js/TypeScript utility package. The ember plugin should be removed since it's not needed for bot-core.

Suggested change
plugins: ['ember'],

Copilot uses AI. Check for mistakes.
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
rules: {
'@typescript-eslint/consistent-type-imports': [
'error',
{
disallowTypeAnnotations: false,
},
],
'@typescript-eslint/no-import-type-side-effects': 'error',
// this doesn't work well with the monorepo. Typescript already complains if you try to import something that's not found
'import/no-unresolved': 'off',
'prefer-const': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/prefer-as-const': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
};
29 changes: 29 additions & 0 deletions packages/bot-core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Matrix client utilities
export {
createBotMatrixClient,
type BotMatrixClientConfig,
} from './lib/matrix-client';

// Room locking for concurrency control
export { acquireRoomLock, releaseRoomLock } from './lib/room-lock';

// Graceful shutdown utilities
export {
isShuttingDown,
setShuttingDown,
handleShutdown,
createShutdownHandler,
type ShutdownConfig,
} from './lib/shutdown';

// Signal handlers (SIGTERM, SIGINT)
export {
setupSignalHandlers,
type SignalHandlerConfig,
} from './lib/signal-handlers';

// Sliding sync setup
export { createSlidingSync, type SlidingSyncConfig } from './lib/sliding-sync';

// Membership utilities (auto-join on invite)
export { setupAutoJoinOnInvite, type AutoJoinConfig } from './lib/membership';
Loading
Loading