Skip to content

Commit cc79f03

Browse files
committed
Merge branch 'main' into crm-use-case
2 parents ec5fcc2 + c21d80e commit cc79f03

File tree

250 files changed

+17167
-10362
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

250 files changed

+17167
-10362
lines changed

.github/workflows/ci-host.yaml

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ jobs:
2929
- name: Build boxel-ui
3030
run: pnpm build
3131
working-directory: packages/boxel-ui/addon
32-
- name: Build host dist/ for fastboot
33-
run: pnpm build
32+
- name: Start host to serve assets for fastboot
33+
run: pnpm start &
3434
env:
3535
NODE_OPTIONS: --max_old_space_size=4096
3636
working-directory: packages/host
37+
- name: Wait for ember-cli to be ready
38+
run: pnpm npx wait-for-localhost 4200
39+
working-directory: packages/host
3740
- name: Start realm servers
3841
run: pnpm start:all &
3942
working-directory: packages/realm-server

.github/workflows/ci.yaml

+21-20
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,14 @@ jobs:
9292
- name: Build boxel-motion
9393
run: pnpm build
9494
working-directory: packages/boxel-motion/addon
95-
- name: Build host dist/ for fastboot
96-
run: pnpm build
95+
- name: Start host to serve assets for fastboot
96+
run: pnpm start &
9797
env:
9898
NODE_OPTIONS: --max_old_space_size=4096
9999
working-directory: packages/host
100+
- name: Wait for ember-cli to be ready
101+
run: pnpm npx wait-for-localhost 4200
102+
working-directory: packages/host
100103
- name: Start realm servers
101104
run: pnpm start:without-matrix &
102105
working-directory: packages/realm-server
@@ -112,6 +115,15 @@ jobs:
112115
path: packages/matrix/blob-report
113116
retention-days: 1
114117

118+
- name: Upload Playwright traces
119+
if: ${{ !cancelled() }}
120+
uses: actions/upload-artifact@v4
121+
with:
122+
name: playwright-traces-${{ matrix.shardIndex }}
123+
path: packages/matrix/test-results/**/trace.zip
124+
retention-days: 30
125+
if-no-files-found: ignore
126+
115127
matrix-client-merge-reports-and-publish:
116128
name: Merge Matrix reports and publish
117129
if: always()
@@ -198,11 +210,14 @@ jobs:
198210
- name: Build boxel-ui
199211
run: pnpm build
200212
working-directory: packages/boxel-ui/addon
201-
- name: Build host dist/ for fastboot
202-
run: pnpm build
213+
- name: Start host to serve assets for fastboot
214+
run: pnpm start &
203215
env:
204216
NODE_OPTIONS: --max_old_space_size=4096
205217
working-directory: packages/host
218+
- name: Wait for ember-cli to be ready
219+
run: pnpm npx wait-for-localhost 4200
220+
working-directory: packages/realm-server
206221
- name: Start realm servers
207222
run: pnpm start:all &
208223
working-directory: packages/realm-server
@@ -234,37 +249,23 @@ jobs:
234249
- '.github/workflows/deploy-host.yml'
235250
- '.github/workflows/manual-deploy.yml'
236251
- '.github/workflows/ci.yaml'
252+
- 'packages/ai-bot/**'
237253
- 'packages/base/**'
238254
- 'packages/boxel-ui/**'
239255
- 'packages/host/**'
240256
- 'packages/realm-server/**'
241257
- 'packages/runtime-common/**'
242258
- 'pnpm-lock.yaml'
243-
ai-bot:
244-
- '.github/workflows/manual-ai-bot.yml'
245-
- '.github/workflows/ci.yaml'
246-
- 'packages/runtime-common/**'
247-
- 'packages/ai-bot/**'
248-
- 'pnpm-lock.yaml'
249259
250260
deploy:
251261
name: Deploy boxel to staging
252262
if: ${{ needs.change-check.outputs.boxel == 'true' }}
253263
needs:
254264
- change-check
265+
- ai-bot-test
255266
- boxel-ui-test
256267
- realm-server-test
257268
uses: ./.github/workflows/manual-deploy.yml
258269
secrets: inherit
259270
with:
260271
environment: "staging"
261-
262-
deploy-ai-bot:
263-
needs:
264-
- ai-bot-test
265-
- change-check
266-
if: ${{ needs.change-check.outputs.ai-bot == 'true' }}
267-
uses: ./.github/workflows/manual-ai-bot.yml
268-
secrets: inherit
269-
with:
270-
environment: "staging"

