@composer-app/mcp 0.0.2 → 0.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@composer-app/mcp",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Composer MCP",
5
5
  "license": "MIT",
6
6
  "author": "Josh Philpott",
@@ -59,6 +59,98 @@ of your response from this:
59
59
  For suggestion craft (anchoring, ripples, multi-span), load
60
60
  **`composer:suggesting`**.
61
61
 
62
+ ## State and the ack-first flow
63
+
64
+ Every agent-authored reply / comment / suggestion carries a lifecycle
65
+ state the UI animates: **`thinking → working → replying → ready`**.
66
+ The mention loop in `composer:monitor` opens this with an ack-first
67
+ reply (`state: "thinking"`); from there you advance state with
68
+ `composer_agent_status`:
69
+
70
+ ```
71
+ composer_agent_status({
72
+ roomId,
73
+ threadId,
74
+ replyId?, // identifies which reply you own; omit for thread-head
75
+ state, // "thinking" | "working" | "replying" | "ready"
76
+ text?, // rewrite the body (only meaningful on "ready")
77
+ note?, // short human-readable progress line
78
+ kind? // "comment" | "suggestion" — disambiguates head records
79
+ })
80
+ ```
81
+
82
+ - `thinking` — initial ack, set by the `composer_add_*` /
83
+ `composer_reply_*` call that posted it.
84
+ - `working` — substantive work in flight (reading the doc, computing,
85
+ drafting). Set this whenever you expect a gap.
86
+ - `replying` — about to write the final text. Optional, brief.
87
+ - `ready` — done. Pass `text: "<final body>"` to rewrite the ack in
88
+ place atomically; the awareness heartbeat is pruned in the same call.
89
+
90
+ **Silence is the failure mode.** If you're about to do something slow
91
+ (fetch the full doc, compute a non-trivial diff, call another tool)
92
+ transition to `working` first. The UI collapses transitions faster
93
+ than ~400 ms, so don't worry about being too fast — worry about being
94
+ silent for >2 s without a `working` flip. Use `note` to surface
95
+ progress where it helps: `note: "Reading section 3…"`,
96
+ `note: "Drafting suggestion…"`.
97
+
98
+ **Rewrite-on-ready replaces a duplicate "done" reply.** When the
99
+ substantive answer is a standalone artifact (a suggestion, a
100
+ cross-span comment, a doc link), DO NOT post a second pointer reply.
101
+ Rewrite the existing ack in place to a thin pointer and mark `ready`
102
+ in the same call:
103
+
104
+ ```
105
+ composer_agent_status({
106
+ roomId, threadId, replyId,
107
+ state: "ready",
108
+ text: "Posted a suggestion below."
109
+ })
110
+ ```
111
+
112
+ When the substantive answer IS the reply text, do the same — rewrite
113
+ the ack to that text and set `ready` in the same call. Do not post a
114
+ separate follow-up reply.
115
+
116
+ ### Worked example — ack-then-suggestion
117
+
118
+ ```
119
+ // 1. Mention arrives via composer_next_event.
120
+ // { kind: "mention", threadId: "t_abc", invokerUserId: "u_jess",
121
+ // invokerName: "Jess", reason: "direct_mention", ... }
122
+
123
+ // 2. Ack first.
124
+ const { replyId } = composer_reply_comment({
125
+ roomId, threadId: "t_abc",
126
+ text: "@Jess — on it",
127
+ mentions: ["u_jess"],
128
+ state: "thinking",
129
+ });
130
+
131
+ // 3. Transition to working before any slow step.
132
+ composer_agent_status({
133
+ roomId, threadId: "t_abc", replyId,
134
+ state: "working",
135
+ note: "Reading section 3…",
136
+ });
137
+
138
+ // 4. Post the substantive artifact.
139
+ composer_add_suggestion({
140
+ roomId, fromThreadId: "t_abc", replacementText: "…",
141
+ });
142
+
143
+ // 5. Rewrite the ack and mark ready atomically.
144
+ composer_agent_status({
145
+ roomId, threadId: "t_abc", replyId,
146
+ state: "ready",
147
+ text: "Posted a suggestion below.",
148
+ });
149
+ ```
150
+
151
+ No extra pointer reply. The rewrite + suggestion card together are
152
+ the complete response.
153
+
62
154
  ## Empty acknowledgements: don't
