Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ac64abb
temporarily add submission bot to newly created rooms
tintinthong Feb 19, 2026
f55043a
build architecture to run in headless chrome
tintinthong Feb 19, 2026
a284ca5
update bot runner
tintinthong Feb 19, 2026
55753c5
update setup-submissions
tintinthong Feb 19, 2026
c2e1202
add test
tintinthong Feb 19, 2026
0197023
add command requests in experiments
tintinthong Feb 19, 2026
16cc21a
introduce bot-request-demo in experiments harness
tintinthong Feb 19, 2026
4c10b2f
fix lint
tintinthong Feb 19, 2026
00585a4
update log naming
tintinthong Feb 19, 2026
bc38e7e
return a serialized result no matter what
tintinthong Feb 19, 2026
f8eb802
refactor bot-request-utils
tintinthong Feb 19, 2026
ecd9392
fix nonce scope
tintinthong Feb 19, 2026
cb54b80
fix lint
tintinthong Feb 19, 2026
2d78dd9
add submission bot to every room
tintinthong Feb 19, 2026
f5499de
clean up create-show-card-requests
tintinthong Feb 19, 2026
36d7498
since there is no more filter we remove this test
tintinthong Feb 19, 2026
b2d4eb1
remove test that checks for filter types
tintinthong Feb 19, 2026
5382690
race condition for submission bot is fixed
tintinthong Feb 20, 2026
59fa926
refactor CommandInvocation to explicitly call .value as .cardResult
tintinthong Feb 20, 2026
a605a05
Rename .result to .resultString
tintinthong Feb 20, 2026
9613119
add command parsing separately
tintinthong Feb 20, 2026
b40ed28
add cardResultString
tintinthong Feb 20, 2026
e1175ce
add error handling for prerender server
tintinthong Feb 20, 2026
fced14f
refactor queryParam to pathParam. Change codeRef json to url encoded url
tintinthong Feb 21, 2026
b7be7cd
fix lint
tintinthong Feb 22, 2026
b938140
add doc
tintinthong Feb 22, 2026
9b2a309
update with new archtiecture
tintinthong Feb 22, 2026
f468a43
fix bot request
tintinthong Feb 22, 2026
3452f40
remove bad realm-guessing function from card-id
tintinthong Feb 22, 2026
50b1e96
add error throw
tintinthong Feb 22, 2026
6c58415
add new test assertion
tintinthong Feb 22, 2026
a1d91df
remove unused variables
tintinthong Feb 22, 2026
425a74d
fix test
tintinthong Feb 22, 2026
ca69404
fix test
tintinthong Feb 22, 2026
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
169 changes: 169 additions & 0 deletions docs/commands-in-headless-chrome.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
## Demo

https://www.loom.com/share/2fe9a3e58a7c459ba574b2c0f747a667

## Testing Locally

1. Start `bot-runner` with `pnpm start:development`.
2. Start realm server with `pnpm start:all`.
3. Inside `packages/matrix`, run `pnpm setup-submission-bot`.
4. Open http://localhost:4200/experiments/BotRequestDemo/bot-request-demo.
5. Click **Send Show Card Bot Request** on the demo card.

## Flow Diagram

### User issuing task

Example source is the experiments demo card, but the same flow is used for any `app.boxel.bot-trigger` event.

```mermaid
flowchart TD

B["Trigger 'Send Show Card Bot Request' from UI"]
B --> D["CreateShowCardRequestCommand.execute()"]
D --> E["SendBotTriggerEventCommand.execute()"]
E --> F["sendEvent('app.boxel.bot-trigger')"]
F --> G["bot-runner receives timeline event"]
G --> H["enqueueRunCommandJob() to jobs table"]
```

### Command runner architecture

```mermaid
flowchart TD

A["bot-runner"]
A -->|enqueue run-command job with command string| B["jobs table"]
B --> C["runtime-common worker"]
C -->|runCommand task| D["remote prerenderer"]
D -->|POST /run-command| E["prerender manager"]
E -->|proxy| F["prerender server app"]
F -->|transitionTo command-runner route| G["headless Chrome tab"]
G --> H["host command-runner route"]
H -->|command executes + DOM status| G
G -->|capture data-prerender + data-command-result| F
F --> D
D --> C
C --> B
```

## Matrix Event Payload (`app.boxel.bot-trigger`)

```ts
const event = {
type: 'app.boxel.bot-trigger',
content: {
type: 'show-card',
realm: 'http://localhost:4201/experiments/',
input: {
cardId: 'http://localhost:4201/experiments/Author/jane-doe',
format: 'isolated',
},
},
};
```

## Bot Trigger Data Structures

```ts
type SendBotTriggerEventInput = {
roomId: string;
type: string;
realm: string;
input: Record<string, unknown>;
};

type BotTriggerContent = {
type: string;
realm: string;
input: unknown;
};
```

## Submission Bot Commands (DB)

`setup-submission-bot` now writes canonical scoped command specifiers into `bot_commands.command`.

