Skip to content

Commit 0a8f733

Browse files
committedFeb 25, 2025
Merge branch 'main' into ECO-172-fixed-crm
2 parents 79bed27 + fd05c79 commit 0a8f733

33 files changed

+284
-124
lines changed
 

‎packages/base/code-ref.gts

+21-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ import {
1616
import { ResolvedCodeRef } from '@cardstack/runtime-common';
1717
import CodeIcon from '@cardstack/boxel-icons/code';
1818

19+
function moduleIsUrlLike(module: string) {
20+
return (
21+
module.startsWith('http') ||
22+
module.startsWith('.') ||
23+
module.startsWith('/')
24+
);
25+
}
26+
1927
class BaseView extends Component<typeof CodeRefField> {
2028
<template>
2129
<div data-test-ref>
@@ -39,7 +47,9 @@ export default class CodeRefField extends FieldDef {
3947
) {
4048
return {
4149
...codeRef,
42-
...(opts?.maybeRelativeURL && !opts?.useAbsoluteURL
50+
...(opts?.maybeRelativeURL &&
51+
!opts?.useAbsoluteURL &&
52+
moduleIsUrlLike(codeRef.module)
4353
? { module: opts.maybeRelativeURL(codeRef.module) }
4454
: {}),
4555
};
@@ -70,12 +80,16 @@ function maybeSerializeCodeRef(
7080
stack: CardDef[] = [],
7181
) {
7282
if (codeRef) {
73-
// if a stack is passed in, use the containing card to resolve relative references
74-
let moduleHref =
75-
stack.length > 0
76-
? new URL(codeRef.module, stack[0][relativeTo]).href
77-
: codeRef.module;
78-
return `${moduleHref}/${codeRef.name}`;
83+
if (moduleIsUrlLike(codeRef.module)) {
84+
// if a stack is passed in, use the containing card to resolve relative references
85+
let moduleHref =
86+
stack.length > 0
87+
? new URL(codeRef.module, stack[0][relativeTo]).href
88+
: codeRef.module;
89+
return `${moduleHref}/${codeRef.name}`;
90+
} else {
91+
return `${codeRef.module}/${codeRef.name}`;
92+
}
7993
}
8094
return undefined;
8195
}

‎packages/base/skill-card.gts

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,57 @@
1+
import {
2+
CardDef,
3+
Component,
4+
FieldDef,
5+
field,
6+
contains,
7+
containsMany,
8+
} from './card-api';
9+
import BooleanField from './boolean';
10+
import CodeRefField from './code-ref';
111
import MarkdownField from './markdown';
2-
import { CardDef, Component, field, contains } from './card-api';
12+
import StringField from './string';
313
import RobotIcon from '@cardstack/boxel-icons/robot';
14+
import { simpleHash } from '@cardstack/runtime-common';
15+
16+
function friendlyModuleName(fullModuleUrl: string) {
17+
return fullModuleUrl
18+
.split('/')
19+
.pop()!
20+
.replace(/\.gts$/, '');
21+
}
22+
23+
export class CommandField extends FieldDef {
24+
static displayName = 'CommandField';
25+
@field codeRef = contains(CodeRefField, {
26+
description: 'An absolute code reference to the command to be executed',
27+
});
28+
@field requiresApproval = contains(BooleanField, {
29+
description:
30+
'If true, this command will require human approval before it is executed in the host.',
31+
});
32+
33+
@field functionName = contains(StringField, {
34+
description: 'The name of the function to be executed',
35+
computeVia: function (this: CommandField) {
36+
if (!this.codeRef?.module || !this.codeRef?.name) {
37+
return '';
38+
}
39+
40+
const hashed = simpleHash(`${this.codeRef.module}#${this.codeRef.name}`);
41+
let name =
42+
this.codeRef.name === 'default'
43+
? friendlyModuleName(this.codeRef.module)
44+
: this.codeRef.name;
45+
return `${name}_${hashed.slice(0, 4)}`;
46+
},
47+
});
48+
}
449

550
export class SkillCard extends CardDef {
651
static displayName = 'Skill';
752
static icon = RobotIcon;
853
@field instructions = contains(MarkdownField);
54+
@field commands = containsMany(CommandField);
955
static embedded = class Embedded extends Component<typeof this> {
1056
<template>
1157
<@fields.title />

‎packages/host/app/lib/matrix-classes/room.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { tracked } from '@glimmer/tracking';
1+
import { cached, tracked } from '@glimmer/tracking';
22

33
import { type IEvent } from 'matrix-js-sdk';
44

@@ -44,6 +44,15 @@ export default class Room {
4444
?.name;
4545
}
4646

47+
@cached
48+
get memberIds(): string[] {
49+
let memberEvents = (this._roomState?.events
50+
.get('m.room.member')
51+
?.values() ?? []) as MatrixSDK.MatrixEvent[];
52+
let memberIds = [...memberEvents.map((ev) => ev.event.state_key)];
53+
return memberIds.filter((id) => id !== undefined) as string[];
54+
}
55+
4756
notifyRoomStateUpdated(rs: MatrixSDK.RoomState) {
4857
this._roomState = rs; // this is usually the same object, but some internal state has changed. This assignment kicks off reactivity.
4958
}

‎packages/host/app/resources/room.ts

+30-26
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,36 @@ export class RoomResource extends Resource<Args> {
9595
this.matrixRoom = roomId
9696
? await this.matrixService.getRoomData(roomId)
9797
: undefined; //look at the note in the EventSendingContext interface for why this is awaited
98-
if (this.matrixRoom) {
99-
await this.loadFromEvents(roomId);
98+
if (!this.matrixRoom) {
99+
return;
100+
}
101+
let memberIds = this.matrixRoom.memberIds;
102+
// If the AI bot is not in the room, don't process the events
103+
if (!memberIds || !memberIds.includes(this.matrixService.aiBotUserId)) {
104+
return;
105+
}
106+
107+
let index = this._messageCache.size;
108+
// This is brought up to this level so if the
109+
// load task is rerun we can stop processing
110+
for (let event of this.sortedEvents) {
111+
switch (event.type) {
112+
case 'm.room.member':
113+
await this.loadRoomMemberEvent(roomId, event);
114+
break;
115+
case 'm.room.message':
116+
await this.loadRoomMessage({ roomId, event, index });
117+
break;
118+
case APP_BOXEL_COMMAND_RESULT_EVENT_TYPE:
119+
this.updateMessageCommandResult({ roomId, event, index });
120+
break;
121+
case 'm.room.create':
122+
await this.loadRoomCreateEvent(event);
123+
break;
124+
case 'm.room.name':
125+
await this.loadRoomNameEvent(event);
126+
break;
127+
}
100128
}
101129
} catch (e) {
102130
throw new Error(`Error loading room ${e}`);
@@ -274,30 +302,6 @@ export class RoomResource extends Resource<Args> {
274302
}
275303
});
276304

277-
private async loadFromEvents(roomId: string) {
278-
let index = this._messageCache.size;
279-
280-
for (let event of this.sortedEvents) {
281-
switch (event.type) {
282-
case 'm.room.member':
283-
await this.loadRoomMemberEvent(roomId, event);
284-
break;
285-
case 'm.room.message':
286-
this.loadRoomMessage({ roomId, event, index });
287-
break;
288-
case APP_BOXEL_COMMAND_RESULT_EVENT_TYPE:
289-
this.updateMessageCommandResult({ roomId, event, index });
290-
break;
291-
case 'm.room.create':
292-
await this.loadRoomCreateEvent(event);
293-
break;
294-
case 'm.room.name':
295-
await this.loadRoomNameEvent(event);
296-
break;
297-
}
298-
}
299-
}
300-
301305
private async loadRoomMemberEvent(
302306
roomId: string,
303307
event: InviteEvent | JoinEvent | LeaveEvent,

‎packages/host/tests/acceptance/ai-assistant-test.gts

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,13 @@ module('Acceptance | AI Assistant tests', function (hooks) {
127127
setupServerSentEvents(hooks);
128128
setupOnSave(hooks);
129129
let { createAndJoinRoom, getRoomState } = setupMockMatrix(hooks, {
130-
loggedInAs: '@testuser:staging',
130+
loggedInAs: '@testuser:localhost',
131131
activeRealms: [baseRealm.url, testRealmURL],
132132
});
133133
setupBaseRealm(hooks);
134134

135135
hooks.beforeEach(async function () {
136-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
136+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
137137
setupUserSubscription(matrixRoomId);
138138

139139
class Pet extends CardDef {
@@ -366,7 +366,7 @@ module('Acceptance | AI Assistant tests', function (hooks) {
366366
await click('[data-test-open-ai-assistant]');
367367
assert.dom('[data-test-llm-select-selected]').hasText('claude-3.5-sonnet');
368368

369-
createAndJoinRoom('@testuser:staging', 'room-test-2');
369+
createAndJoinRoom('@testuser:localhost', 'room-test-2');
370370

371371
await click('[data-test-past-sessions-button]');
372372
await waitFor("[data-test-enter-room='mock_room_2']");

‎packages/host/tests/acceptance/basic-test.gts

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ module('Acceptance | basic tests', function (hooks) {
2121
setupLocalIndexing(hooks);
2222
setupServerSentEvents(hooks);
2323
let { createAndJoinRoom } = setupMockMatrix(hooks, {
24-
loggedInAs: '@testuser:staging',
24+
loggedInAs: '@testuser:localhost',
2525
activeRealms: [testRealmURL],
2626
});
2727

2828
hooks.beforeEach(async function () {
29-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
29+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
3030
setupUserSubscription(matrixRoomId);
3131

3232
let loaderService = lookupLoaderService();

‎packages/host/tests/acceptance/code-submode-test.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ module('Acceptance | code submode tests', function (_hooks) {
418418
setupLocalIndexing(hooks);
419419
setupServerSentEvents(hooks);
420420
let { setActiveRealms, createAndJoinRoom } = setupMockMatrix(hooks, {
421-
loggedInAs: '@testuser:staging',
421+
loggedInAs: '@testuser:localhost',
422422
});
423423

424424
async function openNewFileModal(menuSelection: string) {
@@ -428,7 +428,7 @@ module('Acceptance | code submode tests', function (_hooks) {
428428
}
429429

430430
hooks.beforeEach(async function () {
431-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
431+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
432432
setupUserSubscription(matrixRoomId);
433433

434434
let realmServerService = this.owner.lookup(
@@ -442,7 +442,7 @@ module('Acceptance | code submode tests', function (_hooks) {
442442
await setupAcceptanceTestRealm({
443443
realmURL: personalRealmURL,
444444
permissions: {
445-
'@testuser:staging': ['read', 'write', 'realm-owner'],
445+
'@testuser:localhost': ['read', 'write', 'realm-owner'],
446446
},
447447
contents: {
448448
'hello.txt': txtSource,
@@ -456,7 +456,7 @@ module('Acceptance | code submode tests', function (_hooks) {
456456
await setupAcceptanceTestRealm({
457457
realmURL: additionalRealmURL,
458458
permissions: {
459-
'@testuser:staging': ['read', 'write', 'realm-owner'],
459+
'@testuser:localhost': ['read', 'write', 'realm-owner'],
460460
},
461461
contents: {
462462
'hello.txt': txtSource,
@@ -528,12 +528,12 @@ module('Acceptance | code submode tests', function (_hooks) {
528528
setupLocalIndexing(hooks);
529529
setupServerSentEvents(hooks);
530530
let { setActiveRealms, createAndJoinRoom } = setupMockMatrix(hooks, {
531-
loggedInAs: '@testuser:staging',
531+
loggedInAs: '@testuser:localhost',
532532
activeRealms: [testRealmURL],
533533
});
534534

535535
hooks.beforeEach(async function () {
536-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
536+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
537537
setupUserSubscription(matrixRoomId);
538538

539539
monacoService = this.owner.lookup(

‎packages/host/tests/acceptance/code-submode/create-file-test.gts

+2-2
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ module('Acceptance | code submode | create-file tests', function (hooks) {
202202
setupServerSentEvents(hooks);
203203
setupOnSave(hooks);
204204
let { setRealmPermissions, createAndJoinRoom } = setupMockMatrix(hooks, {
205-
loggedInAs: '@testuser:staging',
205+
loggedInAs: '@testuser:localhost',
206206
activeRealms: [baseRealm.url, testRealmURL, testRealmURL2],
207207
});
208208

@@ -215,7 +215,7 @@ module('Acceptance | code submode | create-file tests', function (hooks) {
215215
contents: files,
216216
}));
217217

218-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
218+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
219219
setupUserSubscription(matrixRoomId);
220220

221221
lookupNetworkService().mount(

‎packages/host/tests/acceptance/code-submode/editor-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ module('Acceptance | code submode | editor tests', function (hooks) {
4545
setupServerSentEvents(hooks);
4646
setupOnSave(hooks);
4747
let { setRealmPermissions, createAndJoinRoom } = setupMockMatrix(hooks, {
48-
loggedInAs: '@testuser:staging',
48+
loggedInAs: '@testuser:localhost',
4949
activeRealms: [baseRealm.url, testRealmURL],
5050
});
5151

5252
hooks.beforeEach(async function () {
5353
setRealmPermissions({ [testRealmURL]: ['read', 'write'] });
5454

55-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
55+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
5656
setupUserSubscription(matrixRoomId);
5757

5858
monacoService = this.owner.lookup(

‎packages/host/tests/acceptance/code-submode/file-tree-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,14 @@ module('Acceptance | code submode | file-tree tests', function (hooks) {
192192
setupApplicationTest(hooks);
193193
setupLocalIndexing(hooks);
194194
let { setRealmPermissions, createAndJoinRoom } = setupMockMatrix(hooks, {
195-
loggedInAs: '@testuser:staging',
195+
loggedInAs: '@testuser:localhost',
196196
activeRealms: [testRealmURL],
197197
});
198198

199199
hooks.beforeEach(async function () {
200200
setRealmPermissions({ [testRealmURL]: ['read', 'write'] });
201201

202-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
202+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
203203
setupUserSubscription(matrixRoomId);
204204

205205
const numStubFiles = 100;

‎packages/host/tests/acceptance/code-submode/inspector-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ module('Acceptance | code submode | inspector tests', function (hooks) {
404404
setupServerSentEvents(hooks);
405405
setupOnSave(hooks);
406406
let { setRealmPermissions, createAndJoinRoom } = setupMockMatrix(hooks, {
407-
loggedInAs: '@testuser:staging',
407+
loggedInAs: '@testuser:localhost',
408408
activeRealms: [testRealmURL, testRealmURL2],
409409
});
410410

@@ -414,7 +414,7 @@ module('Acceptance | code submode | inspector tests', function (hooks) {
414414
[testRealmURL2]: ['read', 'write'],
415415
});
416416

417-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
417+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
418418
setupUserSubscription(matrixRoomId);
419419

420420
// this seeds the loader used during index which obtains url mappings

‎packages/host/tests/acceptance/code-submode/playground-test.gts

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ module('Acceptance | code-submode | playground panel', function (hooks) {
3434
setupServerSentEvents(hooks);
3535
let { setRealmPermissions, setActiveRealms, createAndJoinRoom } =
3636
setupMockMatrix(hooks, {
37-
loggedInAs: '@testuser:staging',
37+
loggedInAs: '@testuser:localhost',
3838
activeRealms: [testRealmURL],
3939
});
4040
setupOnSave(hooks);
@@ -125,7 +125,7 @@ export class BlogPost extends CardDef {
125125
}`;
126126

127127
hooks.beforeEach(async function () {
128-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
128+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
129129
setupUserSubscription(matrixRoomId);
130130

131131
({ realm } = await setupAcceptanceTestRealm({

‎packages/host/tests/acceptance/code-submode/recent-files-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,12 @@ module('Acceptance | code submode | recent files tests', function (hooks) {
185185
setupApplicationTest(hooks);
186186
setupLocalIndexing(hooks);
187187
let { createAndJoinRoom } = setupMockMatrix(hooks, {
188-
loggedInAs: '@testuser:staging',
188+
loggedInAs: '@testuser:localhost',
189189
activeRealms: [baseRealm.url, testRealmURL],
190190
});
191191

192192
hooks.beforeEach(async function () {
193-
matrixRoomId = createAndJoinRoom('@testuser:staging', 'room-test');
193+
matrixRoomId = createAndJoinRoom('@testuser:localhost', 'room-test');
194194
setupUserSubscription(matrixRoomId);
195195

196196
// this seeds the loader used during index which obtains url mappings

0 commit comments

Comments
 (0)