viberoom 0.3.0 → 0.4.0
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/NOTICE +12 -0
- package/README.md +119 -96
- package/dist/hub.js +18 -0
- package/dist/launcher.js +22 -0
- package/dist/main.js +4 -3
- package/dist/persona.js +2 -2
- package/dist/recipes.js +37 -14
- package/dist/room.js +3 -2
- package/dist/server.js +16 -3
- package/package.json +8 -5
- package/scripts/vendor-acp.mjs +85 -0
- package/ui/app.css +7 -0
- package/ui/app.js +116 -26
- package/ui/index.html +2 -0
- package/vendor/acp/claude-agent-acp/LICENSE +191 -0
- package/vendor/acp/claude-agent-acp/dist/acp-agent.js +7694 -0
- package/vendor/acp/claude-agent-acp/dist/acp-subagents.js +13 -0
- package/vendor/acp/claude-agent-acp/dist/air-extension.js +63 -0
- package/vendor/acp/claude-agent-acp/dist/async-tasks.js +613 -0
- package/vendor/acp/claude-agent-acp/dist/clear-context-coordinator.js +80 -0
- package/vendor/acp/claude-agent-acp/dist/elicitation.js +304 -0
- package/vendor/acp/claude-agent-acp/dist/exit-plan.js +154 -0
- package/vendor/acp/claude-agent-acp/dist/file-change-audit.js +350 -0
- package/vendor/acp/claude-agent-acp/dist/fork-session.js +41 -0
- package/vendor/acp/claude-agent-acp/dist/goal-extension.js +50 -0
- package/vendor/acp/claude-agent-acp/dist/index.js +98 -0
- package/vendor/acp/claude-agent-acp/dist/lib.js +5 -0
- package/vendor/acp/claude-agent-acp/dist/native-subagents.js +422 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/effects.js +166 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/modes.js +41 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/normalization.js +100 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/options/filesystem.js +124 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/options/shared.js +64 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/options/shell.js +100 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/options/tools.js +135 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/options.js +60 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/presentation.js +83 -0
- package/vendor/acp/claude-agent-acp/dist/permissions/response.js +19 -0
- package/vendor/acp/claude-agent-acp/dist/session-config-ids.js +4 -0
- package/vendor/acp/claude-agent-acp/dist/session-failure-extension.js +324 -0
- package/vendor/acp/claude-agent-acp/dist/session-mode.js +234 -0
- package/vendor/acp/claude-agent-acp/dist/session-titles.js +199 -0
- package/vendor/acp/claude-agent-acp/dist/settings.js +185 -0
- package/vendor/acp/claude-agent-acp/dist/tool-result-meta.js +19 -0
- package/vendor/acp/claude-agent-acp/dist/tools.js +1235 -0
- package/vendor/acp/claude-agent-acp/dist/utils.js +81 -0
- package/vendor/acp/claude-agent-acp/package.json +7 -0
- package/vendor/acp/claude-agent-sdk/LICENSE.md +1 -0
- package/vendor/acp/claude-agent-sdk/agentSdkTypes.d.ts +1 -0
- package/vendor/acp/claude-agent-sdk/bridge.d.ts +378 -0
- package/vendor/acp/claude-agent-sdk/bridge.mjs +221 -0
- package/vendor/acp/claude-agent-sdk/browser-sdk.d.ts +107 -0
- package/vendor/acp/claude-agent-sdk/browser-sdk.js +185 -0
- package/vendor/acp/claude-agent-sdk/extractFromBunfs.d.ts +1 -0
- package/vendor/acp/claude-agent-sdk/extractFromBunfs.js +156 -0
- package/vendor/acp/claude-agent-sdk/manifest.json +65 -0
- package/vendor/acp/claude-agent-sdk/manifest.zst.json +73 -0
- package/vendor/acp/claude-agent-sdk/package.json +7 -0
- package/vendor/acp/claude-agent-sdk/sdk-tools.d.ts +4129 -0
- package/vendor/acp/claude-agent-sdk/sdk.d.ts +8687 -0
- package/vendor/acp/claude-agent-sdk/sdk.mjs +204 -0
- package/vendor/acp/codex-acp/LICENSE +190 -0
- package/vendor/acp/codex-acp/dist/index.js +34238 -0
- package/vendor/acp/codex-acp/package.json +7 -0
package/NOTICE
CHANGED
|
@@ -11,6 +11,18 @@ This product bundles the following third-party material:
|
|
|
11
11
|
registry (https://agentclientprotocol.com). Each mark belongs to its owner and
|
|
12
12
|
is used only to identify the agent it stands for.
|
|
13
13
|
|
|
14
|
+
- ACP adapters (vendor/acp/, copied from the npm packages at build time by
|
|
15
|
+
scripts/vendor-acp.mjs, each with its licence file):
|
|
16
|
+
- @agentclientprotocol/claude-agent-acp: Apache License 2.0, Copyright (c)
|
|
17
|
+
the Agent Client Protocol contributors.
|
|
18
|
+
- @agentclientprotocol/codex-acp: Apache License 2.0, Copyright (c) the Agent
|
|
19
|
+
Client Protocol contributors.
|
|
20
|
+
- @anthropic-ai/claude-agent-sdk (the library the Claude adapter is built on,
|
|
21
|
+
unmodified): (c) Anthropic PBC, all rights reserved; use is subject to
|
|
22
|
+
Anthropic's legal agreements (https://code.claude.com/docs/en/legal-and-compliance).
|
|
23
|
+
The agents themselves (Claude Code, Codex CLI) are not part of viberoom: it
|
|
24
|
+
runs the copies the user installed.
|
|
25
|
+
|
|
14
26
|
- Mermaid (served from node_modules at run time, not copied): MIT License,
|
|
15
27
|
Copyright (c) 2014-2025 Knut Sveidqvist.
|
|
16
28
|
|
package/README.md
CHANGED
|
@@ -5,29 +5,106 @@
|
|
|
5
5
|
<h1 align="center">viberoom</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center"><strong>One chat, many coding agents.</strong><br>
|
|
8
|
-
Open a room, summon
|
|
8
|
+
Open a room, summon the agents you already have, give each one a role, and let them work it out with you and with each other.</p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
11
|
<a href="https://www.npmjs.com/package/viberoom"><img src="https://img.shields.io/npm/v/viberoom?color=6c63ff&label=npm" alt="npm"></a>
|
|
12
12
|
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%E2%89%A5%2022-6c63ff" alt="Node 22+"></a>
|
|
13
|
-
<a href="https://agentclientprotocol.com"><img src="https://img.shields.io/badge/protocol-ACP-6c63ff" alt="Agent Client Protocol"></a>
|
|
14
13
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-6c63ff" alt="AGPL-3.0"></a>
|
|
15
14
|
</p>
|
|
16
15
|
|
|
17
16
|
<p align="center">
|
|
18
|
-
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/conversation.png" width="
|
|
17
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/conversation.png" width="960" alt="Three vibemates arguing out a dedupe rule in a room">
|
|
19
18
|
</p>
|
|
20
19
|
|
|
21
20
|
<p align="center">
|
|
22
21
|
<b>Claude</b> · <b>Codex</b> · <b>Gemini</b> · <b>Cursor</b> · <b>OpenCode</b> · <b>GitHub Copilot</b><br>
|
|
23
|
-
<sub>
|
|
22
|
+
<sub>whichever of them you have installed, with your own logins, in one room</sub>
|
|
24
23
|
</p>
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
<br>
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
## The idea
|
|
28
|
+
|
|
29
|
+
One agent is a conversation. Three agents in one room are a **team**: one decides what "done" means,
|
|
30
|
+
one builds it, one says no. You set the roles and the rules, then talk to the room the way you would
|
|
31
|
+
talk to people. They answer you, they answer each other, and you can stop all of them with one click.
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/art/room.svg" width="960" alt="What a room is: a folder and a topic, vibemates with a character, rules and skills">
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<br>
|
|
38
|
+
|
|
39
|
+
## Give them roles
|
|
40
|
+
|
|
41
|
+
A vibemate is any agent on your machine plus a character: a **vibename**, a **vibeface**, a one-line
|
|
42
|
+
**vibersona** everyone sees, and a private **vibio** only this vibemate reads. Pick the model, the
|
|
43
|
+
effort and how much it may do on its own. Three vibemates from the same agent with three different
|
|
44
|
+
vibios behave like three different people, which is exactly the point.
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/summon.png" width="960" alt="Summon a vibemate: agent, model, effort, mode, name, face and a private brief">
|
|
48
|
+
</p>
|
|
49
|
+
|
|
50
|
+
<br>
|
|
51
|
+
|
|
52
|
+
## Set the room rules
|
|
53
|
+
|
|
54
|
+
Rules are plain sentences. Who talks to whom, how long a reply may be, what happens on step three.
|
|
55
|
+
Type `@` and the name becomes a live reference: rename the vibemate and the rule follows. Every
|
|
56
|
+
vibemate gets the rules as instructions, so the room runs the way you wrote it down.
|
|
57
|
+
|
|
58
|
+
<p align="center">
|
|
59
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/rules.png" width="820" alt="Room rules with live @references to the vibemates">
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
Here is what a five-line ruleset buys you: a handoff chain that runs on its own, with the human at
|
|
63
|
+
both ends.
|
|
64
|
+
|
|
65
|
+
<p align="center">
|
|
66
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/art/handoff.svg" width="960" alt="One task, four hands: you to Pip to Nova to Rex, review loop, report back">
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
<br>
|
|
70
|
+
|
|
71
|
+
## Teach them skills
|
|
72
|
+
|
|
73
|
+
A skill is a reusable instruction with a name. Keep them in one library, attach them to the vibemates
|
|
74
|
+
that need them, and run one with `/name` in the chat. A vibemate can write a skill too, and the
|
|
75
|
+
library marks it as theirs until you have read it.
|
|
76
|
+
|
|
77
|
+
<p align="center">
|
|
78
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/skills.png" width="820" alt="The skills library: a skill written by a vibemate, and a built-in one">
|
|
79
|
+
</p>
|
|
80
|
+
|
|
81
|
+
<br>
|
|
82
|
+
|
|
83
|
+
## Talk to all, or to one
|
|
84
|
+
|
|
85
|
+
<p align="center">
|
|
86
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/art/ways-to-talk.svg" width="960" alt="Four ways to speak: to everyone, to one with @, run a skill with /, and Hush">
|
|
87
|
+
</p>
|
|
88
|
+
|
|
89
|
+
Write to the room and every vibemate answers, each after a short pause so replies do not trip over
|
|
90
|
+
each other. `@Name` one of them and the rest read along. Vibemates address each other the same way,
|
|
91
|
+
and a hop limit keeps a two-agent argument from running all night. **Hush** stops every running reply
|
|
92
|
+
at once; the room stays quiet until you write again.
|
|
93
|
+
|
|
94
|
+
<br>
|
|
95
|
+
|
|
96
|
+
## What you get back
|
|
97
|
+
|
|
98
|
+
Replies come as proper text: lists, tables, code. A file path in a reply opens the file; a `.md` or
|
|
99
|
+
`.csv` opens right in the room, rendered or as a table. A path with a line number opens your editor
|
|
100
|
+
at that line. A diagram block becomes a diagram. Change one of your own messages after the fact and
|
|
101
|
+
the vibemates are told, or the conversation rewinds to that point.
|
|
102
|
+
|
|
103
|
+
<p align="center">
|
|
104
|
+
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/home.png" width="960" alt="The home page: welcome back, your rooms, what you can do here">
|
|
105
|
+
</p>
|
|
106
|
+
|
|
107
|
+
<br>
|
|
31
108
|
|
|
32
109
|
## Sixty seconds to a room
|
|
33
110
|
|
|
@@ -41,76 +118,31 @@ viberoom
|
|
|
41
118
|
3. **Summon vibemates.** Pick an agent, name it, give it a character. Repeat.
|
|
42
119
|
4. **Say hello.** Everyone answers in turn. `@Name` one of them, `/name` runs a skill.
|
|
43
120
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- **Rooms.** A room is a folder and a topic. Every vibemate works in that folder, and the history
|
|
47
|
-
stays with the room. Move the folder and the vibemates move with it.
|
|
48
|
-
- **Vibemates.** Summon any installed agent, give it a name, a one-line persona, a private brief and
|
|
49
|
-
a face. Pick the model, the effort and the permission mode per vibemate.
|
|
50
|
-
- **Talk to all, or to one.** Write to the room and everyone answers in turn; `@Name` one of them and
|
|
51
|
-
the others listen. Vibemates read each other's replies and address each other the same way.
|
|
52
|
-
- **Turn taking.** Every addressed vibemate answers at once by default, each after a short random
|
|
53
|
-
delay so replies do not cross; or one at a time, the others waiting their turn. A hop limit keeps
|
|
54
|
-
agent-to-agent chatter from running away.
|
|
55
|
-
- **Hush.** One click stops every running reply; the vibemates stay quiet until you write again.
|
|
56
|
-
- **Skills.** Reusable instructions in a library. Attach them to vibemates, invoke one with `/name`,
|
|
57
|
-
or let a vibemate write its own.
|
|
58
|
-
- **Markdown, links, files and diagrams.** Replies render as Markdown: lists, tables, code. Links and
|
|
59
|
-
file paths open on your machine; a `.md` or `.csv` path opens right in the room (rendered, or as a
|
|
60
|
-
table); a path with a `:line` opens in your editor at that line; a ```` ```mermaid ```` block
|
|
61
|
-
becomes a diagram.
|
|
62
|
-
- **Edit a message.** Change what you said: the vibemates are told, or the conversation is rewound.
|
|
63
|
-
- **A desktop app.** A hidden hub, an app window of its own, a Start Menu / Dock / desktop icon.
|
|
64
|
-
|
|
65
|
-
## A tour
|
|
66
|
-
|
|
67
|
-
<table>
|
|
68
|
-
<tr>
|
|
69
|
-
<td width="50%" valign="top">
|
|
70
|
-
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/welcome.png" alt="Welcome: set your vibe"><br>
|
|
71
|
-
<sub><strong>Your vibe.</strong> The first start asks how the vibemates should know you: a name, a face, a line about you.</sub>
|
|
72
|
-
</td>
|
|
73
|
-
<td width="50%" valign="top">
|
|
74
|
-
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/home.png" alt="Home page"><br>
|
|
75
|
-
<sub><strong>Home.</strong> Open a room, come back to the last ones, and see what a room can do.</sub>
|
|
76
|
-
</td>
|
|
77
|
-
</tr>
|
|
78
|
-
<tr>
|
|
79
|
-
<td width="50%" valign="top">
|
|
80
|
-
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/summon.png" alt="Summon a vibemate"><br>
|
|
81
|
-
<sub><strong>Summon a vibemate.</strong> Pick the agent, the model, the effort and the permission mode; name it and give it a face.</sub>
|
|
82
|
-
</td>
|
|
83
|
-
<td width="50%" valign="top">
|
|
84
|
-
<img src="https://raw.githubusercontent.com/todor-rusev/viberoom/main/docs/screenshots/vibemate.png" alt="A vibemate's persona"><br>
|
|
85
|
-
<sub><strong>Its character.</strong> A public one-liner under the name, and a private brief only this vibemate reads.</sub>
|
|
86
|
-
</td>
|
|
87
|
-
</tr>
|
|
88
|
-
</table>
|
|
89
|
-
|
|
90
|
-
## Requirements
|
|
121
|
+
The menu that appears can put an icon on your desktop, open viberoom in a window of its own, or open
|
|
122
|
+
it in a browser tab. Later, `npm install -g viberoom@latest` and the next start picks up the new version.
|
|
91
123
|
|
|
92
|
-
|
|
93
|
-
- At least one coding agent installed on this machine. viberoom bundles the ACP adapters for Claude
|
|
94
|
-
Code and Codex and finds Gemini CLI, Cursor, OpenCode and Copilot when they are installed. Each
|
|
95
|
-
agent uses its own login and subscription; viberoom never sees your keys.
|
|
96
|
-
- A Chromium-based browser (Chrome, Edge, Brave) for the app window; any modern browser for a tab.
|
|
124
|
+
<br>
|
|
97
125
|
|
|
98
|
-
##
|
|
126
|
+
## Small things you will like
|
|
99
127
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
- **Rooms remember.** The history stays with the room. Move the folder and the vibemates move with it.
|
|
129
|
+
- **The window remembers.** Where it was, how big, which screen. Unplug that screen and it walks back.
|
|
130
|
+
- **Nothing leaves your machine** except what each agent sends to its own provider. viberoom never
|
|
131
|
+
sees your keys; every agent keeps its own login.
|
|
132
|
+
- **Edit a message.** Fix what you said; the vibemates get the memo, or the conversation rewinds.
|
|
133
|
+
- **Search the room.** Everything anyone said, one search box.
|
|
134
|
+
- **For geeks.** Every panel folds its technical settings behind a toggle. You never have to open it.
|
|
103
135
|
|
|
104
|
-
|
|
136
|
+
<br>
|
|
105
137
|
|
|
106
|
-
|
|
107
|
-
git clone https://github.com/todor-rusev/viberoom.git
|
|
108
|
-
cd viberoom
|
|
109
|
-
npm run install:global
|
|
110
|
-
```
|
|
138
|
+
## What you need
|
|
111
139
|
|
|
112
|
-
|
|
113
|
-
|
|
140
|
+
- Node.js 22 or newer.
|
|
141
|
+
- At least one coding agent installed and logged in: Claude Code, Codex, Gemini CLI, Cursor, OpenCode
|
|
142
|
+
or GitHub Copilot. viberoom finds the ones you have and offers only those. It installs none of them.
|
|
143
|
+
- A browser. Chrome, Edge or Brave for the app window; anything modern for a tab.
|
|
144
|
+
|
|
145
|
+
<br>
|
|
114
146
|
|
|
115
147
|
## Use
|
|
116
148
|
|
|
@@ -124,38 +156,29 @@ viberoom logs the background hub's log
|
|
|
124
156
|
```
|
|
125
157
|
|
|
126
158
|
`Enter` sends, `Shift+Enter` is a new line, `@Name` addresses one vibemate, `/name` invokes a skill.
|
|
159
|
+
Your data lives in `~/.viberoom` (or `$VIBEROOM_DATA_DIR`): settings, rooms with their history,
|
|
160
|
+
skills and the log.
|
|
127
161
|
|
|
128
|
-
|
|
129
|
-
transcripts and the hub log. Nothing leaves your machine except what each agent sends to its own
|
|
130
|
-
provider.
|
|
131
|
-
|
|
132
|
-
## Settings worth knowing
|
|
133
|
-
|
|
134
|
-
- **Vibemates act without asking.** On by default: agents edit files and run commands in the room's
|
|
135
|
-
folder without a permission prompt. Turn it off to be asked, or change it per vibemate.
|
|
136
|
-
- **Custom rules.** One rule per line in the room's panel, handed to every vibemate as instructions:
|
|
137
|
-
who speaks to whom, how long a reply may be, where code goes.
|
|
138
|
-
- **Language.** Vibemates follow the language you write in, or a fixed one for the room.
|
|
139
|
-
- **Reply delay.** With two or more vibemates in a room, each waits a random few seconds before it
|
|
140
|
-
answers. A vibemate can override it; alone it answers at once.
|
|
141
|
-
- **Open files at a line.** Which editor gets `path:line` clicks: the first one found (VS Code,
|
|
142
|
-
Cursor, Windsurf, Zed, Sublime Text, Notepad++, JetBrains) or your own command.
|
|
143
|
-
- **Diagrams.** Colours for Mermaid diagrams: multicolour boxes by default, or a single tone.
|
|
144
|
-
- **For geeks.** Every panel folds its technical settings behind a "for geeks" toggle.
|
|
162
|
+
<br>
|
|
145
163
|
|
|
146
164
|
## Development
|
|
147
165
|
|
|
148
166
|
```sh
|
|
167
|
+
git clone https://github.com/todor-rusev/viberoom.git
|
|
168
|
+
cd viberoom
|
|
149
169
|
npm install
|
|
150
|
-
npm run build #
|
|
151
|
-
node dist/main.js # run the hub in this terminal
|
|
170
|
+
npm run build # then: node dist/main.js
|
|
152
171
|
```
|
|
153
172
|
|
|
154
|
-
|
|
155
|
-
|
|
173
|
+
`npm run install:global` builds, links the `viberoom` command and creates the launcher; later,
|
|
174
|
+
`npm run update` rebuilds and replaces the running hub.
|
|
156
175
|
|
|
157
176
|
## License
|
|
158
177
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
AGPL-3.0-or-later. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
|
|
179
|
+
|
|
180
|
+
<br>
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
<p align="center">🎬 Idea, screenplay and direction of viberoom: <strong>Silviya Ruzhina</strong></p>
|
package/dist/hub.js
CHANGED
|
@@ -122,6 +122,24 @@ export class Hub extends EventEmitter {
|
|
|
122
122
|
return defaults;
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
+
saveWindowPlacement(report) {
|
|
126
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v);
|
|
127
|
+
if (!num(report.left) || !num(report.top) || !num(report.width) || !num(report.height))
|
|
128
|
+
throw new Error("left, top, width and height must be numbers");
|
|
129
|
+
const screen = report.screen;
|
|
130
|
+
const record = {
|
|
131
|
+
left: Math.round(report.left),
|
|
132
|
+
top: Math.round(report.top),
|
|
133
|
+
width: Math.round(report.width),
|
|
134
|
+
height: Math.round(report.height),
|
|
135
|
+
maximized: !!report.maximized,
|
|
136
|
+
savedAt: new Date().toISOString(),
|
|
137
|
+
};
|
|
138
|
+
if (screen && num(screen.left) && num(screen.top) && num(screen.width) && num(screen.height)) {
|
|
139
|
+
record.screen = { left: Math.round(screen.left), top: Math.round(screen.top), width: Math.round(screen.width), height: Math.round(screen.height) };
|
|
140
|
+
}
|
|
141
|
+
writeJson(join(this.dataDir, "window.json"), record);
|
|
142
|
+
}
|
|
125
143
|
updateSettings(patch) {
|
|
126
144
|
const next = { ...this.settings, roomDefaults: { ...this.settings.roomDefaults }, vendorPresets: { ...this.settings.vendorPresets } };
|
|
127
145
|
if (patch.humanName !== undefined) {
|
package/dist/launcher.js
CHANGED
|
@@ -120,6 +120,28 @@ export function savedWindowPlacement(profileDir) {
|
|
|
120
120
|
return null;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
export function windowFilePath(dataDir) {
|
|
124
|
+
return join(dataDir, "window.json");
|
|
125
|
+
}
|
|
126
|
+
export function recordedWindowPlacement(dataDir) {
|
|
127
|
+
try {
|
|
128
|
+
const o = JSON.parse(readFileSync(windowFilePath(dataDir), "utf8"));
|
|
129
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v);
|
|
130
|
+
if (!num(o.left) || !num(o.top) || !num(o.width) || !num(o.height))
|
|
131
|
+
return null;
|
|
132
|
+
if (o.width < 200 || o.height < 150)
|
|
133
|
+
return null;
|
|
134
|
+
const placement = { left: o.left, top: o.top, width: o.width, height: o.height, maximized: !!o.maximized };
|
|
135
|
+
const s = o.screen;
|
|
136
|
+
if (s && num(s.left) && num(s.top) && num(s.width) && num(s.height) && s.width >= 200 && s.height >= 150) {
|
|
137
|
+
placement.workArea = { left: s.left, top: s.top, right: s.left + s.width, bottom: s.top + s.height };
|
|
138
|
+
}
|
|
139
|
+
return placement;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
123
145
|
export function windowFlags(placement) {
|
|
124
146
|
const wa = placement.workArea;
|
|
125
147
|
if (placement.maximized && wa && wa.right - wa.left >= 200 && wa.bottom - wa.top >= 150) {
|
package/dist/main.js
CHANGED
|
@@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { Hub } from "./hub.js";
|
|
9
9
|
import { Logger } from "./log.js";
|
|
10
10
|
import { startServer } from "./server.js";
|
|
11
|
-
import { appWindowArgs, savedWindowPlacement, findChromium, isProcessAlive, logFilePath, openUrlCommand, pidFilePath, readPidFile, rotateLog, splitCommand, tailFile, writePidFile, } from "./launcher.js";
|
|
11
|
+
import { appWindowArgs, recordedWindowPlacement, savedWindowPlacement, findChromium, isProcessAlive, logFilePath, openUrlCommand, pidFilePath, readPidFile, rotateLog, splitCommand, tailFile, writePidFile, } from "./launcher.js";
|
|
12
12
|
import { aumidSyncScript, installShortcuts, windowsShortcutPaths } from "./shortcuts.js";
|
|
13
13
|
import { runMenu } from "./tui.js";
|
|
14
14
|
function parseArgs(argv) {
|
|
@@ -177,10 +177,11 @@ function openWindow(url, options, log) {
|
|
|
177
177
|
const fresh = !existsSync(profile);
|
|
178
178
|
mkdirSync(profile, { recursive: true });
|
|
179
179
|
reapHiddenBrowser(profile, log);
|
|
180
|
-
const
|
|
180
|
+
const recorded = fresh ? null : recordedWindowPlacement(options.dataDir);
|
|
181
|
+
const placement = recorded ?? (fresh ? null : savedWindowPlacement(profile));
|
|
181
182
|
const args = appWindowArgs(url, profile, fresh, placement);
|
|
182
183
|
const where = placement
|
|
183
|
-
? `last seen at ${placement.left},${placement.top} ${placement.width}x${placement.height}${placement.maximized ? " maximized" : ""}${placement.workArea ? ` on the screen ${placement.workArea.left},${placement.workArea.top}-${placement.workArea.right},${placement.workArea.bottom}` : ""}; flags ${args.filter((a) => a.startsWith("--window-")).join(" ")}`
|
|
184
|
+
? `last seen (${recorded ? "window's own report" : "browser profile"}) at ${placement.left},${placement.top} ${placement.width}x${placement.height}${placement.maximized ? " maximized" : ""}${placement.workArea ? ` on the screen ${placement.workArea.left},${placement.workArea.top}-${placement.workArea.right},${placement.workArea.bottom}` : ""}; flags ${args.filter((a) => a.startsWith("--window-")).join(" ")}`
|
|
184
185
|
: "no saved placement";
|
|
185
186
|
log.info(`opening the app window with ${chromium}: ${where}`);
|
|
186
187
|
try {
|
package/dist/persona.js
CHANGED
|
@@ -23,7 +23,7 @@ export const DEFAULT_ROOM_SETTINGS = {
|
|
|
23
23
|
humanDescriptionMode: "inherit",
|
|
24
24
|
refereeAction: "next-header",
|
|
25
25
|
turnTaking: "parallel",
|
|
26
|
-
replyDelay:
|
|
26
|
+
replyDelay: 4,
|
|
27
27
|
waitWhileHumanTypes: true,
|
|
28
28
|
};
|
|
29
29
|
export const BRIEF_AFFECTING_SETTINGS = [
|
|
@@ -113,7 +113,7 @@ export function buildBrief(settings, persona, roster, previousNotes, skills) {
|
|
|
113
113
|
.filter((l) => l.length > 0);
|
|
114
114
|
if (custom.length) {
|
|
115
115
|
lines.push("");
|
|
116
|
-
lines.push(`
|
|
116
|
+
lines.push(`Room rules (set by ${human}):`);
|
|
117
117
|
for (const rule of custom)
|
|
118
118
|
lines.push(`- ${rule}`);
|
|
119
119
|
}
|
package/dist/recipes.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, readdirSync } from "node:fs";
|
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
const isWindows = process.platform === "win32";
|
|
8
9
|
function resolvePackageEntry(packageName, relativeEntry) {
|
|
9
10
|
try {
|
|
@@ -102,8 +103,30 @@ function resolveCopilot() {
|
|
|
102
103
|
}
|
|
103
104
|
return null;
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
-
const
|
|
106
|
+
function resolveGlobalNpmBin(name) {
|
|
107
|
+
const root = resolveGlobalNpmRoot();
|
|
108
|
+
if (!root)
|
|
109
|
+
return null;
|
|
110
|
+
const candidates = isWindows ? [join(root, "..", `${name}.cmd`)] : [join(root, "..", "..", "bin", name)];
|
|
111
|
+
return candidates.find((c) => existsSync(c)) ?? null;
|
|
112
|
+
}
|
|
113
|
+
function resolveClaudeCode() {
|
|
114
|
+
const onPath = resolveOnPath(["claude"]);
|
|
115
|
+
if (onPath && !/\.(cmd|bat)$/i.test(onPath))
|
|
116
|
+
return onPath;
|
|
117
|
+
const native = join(homedir(), ".local", "bin", isWindows ? "claude.exe" : "claude");
|
|
118
|
+
if (existsSync(native))
|
|
119
|
+
return native;
|
|
120
|
+
return resolveGlobalPackageEntry("@anthropic-ai/claude-code", "cli.js");
|
|
121
|
+
}
|
|
122
|
+
function resolveCodex() {
|
|
123
|
+
return resolveGlobalNpmBin("codex") ?? resolveOnPath(["codex"]);
|
|
124
|
+
}
|
|
125
|
+
const vendorDir = join(dirname(fileURLToPath(import.meta.url)), "..", "vendor", "acp");
|
|
126
|
+
const claudeAdapter = join(vendorDir, "claude-agent-acp", "dist", "index.js");
|
|
127
|
+
const codexAdapter = join(vendorDir, "codex-acp", "dist", "index.js");
|
|
128
|
+
const claudeExe = resolveClaudeCode();
|
|
129
|
+
const codexExe = resolveCodex();
|
|
107
130
|
const geminiEntry = resolvePackageEntry("@google/gemini-cli", join("bundle", "gemini.js")) ??
|
|
108
131
|
resolveGlobalPackageEntry("@google/gemini-cli", join("bundle", "gemini.js"));
|
|
109
132
|
const cursorAgent = resolveCursorAgent();
|
|
@@ -116,7 +139,7 @@ const recipes = [
|
|
|
116
139
|
vendor: "Claude",
|
|
117
140
|
icon: "/vendor-icons/claude.svg",
|
|
118
141
|
tested: true,
|
|
119
|
-
note: "Adapter around the Claude Agent SDK
|
|
142
|
+
note: "Adapter around the Claude Agent SDK, driving the Claude Code installed on this machine with its login and settings.",
|
|
120
143
|
modelPresets: ["haiku", "sonnet", "opus", "default"],
|
|
121
144
|
defaultModel: "sonnet",
|
|
122
145
|
effortPresets: ["low", "medium", "high", "default"],
|
|
@@ -124,13 +147,13 @@ const recipes = [
|
|
|
124
147
|
modePresets: ["default", "acceptEdits", "plan", "auto", "bypassPermissions"],
|
|
125
148
|
defaultMode: "default",
|
|
126
149
|
bypassMode: "bypassPermissions",
|
|
127
|
-
unavailableReason:
|
|
128
|
-
installedAt:
|
|
129
|
-
installHint: "npm install
|
|
150
|
+
unavailableReason: claudeExe ? null : "Claude Code not found",
|
|
151
|
+
installedAt: claudeExe,
|
|
152
|
+
installHint: "install Claude Code (npm install -g @anthropic-ai/claude-code, or the native installer) and log in with `claude`",
|
|
130
153
|
build: ({ model }) => ({
|
|
131
154
|
command: process.execPath,
|
|
132
|
-
args: [
|
|
133
|
-
env: model && model !== "default" ? { ANTHROPIC_MODEL: model } :
|
|
155
|
+
args: [claudeAdapter],
|
|
156
|
+
env: { CLAUDE_CODE_EXECUTABLE: claudeExe ?? "", ...(model && model !== "default" ? { ANTHROPIC_MODEL: model } : {}) },
|
|
134
157
|
}),
|
|
135
158
|
},
|
|
136
159
|
{
|
|
@@ -139,7 +162,7 @@ const recipes = [
|
|
|
139
162
|
vendor: "Codex",
|
|
140
163
|
icon: "/vendor-icons/codex.svg",
|
|
141
164
|
tested: true,
|
|
142
|
-
note: "Adapter around the Codex App Server
|
|
165
|
+
note: "Adapter around the Codex App Server of the Codex CLI installed on this machine; uses its login (~/.codex) or CODEX_API_KEY. Mode 'agent' edits the working directory without asking; 'read-only' for a chat-only participant.",
|
|
143
166
|
modelPresets: ["gpt-5.4-mini", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"],
|
|
144
167
|
defaultModel: "gpt-5.4-mini",
|
|
145
168
|
effortPresets: ["low", "medium", "high", "xhigh"],
|
|
@@ -147,13 +170,13 @@ const recipes = [
|
|
|
147
170
|
modePresets: ["read-only", "agent", "agent-full-access"],
|
|
148
171
|
defaultMode: "read-only",
|
|
149
172
|
bypassMode: "agent-full-access",
|
|
150
|
-
unavailableReason:
|
|
151
|
-
installedAt:
|
|
152
|
-
installHint: "npm install
|
|
173
|
+
unavailableReason: codexExe ? null : "Codex CLI not found",
|
|
174
|
+
installedAt: codexExe,
|
|
175
|
+
installHint: "install Codex (npm install -g @openai/codex) and log in with `codex login`",
|
|
153
176
|
build: () => ({
|
|
154
177
|
command: process.execPath,
|
|
155
|
-
args: [
|
|
156
|
-
env: { NO_BROWSER: "1" },
|
|
178
|
+
args: [codexAdapter],
|
|
179
|
+
env: { CODEX_PATH: codexExe ?? "", NO_BROWSER: "1" },
|
|
157
180
|
}),
|
|
158
181
|
},
|
|
159
182
|
{
|
package/dist/room.js
CHANGED
|
@@ -556,8 +556,9 @@ export class Room extends EventEmitter {
|
|
|
556
556
|
runtime.briefPending = `room rules: ${briefChanges.join(", ")}`;
|
|
557
557
|
this.postSystem(`Room settings updated (${briefChanges.join(", ")}); agents get refreshed instructions on their next turn.`);
|
|
558
558
|
}
|
|
559
|
-
|
|
560
|
-
|
|
559
|
+
const missing = unknownRefs.filter((n) => n !== "Name");
|
|
560
|
+
if (missing.length) {
|
|
561
|
+
this.notice(`Room rules mention ${missing.map((n) => `@${n}`).join(", ")}, who ${missing.length > 1 ? "are" : "is"} not in the room; left as plain text.`, "warn");
|
|
561
562
|
}
|
|
562
563
|
return this.settings;
|
|
563
564
|
}
|
package/dist/server.js
CHANGED
|
@@ -3,6 +3,8 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { readFile, stat } from "node:fs/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
6
8
|
import { existsSync as fileExists } from "node:fs";
|
|
7
9
|
import { classifyOpenTarget, describeOpen, detectEditor, editorCommand, isExecutablePath, openCommand } from "./open.js";
|
|
8
10
|
import { parseCsv, viewerKind, VIEWER_MAX_BYTES } from "./viewer.js";
|
|
@@ -38,8 +40,14 @@ const STATIC_FILES = {
|
|
|
38
40
|
export function startServer(hub, port, log, info, onShutdownRequest) {
|
|
39
41
|
const uiDir = fileURLToPath(new URL("../ui/", import.meta.url));
|
|
40
42
|
const assetsDir = fileURLToPath(new URL("../assets/", import.meta.url));
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
+
const resolveModule = createRequire(import.meta.url).resolve;
|
|
44
|
+
const packageDir = (name) => dirname(resolveModule(`${name}/package.json`));
|
|
45
|
+
const staticPath = (entry) => {
|
|
46
|
+
if (entry.dir !== "node_modules")
|
|
47
|
+
return (entry.dir === "assets" ? assetsDir : uiDir) + entry.file;
|
|
48
|
+
const slash = entry.file.indexOf("/");
|
|
49
|
+
return join(packageDir(entry.file.slice(0, slash)), entry.file.slice(slash + 1));
|
|
50
|
+
};
|
|
43
51
|
const clients = new Set();
|
|
44
52
|
const snapshot = () => ({ ...hub.snapshot(), version: info });
|
|
45
53
|
const broadcast = (event) => {
|
|
@@ -82,7 +90,7 @@ export function startServer(hub, port, log, info, onShutdownRequest) {
|
|
|
82
90
|
}
|
|
83
91
|
if (req.method === "GET" && STATIC_FILES[path]) {
|
|
84
92
|
const entry = STATIC_FILES[path];
|
|
85
|
-
const body = await readFile(
|
|
93
|
+
const body = await readFile(staticPath(entry));
|
|
86
94
|
res.writeHead(200, { "Content-Type": entry.type, "Cache-Control": entry.dir === "ui" || !entry.dir ? "no-cache" : "public, max-age=3600" });
|
|
87
95
|
res.end(body);
|
|
88
96
|
return;
|
|
@@ -194,6 +202,11 @@ export function startServer(hub, port, log, info, onShutdownRequest) {
|
|
|
194
202
|
sendJson(res, 200, { ok: true, settings: hub.updateSettings(body) });
|
|
195
203
|
return;
|
|
196
204
|
}
|
|
205
|
+
if (path === "/api/window") {
|
|
206
|
+
hub.saveWindowPlacement(body);
|
|
207
|
+
sendJson(res, 200, { ok: true });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
197
210
|
if (path === "/api/open") {
|
|
198
211
|
const target = classifyOpenTarget(String(body.target ?? ""));
|
|
199
212
|
if (!target)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viberoom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "viberoom: group chat rooms for a human and several coding agents over the Agent Client Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"viberoom": "dist/main.js"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"build": "tsc -p tsconfig.json",
|
|
13
|
+
"build": "node scripts/vendor-acp.mjs && tsc -p tsconfig.json",
|
|
14
14
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
15
15
|
"start": "node dist/main.js",
|
|
16
16
|
"dev": "npm run build && node dist/main.js --open",
|
|
@@ -22,18 +22,21 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
|
+
"vendor",
|
|
25
26
|
"ui",
|
|
26
27
|
"assets",
|
|
27
28
|
"scripts",
|
|
28
29
|
"NOTICE"
|
|
29
30
|
],
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"@agentclientprotocol/
|
|
32
|
-
"@agentclientprotocol/codex-acp": "^1.8.0",
|
|
32
|
+
"@agentclientprotocol/sdk": "1.4.0",
|
|
33
33
|
"marked": "^16.4.2",
|
|
34
|
-
"mermaid": "^11.17.2"
|
|
34
|
+
"mermaid": "^11.17.2",
|
|
35
|
+
"zod": "^4.0.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
38
|
+
"@agentclientprotocol/claude-agent-acp": "0.73.0",
|
|
39
|
+
"@agentclientprotocol/codex-acp": "1.8.0",
|
|
37
40
|
"@types/node": "^24.0.0",
|
|
38
41
|
"typescript": "^5.9.0"
|
|
39
42
|
},
|