@composer-app/mcp 0.0.1-beta.5 → 0.0.1-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/README.md +160 -0
- package/dist/{chunk-A5KBJAJW.js → chunk-EJUJPBX2.js} +1126 -213
- package/dist/cli.js +1 -1
- package/dist/mcp.js +21 -1
- package/package.json +3 -2
- package/skill/SKILL.md +196 -80
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-EJUJPBX2.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.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.7",
|
|
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",
|
package/skill/SKILL.md
CHANGED
|
@@ -36,14 +36,13 @@ two ordered steps — the field names encode the order:
|
|
|
36
36
|
also carries the `@<your-name>` tagging hint. Relay it; you can
|
|
37
37
|
paraphrase lightly but do not drop the URL or the mention syntax.
|
|
38
38
|
2. `step2_callTool` — a structured `{ tool, args, why }` directive for
|
|
39
|
-
the `composer_next_event`
|
|
40
|
-
|
|
41
|
-
below). End your turn once the subagent is spawned.
|
|
39
|
+
the follow-up `composer_next_event` call. Execute it AFTER step 1,
|
|
40
|
+
before ending your turn.
|
|
42
41
|
|
|
43
42
|
Skipping step 1 leaves the user without the URL — they have no way into
|
|
44
|
-
the doc. Skipping
|
|
45
|
-
|
|
46
|
-
every mention
|
|
43
|
+
the doc. Skipping step 2 leaves the room attached but silent; saying
|
|
44
|
+
"I'm monitoring" without making the call is a lie, the agent will miss
|
|
45
|
+
every mention.
|
|
47
46
|
|
|
48
47
|
**Seeding — prefer a file path when one exists.** Pick exactly one:
|
|
49
48
|
|
|
@@ -65,63 +64,16 @@ Triggers: a share prompt with a Composer URL, "/composer join <url>".
|
|
|
65
64
|
Action: extract the URL from the prompt and call `composer_join_room({ url })`.
|
|
66
65
|
Same first-run rule as Create. On success, the return carries the same
|
|
67
66
|
ordered pair: output `step1_sayToUser` first (confirms the URL the user
|
|
68
|
-
just joined), then
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
The monitor loop is delegated to a background subagent. Polling
|
|
76
|
-
`composer_next_event` from the main thread fills the conversation
|
|
77
|
-
context with idle-tick chatter and mention-handling that belongs in
|
|
78
|
-
the doc, not the terminal. Spawn the subagent, end your turn, let the
|
|
79
|
-
doc be the conversation.
|
|
80
|
-
|
|
81
|
-
**How to spawn (Claude Code).** Use the `Agent` tool with:
|
|
82
|
-
|
|
83
|
-
- `subagent_type: "general-purpose"`
|
|
84
|
-
- `run_in_background: true`
|
|
85
|
-
- `description: "Composer monitor: <roomId>"` — short, identifies the room
|
|
86
|
-
- `prompt`: the template below, with `{roomId}` and `{actingAs}` filled in
|
|
87
|
-
|
|
88
|
-
Prompt template:
|
|
89
|
-
|
|
90
|
-
> Invoke the composer skill, then run the monitor loop for room
|
|
91
|
-
> `{roomId}` as `{actingAs}`. Your only job is the in-doc conversation:
|
|
92
|
-
> call `composer_next_event({ roomId: "{roomId}" })` and follow each
|
|
93
|
-
> return's `requiredNextToolCall` directive verbatim, looping until the
|
|
94
|
-
> goodbye branch fires.
|
|
95
|
-
>
|
|
96
|
-
> All write tools (`composer_add_comment`, `composer_add_suggestion`,
|
|
97
|
-
> `composer_reply_comment`, `composer_reply_suggestion`,
|
|
98
|
-
> `composer_resolve_thread`) are yours to use as the skill describes.
|
|
99
|
-
> The doc IS your conversation — do not narrate to the parent between
|
|
100
|
-
> ticks.
|
|
101
|
-
>
|
|
102
|
-
> Exit and return when ANY of these happen:
|
|
103
|
-
> 1. `composer_next_event` returns `kind: "timeout"` with
|
|
104
|
-
> `recentActivity: false` — say `userMessage` EXACTLY in the doc
|
|
105
|
-
> (it's the goodbye line), then exit.
|
|
106
|
-
> 2. A request inside the doc clearly needs the parent terminal
|
|
107
|
-
> (a code change, a shell command, an external action the parent
|
|
108
|
-
> would do). Post a short reply: "I'll get on this in the terminal,"
|
|
109
|
-
> then exit with a one-sentence summary of the ask.
|
|
110
|
-
> 3. An unrecoverable error (auth lost, room destroyed). Exit with
|
|
111
|
-
> the error.
|
|
112
|
-
|
|
113
|
-
**Main thread after spawning.** Turn ends. Do **not** also call
|
|
114
|
-
`composer_next_event` — two listeners on the same room means duplicated
|
|
115
|
-
replies. If the user asks "what's happening in Composer?", check the
|
|
116
|
-
subagent's status (`TaskList` / `TaskOutput`) rather than re-entering
|
|
117
|
-
the loop yourself.
|
|
118
|
-
|
|
119
|
-
**Inside the loop (what the subagent does).** Default timeout is 30
|
|
120
|
-
seconds. Every return carries a structured directive — follow it
|
|
121
|
-
without waiting for user input.
|
|
67
|
+
just joined), then execute `step2_callTool`.
|
|
68
|
+
|
|
69
|
+
### 3. Monitor
|
|
70
|
+
Triggers: "watch this doc", or automatically after join/create.
|
|
71
|
+
Action: call `composer_next_event({ roomId })` in a loop (default timeout
|
|
72
|
+
is 30 seconds). **The loop is always-on.** Every return carries a
|
|
73
|
+
structured directive — follow it without waiting for user input.
|
|
122
74
|
|
|
123
75
|
On `mention`: handle the event (reply / suggestion / resolve as needed),
|
|
124
|
-
output any
|
|
76
|
+
output any user-facing text, then execute `requiredNextToolCall` — which
|
|
125
77
|
is another `composer_next_event` call. Do not pause for the user to
|
|
126
78
|
acknowledge. The doc is the conversation.
|
|
127
79
|
|
|
@@ -130,8 +82,9 @@ On `timeout`: check `recentActivity`.
|
|
|
130
82
|
Execute it — the user is still working, just not tagging you.
|
|
131
83
|
- `recentActivity: false` → the return includes `userMessage` and
|
|
132
84
|
`instruction` but NO `requiredNextToolCall`. Say `userMessage`
|
|
133
|
-
EXACTLY ("I've left the document…") and
|
|
134
|
-
|
|
85
|
+
EXACTLY ("I've left the document…") and stop calling
|
|
86
|
+
`composer_next_event` until the user asks you to rejoin. Do not
|
|
87
|
+
paraphrase — users recognize the line across sessions.
|
|
135
88
|
|
|
136
89
|
On `mention`, the event contains everything you need to act in one turn:
|
|
137
90
|
|
|
@@ -142,11 +95,13 @@ On `mention`, the event contains everything you need to act in one turn:
|
|
|
142
95
|
threadKind: "comment" | "suggestion",
|
|
143
96
|
threadText: "...", // the exact message that triggered you
|
|
144
97
|
replyId?: "...", // present when it's a reply on an existing thread
|
|
145
|
-
reason: "direct_mention" | "active_thread",
|
|
98
|
+
reason: "direct_mention" | "active_thread" | "solo_room",
|
|
146
99
|
anchoredText?: "...", // the doc text the thread is anchored to
|
|
147
100
|
headingId?: "...", // the section's headingId (use with write tools)
|
|
148
101
|
headingText?: "...",
|
|
149
|
-
sectionMarkdown?: "..."
|
|
102
|
+
sectionMarkdown?: "...", // full containing section as markdown
|
|
103
|
+
invokerUserId?: "...", // userId of whoever triggered you (use in mentions[])
|
|
104
|
+
invokerName?: "..." // their display name (use in @mention literal)
|
|
150
105
|
}
|
|
151
106
|
```
|
|
152
107
|
|
|
@@ -155,6 +110,38 @@ or `composer_add_comment` — no extra `composer_get_section` call is needed in
|
|
|
155
110
|
the common case. Reach for `sectionMarkdown` to understand surrounding context
|
|
156
111
|
before replying or suggesting.
|
|
157
112
|
|
|
113
|
+
**Ack first, then do the work.** On every `mention` event you intend to act
|
|
114
|
+
on, post a brief ack reply FIRST — before reading the full doc, before
|
|
115
|
+
drafting a suggestion. This is the "I heard you, I'm on it" signal; without
|
|
116
|
+
it the user stares at a silent sidebar while you think.
|
|
117
|
+
|
|
118
|
+
- Call `composer_reply_comment` (or `composer_reply_suggestion`) with
|
|
119
|
+
`state: "thinking"` and `mentions: [event.invokerUserId]`. For a brand-new
|
|
120
|
+
thread response use `composer_add_comment` / `composer_add_suggestion`
|
|
121
|
+
with the same `state` + `mentions` shape.
|
|
122
|
+
- Body: `@<invokerName> — on it` or equivalent terse phrasing (≤ 24 chars).
|
|
123
|
+
"On it.", "Looking.", "Checking.". No preamble, no promise of structure.
|
|
124
|
+
- Read `invokerUserId` and `invokerName` directly from the event payload
|
|
125
|
+
above — do NOT try to reconstruct them from `threadText` or from an
|
|
126
|
+
awareness lookup. They're the authoritative values the server resolved.
|
|
127
|
+
- **Skip the ack** for empty thank-yous or conversational dead-ends you
|
|
128
|
+
wouldn't reply to at all (see `reason` gates below) — the ack is a
|
|
129
|
+
signal of intent to act, not a reflex.
|
|
130
|
+
- **Skip the ack** when this mention is a yes-variant confirmation inside
|
|
131
|
+
an ask-then-auto-suggest round-trip (see §"Auto-suggest when the user
|
|
132
|
+
confirms a concrete proposal"): if the mention fired as `active_thread`
|
|
133
|
+
AND your own prior reply on this thread was a concrete yes/no
|
|
134
|
+
counter-proposal AND the human's message is a yes-variant, drop the
|
|
135
|
+
suggestion directly via `composer_add_suggestion` with
|
|
136
|
+
`fromThreadId: event.threadId`. The "I heard you" signal was already
|
|
137
|
+
delivered in the prior ack; a second ack here is noise. Only the
|
|
138
|
+
initial substantive-work mention gets an ack.
|
|
139
|
+
|
|
140
|
+
Once the ack is posted, drive state with `composer_agent_status` as you
|
|
141
|
+
work (see §"Progress status" below). On completion, rewrite the ack to
|
|
142
|
+
its final form in the same call that transitions to `ready` — do not
|
|
143
|
+
post a duplicate pointer reply.
|
|
144
|
+
|
|
158
145
|
**The event only carries the triggering message.** If the thread already has
|
|
159
146
|
replies (from the user, or from another agent), call `composer_get_thread({
|
|
160
147
|
roomId, threadId })` before replying. The return has every reply with author
|
|
@@ -180,13 +167,19 @@ need to catch up on what's already been said.
|
|
|
180
167
|
drafts they're jotting down).
|
|
181
168
|
When in doubt, reply — the user can always ignore you.
|
|
182
169
|
|
|
170
|
+
**When you skip a mention, call `composer_done({ roomId, threadId })`.**
|
|
171
|
+
The instant a mention is dequeued via `composer_next_event`, the server
|
|
172
|
+
publishes a `thinking…` indicator on that thread so the user sees you
|
|
173
|
+
picked it up — there's no flicker waiting for your first
|
|
174
|
+
`composer_agent_status` call. If you reply, the `state: "ready"`
|
|
175
|
+
transition clears the indicator on its own. If you skip without
|
|
176
|
+
replying, nothing else clears it and the user's avatar pulses forever.
|
|
177
|
+
`composer_done` is idempotent — safe to call even if the indicator was
|
|
178
|
+
already cleared.
|
|
179
|
+
|
|
183
180
|
### 4. Act
|
|
184
|
-
Triggers: direct requests
|
|
185
|
-
Action:
|
|
186
|
-
tools from here and report back concisely. Don't hand terminal directives
|
|
187
|
-
to the monitor subagent; the subagent handles in-doc mentions, the main
|
|
188
|
-
thread handles in-terminal asks. They share the same MCP, so writes from
|
|
189
|
-
either show up in the doc.
|
|
181
|
+
Triggers: direct requests like "add a summary to section 2".
|
|
182
|
+
Action: already attached; call the write tools and report back concisely.
|
|
190
183
|
|
|
191
184
|
## Tools
|
|
192
185
|
|
|
@@ -200,13 +193,25 @@ Read tools:
|
|
|
200
193
|
|
|
201
194
|
Write tools:
|
|
202
195
|
- `composer_add_comment` — NEW comment on any span in the doc. Use when
|
|
203
|
-
raising something outside the current thread's anchor.
|
|
196
|
+
raising something outside the current thread's anchor. Accepts an
|
|
197
|
+
optional `state` ("thinking" on an ack) so the first-ever reply on a
|
|
198
|
+
thread can be the ack.
|
|
204
199
|
- `composer_add_suggestion` — propose a text replacement (lands as
|
|
205
200
|
pending). Can target any span — `fromThreadId` inherits the source
|
|
206
201
|
thread's anchor; `anchor` specifies a span elsewhere. Call it multiple
|
|
207
202
|
times in a turn to suggest in several spots.
|
|
208
203
|
- `composer_reply_comment` / `composer_reply_suggestion` — reply on an
|
|
209
|
-
existing thread.
|
|
204
|
+
existing thread. Accept an optional `state` field for the ack-first
|
|
205
|
+
flow; post with `state: "thinking"` and the invoker in `mentions[]`.
|
|
206
|
+
- `composer_agent_status` — drive state transitions (`thinking →
|
|
207
|
+
working → replying → ready`) and rewrite the final ack body on the
|
|
208
|
+
reply/comment/suggestion you own. See §"Progress status".
|
|
209
|
+
- `composer_done` — clear the live indicator on a thread you decided
|
|
210
|
+
NOT to reply to. Required when you skip a mention (off-topic chatter,
|
|
211
|
+
self-mention, conversational dead-end), because the indicator is
|
|
212
|
+
published optimistically the moment a mention is dequeued. Replying
|
|
213
|
+
with `state: "ready"` clears it on its own — only call `composer_done`
|
|
214
|
+
for the skip case.
|
|
210
215
|
- `composer_resolve_thread` — mark resolved.
|
|
211
216
|
|
|
212
217
|
There is no "just edit" tool in v1. All text changes go through suggestions
|
|
@@ -220,11 +225,18 @@ a chat window. Long replies get unwieldy fast. Rules:
|
|
|
220
225
|
- Answer in 1–3 sentences. Prefer one.
|
|
221
226
|
- Reply directly to the question asked — no preamble ("Great question!"),
|
|
222
227
|
no restating the ask, no trailing summary of what you just did.
|
|
223
|
-
- **
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
- **When the substantive answer is a standalone artifact, that artifact
|
|
229
|
+
IS the reply.** A "standalone artifact" means a suggestion, a cross-span
|
|
230
|
+
comment on a different anchor, or a separate document link. In that
|
|
231
|
+
case do NOT post a duplicate pointer comment — instead, rewrite your
|
|
232
|
+
existing ack in place to a thin pointer (`"Posted a suggestion below."`,
|
|
233
|
+
`"Added a comment in Section 3."`, `"See doc <link>."`) and set
|
|
234
|
+
`state: "ready"` on the same call. The suggestion / cross-span comment
|
|
235
|
+
renders as its own card; a pointer reply just duplicates what the user
|
|
236
|
+
can already see.
|
|
237
|
+
- **When the substantive answer IS a reply with text**, rewrite the ack
|
|
238
|
+
to that text and clear the state (or set `state: "ready"`) in the same
|
|
239
|
+
`composer_agent_status` call. Do not post a separate follow-up reply.
|
|
228
240
|
- If the answer genuinely needs structure (a list of 4+ items, code, a
|
|
229
241
|
table) and a suggestion isn't the right shape, post it as a suggestion
|
|
230
242
|
in the doc body instead of as a comment reply.
|
|
@@ -349,8 +361,10 @@ Two turns, not three:
|
|
|
349
361
|
2. **Turn 2 (commit on confirmation).** When the user replies with any
|
|
350
362
|
variant of yes ("yes", "sure", "go for it", "perfect", a thumbs-up
|
|
351
363
|
emoji), call `composer_add_suggestion` with `fromThreadId: event.threadId`
|
|
352
|
-
and the concrete replacement.
|
|
353
|
-
|
|
364
|
+
and the concrete replacement. **Skip the ack in this case** — the
|
|
365
|
+
prior reply already delivered the "I heard you" signal, and the
|
|
366
|
+
suggestion card IS your answer. Do NOT also post a comment reply
|
|
367
|
+
(see §"Keep comment text terse" and the ack-skip rule in §"Monitor").
|
|
354
368
|
|
|
355
369
|
If the user says no / picks a different value / redirects, follow their
|
|
356
370
|
lead — do not post the original proposal anyway.
|
|
@@ -360,6 +374,108 @@ abstract to guess a number), ask a clarifying question instead. Don't
|
|
|
360
374
|
propose something generic just to fill the slot — "Would you like me
|
|
361
375
|
to shorten this?" is worthless without a target length.
|
|
362
376
|
|
|
377
|
+
### Progress status
|
|
378
|
+
|
|
379
|
+
Once the ack is posted, drive the owning reply through a small state
|
|
380
|
+
machine using `composer_agent_status`. The UI animates state changes
|
|
381
|
+
on the reply card; the user sees progress instead of a silent pause.
|
|
382
|
+
|
|
383
|
+
```
|
|
384
|
+
composer_agent_status({
|
|
385
|
+
roomId,
|
|
386
|
+
threadId,
|
|
387
|
+
replyId?, // identifies which reply you own; omit for thread-head
|
|
388
|
+
state, // "thinking" | "working" | "replying" | "ready"
|
|
389
|
+
text?, // rewrite the reply body (only meaningful on "ready")
|
|
390
|
+
note?, // short human-readable progress line
|
|
391
|
+
kind? // "comment" | "suggestion" — disambiguates head records
|
|
392
|
+
})
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
State machine: **`thinking → working → replying → ready`**.
|
|
396
|
+
|
|
397
|
+
- `thinking` — initial ack, set by the reply/add tool that posted it.
|
|
398
|
+
- `working` — you're doing substantive work (reading the doc, computing,
|
|
399
|
+
drafting). Set this whenever you expect a gap.
|
|
400
|
+
- `replying` — you're about to write the final text. Optional, brief.
|
|
401
|
+
- `ready` — done. Call with `text: "<final ack body>"` to rewrite the
|
|
402
|
+
ack in place atomically; the awareness heartbeat for this entry is
|
|
403
|
+
pruned in the same call.
|
|
404
|
+
|
|
405
|
+
**Minimum-visible rule.** The UI collapses state changes that happen
|
|
406
|
+
faster than 400 ms, so don't worry about being too fast. DO worry about
|
|
407
|
+
being SILENT for more than ~2 seconds without calling
|
|
408
|
+
`composer_agent_status({ state: "working" })` — silence is the failure
|
|
409
|
+
mode. If you're about to do something slow (fetch the full doc, compute
|
|
410
|
+
a non-trivial diff, call another tool), transition to `working` first.
|
|
411
|
+
|
|
412
|
+
Use `note` to surface human-readable progress where it helps — e.g.
|
|
413
|
+
`note: "Reading section 3…"`, `note: "Drafting suggestion…"`,
|
|
414
|
+
`note: "Checking cross-references…"`. Short sentence fragments; the
|
|
415
|
+
user reads them at a glance.
|
|
416
|
+
|
|
417
|
+
On completion, one call does everything:
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
composer_agent_status({
|
|
421
|
+
roomId, threadId, replyId,
|
|
422
|
+
state: "ready",
|
|
423
|
+
text: "Posted a suggestion below."
|
|
424
|
+
})
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
This rewrites the ack body and prunes the awareness heartbeat
|
|
428
|
+
atomically. Do NOT post a separate reply to say "done" — the rewrite
|
|
429
|
+
IS the final reply.
|
|
430
|
+
|
|
431
|
+
### Worked example — ack-then-suggestion flow
|
|
432
|
+
|
|
433
|
+
A mention arrives; the user asked you to tighten a sentence in Section 3.
|
|
434
|
+
The full round-trip is four tool calls:
|
|
435
|
+
|
|
436
|
+
```
|
|
437
|
+
// 1. Mention arrives via composer_next_event:
|
|
438
|
+
// { kind: "mention", threadId: "t_abc", invokerUserId: "u_jess",
|
|
439
|
+
// invokerName: "Jess", reason: "direct_mention", ... }
|
|
440
|
+
|
|
441
|
+
// 2. Ack first — posts the "on it" reply with a thinking indicator.
|
|
442
|
+
const { replyId } = composer_reply_comment({
|
|
443
|
+
roomId,
|
|
444
|
+
threadId: "t_abc",
|
|
445
|
+
text: "@Jess — on it",
|
|
446
|
+
mentions: ["u_jess"],
|
|
447
|
+
state: "thinking",
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// 3. Transition to working before any slow step.
|
|
451
|
+
composer_agent_status({
|
|
452
|
+
roomId,
|
|
453
|
+
threadId: "t_abc",
|
|
454
|
+
replyId,
|
|
455
|
+
state: "working",
|
|
456
|
+
note: "Reading section 3…",
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// 4. Do the work and post the substantive artifact.
|
|
460
|
+
composer_add_suggestion({
|
|
461
|
+
roomId,
|
|
462
|
+
fromThreadId: "t_abc",
|
|
463
|
+
replacementText: "…",
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// 5. Rewrite the ack to a thin pointer and mark ready atomically.
|
|
467
|
+
composer_agent_status({
|
|
468
|
+
roomId,
|
|
469
|
+
threadId: "t_abc",
|
|
470
|
+
replyId,
|
|
471
|
+
state: "ready",
|
|
472
|
+
text: "Posted a suggestion below.",
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
No extra pointer reply. No "done" message. The rewrite + suggestion
|
|
477
|
+
card together are the complete response.
|
|
478
|
+
|
|
363
479
|
## Anchors
|
|
364
480
|
|
|
365
481
|
Write tools take:
|