From 2ad413f1470b5c31fd2585685e848fa3a377d8ec Mon Sep 17 00:00:00 2001 From: tintinthong Date: Tue, 20 Jan 2026 21:46:52 +0800 Subject: [PATCH] wip --- packages/bot-runner/README.md | 15 +++ packages/bot-runner/main.ts | 93 +++++++++++++++++++ packages/bot-runner/package.json | 21 +++++ packages/bot-runner/setup-logger.ts | 5 + packages/bot-runner/tsconfig.json | 18 ++++ .../app/commands/create-ai-assistant-room.ts | 4 +- packages/host/app/services/matrix-service.ts | 6 ++ packages/matrix/package.json | 3 +- packages/runtime-common/constants.ts | 1 + pnpm-lock.yaml | 31 ++++++- 10 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 packages/bot-runner/README.md create mode 100644 packages/bot-runner/main.ts create mode 100644 packages/bot-runner/package.json create mode 100644 packages/bot-runner/setup-logger.ts create mode 100644 packages/bot-runner/tsconfig.json diff --git a/packages/bot-runner/README.md b/packages/bot-runner/README.md new file mode 100644 index 0000000000..077d063759 --- /dev/null +++ b/packages/bot-runner/README.md @@ -0,0 +1,15 @@ +# Bot Runner + +Minimal Matrix event listener that will enqueue bot jobs in the future. + +## Environment + +- `MATRIX_URL` (default: `http://localhost:8008`) +- `BOT_RUNNER_USERNAME` (default: `bot-runner`) +- `BOT_RUNNER_PASSWORD` (default: `password`) +- `LOG_LEVELS` (optional, default: `*=info`) + +## Running + +- `pnpm start` +- `pnpm start:development` diff --git a/packages/bot-runner/main.ts b/packages/bot-runner/main.ts new file mode 100644 index 0000000000..7db33c605a --- /dev/null +++ b/packages/bot-runner/main.ts @@ -0,0 +1,93 @@ +import './setup-logger'; // This should be first +import { RoomMemberEvent, RoomEvent, createClient } from 'matrix-js-sdk'; +import { PgAdapter, PgQueuePublisher } from '@cardstack/postgres'; +import { logger } from '@cardstack/runtime-common'; + +const log = logger('bot-runner'); +const startTime = Date.now(); + +const matrixUrl = process.env.MATRIX_URL || 'http://localhost:8008'; +const botUsername = process.env.BOT_RUNNER_USERNAME || 'bot-runner'; +const botPassword = process.env.BOT_RUNNER_PASSWORD || 'password'; + +(async () => { + let client = createClient({ + baseUrl: matrixUrl, + }); + + let auth = await client.loginWithPassword(botUsername, botPassword).catch( + (error) => { + log.error(error); + log.error( + `Bot runner could not login to Matrix at ${matrixUrl}. Check credentials and server availability.`, + ); + process.exit(1); + }, + ); + + log.info(`logged in as ${auth.user_id}`); + + let dbAdapter = new PgAdapter(); + let queuePublisher = new PgQueuePublisher(dbAdapter); + + const shutdown = async () => { + log.info('shutting down bot runner...'); + try { + await queuePublisher.destroy(); + await dbAdapter.close(); + } catch (error) { + log.error('error during shutdown', error); + process.exit(1); + } + process.exit(0); + }; + + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); + + client.on(RoomMemberEvent.Membership, function (event, member) { + if (event.event.origin_server_ts! < startTime) { + return; + } + if (member.membership === 'invite' && member.userId === auth.user_id) { + client + .joinRoom(member.roomId) + .then(function () { + log.info('%s auto-joined %s', member.name, member.roomId); + }) + .catch(function (err) { + log.info( + 'Error joining room after invite (user may have left before join)', + err, + ); + }); + } + }); + + client.on(RoomEvent.Timeline, async (event, room, toStartOfTimeline) => { + if (!room || toStartOfTimeline) { + return; + } + + let senderMatrixUserId = event.getSender(); + if (!senderMatrixUserId || senderMatrixUserId === auth.user_id) { + return; + } + + let eventBody = event.getContent()?.body || ''; + log.info( + 'received event in room %s (%s): %s', + room.name, + room.roomId, + eventBody, + ); + + // TODO: enqueue bot command job via queuePublisher.publish(...) + }); + + client.startClient(); + log.info('bot runner listening for Matrix events'); +})().catch((error) => { + log.error('bot runner failed to start', error); + process.exit(1); +}); diff --git a/packages/bot-runner/package.json b/packages/bot-runner/package.json new file mode 100644 index 0000000000..3e9641c1b2 --- /dev/null +++ b/packages/bot-runner/package.json @@ -0,0 +1,21 @@ +{ + "name": "@cardstack/bot-runner", + "dependencies": { + "@cardstack/postgres": "workspace:*", + "@cardstack/runtime-common": "workspace:*", + "matrix-js-sdk": "catalog:", + "ts-node": "^10.9.2", + "typescript": "catalog:" + }, + "devDependencies": { + "@cardstack/local-types": "workspace:*", + "@types/node": "catalog:" + }, + "scripts": { + "start": "NODE_NO_WARNINGS=1 ts-node --transpileOnly main", + "start:development": "NODE_NO_WARNINGS=1 PGDATABASE=boxel PGPORT=5435 ts-node --transpileOnly main" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/bot-runner/setup-logger.ts b/packages/bot-runner/setup-logger.ts new file mode 100644 index 0000000000..ce3997ba4f --- /dev/null +++ b/packages/bot-runner/setup-logger.ts @@ -0,0 +1,5 @@ +import { makeLogDefinitions } from '@cardstack/runtime-common'; + +(globalThis as any)._logDefinitions = makeLogDefinitions( + process.env.LOG_LEVELS || '*=info', +); diff --git a/packages/bot-runner/tsconfig.json b/packages/bot-runner/tsconfig.json new file mode 100644 index 0000000000..6e3112f2bb --- /dev/null +++ b/packages/bot-runner/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es2023", + "lib": ["es2023", "dom"], + "module": "nodenext", + "moduleResolution": "nodenext", + "allowSyntheticDefaultImports": true, + "noEmit": true, + "inlineSourceMap": true, + "inlineSources": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "skipLibCheck": true, + "strict": true, + "types": ["@cardstack/local-types"] + }, + "include": ["./**/*"] +} diff --git a/packages/host/app/commands/create-ai-assistant-room.ts b/packages/host/app/commands/create-ai-assistant-room.ts index aa0faa03ea..88b712d27b 100644 --- a/packages/host/app/commands/create-ai-assistant-room.ts +++ b/packages/host/app/commands/create-ai-assistant-room.ts @@ -54,6 +54,7 @@ export default class CreateAiAssistantRoomCommand extends HostBaseCommand< let { matrixService } = this; let userId = matrixService.userId; let aiBotFullId = matrixService.aiBotUserId; + let botRunnerFullId = matrixService.botRunnerUserId; if (!userId) { throw new Error( @@ -88,7 +89,8 @@ export default class CreateAiAssistantRoomCommand extends HostBaseCommand< const [roomResult, commandModule] = await Promise.all([ await matrixService.createRoom({ preset: matrixService.privateChatPreset, - invite: [aiBotFullId], + //TODO: botRunner is here for testing purposes. Remove once finished + invite: [aiBotFullId, botRunnerFullId], name: input.name, room_alias_name: encodeURIComponent( `${input.name} - ${format( diff --git a/packages/host/app/services/matrix-service.ts b/packages/host/app/services/matrix-service.ts index 446da992df..7168a025e4 100644 --- a/packages/host/app/services/matrix-service.ts +++ b/packages/host/app/services/matrix-service.ts @@ -28,6 +28,7 @@ import type { } from '@cardstack/runtime-common'; import { aiBotUsername, + botRunnerUsername, logger, isCardInstance, Deferred, @@ -400,6 +401,11 @@ export default class MatrixService extends Service { return `@${aiBotUsername}:${server}`; } + get botRunnerUserId() { + let server = this.userId!.split(':')[1]; + return `@${botRunnerUsername}:${server}`; + } + get userName() { return this.userId ? getMatrixUsername(this.userId) : null; } diff --git a/packages/matrix/package.json b/packages/matrix/package.json index ef4c284d0a..d478dff8ef 100644 --- a/packages/matrix/package.json +++ b/packages/matrix/package.json @@ -31,13 +31,14 @@ "start:admin": "ts-node --transpileOnly ./scripts/admin-console", "stop:admin": "docker stop synapse-admin && docker rm synapse-admin", "register-bot-user": "MATRIX_USERNAME=aibot MATRIX_PASSWORD=pass ts-node --transpileOnly ./scripts/register-test-user.ts", + "register-bot-runner": "MATRIX_IS_ADMIN=TRUE MATRIX_USERNAME=bot-runner MATRIX_PASSWORD=password ts-node --transpileOnly ./scripts/register-test-user.ts", "register-test-user": "MATRIX_USERNAME=user MATRIX_PASSWORD=password ts-node --transpileOnly ./scripts/register-test-user.ts", "register-skills-writer": "MATRIX_USERNAME=skills_writer MATRIX_PASSWORD=password ts-node --transpileOnly ./scripts/register-test-user.ts", "register-homepage-writer": "MATRIX_USERNAME=homepage_writer MATRIX_PASSWORD=password ts-node --transpileOnly ./scripts/register-test-user.ts", "register-realm-users": "./scripts/register-realm-users.sh", "register-test-admin": "MATRIX_IS_ADMIN=TRUE MATRIX_USERNAME=admin MATRIX_PASSWORD=password ts-node --transpileOnly ./scripts/register-test-user.ts", "register-test-admin-and-token": "pnpm register-test-admin && ts-node --transpileOnly ./scripts/register-test-token.ts", - "register-all": "pnpm register-test-admin-and-token && pnpm register-realm-users && pnpm register-bot-user && pnpm register-test-user && pnpm register-skills-writer && pnpm register-homepage-writer", + "register-all": "pnpm register-test-admin-and-token && pnpm register-realm-users && pnpm register-bot-user && pnpm register-bot-runner && pnpm register-test-user && pnpm register-skills-writer && pnpm register-homepage-writer", "test": "./scripts/test.sh", "test:group": "./scripts/test.sh", "wait": "sleep 10000000", diff --git a/packages/runtime-common/constants.ts b/packages/runtime-common/constants.ts index 8d8c913b99..9e4570c8cc 100644 --- a/packages/runtime-common/constants.ts +++ b/packages/runtime-common/constants.ts @@ -42,6 +42,7 @@ export const realmURL = Symbol.for('cardstack-realm-url'); export const relativeTo = Symbol.for('cardstack-relative-to'); export const aiBotUsername = 'aibot'; +export const botRunnerUsername = 'bot-runner'; export const CardContextName = 'card-context'; export const CardCrudFunctionsContextName = 'card-crud-functions-context'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41882fe274..8af69cb7ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -946,6 +946,31 @@ importers: specifier: 'catalog:' version: 8.2.2 + packages/bot-runner: + dependencies: + '@cardstack/postgres': + specifier: workspace:* + version: link:../postgres + '@cardstack/runtime-common': + specifier: workspace:* + version: link:../runtime-common + matrix-js-sdk: + specifier: 'catalog:' + version: 38.3.0(patch_hash=0472d34281d936a5dcdacff67d2851d88c1df9593cd06b7725ee8414c12aa1d5) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@24.3.0)(typescript@5.8.3) + typescript: + specifier: 'catalog:' + version: 5.8.3 + devDependencies: + '@cardstack/local-types': + specifier: workspace:* + version: link:../local-types + '@types/node': + specifier: 'catalog:' + version: 24.3.0 + packages/boxel-homepage-realm: {} packages/boxel-icons: @@ -2028,7 +2053,7 @@ importers: version: link:../runtime-common '@cardstack/view-transitions': specifier: 'catalog:' - version: 0.2.0(@babel/core@7.26.10)(ember-modifier@4.1.0(ember-source@5.4.1(patch_hash=f4d53cc0efd30368b913bdc898f342658768eaf0a8fb44678c0a07cf3aac24c1)(@babel/core@7.26.10)(@glimmer/component@2.0.0)(@glint/template@1.3.0)(rsvp@4.8.5))) + version: 0.2.0(@babel/core@7.26.10)(ember-modifier@4.1.0(ember-source@5.4.1(patch_hash=f4d53cc0efd30368b913bdc898f342658768eaf0a8fb44678c0a07cf3aac24c1)(@babel/core@7.26.10)(@glimmer/component@2.0.0)(@glint/template@1.3.0)(rsvp@4.8.5)(webpack@5.99.6))) '@types/lodash': specifier: 'catalog:' version: 4.17.15 @@ -2104,7 +2129,7 @@ importers: version: link:../runtime-common '@cardstack/view-transitions': specifier: 'catalog:' - version: 0.2.0(@babel/core@7.26.10)(ember-modifier@4.1.0(ember-source@5.4.1(patch_hash=f4d53cc0efd30368b913bdc898f342658768eaf0a8fb44678c0a07cf3aac24c1)(@babel/core@7.26.10)(@glimmer/component@2.0.0)(@glint/template@1.3.0)(rsvp@4.8.5))) + version: 0.2.0(@babel/core@7.26.10)(ember-modifier@4.1.0(ember-source@5.4.1(patch_hash=f4d53cc0efd30368b913bdc898f342658768eaf0a8fb44678c0a07cf3aac24c1)(@babel/core@7.26.10)(@glimmer/component@2.0.0)(@glint/template@1.3.0)(rsvp@4.8.5)(webpack@5.99.6))) '@ember/optional-features': specifier: ^2.0.0 version: 2.0.0 @@ -14693,7 +14718,7 @@ snapshots: '@cardstack/requirejs-monaco-ember-polyfill@0.0.1': {} - '@cardstack/view-transitions@0.2.0(@babel/core@7.26.10)(ember-modifier@4.1.0(ember-source@5.4.1(patch_hash=f4d53cc0efd30368b913bdc898f342658768eaf0a8fb44678c0a07cf3aac24c1)(@babel/core@7.26.10)(@glimmer/component@2.0.0)(@glint/template@1.3.0)(rsvp@4.8.5)))': + '@cardstack/view-transitions@0.2.0(@babel/core@7.26.10)(ember-modifier@4.1.0(ember-source@5.4.1(patch_hash=f4d53cc0efd30368b913bdc898f342658768eaf0a8fb44678c0a07cf3aac24c1)(@babel/core@7.26.10)(@glimmer/component@2.0.0)(@glint/template@1.3.0)(rsvp@4.8.5)(webpack@5.99.6)))': dependencies: '@embroider/addon-shim': 1.10.2 decorator-transforms: 2.3.0(@babel/core@7.26.10)