```json
[
{
"name": "create-listing-pr",
"command": "@cardstack/boxel-host/commands/create-listing-pr/default",
"filter": {
"type": "matrix-event",
"event_type": "app.boxel.bot-trigger",
"content_type": "create-listing-pr"
}
},
{
"name": "show-card",
"command": "@cardstack/boxel-host/commands/show-card/default",
"filter": {
"type": "matrix-event",
"event_type": "app.boxel.bot-trigger",
"content_type": "show-card"
}
},
{
"name": "patch-card-instance",
"command": "@cardstack/boxel-host/commands/patch-card-instance/default",
"filter": {
"type": "matrix-event",
"event_type": "app.boxel.bot-trigger",
"content_type": "patch-card-instance"
}
}
]
```

## Run Command Job Payload

`RunCommandArgs.realmURL` is derived from `event.content.realm`.

```json
{
"realmURL": "http://localhost:4201/experiments/",
"realmUsername": "@alice:localhost",
"runAs": "@alice:localhost",
"command": "@cardstack/boxel-host/commands/show-card/default",
"commandInput": {
"cardId": "http://localhost:4201/experiments/Author/jane-doe",
"format": "isolated"
}
}
```

### Command Normalization in `run-command` task

- Command stays a `string` across bot-runner, job queue, and prerender request.
- `runtime-common/tasks/run-command.ts` normalizes legacy realm-server URL forms (`/commands/<name>/<export>`) into a realm-local module specifier path when needed.
- String -> `ResolvedCodeRef` conversion happens in the host `command-runner` route when it parses `:command`.

## Host `command-runner` Route

```ts
route: /command-runner/:command/:input/:nonce

type CommandRunnerRouteParams = {
command: string;
input: string;
nonce: string;
};
```

### Example (`command` and `input` as path params)

```ts
const command = '@cardstack/boxel-host/commands/show-card/default';
const input = JSON.stringify({
cardId: 'http://localhost:4201/experiments/Author/jane-doe',
format: 'isolated',
});
const nonce = '2';

const url = `http://localhost:4200/command-runner/${encodeURIComponent(command)}/${encodeURIComponent(input)}/${encodeURIComponent(nonce)}`;
```

```txt
http://localhost:4200/command-runner/%40cardstack%2Fboxel-host%2Fcommands%2Fshow-card%2Fdefault/%7B%22cardId%22%3A%22http%3A%2F%2Flocalhost%3A4201%2Fexperiments%2FAuthor%2Fjane-doe%22%2C%22format%22%3A%22isolated%22%7D/2
```
1 change: 1 addition & 0 deletions packages/base/command.gts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ export class SendBotTriggerEventInput extends CardDef {
@field roomId = contains(StringField);
@field type = contains(StringField);
@field input = contains(JsonField);
@field realm = contains(StringField);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems that the worker task requires a realm to be known to check for permissions. So we just constantly send a realm from UI

}

export class PreviewFormatInput extends CardDef {
Expand Down
4 changes: 2 additions & 2 deletions packages/base/matrix-event.gts
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,10 @@ export interface CommandResultEvent extends BaseMatrixEvent {
}

export const BOT_TRIGGER_EVENT_TYPE = 'app.boxel.bot-trigger';
export const BOT_TRIGGER_COMMAND_TYPES = ['create-listing-pr'] as const;

export interface BotTriggerContent {
type: (typeof BOT_TRIGGER_COMMAND_TYPES)[number];
type: string;
realm: string;
input: unknown;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/base/realm.gts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ class EditComponent extends Component<typeof RealmField> {

get writableRealms(): RealmMeta[] {
let resource = this.allRealmsInfoResource;
if (!resource || !resource.isSuccess || !resource.value) {
if (!resource || !resource.isSuccess || !resource.cardResult) {
return [];
}
let results = (resource.value.results ?? []) as RealmMeta[];
let results = (resource.cardResult.results ?? []) as RealmMeta[];
return results.filter((realm) => realm.canWrite);
}

Expand Down
8 changes: 4 additions & 4 deletions packages/base/resources/command-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class CommandExecutionState<CardResultType extends CardDefConstructor>
implements CommandInvocation<CardResultType>
{
@tracked status: 'pending' | 'success' | 'error' = 'pending';
@tracked value: CardInstance<CardResultType> | null = null;
@tracked cardResult: CardInstance<CardResultType> | null = null;
@tracked error: Error | null = null;

get isSuccess() {
Expand All @@ -29,19 +29,19 @@ export class CommandExecutionState<CardResultType extends CardDefConstructor>

setLoading() {
this.status = 'pending';
this.value = null;
this.cardResult = null;
this.error = null;
}

setSuccess(result: CardInstance<CardResultType>) {
this.status = 'success';
this.value = result;
this.cardResult = result;
this.error = null;
}

setError(error: Error) {
this.status = 'error';
this.value = null;
this.cardResult = null;
this.error = error;
}
}
Expand Down
Loading
Loading