63
155
 
64
156
  Never post a reply that's just "👍", "got it", or "thanks". If the
@@ -114,7 +114,9 @@ post in the doc.
114
114
  anchoredText?: "...", // the doc text the thread is anchored to
115
115
  headingId?: "...", // the section's headingId (use with write tools)
116
116
  headingText?: "...",
117
- sectionMarkdown?: "..." // full containing section as markdown
117
+ sectionMarkdown?: "...", // full containing section as markdown
118
+ invokerUserId?: "...", // userId of whoever triggered you (use in mentions[])
119
+ invokerName?: "..." // their display name (use in @mention literal)
118
120
  }
119
121
  ```
120
122
 
@@ -123,6 +125,10 @@ Use `headingId` + `anchoredText` directly when calling
123
125
  `composer_get_section` is needed in the common case. Reach for
124
126
  `sectionMarkdown` to understand surrounding context before replying.
125
127
 
128
+ Read `invokerUserId` and `invokerName` directly from the payload — do
129
+ NOT try to reconstruct them from `threadText` or from an awareness
130
+ lookup. They're the authoritative values the server resolved.
131
+
126
132
  **The event only carries the triggering message.** If the thread already
127
133
  has replies, call `composer_get_thread({ roomId, threadId })` before
128
134
  replying. The return has every reply with author and timestamp —
@@ -148,6 +154,41 @@ essential when the user tagged you mid-conversation.
148
154
  they're jotting down).
149
155
  When in doubt, reply — the user can always ignore you.
150
156
 
157
+ ## Ack first, then do the work
158
+
159
+ Whenever you intend to act on a mention, post a brief ack reply FIRST —
160
+ before reading the full doc, before drafting a suggestion. This is the
161
+ "I heard you, I'm on it" signal; without it the user stares at a silent
162
+ sidebar while you think.
163
+
164
+ - Call `composer_reply_comment` (or `composer_reply_suggestion`) with
165
+ `state: "thinking"` and `mentions: [event.invokerUserId]`. For a
166
+ brand-new thread response use `composer_add_comment` /
167
+ `composer_add_suggestion` with the same `state` + `mentions` shape.
168
+ - Body: `@<invokerName> — on it` or equivalent terse phrasing
169
+ (≤ 24 chars). "On it.", "Looking.", "Checking.". No preamble.
170
+ - **Skip the ack** for empty thank-yous or conversational dead-ends you
171
+ wouldn't reply to at all (see `reason` gates above) — the ack is a
172
+ signal of intent to act, not a reflex.
173
+ - **Skip the ack** when the mention fired as `active_thread` AND your
174
+ own prior reply on this thread was a concrete yes/no counter-proposal
175
+ AND the human's message is a yes-variant. The "I heard you" signal
176
+ was delivered in the prior ack; drop the suggestion directly via
177
+ `composer_add_suggestion({ fromThreadId: event.threadId })`. A second
178
+ ack here is noise.
179
+
180
+ Once the ack is posted, drive its state with `composer_agent_status`
181
+ as you work. On completion, rewrite the ack to its final form in the
182
+ same call that transitions to `ready` — do not post a duplicate
183
+ pointer reply. See `composer:commenting` for the state machine and the
184
+ ack-then-suggestion flow.
185
+
186
+ If you decide to skip a mention without replying, call
187
+ `composer_done({ roomId, threadId })` so the thread-head "thinking…"
188
+ heartbeat (auto-published when the mention was dequeued) gets cleared.
189
+ The normal reply-with-`ready` path clears its own placeholder; this is
190
+ only for the skip case.
191
+
151
192
  ## Where to go for richer rules
152
193
 
153
194
  - About to **reply on a thread** with text? Load **`composer:commenting`**