@composer-app/mcp 0.0.1-beta.7 → 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/dist/{chunk-EJUJPBX2.js → chunk-GPFWLOYB.js} +279 -27
- package/dist/cli.js +1 -1
- package/dist/mcp.js +1 -1
- package/package.json +1 -1
- 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 -533
|
@@ -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.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: suggesting
|
|
3
|
+
description: Use when you're about to call `composer_add_suggestion` — proposing a text replacement in a Composer doc. Covers span scoping (don't replace more than asked), cross-span responses, ripple coverage (so accepting leaves the doc correct), the auto-suggest-on-confirm pattern, and anchor mechanics (`textToFind` / `occurrence` / whitespace / formatting).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Composer suggestions
|
|
7
|
+
|
|
8
|
+
A suggestion proposes a text replacement: `textToFind` is the literal
|
|
9
|
+
span that gets cut out when the user accepts; `replacementText` is what
|
|
10
|
+
gets inserted in its place. Pending suggestions render as Replace/With
|
|
11
|
+
cards in the doc's sidebar. There's no "just edit" — every text change
|
|
12
|
+
goes through the user's accept click.
|
|
13
|
+
|
|
14
|
+
This skill is the full guide for `composer_add_suggestion`. Load it any
|
|
15
|
+
time you're about to call the tool, even on familiar territory — the
|
|
16
|
+
failure modes are precise.
|
|
17
|
+
|
|
18
|
+
## Match the span to the request
|
|
19
|
+
|
|
20
|
+
Before calling `composer_add_suggestion`, decide what span the user is
|
|
21
|
+
actually asking you to change. Three patterns:
|
|
22
|
+
|
|
23
|
+
### 1. Request scoped to their selection (the common case)
|
|
24
|
+
|
|
25
|
+
"Rewrite this", "make this clearer", "fix the grammar" — no new span
|
|
26
|
+
mentioned. Pass `fromThreadId: <threadId>`. The suggestion inherits the
|
|
27
|
+
source thread's exact anchor — the span the user selected,
|
|
28
|
+
character-for-character.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
composer_add_suggestion({
|
|
32
|
+
roomId, fromThreadId: event.threadId, replacementText: "…"
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Request targets a different span
|
|
37
|
+
|
|
38
|
+
"Rewrite this whole paragraph", "replace the entire list", "change the
|
|
39
|
+
heading". Supply `anchor` (`headingId` + `textToFind`) for the span the
|
|
40
|
+
user actually named. Do **not** pass `fromThreadId` — you're no longer
|
|
41
|
+
inheriting the thread's span.
|
|
42
|
+
|
|
43
|
+
### 3. Proactive suggestion with no source thread
|
|
44
|
+
|
|
45
|
+
Supply `anchor`. Keep `textToFind` tight — do not widen beyond what
|
|
46
|
+
you're actually replacing.
|
|
47
|
+
|
|
48
|
+
**Picking a broader `textToFind` than the user asked for is the main
|
|
49
|
+
failure mode.** When in doubt, default to path 1.
|
|
50
|
+
|
|
51
|
+
## Cross-span: respond anywhere in the doc
|
|
52
|
+
|
|
53
|
+
A thread is anchored to one span, but your response isn't confined to
|
|
54
|
+
it. When the user's question (or your own judgment) points elsewhere:
|
|
55
|
+
|
|
56
|
+
- **Suggest on a different span** — call `composer_add_suggestion` with
|
|
57
|
+
an explicit `anchor`. You can post multiple suggestions in one turn.
|
|
58
|
+
E.g. user says *"the flour amount is off and so is the bake time"* →
|
|
59
|
+
two suggestions, each anchored to its own span.
|
|
60
|
+
- **Open a new thread elsewhere** — `composer_add_comment` with its own
|
|
61
|
+
anchor. Useful for cross-references ("see also the conclusion") or
|
|
62
|
+
raising something the user didn't ask about but should see.
|
|
63
|
+
- **Still reply on the original thread too** if the user's question
|
|
64
|
+
deserves a direct answer — but only when the reply says something the
|
|
65
|
+
suggestion/new-comment doesn't already convey. Don't post "see my
|
|
66
|
+
suggestion"; the card IS the answer.
|
|
67
|
+
|
|
68
|
+
Order of operations for a multi-span response: post the
|
|
69
|
+
suggestion(s) / new comment(s) first, then (optionally) a reply on the
|
|
70
|
+
originating thread pointing out the bigger picture.
|
|
71
|
+
|
|
72
|
+
## Suggest completely — accepting must leave the doc correct
|
|
73
|
+
|
|
74
|
+
The user clicks Accept and is done. They should never have to hunt down
|
|
75
|
+
downstream edits you forgot.
|
|
76
|
+
|
|
77
|
+
**Load enough context before you suggest.** The mention event gives you
|
|
78
|
+
`sectionMarkdown` for the containing section — usually enough for
|
|
79
|
+
wording changes. For anything that might appear elsewhere in the doc
|
|
80
|
+
(numbers, names, product/feature references, versions, dates,
|
|
81
|
+
terminology, heading text), call `composer_get_full_doc` first. One
|
|
82
|
+
extra read is much cheaper than shipping a broken doc.
|
|
83
|
+
|
|
84
|
+
**Scan for ripples before posting.** Common ones:
|
|
85
|
+
|
|
86
|
+
- **Counts and enumerations.** "The three examples below" / "three
|
|
87
|
+
things to remember" — adding or removing an item ripples to the count
|
|
88
|
+
and ordinal words ("first", "finally").
|
|
89
|
+
- **Cross-references.** "As in section 2", "see the conclusion", "per
|
|
90
|
+
step 3 above". If your edit moves or renames the target, update the
|
|
91
|
+
reference too.
|
|
92
|
+
- **Restated facts.** Recipes mention an ingredient twice; release notes
|
|
93
|
+
cite a version in both intro and body; specs quote a number in a
|
|
94
|
+
heading and a paragraph. One fact, multiple spans — cover all of them.
|
|
95
|
+
- **Subject/verb and pronoun agreement.** "X and Y are" → trim to just X
|
|
96
|
+
→ "X is".
|
|
97
|
+
- **Neighboring flow.** Rewriting sentence 2 can break sentence 3 ("This
|
|
98
|
+
is why..."). Fix the continuation.
|
|
99
|
+
- **Heading changes.** If you change heading text, prose that says "see
|
|
100
|
+
the Intro section" may need updating.
|
|
101
|
+
|
|
102
|
+
**Post every ripple as its own suggestion, in the same turn.** The tool
|
|
103
|
+
accepts one anchor per call — call it multiple times. Each suggestion
|
|
104
|
+
stays tight to its own span (this is NOT oversuggesting — it's covering
|
|
105
|
+
the actual surface of the change).
|
|
106
|
+
|
|
107
|
+
If a ripple is too structural for a clean suggestion (reorder a list,
|
|
108
|
+
split a paragraph), post the ones you can AND a short reply flagging
|
|
109
|
+
what's still open. The user shouldn't be surprised.
|
|
110
|
+
|
|
111
|
+
When in doubt about the scope of a ripple, fetch the full doc. Don't
|
|
112
|
+
guess.
|
|
113
|
+
|
|
114
|
+
## Auto-suggest when the user confirms a concrete proposal
|
|
115
|
+
|
|
116
|
+
Two turns, not three. When a user flags something qualitative (*"this
|
|
117
|
+
is too much flour"*, *"this sentence is clunky"*, *"this number feels
|
|
118
|
+
off"*):
|
|
119
|
+
|
|
120
|
+
1. **Turn 1 — propose.** Reply on the thread with one specific
|
|
121
|
+
alternative phrased as a check: *"Does 200g seem right?"*, *"How
|
|
122
|
+
about 'gently fold' instead of 'stir'?"*, *"Would 45 minutes read
|
|
123
|
+
better than 90?"*. Pick a real number or phrase — not *"would you
|
|
124
|
+
like me to suggest a different amount?"* (that's a question about
|
|
125
|
+
your behavior, not a proposal).
|
|
126
|
+
2. **Turn 2 — commit on confirmation.** When the user replies with any
|
|
127
|
+
variant of yes ("yes", "sure", "go for it", "perfect", a thumbs-up
|
|
128
|
+
emoji), call `composer_add_suggestion` with `fromThreadId:
|
|
129
|
+
event.threadId` and the concrete replacement. Do **not** also post a
|
|
130
|
+
comment reply — the suggestion card IS your reply.
|
|
131
|
+
|
|
132
|
+
If the user says no / picks a different value / redirects, follow their
|
|
133
|
+
lead — don't post the original proposal anyway.
|
|
134
|
+
|
|
135
|
+
If you can't name a concrete alternative (the thread is too abstract to
|
|
136
|
+
guess a number), ask a clarifying question instead. Don't propose
|
|
137
|
+
something generic just to fill the slot — *"Would you like me to
|
|
138
|
+
shorten this?"* is worthless without a target length.
|
|
139
|
+
|
|
140
|
+
## Anchor mechanics
|
|
141
|
+
|
|
142
|
+
Write tools take:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
{ headingId: "intro-0", textToFind: "the exact words to anchor on", occurrence?: 1 }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`textToFind` is literally cut out on accept. Pick the right span:
|
|
149
|
+
|
|
150
|
+
- **Anchor the whole unit you're changing.** Replacing a sentence →
|
|
151
|
+
include the terminal punctuation (`.`, `?`, `!`). Replacing a bullet
|
|
152
|
+
item → anchor the item's text (not the `- ` marker; that's block
|
|
153
|
+
structure). Replacing a paragraph → anchor the whole paragraph.
|
|
154
|
+
- **Include any trailing punctuation you're changing.** Converting a
|
|
155
|
+
statement to a question? End the anchor at the `.`, end the
|
|
156
|
+
replacement with `?`. Don't anchor "the statement" alone and replace
|
|
157
|
+
with "the question?" — you'll end up with `the question?.`.
|
|
158
|
+
- **Match `replacementText`'s shape to the anchor's shape.** Inline
|
|
159
|
+
replacement inside a paragraph → replacement is inline (no leading
|
|
160
|
+
`- `, `#`, or blank line). Replacing a full list → replacement is a
|
|
161
|
+
full markdown list.
|
|
162
|
+
- **Formatting is part of your replacement, not the anchor.** If the
|
|
163
|
+
original had `**bold**` or a link, the anchor's formatting is gone on
|
|
164
|
+
accept — your replacement must include the markdown for any
|
|
165
|
+
formatting you want preserved.
|
|
166
|
+
- **Anchor at token boundaries, not mid-word.** `textToFind: "istrat"`
|
|
167
|
+
to hit the middle of "administration" is fragile. Use whole words or
|
|
168
|
+
sentence boundaries. Use `occurrence` when the same phrase appears
|
|
169
|
+
multiple times.
|
|
170
|
+
- **Whitespace.** Default: no leading or trailing whitespace in the
|
|
171
|
+
anchor, end `replacementText` at the same boundary. If you include a
|
|
172
|
+
trailing space in the anchor, include one in the replacement; otherwise
|
|
173
|
+
words smash together.
|
|
174
|
+
|
|
175
|
+
If you get `text_not_found`, the error message includes the current
|
|
176
|
+
section text. Re-plan against the fresh text and retry. Never retry
|
|
177
|
+
with stale content.
|