.github/workflows/manual-ai-bot.yml

-36
This file was deleted.

.github/workflows/manual-deploy.yml

+22-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,27 @@ on:
1414
type: string
1515

1616
jobs:
17+
build-ai-bot:
18+
name: Build ai-bot Docker image
19+
uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main
20+
secrets: inherit
21+
with:
22+
repository: "boxel-ai-bot-${{ inputs.environment }}"
23+
environment: ${{ inputs.environment }}
24+
dockerfile: "packages/ai-bot/Dockerfile"
25+
26+
deploy-ai-bot:
27+
needs: [build-ai-bot]
28+
name: Deploy ai-bot to AWS ECS
29+
uses: cardstack/gh-actions/.github/workflows/ecs-deploy.yml@main
30+
secrets: inherit
31+
with:
32+
container-name: "boxel-ai-bot"
33+
environment: ${{ inputs.environment }}
34+
cluster: ${{ inputs.environment }}
35+
service-name: "boxel-ai-bot-${{ inputs.environment }}"
36+
image: ${{ needs.build-ai-bot.outputs.image }}
37+
1738
build-host:
1839
name: Build host
1940
uses: ./.github/workflows/build-host.yml
@@ -44,7 +65,7 @@ jobs:
4465
environment: staging
4566

4667
build-realm-server:
47-
name: Build Docker image
68+
name: Build realm-server Docker image
4869
uses: cardstack/gh-actions/.github/workflows/docker-ecr.yml@main
4970
secrets: inherit
5071
with:

QUICKSTART.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ To build the entire repository and run the application, follow these steps:
4242

4343
```zsh
4444
cd ./packages/realm-server
45-
pnpm start:all
45+
DISABLE_MODULE_CACHING=true pnpm start:all
4646
```
4747

4848
Note: Ensure that the realm-server is completely started by looking out for tor the test-realm indexing output.
@@ -84,7 +84,10 @@ To build the entire repository and run the application, follow these steps:
8484
11. Validate email for login
8585
- Visit SMTP UI at http://localhost:5001/
8686
- Validate email
87-
- Go back to Host and login
87+
- Go back to Host http://localhost:4201/ and login
88+
89+
12. Trigger interact mode
90+
- After you see a list of cards, Ctrl+ . OR Ctrl +, to "interact" with them
8891

8992
13. Run ai bot (Optional):
9093

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ In order to run the realm server hosted app:
6666

6767
You can visit the URL of each realm server to view that realm's app. So for instance, the base realm's app is available at `http://localhost:4201/base` and the draft realm's app is at `http://localhost:4201/draft`.
6868

