refactor(5/12): change handler contract to event-based emission#323
refactor(5/12): change handler contract to event-based emission#323cameroncooke wants to merge 2 commits intorefactor/build-test-utility-extractionfrom
Conversation
089f6a3 to
fa1ece2
Compare
344aeba to
f7616d2
Compare
fa1ece2 to
e6e44bb
Compare
f7616d2 to
7b1a15a
Compare
e6e44bb to
8208efa
Compare
7b1a15a to
379222d
Compare
379222d to
7ffc681
Compare
8208efa to
3d0b705
Compare
3d0b705 to
a798b24
Compare
7ffc681 to
9d23583
Compare
9d23583 to
2496ad9
Compare
2496ad9 to
7ac6621
Compare
84180f1 to
1a1c722
Compare
7ac6621 to
b6ea0c1
Compare
9396aff to
fef58c3
Compare
30231f9 to
a36e4d5
Compare
fef58c3 to
03324df
Compare
a36e4d5 to
c0d77e5
Compare
03324df to
589573e
Compare
c0d77e5 to
7315fff
Compare
589573e to
886a46f
Compare
886a46f to
a42228b
Compare
7315fff to
80b323f
Compare
a42228b to
696acb9
Compare
80b323f to
3126be4
Compare
3126be4 to
09a2d7d
Compare
09a2d7d to
bee0b39
Compare
696acb9 to
0384076
Compare
Four tool files still accessed the removed errorResponse property on ValidationResult. Updated to construct error responses from the new errorMessage field. Restore responses/index.ts barrel with shim functions for createErrorResponse/createTextResponse (removed in handler-contract refactor but still consumed by ~35 files migrated in PRs 6-9). Also restore processToolResponse in next-steps-renderer.ts. Note: --no-verify required because docs:check boots the full CLI which hits forward-references to later PRs in the Graphite stack.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c7b7e13. Configure here.
| const hasTextContent = response.content.some((item) => item.type === 'text'); | ||
| if (!hasTextContent && nextStepsSection) { | ||
| processedContent.push({ type: 'text', text: nextStepsSection.trim() }); | ||
| } |
There was a problem hiding this comment.
Next steps silently dropped for mixed content types
Medium Severity
processToolResponse appends the next-steps section only when the last content item is text. The fallback (if (!hasTextContent)) checks whether any item is text. When the last item is non-text (e.g., an image) but earlier items are text, hasTextContent is true so the fallback is skipped, and the next-steps section is silently dropped from the output.
Reviewed by Cursor Bugbot for commit c7b7e13. Configure here.



Summary
This is PR 5 of 12 in a stacked PR series that decouples the rendering pipeline from MCP transport. Depends on PR 4 (utility extraction). This is the most architecturally significant PR in the stack -- it changes the fundamental contract between tool handlers and the runtime.
The contract change
Before: Tool handlers returned a
ToolResponseobject containing pre-rendered MCP content:```typescript
handler: (params) => Promise
```
After: Tool handlers receive a context object and emit events through it, returning nothing:
```typescript
handler: (params, ctx: ToolHandlerContext) => Promise
```
The
ToolHandlerContextprovides:emit(event: PipelineEvent): Push a structured event into the render sessionattach(image: ImageAttachment): Attach binary content (screenshots)This decouples tools from rendering entirely. A tool says "the build succeeded" via
ctx.emit(statusLine('success', 'Build succeeded'))without knowing whether the output will be rendered as colored terminal text, JSON, or MCP protocol content.Tool invoker refactor
The invoker (
tool-invoker.ts) is significantly simplified:postProcessToolResponse,emitNextStepsEvent,renderNextStepsIntoContent,finalizeResultToolHandlerContext, passes it to the handler, then emits next-steps into the same render session after the handler completesPendingBuildResultpattern -- the handler just awaits the pipeline and the render session accumulates events progressivelyTyped tool factory
typed-tool-factory.tsupdated to produce handlers matching the new signature. Session-aware wrappers now thread the context through.Core manifest and schema
import-resource-module.tsadded for resource manifest loading (split from tool module loading)plugin-types.tsupdated to reflect new handler signaturesDeleted
typed-tool-factory-consolidation.test.ts(tested removed consolidation logic)load-manifest.test.ts(moved to schema test coverage)Stack navigation
ctx.emit)Note for reviewers
PRs 6-9 (tool migrations) depend on this contract change. Each tool handler is updated from
return toolResponse([...])toctx.emit(...)calls. Those are mechanical transformations but they cannot compile without this PR landing first. If reviewing the stack incrementally, this PR is the critical design decision point.Test plan
npx vitest runpasses -- invoker tests updated for new contract