feat(claude-tasks): split replies into a block hierarchy (splitReply)#314
feat(claude-tasks): split replies into a block hierarchy (splitReply)#314Stvad wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 800077aeb8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const fenceLine = lines[idx]; | ||
| fenceLines.push(stripOpeningIndent(fenceLine)); | ||
| idx++; | ||
| if (fenceLine.trim() === '```') break; // include the closing fence, then stop |
There was a problem hiding this comment.
Accept matching longer code fences
When a reply uses a longer Markdown fence such as to show content that itself contains triple backticks, this check never recognizes the matching closing fence, so the parser consumes the rest of the reply as one code block and the intended sibling/child blocks are lost. Track the opening fence marker/length and close on a fence of at least that length instead of only ```.
Useful? React with 👍 / 👎.
800077a to
db08d62
Compare
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db08d62d0f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const existing = await tx.get(rootBlockId) | ||
| if (!existing) throw new Error(`create-blocks-from-markdown: rootBlockId ${rootBlockId} not found`) |
There was a problem hiding this comment.
Treat stale stream roots as optional
When streamReply and splitReply are both enabled, rootBlockId is only the best-effort placeholder created at run start. If the user deletes it before the successful run finishes, this throw makes the daemon treat the completed answer as an infrastructure error instead of posting the final reply; if the user moves the placeholder, the later update overwrites a block that is no longer under parentId. Validate that the reusable root still exists as a live child of parentId, and otherwise fall back to creating the split roots fresh.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9602934d25
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let currentLineType: string; | ||
| let processedContent: string; | ||
|
|
||
| const fenceOpen = /^(`{3,})/.exec(trimmedLine); |
There was a problem hiding this comment.
Preserve tilde-fenced code blocks
When a reply or paste uses a Markdown tilde fence such as ~~~ts, this opener is not recognized because the fence parser only matches backticks, so any - or # lines inside the fenced code are still split into outline blocks. Since split replies now rely on this parser and promise that fenced code stays whole, handle ~{3,} fences with the same matching-character/length rules as backtick fences.
Useful? React with 👍 / 👎.
d7c953a to
796ae94
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 796ae9443a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // its current last child (which already includes a reused placeholder). | ||
| const existingChildren = await tx.childrenOf(input.parentId, workspaceId) | ||
| const lastKeyByParent = new Map<string, string | null>([ | ||
| [input.parentId, existingChildren.at(-1)?.orderKey ?? null], |
There was a problem hiding this comment.
Seed root insertion from the reused placeholder
When streamReply + splitReply reuses a placeholder that is no longer the last child—for example, the user adds a sibling after the live “Claude is working…” block while the run is streaming—this cursor is seeded from the last existing child. The first parsed root keeps the placeholder’s old order key, but every later root is created after the newer sibling, so one reply is split around unrelated user content instead of landing as a contiguous tree.
Useful? React with 👍 / 👎.
796ae94 to
97d18c7
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 97d18c7e47
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| content: block.content, | ||
| ...(mergedProperties !== undefined ? {properties: mergedProperties} : {}), | ||
| }) | ||
| idMap.set(block.id, reusableRoot.id) |
There was a problem hiding this comment.
Seed child cursor when reusing the placeholder
When streamReply reuses a placeholder that the user has already added sub-blocks to, this maps the parsed first root onto an existing block but leaves lastKeyByParent without an entry for that existing block. The first parsed child of the reply then reaches the keyAtEnd(lastKeyByParent.get(realParentId) ?? null) path as if the placeholder had no children, so it can be ordered around the user's existing children instead of preserving them and appending the generated subtree. Seed lastKeyByParent from tx.childrenOf(reusableRoot.id, workspaceId) before continuing.
Useful? React with 👍 / 👎.
Cloud replies were always one big block. Add `splitReply` (backlinks watchers, default ON) so a reply is split along its markdown outline into a block hierarchy under the mention — nested bullets → child blocks, headings nest their content, fenced code stays whole. Reuse the app's own paste parser instead of a second markdown dialect: parsing + insertion happen APP-SIDE via a new `create-blocks-from-markdown` bridge command that runs `parseMarkdownToBlocks` and inserts the whole subtree in one `repo.tx` (atomic — never a partial reply), tagging every block `claude:reply`. A streamed placeholder is reused as the first root rather than orphaned. Also improve the shared paste parser: fenced code blocks now stay one block (markers/headers inside are not misparsed) — a latent paste bug fixed for both paste and replies. The spawned run is nudged to author a nested outline (the "prompting" half; the parsing is the "injection" half). Set `splitReply: false` to keep the single-block, idempotent-retry terminal write. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MngxzayB9nBcwMhoNZPb6v
- markdown parser: close a fenced block on a run of AT LEAST as many backticks as the opener (CommonMark), so a ````-fenced reply that wraps content containing ``` no longer swallows the rest of the reply. - create-blocks-from-markdown: reuse the streamed placeholder only when it's still a LIVE CHILD of the mention. If the user deleted or moved it mid-run, create the split roots fresh instead of throwing away the billed answer (deleted) or overwriting a moved block. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MngxzayB9nBcwMhoNZPb6v
The fence parser only matched backtick fences, so a ~~~-fenced block (the canonical way to fence code that itself contains ```) had its inner - / # lines mis-split into outline blocks. Generalize to both ` ` ` and ~ with the same matching-char / minimum-length close rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MngxzayB9nBcwMhoNZPb6v
97d18c7 to
8b1f43f
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
What
Cloud replies (the "browser emacs" mention flow) were always one big block. This adds
splitReply— a per-backlinks-watcher flag, default ON — so a reply is split along its markdown outline into a block hierarchy under the mention: nested bullets → child blocks, headings nest their following content, fenced code stays whole.Prompted by a reflection note asking for replies "split up among the blocks appropriately … a block hierarchy — maybe a question of prompting or injection." The answer turned out to be both: injection (deterministic parse → blocks) as the backbone, plus a light prompt nudge.
How (design)
create-blocks-from-markdownbridge command that runsparseMarkdownToBlocksand inserts the whole subtree in onerepo.tx— atomic, so a failure never leaves a partial reply. Every block is taggedclaude:reply.streamReplyon, the live text still streams into a single placeholder; at the end that placeholder is reused as the reply's first root instead of being orphaned (newrootBlockIdarg).splitReplyis on, so the run authors a nested outline that threads well.splitReply: falsekeeps today's single-block, idempotent-retry terminal write.Also: a latent paste-parser fix
Added fenced-code-block handling to
parseMarkdownToBlocks: a```block now stays one block and its inner-/#lines aren't misparsed as bullets/headers. This fixes code-mangling for both paste and replies.Files
packages/agent-cli/src/protocol.ts— newcreate-blocks-from-markdowncommand (schema, unions, registry).src/plugins/agent-runtime/{protocol,commands}.ts— app-side handler (atomic tx, append-after-existing-children, placeholder reuse) + context wiring.src/utils/markdownParser.ts— fenced-code handling.packages/claude-tasks/src/{config,prompt,engine,graph}.ts—splitReplyflag (default true), outline nudge,graph.createReplyTree, success-path wiring.Verification
yarn run checkgreen — compile + lint + 4605 tests + sync-config/rpc/service-role checks.Notes for the reviewer
splitReplydefaults true, so all backlinks replies now thread. Existing single-line replies are unaffected (they land as one block either way). Setfalseper-watcher to keep the idempotent single-block write.🤖 Generated with Claude Code
https://claude.ai/code/session_01MngxzayB9nBcwMhoNZPb6v