69-
Live reloads are not available in this mode, but you can just refresh the page to grab the latest code changes if you are running rebuilds (step #1 and #2 above).
69+
Live reloads are not available in this mode, however, if you use start the server with the environment variable `DISABLE_MODULE_CACHING=true` you can just refresh the page to grab the latest code changes if you are running rebuilds (step #1 and #2 above).
7070

7171
#### Using `start:all`
7272

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"pnpm": {
2121
"overrides": {
2222
"@types/eslint": "8.4.1",
23-
"@embroider/util": "1.12.0",
23+
"@embroider/util": "1.13.1",
2424
"@glimmer/tracking>@glimmer/validator": "0.84.3",
2525
"jsesc": "^3.0.0",
2626
"ember-modifier": "^4.1.0",

packages/ai-bot/helpers.ts

+68-31
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
import { type LooseSingleCardDocument } from '@cardstack/runtime-common';
1+
import type {
2+
CardResource,
3+
LooseSingleCardDocument,
4+
} from '@cardstack/runtime-common';
25
import type {
36
MatrixEvent as DiscreteMatrixEvent,
47
CardFragmentContent,
58
CommandEvent,
6-
} from 'https://cardstack.com/base/room';
9+
} from 'https://cardstack.com/base/matrix-event';
710
import { MatrixEvent, type IRoomEvent } from 'matrix-js-sdk';
811

912
const MODIFY_SYSTEM_MESSAGE =
@@ -23,6 +26,9 @@ If a user asks you about things in the world, use your existing knowledge to hel
2326
If you need access to the cards the user can see, you can ask them to attach the cards. \
2427
If you encounter JSON structures, please enclose them within backticks to ensure they are displayed stylishly in Markdown.';
2528

29+
export const SKILL_INSTRUCTIONS_MESSAGE =
30+
'\nThe user has given you the following instructions. You must obey these instructions when responding to the user:\n';
31+
2632
type CommandMessage = {
2733
type: 'command';
2834
content: any;
@@ -62,14 +68,16 @@ export function constructHistory(history: IRoomEvent[]) {
6268
fragments.set(eventId, event.content);
6369
continue;
6470
} else if (event.content.msgtype === 'org.boxel.message') {
65-
if (
66-
event.content.data.attachedCardsEventIds &&
67-
event.content.data.attachedCardsEventIds.length > 0
68-
) {
69-
event.content.data.attachedCards =
70-
event.content.data.attachedCardsEventIds!.map((id) =>
71-
serializedCardFromFragments(id, fragments),
72-
);
71+
let { attachedCardsEventIds, attachedSkillEventIds } = event.content.data;
72+
if (attachedCardsEventIds && attachedCardsEventIds.length > 0) {
73+
event.content.data.attachedCards = attachedCardsEventIds.map((id) =>
74+
serializedCardFromFragments(id, fragments),
75+
);
76+
}
77+
if (attachedSkillEventIds && attachedSkillEventIds.length > 0) {
78+
event.content.data.skillCards = attachedSkillEventIds.map((id) =>
79+
serializedCardFromFragments(id, fragments),
80+
);
7381
}
7482
}
7583

@@ -142,35 +150,55 @@ export interface OpenAIPromptMessage {
142150
role: 'system' | 'user' | 'assistant';
143151
}
144152

153+
function setRelevantCards(
154+
cardMap: Map<string, CardResource> = new Map(),
155+
cards: LooseSingleCardDocument[] = [],
156+
) {
157+
for (let card of cards) {
158+
if (card.data.id) {
159+
cardMap.set(card.data.id, card.data as CardResource);
160+
} else {
161+
throw new Error(`bug: don't know how to handle card without ID`);
162+
}
163+
}
164+
return cardMap;
165+
}
166+
145167
export function getRelevantCards(
146168
history: DiscreteMatrixEvent[],
147169
aiBotUserId: string,
148170
) {
149-
let relevantCards: Map<string, any> = new Map();
171+
let attachedCardMap = new Map<string, CardResource>();
172+
let skillCardMap = new Map<string, CardResource>();
173+
let latestMessageEventId = history
174+
.filter((ev) => ev.sender !== aiBotUserId && ev.type === 'm.room.message')
175+
.slice(-1)[0]?.event_id;
150176
for (let event of history) {
151177
if (event.type !== 'm.room.message') {
152178
continue;
153179
}
154180
if (event.sender !== aiBotUserId) {
155181
let { content } = event;
156182
if (content.msgtype === 'org.boxel.message') {
157-
const attachedCards = content.data?.attachedCards || [];
158-
for (let card of attachedCards) {
159-
if (card.data.id) {
160-
relevantCards.set(card.data.id, card.data);
161-
} else {
162-
throw new Error(`bug: don't know how to handle card without ID`);
163-
}
183+
setRelevantCards(attachedCardMap, content.data?.attachedCards);
184+
185+
// setting skill card instructions only based on the latest boxel message event (not cumulative)
186+
if (event.event_id === latestMessageEventId) {
187+
setRelevantCards(skillCardMap, content.data?.skillCards);
164188
}
165189
}
166190
}
167191
}
168-
169-
// Return the cards in a consistent manner
170-
let sortedCards = Array.from(relevantCards.values()).sort((a, b) => {
171-
return a.id.localeCompare(b.id);
172-
});
173-
return sortedCards;
192+
let attachedCards = Array.from(attachedCardMap.values()).sort((a, b) =>
193+
a.id.localeCompare(b.id),
194+
);
195+
let skillCards = Array.from(skillCardMap.values()).sort((a, b) =>
196+
a.id.localeCompare(b.id),
197+
);
198+
return {
199+
attachedCards,
200+
skillCards,
201+
};
174202
}
175203

176204
export function getTools(history: DiscreteMatrixEvent[], aiBotUserId: string) {
@@ -227,12 +255,20 @@ export function getModifyPrompt(
227255
}
228256
}
229257

258+
let { attachedCards, skillCards } = getRelevantCards(history, aiBotUserId);
230259
let systemMessage =
231260
MODIFY_SYSTEM_MESSAGE +
232261
`
233262
The user currently has given you the following data to work with:
234263
Cards:\n`;
235-
systemMessage += attachedCardsToMessage(history, aiBotUserId);
264+
systemMessage += attachedCardsToMessage(attachedCards);
265+
266+
if (skillCards.length) {
267+
systemMessage += SKILL_INSTRUCTIONS_MESSAGE;
268+
systemMessage += skillCardsToMessage(skillCards);
269+
systemMessage += '\n';
270+
}
271+
236272
if (tools.length == 0) {
237273
systemMessage +=
238274
'You are unable to edit any cards, the user has not given you access, they need to open the card on the stack and let it be auto-attached';
@@ -249,12 +285,13 @@ export function getModifyPrompt(
249285
return messages;
250286
}
251287

252-
export const attachedCardsToMessage = (
253-
history: DiscreteMatrixEvent[],
254-
aiBotUserId: string,
255-
) => {
256-
return `Full card data: ${JSON.stringify(
257-
getRelevantCards(history, aiBotUserId),
288+
export const attachedCardsToMessage = (cards: CardResource[]) => {
289+
return `Full card data: ${JSON.stringify(cards)}`;
290+
};
291+
292+
export const skillCardsToMessage = (cards: CardResource[]) => {
293+
return `${JSON.stringify(
294+
cards.map((card) => card.attributes?.instructions),
258295
)}`;
259296
};
260297

packages/ai-bot/lib/set-title.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
attachedCardsToMessage,
88
} from '../helpers';
99
import { MatrixClient } from './matrix';
10-
import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/room';
10+
import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/matrix-event';
1111

1212
const SET_TITLE_SYSTEM_MESSAGE = `You are a chat titling system, you must read the conversation and return a suggested title of no more than six words.
1313
Do NOT say talk or discussion or discussing or chat or chatting, this is implied by the context.

packages/ai-bot/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
import { Responder } from './lib/send-response';
2222
import { handleDebugCommands } from './lib/debug';
2323
import { MatrixClient } from './lib/matrix';
24-
import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/room';
24+
import type { MatrixEvent as DiscreteMatrixEvent } from 'https://cardstack.com/base/matrix-event';
2525
import * as Sentry from '@sentry/node';
2626

2727
if (process.env.SENTRY_DSN) {

0 commit comments

Comments
 (0)