@composer-app/mcp 0.0.1-beta.5 → 0.0.2
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/CHANGELOG.md +145 -0
- package/README.md +160 -0
- package/dist/{chunk-A5KBJAJW.js → chunk-GPFWLOYB.js} +1402 -237
- package/dist/cli.js +1 -1
- package/dist/mcp.js +21 -1
- package/package.json +3 -2
- package/skill/commenting/SKILL.md +67 -0
- package/skill/create/SKILL.md +131 -0
- package/skill/export/SKILL.md +67 -0
- package/skill/join/SKILL.md +117 -0
- package/skill/monitor/SKILL.md +159 -0
- package/skill/suggesting/SKILL.md +177 -0
- package/skill/.claude/settings.local.json +0 -8
- package/skill/SKILL.md +0 -417
package/dist/cli.js
CHANGED
package/dist/mcp.js
CHANGED
|
@@ -2,14 +2,34 @@ import {
|
|
|
2
2
|
__test_clearRooms,
|
|
3
3
|
__test_dispatch,
|
|
4
4
|
__test_setRoom,
|
|
5
|
+
_clearRoomsForTest,
|
|
6
|
+
_registerRoomForTest,
|
|
7
|
+
dispatchTool,
|
|
8
|
+
performAddComment,
|
|
9
|
+
performAddSuggestion,
|
|
10
|
+
performAgentStatus,
|
|
11
|
+
performDone,
|
|
12
|
+
performReplyComment,
|
|
13
|
+
performReplySuggestion,
|
|
14
|
+
performResolveThread,
|
|
5
15
|
startMcpHttpServer,
|
|
6
16
|
startMcpServer,
|
|
7
17
|
teardownAllRooms
|
|
8
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-GPFWLOYB.js";
|
|
9
19
|
export {
|
|
10
20
|
__test_clearRooms,
|
|
11
21
|
__test_dispatch,
|
|
12
22
|
__test_setRoom,
|
|
23
|
+
_clearRoomsForTest,
|
|
24
|
+
_registerRoomForTest,
|
|
25
|
+
dispatchTool,
|
|
26
|
+
performAddComment,
|
|
27
|
+
performAddSuggestion,
|
|
28
|
+
performAgentStatus,
|
|
29
|
+
performDone,
|
|
30
|
+
performReplyComment,
|
|
31
|
+
performReplySuggestion,
|
|
32
|
+
performResolveThread,
|
|
13
33
|
startMcpHttpServer,
|
|
14
34
|
startMcpServer,
|
|
15
35
|
teardownAllRooms
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@composer-app/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Composer MCP",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Josh Philpott",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
-
"skill"
|
|
13
|
+
"skill",
|
|
14
|
+
"CHANGELOG.md"
|
|
14
15
|
],
|
|
15
16
|
"keywords": [
|
|
16
17
|
"mcp",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commenting
|
|
3
|
+
description: Use when you're about to call `composer_reply_comment`, `composer_reply_suggestion`, or `composer_add_comment` — replying or commenting in a Composer thread. Covers terseness rules, when a suggestion replaces a reply, and the reply-vs-new-comment-vs-suggestion decision.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Composer comments and replies
|
|
7
|
+
|
|
8
|
+
Comment threads in Composer render in a narrow sidebar — think Figma's
|
|
9
|
+
comment box, not a chat window. Long replies get unwieldy fast. The
|
|
10
|
+
rules below keep threads readable.
|
|
11
|
+
|
|
12
|
+
This skill is the full guide for any text reply or new comment. Load it
|
|
13
|
+
when you're about to call a `composer_reply_*` or `composer_add_comment`
|
|
14
|
+
tool.
|
|
15
|
+
|
|
16
|
+
## Terseness
|
|
17
|
+
|
|
18
|
+
- **Answer in 1–3 sentences. Prefer one.**
|
|
19
|
+
- **Reply directly to the question asked** — no preamble ("Great
|
|
20
|
+
question!"), no restating the ask, no trailing summary of what you
|
|
21
|
+
just did.
|
|
22
|
+
- Never dump your reasoning or tool-call chatter into a comment.
|
|
23
|
+
- If the answer genuinely needs structure (a list of 4+ items, code, a
|
|
24
|
+
table) and a suggestion isn't the right shape, post it as a suggestion
|
|
25
|
+
in the doc body instead of as a comment reply. The sidebar is the
|
|
26
|
+
wrong shape for that content.
|
|
27
|
+
|
|
28
|
+
Terse beats thorough. If the user wants more, they'll ask.
|
|
29
|
+
|
|
30
|
+
## Don't double-post: a suggestion IS your reply
|
|
31
|
+
|
|
32
|
+
If you post a suggestion in response to the thread, **do not also post
|
|
33
|
+
a comment reply.** The suggestion renders as a Replace/With card in the
|
|
34
|
+
sidebar already; a pointer comment ("see the suggestion") just
|
|
35
|
+
duplicates what the user can already see.
|
|
36
|
+
|
|
37
|
+
Silent suggestion-only responses are correct and expected. The user
|
|
38
|
+
gets a visible card; the agent's reasoning is in the suggestion's
|
|
39
|
+
shape, not in a separate sentence.
|
|
40
|
+
|
|
41
|
+
The only time to post both is when the reply says something the
|
|
42
|
+
suggestion can't convey on its own — context, a follow-up question, an
|
|
43
|
+
explicit flag that there are companion edits elsewhere. That's rare;
|
|
44
|
+
default to suggestion-only.
|
|
45
|
+
|
|
46
|
+
## Reply vs new comment vs suggestion — quick decision
|
|
47
|
+
|
|
48
|
+
When the user's message in a thread asks for something, pick the shape
|
|
49
|
+
of your response from this:
|
|
50
|
+
|
|
51
|
+
| Shape | Use when |
|
|
52
|
+
|---|---|
|
|
53
|
+
| **Suggestion only** | The user asked for a text change ("rewrite this", "make it 200g", "tighten the wording"). The change IS the response. |
|
|
54
|
+
| **Suggestion + new comment elsewhere** | The text change ripples to a different span. Suggest each span separately, then leave a comment on the originating thread saying *"also touched X"* — only if the ripples aren't otherwise obvious. |
|
|
55
|
+
| **Reply only** | The user asked a question that has a textual answer ("when did we ship that?", "is Jesse still on this?"). No text change involved. Keep it short. |
|
|
56
|
+
| **Suggestion + reply on origin thread** | You changed text AND need to say something the change can't convey alone (caveat, a follow-up question, "I changed two other places too"). |
|
|
57
|
+
| **New comment elsewhere** | You're raising something the user didn't ask about but should see — a cross-reference, an inconsistency, a heads-up. Anchor it where it actually belongs, not on the thread you were called from. |
|
|
58
|
+
|
|
59
|
+
For suggestion craft (anchoring, ripples, multi-span), load
|
|
60
|
+
**`composer:suggesting`**.
|
|
61
|
+
|
|
62
|
+
## Empty acknowledgements: don't
|
|
63
|
+
|
|
64
|
+
Never post a reply that's just "👍", "got it", or "thanks". If the
|
|
65
|
+
user's message doesn't actually need a response (a thank-you back to
|
|
66
|
+
you, a stage direction, a note-to-self), stay silent. Posting an empty
|
|
67
|
+
ack adds noise to the sidebar.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create
|
|
3
|
+
description: Use when the user wants to start a NEW Composer doc — pasting markdown to "send to Composer", asking to create/seed a Composer room from a file or inline content, or accepting your offer to put a draft into Composer. Covers first-run agent-name prompt, seed selection (file path vs inline), the ordered `step1_sayToUser` / `step2_callTool` return, and the monitor-subagent handoff.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Composer — Create a doc
|
|
7
|
+
|
|
8
|
+
Composer is a real-time collaborative markdown editor. `composer_create_room`
|
|
9
|
+
seeds a new room with markdown and gives you back a shareable URL.
|
|
10
|
+
|
|
11
|
+
## When to load this skill
|
|
12
|
+
|
|
13
|
+
- The user asks to "send this to Composer", "make a Composer doc with
|
|
14
|
+
this", "create a Composer room with the plan".
|
|
15
|
+
- They accept your offer to put a draft into Composer (the SessionStart
|
|
16
|
+
hook may have nudged you to make that offer; this skill tells you how
|
|
17
|
+
to follow through).
|
|
18
|
+
|
|
19
|
+
If they pasted an existing `usecomposer.app/r/<id>` URL, they want to
|
|
20
|
+
**join**, not create — load `composer:join` instead.
|
|
21
|
+
|
|
22
|
+
## Steps
|
|
23
|
+
|
|
24
|
+
### 1. First-run only — agree on a name
|
|
25
|
+
|
|
26
|
+
If the MCP returns an "ask the user what to call you" error, no `actingAs`
|
|
27
|
+
name is saved on this machine yet. Stop and ask. Suggest one default:
|
|
28
|
+
|
|
29
|
+
- If you know the user's first name → `"<FirstName>'s Agent"` (e.g.
|
|
30
|
+
`"Josh's Agent"`).
|
|
31
|
+
- Otherwise something playful that isn't a model family — `Monty`,
|
|
32
|
+
`Gerty`, `Rosie`, `Otto`, `Pip`. **Never** suggest Claude, Gemini,
|
|
33
|
+
Sonnet, Opus, Haiku, GPT, or any other model name.
|
|
34
|
+
|
|
35
|
+
Phrase: *"I'll go by Monty in Composer docs — sound good, or pick your
|
|
36
|
+
own?"* Retry the call with their answer as `actingAs`. The name persists
|
|
37
|
+
to `~/.composer/user.json` and is reused forever.
|
|
38
|
+
|
|
39
|
+
### 2. Pick the seed source
|
|
40
|
+
|
|
41
|
+
Pick exactly one:
|
|
42
|
+
|
|
43
|
+
- **`seedMarkdownPath: "<absolute path>"`** — preferred whenever the
|
|
44
|
+
markdown already lives in a file on disk (a plan, a journal entry, any
|
|
45
|
+
`.md` the user pointed at). The MCP reads the file itself, so you don't
|
|
46
|
+
stream the content through the model. Faster, cheaper.
|
|
47
|
+
- **`seedMarkdown: "<inline string>"`** — only when the content was
|
|
48
|
+
generated this turn and isn't on disk yet.
|
|
49
|
+
|
|
50
|
+
The seed is read **once**. Composer never writes back to the source
|
|
51
|
+
file. If the user later wants the room contents back on disk, that's the
|
|
52
|
+
`/composer:export` slash command — explicit, one-way overwrite.
|
|
53
|
+
|
|
54
|
+
### 3. Call `composer_create_room`
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
composer_create_room({
|
|
58
|
+
seedMarkdownPath: "<absolute path>" // OR seedMarkdown: "<inline>"
|
|
59
|
+
})
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. Honor the ordered return
|
|
63
|
+
|
|
64
|
+
Success returns two ordered steps. The field names encode the order:
|
|
65
|
+
|
|
66
|
+
1. **`step1_sayToUser`** — output this **first**. It always starts with
|
|
67
|
+
the `browserUrl` so the user has a way into the doc, and carries the
|
|
68
|
+
`@<your-name>` tagging hint they'll need to mention you. Light
|
|
69
|
+
paraphrasing is fine; **do not drop the URL or the mention syntax**.
|
|
70
|
+
2. **`step2_callTool`** — a structured `{ tool, args, why }` directive
|
|
71
|
+
pointing at `composer_next_event`. **Do not run it inline.** Spawn
|
|
72
|
+
the monitor subagent (next step), then end your turn.
|
|
73
|
+
|
|
74
|
+
Skipping `step1_sayToUser` strands the user with no link. Skipping the
|
|
75
|
+
subagent spawn leaves the room attached but silent — saying "I'm
|
|
76
|
+
monitoring" without spawning the loop is a lie; every mention gets missed.
|
|
77
|
+
|
|
78
|
+
### 5. Spawn the monitor subagent
|
|
79
|
+
|
|
80
|
+
Use the `Agent` tool with:
|
|
81
|
+
|
|
82
|
+
- `subagent_type: "general-purpose"`
|
|
83
|
+
- `run_in_background: true`
|
|
84
|
+
- `description: "Composer monitor: <roomId>"`
|
|
85
|
+
- `prompt`: tell the subagent to **invoke the `composer:monitor`
|
|
86
|
+
skill**, then run the loop on `{roomId}` as `{actingAs}`. Don't paste
|
|
87
|
+
the loop rules inline — the `composer:monitor` skill carries them
|
|
88
|
+
(spawn template, mention filtering, exit rules, event payload).
|
|
89
|
+
|
|
90
|
+
Once spawned, **end your turn immediately**. Do not output any text
|
|
91
|
+
after the spawn — no closing recap, no "monitor is running" status
|
|
92
|
+
line, no restating the mention syntax. The host already shows a
|
|
93
|
+
status indicator for the backgrounded `Agent` tool call; that's the
|
|
94
|
+
user's confirmation. Anything you say after `step1_sayToUser` just
|
|
95
|
+
duplicates it. The protocol is strict:
|
|
96
|
+
|
|
97
|
+
1. Output `step1_sayToUser` (verbatim or lightly paraphrased).
|
|
98
|
+
2. Spawn the `Agent`.
|
|
99
|
+
3. End turn. No closing remark.
|
|
100
|
+
|
|
101
|
+
Also: do **not** poll `composer_next_event` from the main thread —
|
|
102
|
+
two listeners on the same room means duplicated replies.
|
|
103
|
+
|
|
104
|
+
### When the monitor exits
|
|
105
|
+
|
|
106
|
+
You'll get a notification when the background subagent finishes. Its
|
|
107
|
+
final output line is the agent's goodbye — written in the agent's
|
|
108
|
+
voice (idle timeout, server kick, reconnect aborted, etc., per the
|
|
109
|
+
`composer:monitor` exit rules). **Relay that line verbatim to the user
|
|
110
|
+
in the main thread** so they see why the agent left and how to bring
|
|
111
|
+
it back. Don't paraphrase or wrap it in extra explanation; the
|
|
112
|
+
goodbye line already carries the next step (`/composer:join` etc.).
|
|
113
|
+
|
|
114
|
+
## Terminal-side asks after creation
|
|
115
|
+
|
|
116
|
+
The main thread is also attached to the room. If the user later asks
|
|
117
|
+
something in the terminal like *"add a summary to section 2"*, call the
|
|
118
|
+
write tools from here and report back concisely. Don't hand terminal
|
|
119
|
+
directives to the monitor subagent — the subagent handles in-doc
|
|
120
|
+
mentions, the main thread handles in-terminal asks. They share the same
|
|
121
|
+
MCP, so writes from either show up in the doc.
|
|
122
|
+
|
|
123
|
+
For comment etiquette, load **`composer:commenting`**. For suggestion
|
|
124
|
+
craft, load **`composer:suggesting`**.
|
|
125
|
+
|
|
126
|
+
## Failure handling
|
|
127
|
+
|
|
128
|
+
- **Setup not done** → `npx @composer-app/mcp@latest setup`, restart the CLI.
|
|
129
|
+
- **Server kicked you (4403/4410) or reconnect aborted** → the agent
|
|
130
|
+
left the room. Run `/composer:join <url>` to bring it back. The kick
|
|
131
|
+
reason is in the error message.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: export
|
|
3
|
+
description: Use when the user wants to dump a Composer doc's current contents to a local markdown file — running `/composer:export <path>`, asking to "save this Composer doc to disk", or to "export the room as markdown". This is ONE-WAY — Composer is the source of truth and the local file is overwritten on every export. There is no merge, no diff, no two-way sync.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Composer — Export to a local file
|
|
7
|
+
|
|
8
|
+
`composer_export_to_file` writes the current room contents to a local
|
|
9
|
+
markdown file. **There is no merge, no diff, no two-way sync** —
|
|
10
|
+
Composer is authoritative; whatever's at the path becomes a fresh
|
|
11
|
+
snapshot on every export. **If a file already exists at the path, the
|
|
12
|
+
export will overwrite it.** Always confirm with the user first when
|
|
13
|
+
that's the case — see step 3 below.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
### 1. Resolve the room
|
|
18
|
+
|
|
19
|
+
- If exactly one Composer room is attached this session, use it.
|
|
20
|
+
- If multiple are attached, ask: *"Which doc — paste the URL or
|
|
21
|
+
roomId."* Stop. Do not guess.
|
|
22
|
+
- If none are attached, ask the user to `/composer:join <url>` first,
|
|
23
|
+
then re-run.
|
|
24
|
+
|
|
25
|
+
### 2. Resolve the path
|
|
26
|
+
|
|
27
|
+
- The argument **must be an absolute path**. If it's relative, ask the
|
|
28
|
+
user for an absolute one (or resolve it against the current working
|
|
29
|
+
directory if unambiguous — but confirm before doing so).
|
|
30
|
+
- The parent directory must already exist. The MCP will not create
|
|
31
|
+
directories.
|
|
32
|
+
|
|
33
|
+
### 3. Check for an existing file and ask permission before overwriting
|
|
34
|
+
|
|
35
|
+
Before calling the export tool, check whether anything is at the
|
|
36
|
+
target path (use whatever read-only tool your host gives you — `ls`,
|
|
37
|
+
`stat`, `Read`, etc.):
|
|
38
|
+
|
|
39
|
+
- **File does not exist** — proceed straight to step 4. No confirmation
|
|
40
|
+
needed; nothing is at risk.
|
|
41
|
+
- **File exists** — STOP and ask the user explicitly. Show them the
|
|
42
|
+
path and the consequence in one short line, e.g.:
|
|
43
|
+
|
|
44
|
+
> *`{path}` already exists. Exporting will overwrite it. Want me to
|
|
45
|
+
> go ahead?*
|
|
46
|
+
|
|
47
|
+
Wait for an affirmative answer (yes / sure / go ahead / 👍) before
|
|
48
|
+
calling the tool. If they say no or pick a different path, follow
|
|
49
|
+
their lead — do NOT call the tool with the original path.
|
|
50
|
+
|
|
51
|
+
Do not silently overwrite. The user typing `/composer:export <path>`
|
|
52
|
+
opts them into the export, not into clobbering arbitrary local files
|
|
53
|
+
without warning.
|
|
54
|
+
|
|
55
|
+
### 4. Call `composer_export_to_file`
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
composer_export_to_file({ roomId, path: "<absolute>" })
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Returns `{ roomId, path, bytesWritten }`.
|
|
62
|
+
|
|
63
|
+
### 5. Confirm in one short line
|
|
64
|
+
|
|
65
|
+
> *"Exported to `{path}` ({bytes} bytes)."*
|
|
66
|
+
|
|
67
|
+
End your turn. No follow-up call needed.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: join
|
|
3
|
+
description: Use when the user wants to JOIN an existing Composer doc — pasting a `usecomposer.app/r/<id>` URL, running `/composer:join`, or otherwise asking you to attach to a room they already have. Covers first-run agent-name prompt, the `composer_join_room` call, the ordered `step1_sayToUser` / `step2_callTool` return, and the monitor-subagent handoff.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Composer — Join a doc
|
|
7
|
+
|
|
8
|
+
Composer is a real-time collaborative markdown editor. `composer_join_room`
|
|
9
|
+
attaches you to an existing room so you can read, comment, suggest, and
|
|
10
|
+
respond to mentions.
|
|
11
|
+
|
|
12
|
+
## When to load this skill
|
|
13
|
+
|
|
14
|
+
- The user pastes a `usecomposer.app/r/<id>` URL.
|
|
15
|
+
- They run `/composer:join <url>` (the slash command delegates here).
|
|
16
|
+
- They ask you to "watch this Composer doc" / "join this room" with a
|
|
17
|
+
URL or roomId.
|
|
18
|
+
|
|
19
|
+
If they want to spin up a NEW doc from markdown they've shown you, that's
|
|
20
|
+
**create**, not join — load `composer:create` instead.
|
|
21
|
+
|
|
22
|
+
## Steps
|
|
23
|
+
|
|
24
|
+
### 1. First-run only — agree on a name
|
|
25
|
+
|
|
26
|
+
If the MCP returns an "ask the user what to call you" error, no
|
|
27
|
+
`actingAs` name is saved on this machine yet. Stop and ask. Suggest one
|
|
28
|
+
default:
|
|
29
|
+
|
|
30
|
+
- If you know the user's first name → `"<FirstName>'s Agent"` (e.g.
|
|
31
|
+
`"Josh's Agent"`).
|
|
32
|
+
- Otherwise something playful that isn't a model family — `Monty`,
|
|
33
|
+
`Gerty`, `Rosie`, `Otto`, `Pip`. **Never** suggest Claude, Gemini,
|
|
34
|
+
Sonnet, Opus, Haiku, GPT, or any other model name.
|
|
35
|
+
|
|
36
|
+
Phrase: *"I'll go by Monty in Composer docs — sound good, or pick your
|
|
37
|
+
own?"* Retry with their answer as `actingAs`. The name persists to
|
|
38
|
+
`~/.composer/user.json` and is reused forever.
|
|
39
|
+
|
|
40
|
+
### 2. Resolve the URL
|
|
41
|
+
|
|
42
|
+
- If the user pasted a `https://usecomposer.app/r/<id>` URL, use it.
|
|
43
|
+
- If `/composer:join` was run with no argument, ask: *"Which Composer
|
|
44
|
+
doc should I join? Paste the URL."* Stop. Do not guess.
|
|
45
|
+
- A bare roomId (no `https://` prefix) is also acceptable.
|
|
46
|
+
|
|
47
|
+
### 3. Call `composer_join_room`
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
composer_join_room({ url: "<the URL or roomId>" })
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4. Honor the ordered return
|
|
54
|
+
|
|
55
|
+
Success returns two ordered steps:
|
|
56
|
+
|
|
57
|
+
1. **`step1_sayToUser`** — output this **first**. It confirms the URL
|
|
58
|
+
the user just joined and carries the `@<your-name>` tagging hint.
|
|
59
|
+
Light paraphrasing OK; don't drop the URL or the mention syntax.
|
|
60
|
+
2. **`step2_callTool`** — a structured `{ tool, args, why }` directive
|
|
61
|
+
pointing at `composer_next_event`. **Do not run it inline.** Spawn
|
|
62
|
+
the monitor subagent (next step), then end your turn.
|
|
63
|
+
|
|
64
|
+
### 5. Spawn the monitor subagent
|
|
65
|
+
|
|
66
|
+
Use the `Agent` tool with:
|
|
67
|
+
|
|
68
|
+
- `subagent_type: "general-purpose"`
|
|
69
|
+
- `run_in_background: true`
|
|
70
|
+
- `description: "Composer monitor: <roomId>"`
|
|
71
|
+
- `prompt`: tell the subagent to **invoke the `composer:monitor`
|
|
72
|
+
skill**, then run the loop on `{roomId}` as `{actingAs}`. Don't paste
|
|
73
|
+
the loop rules inline — the `composer:monitor` skill carries them
|
|
74
|
+
(spawn template, mention filtering, exit rules, event payload).
|
|
75
|
+
|
|
76
|
+
Once spawned, **end your turn immediately**. Do not output any text
|
|
77
|
+
after the spawn — no closing recap, no "monitor is running" status
|
|
78
|
+
line, no restating the mention syntax. The host already shows a
|
|
79
|
+
status indicator for the backgrounded `Agent` tool call; that's the
|
|
80
|
+
user's confirmation. Anything you say after `step1_sayToUser` just
|
|
81
|
+
duplicates it. The protocol is strict:
|
|
82
|
+
|
|
83
|
+
1. Output `step1_sayToUser` (verbatim or lightly paraphrased).
|
|
84
|
+
2. Spawn the `Agent`.
|
|
85
|
+
3. End turn. No closing remark.
|
|
86
|
+
|
|
87
|
+
Also: do **not** poll `composer_next_event` from the main thread —
|
|
88
|
+
two listeners on the same room means duplicated replies.
|
|
89
|
+
|
|
90
|
+
### When the monitor exits
|
|
91
|
+
|
|
92
|
+
You'll get a notification when the background subagent finishes. Its
|
|
93
|
+
final output line is the agent's goodbye — written in the agent's
|
|
94
|
+
voice (idle timeout, server kick, reconnect aborted, etc., per the
|
|
95
|
+
`composer:monitor` exit rules). **Relay that line verbatim to the user
|
|
96
|
+
in the main thread** so they see why the agent left and how to bring
|
|
97
|
+
it back. Don't paraphrase or wrap it in extra explanation; the
|
|
98
|
+
goodbye line already carries the next step (`/composer:join` etc.).
|
|
99
|
+
|
|
100
|
+
## Terminal-side asks after joining
|
|
101
|
+
|
|
102
|
+
The main thread is also attached to the room. If the user asks something
|
|
103
|
+
in the terminal like *"reply to that thread saying we'll ship Friday"*,
|
|
104
|
+
call the write tools from here and report back concisely. Don't hand
|
|
105
|
+
terminal directives to the monitor subagent — the subagent handles
|
|
106
|
+
in-doc mentions, the main thread handles in-terminal asks. They share
|
|
107
|
+
the same MCP, so writes from either show up in the doc.
|
|
108
|
+
|
|
109
|
+
For comment etiquette, load **`composer:commenting`**. For suggestion
|
|
110
|
+
craft, load **`composer:suggesting`**.
|
|
111
|
+
|
|
112
|
+
## Failure handling
|
|
113
|
+
|
|
114
|
+
- **Setup not done** → `npx @composer-app/mcp@latest setup`, restart the CLI.
|
|
115
|
+
- **Server kicked you (4403/4410) or reconnect aborted** → the agent
|
|
116
|
+
left the room. Run `/composer:join <url>` to bring it back. The kick
|
|
117
|
+
reason is in the error message.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monitor
|
|
3
|
+
description: Use when you're running the Composer monitor loop — spawned as a subagent off `composer_create_room` / `composer_join_room`, or otherwise about to call `composer_next_event`. Covers the spawn template, mention filtering, the event payload, and the five exit rules.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Composer monitor loop
|
|
7
|
+
|
|
8
|
+
Composer's monitor loop is **always-on**: every return from
|
|
9
|
+
`composer_next_event` carries a directive (a mention to handle, a
|
|
10
|
+
timeout to noop on, or a goodbye to honor). You execute the directive
|
|
11
|
+
and immediately call the tool again. The doc IS your conversation —
|
|
12
|
+
do not narrate to the parent between ticks.
|
|
13
|
+
|
|
14
|
+
This skill is for the agent **inside** the loop. The parent host that
|
|
15
|
+
spawns you uses `composer:create` (after `composer_create_room`) or
|
|
16
|
+
`composer:join` (after `composer_join_room`).
|
|
17
|
+
|
|
18
|
+
## How the parent spawns you
|
|
19
|
+
|
|
20
|
+
The parent skill tells the host to spawn an `Agent` with:
|
|
21
|
+
|
|
22
|
+
- `subagent_type: "general-purpose"`
|
|
23
|
+
- `run_in_background: true`
|
|
24
|
+
- `description: "Composer monitor: <roomId>"`
|
|
25
|
+
- prompt: a short brief that includes the room's `{roomId}`, the
|
|
26
|
+
agent's `{actingAs}`, and the five exit rules below.
|
|
27
|
+
|
|
28
|
+
## Exit rules
|
|
29
|
+
|
|
30
|
+
When you exit, your **final output line is the user-visible goodbye** —
|
|
31
|
+
the parent surfaces it back to the main thread. Voice: first-person,
|
|
32
|
+
casual, the same agent saying goodbye. Not a system notification, not
|
|
33
|
+
a stack trace. **For terminal exits #1, #3, #4, the goodbye text is
|
|
34
|
+
returned by the MCP as `userMessage` — print it verbatim, do not
|
|
35
|
+
paraphrase.** Exit #5 (unrecoverable error) has no MCP-supplied text
|
|
36
|
+
because the loop never returned cleanly; use the canonical line below.
|
|
37
|
+
|
|
38
|
+
> Exit and return when ANY of these happen:
|
|
39
|
+
>
|
|
40
|
+
> **1. Idle timeout.** `composer_next_event` returns `kind: "timeout"`
|
|
41
|
+
> with `recentActivity: false`, `userMessage`, and `instruction` (no
|
|
42
|
+
> `requiredNextToolCall`). The doc has been silent for the configured
|
|
43
|
+
> quiet window. Do **not** post anything in the doc — awareness has
|
|
44
|
+
> already cleared. Print `userMessage` verbatim to the terminal, then
|
|
45
|
+
> exit.
|
|
46
|
+
>
|
|
47
|
+
> **2. Doc-side handoff.** A request inside the doc clearly needs the
|
|
48
|
+
> parent terminal (a code change, a shell command, an external action
|
|
49
|
+
> the parent would do). Post this short reply **in the thread**, then
|
|
50
|
+
> exit with a one-sentence summary of the ask:
|
|
51
|
+
>
|
|
52
|
+
> > *Let's chat more about this in our connected session.*
|
|
53
|
+
>
|
|
54
|
+
> **3. Server kicked the client.** Close code `4403` (old MCP), `4410`
|
|
55
|
+
> (kill switch), or HTTP 403 on upgrade. `composer_next_event` returns
|
|
56
|
+
> `kind: "timeout"` with `userMessage` carrying the kick goodbye. Print
|
|
57
|
+
> `userMessage` verbatim and exit; don't post in the doc — you're
|
|
58
|
+
> being told to leave the room.
|
|
59
|
+
>
|
|
60
|
+
> **4. Reconnect aborted.** The client circuit breaker tripped after 15
|
|
61
|
+
> consecutive failed reconnects (network down, server unreachable).
|
|
62
|
+
> Same shape as #3 — `composer_next_event` returns
|
|
63
|
+
> `kind: "timeout"` with the appropriate `userMessage`. Print verbatim
|
|
64
|
+
> and exit.
|
|
65
|
+
>
|
|
66
|
+
> **5. Unrecoverable mid-loop error.** Auth lost, room destroyed, or
|
|
67
|
+
> any other thrown error not covered above. The MCP can't supply a
|
|
68
|
+
> `userMessage` because the call didn't return cleanly. Print the
|
|
69
|
+
> error AND this line, then exit:
|
|
70
|
+
>
|
|
71
|
+
> > *Something went wrong and I had to stop. Try running
|
|
72
|
+
> > `/composer:join` in a bit to bring me back.*
|
|
73
|
+
|
|
74
|
+
Default `composer_next_event` timeout is 30s. Don't shorten it
|
|
75
|
+
arbitrarily.
|
|
76
|
+
|
|
77
|
+
## Inside the loop
|
|
78
|
+
|
|
79
|
+
Every return carries a structured directive. Follow it without waiting
|
|
80
|
+
for user input.
|
|
81
|
+
|
|
82
|
+
### `kind: "mention"`
|
|
83
|
+
|
|
84
|
+
Handle the event (reply / suggestion / resolve as needed), then execute
|
|
85
|
+
`requiredNextToolCall` — which is another `composer_next_event` call.
|
|
86
|
+
Do not pause to acknowledge.
|
|
87
|
+
|
|
88
|
+
### `kind: "timeout"` with `recentActivity: true`
|
|
89
|
+
|
|
90
|
+
The user was working in the doc recently — they just didn't tag you.
|
|
91
|
+
The return includes `requiredNextToolCall`. Execute it. Stay in the
|
|
92
|
+
loop.
|
|
93
|
+
|
|
94
|
+
### `kind: "timeout"` without `requiredNextToolCall`
|
|
95
|
+
|
|
96
|
+
The return has `userMessage` and `instruction`. This branch covers
|
|
97
|
+
**three** exits (rules 1, 3, 4) — the MCP picks the right `userMessage`
|
|
98
|
+
based on whether the doc went silent (idle), the server kicked us
|
|
99
|
+
(close code 4403/4410/HTTP 403), or the reconnect breaker tripped
|
|
100
|
+
(15 consecutive failed retries). You don't need to distinguish the
|
|
101
|
+
three: print `userMessage` verbatim to the terminal and exit. Do not
|
|
102
|
+
post in the doc.
|
|
103
|
+
|
|
104
|
+
## The mention event payload
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
{
|
|
108
|
+
kind: "mention",
|
|
109
|
+
threadId: "...",
|
|
110
|
+
threadKind: "comment" | "suggestion",
|
|
111
|
+
threadText: "...", // the exact message that triggered you
|
|
112
|
+
replyId?: "...", // present when this is a reply on an existing thread
|
|
113
|
+
reason: "direct_mention" | "active_thread" | "solo_room",
|
|
114
|
+
anchoredText?: "...", // the doc text the thread is anchored to
|
|
115
|
+
headingId?: "...", // the section's headingId (use with write tools)
|
|
116
|
+
headingText?: "...",
|
|
117
|
+
sectionMarkdown?: "..." // full containing section as markdown
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Use `headingId` + `anchoredText` directly when calling
|
|
122
|
+
`composer_add_suggestion` or `composer_add_comment` — no extra
|
|
123
|
+
`composer_get_section` is needed in the common case. Reach for
|
|
124
|
+
`sectionMarkdown` to understand surrounding context before replying.
|
|
125
|
+
|
|
126
|
+
**The event only carries the triggering message.** If the thread already
|
|
127
|
+
has replies, call `composer_get_thread({ roomId, threadId })` before
|
|
128
|
+
replying. The return has every reply with author and timestamp —
|
|
129
|
+
essential when the user tagged you mid-conversation.
|
|
130
|
+
|
|
131
|
+
## `reason` is your main filter
|
|
132
|
+
|
|
133
|
+
- **`"direct_mention"`** — sidecar or text explicitly tagged you. Always
|
|
134
|
+
reply (unless the content is purely a thank-you that doesn't need an
|
|
135
|
+
answer — never emit empty acknowledgements).
|
|
136
|
+
- **`"active_thread"`** — a plain reply on a thread you're already in.
|
|
137
|
+
Reply if the content invites one; skip if it's plainly addressed to
|
|
138
|
+
another person, is a thank-you, or is otherwise a conversational
|
|
139
|
+
dead-end.
|
|
140
|
+
- **`"solo_room"`** — you're alone with one human who didn't tag anyone.
|
|
141
|
+
**Default to a helpful reply** — they almost certainly want your
|
|
142
|
+
input. Skip only when the text reads like:
|
|
143
|
+
- a **note-to-self** ("TODO: fix this later", "remember to check the
|
|
144
|
+
date"),
|
|
145
|
+
- a bare **acknowledgement** ("k", "got it", "done"),
|
|
146
|
+
- a stage direction / aside ("ugh", "hmm"),
|
|
147
|
+
- or anything that visibly isn't pointed at you (quoted text, drafts
|
|
148
|
+
they're jotting down).
|
|
149
|
+
When in doubt, reply — the user can always ignore you.
|
|
150
|
+
|
|
151
|
+
## Where to go for richer rules
|
|
152
|
+
|
|
153
|
+
- About to **reply on a thread** with text? Load **`composer:commenting`**
|
|
154
|
+
— terseness rules and the suggestion-replaces-reply pattern.
|
|
155
|
+
- About to **post a suggestion**? Load **`composer:suggesting`** — span
|
|
156
|
+
scoping, ripple coverage, anchor mechanics.
|
|
157
|
+
|
|
158
|
+
Don't try to reproduce those skills' rules from memory; load them when
|
|
159
|
+
the situation matches.
|