@framers/agentos-ext-meeting-browser 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/manifest.json +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -114,7 +114,7 @@ TLS belongs to the reverse proxy in front of the scheduler. Every route but `GET
|
|
|
114
114
|
`image/` builds the container the scheduler starts for each run, from this folder (`docker build -f image/Dockerfile .`), since the bot shares `src/protocol.ts` with the scheduler: Debian 12 with Node 24, Chromium through Playwright 1.63.0, PulseAudio, Xvfb, `@livekit/rtc-node`, and the bot program in `image/src/`. One container is one run.
|
|
115
115
|
|
|
116
116
|
- **Start.** `entrypoint.sh` starts PulseAudio on `image/pulse/default.pa` alone: a null sink `meeting`, the browser's output, whose monitor the bot publishes, and a null sink `silence`, whose monitor is the browser's microphone. The root file system is read-only, so PulseAudio's runtime and state folders and its cookie are made under the `/run/pulse` tmpfs as the bot user, and Chromium's config folder (`XDG_CONFIG_HOME`), where its crash handler keeps its database and without which the browser does not start, is `/tmp/.config` in the `/tmp` tmpfs. Then Xvfb on display `:99`, then `bot.js`, which reads the run's token from stdin and opens the command channel at `MEETING_CHANNEL_URL` with it.
|
|
117
|
-
- **The run.** The job is the channel's first message. The bot joins the run's LiveKit room without publishing, launches Chromium on the identity's profile at `/home/bot/profile` when the run has one (a Google Meet identity from the pool) or on a fresh profile, and drives the platform's adapter: it reports `joining`, `waiting_room` and `in_call`, posts the notice, reads it back from the chat panel and reports `notice_posted`, then reads the panel every second. Each step of the posting is bounded and named, so a night that fails there reports the step rather than a bare timeout: `notice: chat_button_missing` (no Chat key was shown and the keyboard shortcut opened no panel), `chat_button_blocked` (the key would not take the click, a dialog over it, and the shortcut opened no panel), `chat_input_missing` (the panel opened without a composer the adapter knows), `chat_input_blocked` (the composer would not take the text), `chat_enter_blocked` (it would not take the Enter) or `chat_echo_missing` (the message was typed and sent and never read back). The message is read back as a new message among those the chat observer reads, so a line the panel already held, the bot's own notice after a rejoin among them, never stands for the one being sent. A failed notice step's report carries, after its code, an outline of the chat panel (`image/src/adapters/chat-outline.ts`): for the main frame and each frame the page embeds, the frame's host and its counts of drawn controls by role (textbox, combobox, button, menuitem), of editables, of list items and of elements carrying a message id, the accessible names of the composer-shaped controls (and of the buttons in an embedded frame, whose controls are the chat's own; a menu item is counted and never named, since no lookup targets one and a menu may list people), each cut to forty characters of letters, digits and a few marks and at most twelve a frame after the filter below, and the lookups the step tried, for example `notice: chat_input_missing; panel: frames=2; f1 meet.google.com textbox=0 combobox=1 button=23 menuitem=0 editable=0 listitem=0 messageid=0; f2 chat.google.com textbox=1 combobox=0 button=6 menuitem=2 editable=1 listitem=3 messageid=3 names=History is on|Send message; tried=composer:role,textarea,editable`. It never holds an address or a link, nor a message's text or a participant's name that the frames draw: a control inside a message element names nothing; a name is tested whole, before any cut, and is dropped when it holds the whole of a tile's, a sender's or a message's text (a message's text read from the element marked as its text, from the element carrying its id, which is all a Google Chat conversation marks, from an item of a list, and from each element of the message that holds no other, so its sender's name and each of its lines count on their own), when such a text holds the whole of it, when it shares a run of three words with one, or when it holds a word of a participant's name, however the name spells them, each text compared whole, read both as the page gives it and with each element in it a word apart (a sender's name beside a message's first line, two lines split by a line break, a tile's name beside an icon's word), with a mark between two words read both as nothing and as a space (`alpha/beta`), and a text or a word of fewer than three characters matched only as a word of the name, except in a script whose words run together (Chinese, Japanese, Korean, Thai, Lao, Khmer, Burmese), where it is matched anywhere in the name and each letter also counts as a word of a run; a name longer than two hundred characters is dropped without its comparison; a name that holds anything shaped like an address or a link (an `@` address, a scheme such as `https://`, `mailto:` or `tel:`, a protocol-relative `//`, a bare host such as `docs.example.com` with or without a path after it, or an IPv4 or an IPv6 address), read as written,
|
|
117
|
+
- **The run.** The job is the channel's first message. The bot joins the run's LiveKit room without publishing, launches Chromium on the identity's profile at `/home/bot/profile` when the run has one (a Google Meet identity from the pool) or on a fresh profile, and drives the platform's adapter: it reports `joining`, `waiting_room` and `in_call`, posts the notice, reads it back from the chat panel and reports `notice_posted`, then reads the panel every second. Each step of the posting is bounded and named, so a night that fails there reports the step rather than a bare timeout: `notice: chat_button_missing` (no Chat key was shown and the keyboard shortcut opened no panel), `chat_button_blocked` (the key would not take the click, a dialog over it, and the shortcut opened no panel), `chat_input_missing` (the panel opened without a composer the adapter knows), `chat_input_blocked` (the composer would not take the text), `chat_enter_blocked` (it would not take the Enter) or `chat_echo_missing` (the message was typed and sent and never read back). The message is read back as a new message among those the chat observer reads, so a line the panel already held, the bot's own notice after a rejoin among them, never stands for the one being sent. A failed notice step's report carries, after its code, an outline of the chat panel (`image/src/adapters/chat-outline.ts`): for the main frame and each frame the page embeds, the frame's host and its counts of drawn controls by role (textbox, combobox, button, menuitem), of editables, of list items and of elements carrying a message id, the accessible names of the composer-shaped controls (and of the buttons in an embedded frame, whose controls are the chat's own; a menu item is counted and never named, since no lookup targets one and a menu may list people), each cut to forty characters of letters, digits and a few marks and at most twelve a frame after the filter below, the Chat key's state, the composer-shaped controls by their fixed facts, and the lookups the step tried, for example `notice: chat_input_missing; panel: frames=2; f1 meet.google.com textbox=0 combobox=1 button=23 menuitem=0 editable=0 listitem=0 messageid=0 key=pressed:true,expanded:none; f2 chat.google.com textbox=1 combobox=0 button=6 menuitem=2 editable=1 listitem=3 messageid=3 cands=div,textbox,ce:1,dis:0,ro:0,ad:0,drawn:1,box:1,vis:visible,ah:0,name:aria,lbl:history,chat:0,land:none names=History is on|Send message; tried=composer:role,textarea,editable`. `key=` is the toolbar's Chat key (the first drawn button named "Chat with everyone") by its `aria-pressed` and `aria-expanded` values (`true`, `false`, `mixed`, `other`, or `none` when absent), the sign of an open panel where the client marks one. `cands=` lists up to four composer-shaped controls (a textbox by role, a textarea, a text-like input, an editable), the drawn ones first and `more:<n>` for the rest, each by fixed facts alone: its tag and role, then `ce` (editable), `dis` (the disabled attribute), `ro` (readonly), `ad` (aria-disabled on it or above it), `drawn` (it has a client rect), `box` (a width and a height, which Playwright's visibility read needs), `vis` (its computed visibility), `ah` (aria-hidden on it or above it, which Playwright's role lookup skips), `name` (how it is named: `labelledby`, `aria`, `label`, `title`, `placeholder` or `none`), `lbl` (whether its name begins as the lookups expect: `send`, `history`, else `other` or `none`), `chat` (an element above it is labelled for the chat) and `land` (the nearest landmark above it). The facts tell apart a composer the adapter's reads pass over (boxless, hidden, under an aria-hidden element, named another way), a disabled one, and a panel that never opened on its key; a name is read for its kind and its start and never written. The outline is fitted to its length (1,900 characters): when the frames' lines would pass it, every frame's counts and key are written first, then each frame's first candidate, then each frame's second, and so on while they fit, then each frame's names while they fit, no token cut; when the counts alone pass it, the frames at the end are dropped whole; the lookups tried always stay at its end. It never holds an address or a link, nor a message's text or a participant's name that the frames draw: a control inside a message element names nothing; a name is tested whole, before any cut, and is dropped when it holds the whole of a tile's, a sender's or a message's text (a message's text read from the element marked as its text, from the element carrying its id, which is all a Google Chat conversation marks, from an item of a list, and from each element of the message that holds no other, so its sender's name and each of its lines count on their own), when such a text holds the whole of it, when it shares a run of three words with one, or when it holds a word of a participant's name, however the name spells them, each text compared whole, read both as the page gives it and with each element in it a word apart (a sender's name beside a message's first line, two lines split by a line break, a tile's name beside an icon's word), with a mark between two words read both as nothing and as a space (`alpha/beta`), a modifier letter written as an apostrophe (`Alʼs`) read as the apostrophe is, and a text or a word of fewer than three characters matched only as a word of the name, except in a script whose words run together (Chinese, Japanese, Korean, Thai, Lao, Khmer, Burmese), where it is matched anywhere in the name and each letter also counts as a word of a run; a name longer than two hundred characters is dropped without its comparison; a name that holds anything shaped like an address or a link (an `@` address, a scheme such as `https://`, `mailto:` or `tel:`, a protocol-relative `//`, a bare host such as `docs.example.com` with or without a path after it, or an IPv4 or an IPv6 address), read as written, with its compatibility forms decomposed and every invisible or combining mark dropped (a format character such as a zero-width space or a soft hyphen, a default-ignorable code point such as a combining grapheme joiner, a combining mark such as a dot above: none sets a scheme's colon or a host's dot apart) and ideographic full stops read as dots, or as it would be shown, is dropped whole, never cleaned and kept; the names are withheld altogether (`names=withheld`) when any frame went unread or any frame's reading stopped at its element limit, since the texts they must not quote may then be unread, and when those texts come to more than twenty thousand terms, which bounds the filter's work; and a frame is named by its host alone. A text the frames do not draw as a message, a sender or a tile (a message outside the part of a long conversation the panel shows, a participant with no tile) is unknown to the filter. The frames are read at once, all of them within three seconds, and a frame that does not answer in time is written as `unread` and holds up no other. A `monitor_heartbeat` goes every ten seconds, and only while the panel was read through in the last ten seconds.
|
|
118
118
|
- **The audio.** The call's audio is published into the room only on `startCapture`: a 48 kHz mono microphone track read from the `meeting` monitor with `parec`, with nothing written to disk. Thirty seconds without a good read of the chat suspend the track inside the container, whatever the manager's clock says, and `startCapture` is refused while the read is that stale, before the notice's chat has been read, or while the bot is leaving. Starts and suspends run one at a time, in the order they come: a `startCapture` that arrives while another is in flight publishes nothing more, so two commands close together publish one track and run one `parec`, and a suspend that arrives while a start is in flight waits for it, then stops the track and the capture that start began.
|
|
119
119
|
- **The chat and the stop.** Each message the bot did not post itself is reported as `chat_message`. A message that is the stop phrase is also reported as `stop_command`, and the bot leaves on it at once with "Leaving now; everything I took is being deleted.". The message is compared with the phrase as AgentOS's meeting manager compares it (`image/src/stop-phrase.ts`): case ignored, each run of whitespace read as one space and the whitespace around it dropped, and `.`, `!` or `?` allowed after it. A message that holds the phrase among other words is reported as `chat_message` alone, another bot's notice in the same meeting among them. A report carries a message's first 4,000 characters, and the bot decides the stop on that text, which is the text the manager reads (`image/src/chat-report.ts`). A message the adapter read from its whole element, because the client drew no body element for it, holds the sender's name and the time on lines of their own, and a line of it that is the phrase, other than the line of the sender's name, is a stop too; another bot's notice is one line that holds the phrase among other words, and stays a chat message. A job whose stop phrase is blank once its whitespace is folded is refused (`src/protocol.ts`), so no run starts with a stop phrase no message could match. The bot's own posts, the notice that names the stop phrase among them, are never reported: the bot knows them by the texts it posted, never by a sender's name, so a participant whose name is You is heard like any other. A message is remembered by the client's id for it, or, when its element carries none, by the element and its text, so the same text sent twice is reported twice.
|
|
120
120
|
- **The limits.** The bot leaves when the waiting room's limit passes (`not_admitted`), when the client's participant count shows nobody else for the alone limit (`alone`), and at the run's whole length (`cap`); the manager's clock is the backstop. A count the client does not show is unknown, never zero. The bot reports `participant_joined` and `participant_left` when the count changes, and `usage` once a minute.
|
|
@@ -142,7 +142,7 @@ TLS belongs to the reverse proxy in front of the scheduler. Every route but `GET
|
|
|
142
142
|
- A Microsoft Teams run with the chat's editable inside `[data-tid="ckeditor"]`, and another with that element itself as the editable, each post the notice and read it back, report the same short message sent twice as two `chat_message` reports, report each message as its body with its author, and leave on the stop phrase, which the second run sends in a card drawn without its body element.
|
|
143
143
|
- A Google Meet run whose chat panel holds Google Chat's editable, a textbox named "History is on", and another whose panel holds that editable under a wrapper carrying the "Send a message" label, neither with a textarea, each post the notice and read it back, report a participant's message as typed, and leave on the stop phrase with the farewell posted through the same composer. Before the adapter read these shapes, each ended as the nightly check's Meet run of 24 October 2026 did: `notice_failed`, thirty seconds after `in_call`.
|
|
144
144
|
- A Google Meet run whose chat's composer, Send key and messages are inside a frame the panel embeds posts the notice through the frame's editable and reads it back from the frame, reports a participant's message drawn in the frame as typed, and leaves on the stop phrase typed there. Before the adapter looked in the page's frames, it ended as the nightly check's Meet run of 25 October 2026 did: `notice_failed` with `notice: chat_input_missing`, ten seconds after `in_call`.
|
|
145
|
-
- A Google Meet run whose panel holds no composer at all reports `notice: chat_input_missing` with the panel's outline after it (the fixture's host, the counts of controls by role and of the message drawn before the panel opened, the lookups tried), in the record line's alphabet and quoting neither that message, its sender nor a tile's name, and leaves with `notice_failed`.
|
|
145
|
+
- A Google Meet run whose panel holds no composer at all reports `notice: chat_input_missing` with the panel's outline after it (the fixture's host, the counts of controls by role and of the message drawn before the panel opened, the Chat key pressed, the pre-join name field as the one composer-shaped control, undrawn under its hidden section, and the lookups tried), in the record line's alphabet and quoting neither that message, its sender nor a tile's name, and leaves with `notice_failed`.
|
|
146
146
|
- `docker inspect` of each live container shows neither its meeting link nor a token, and shows no bind mount, the browser profile in a tmpfs, a read-only root, no capabilities and the seccomp profile.
|
|
147
147
|
- Every report reaches the product under its run's token.
|
|
148
148
|
|
package/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://agentos.sh/schemas/extension-manifest-v1.json",
|
|
3
3
|
"id": "com.framers.meetings.browser",
|
|
4
4
|
"name": "Browser Meeting Bot Provider",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.7",
|
|
6
6
|
"description": "A meeting-participant provider that joins Google Meet and Microsoft Teams through a browser in a container, posts a notice, publishes the call's audio into a LiveKit room, and leaves on any participant's stop command.",
|
|
7
7
|
"categories": ["meetings", "voice"],
|
|
8
8
|
"platforms": ["google_meet", "microsoft_teams"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@framers/agentos-ext-meeting-browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Browser-join meeting bot provider for AgentOS: Google Meet and Microsoft Teams through Chromium in a container, audio into a LiveKit room",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@types/ws": "^8.18.2",
|
|
53
53
|
"typescript": "^5.0.0",
|
|
54
54
|
"vitest": "^3.2.7",
|
|
55
|
-
"@framers/agentos": "^0.
|
|
55
|
+
"@framers/agentos": "^0.15.19"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=22"
|