fix(server): show OpenCode task progress in the composer - #178

Open
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity
Open

fix(server): show OpenCode task progress in the composer#178
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity

Conversation

@rynfar

@rynfarrynfar commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

OpenCode threads never showed task progress. The composer Tasks tab, the turn plan summary, and the sidebar working line were all permanently empty — OpenCode was the only provider in Pylon without plan parity:

AdapterPlan surface
Codexnative turn/plan/updated
ClaudeTodoWrite input + Claude Tasks ✅
CursorACP ext cursor/update_todos
GrokACP PlanUpdated
Prime (ACP + managed daemon)
OpenCodenone

Two causes, both in OpenCodeAdapter.ts:

  1. OpenCode emits a structured todo.updated event that was already arriving at the adapter and already passing the parent-session gate — it just fell off the end of the event switch, which has no default. No turn.plan.updated was ever emitted.
  2. toToolLifecycleItemType matched todowrite on its write branch and classified it as file_change, so every todo update was filed into the edit tool group and counted toward the edit total. OpenCode threads over-reported file edits.

Found while reviewing upstream pingdotgg/t3code#5760, which diagnoses the same bug in T3. That issue is still open with no upstream fix, so this is Pylon's own.

Fix

Handle todo.updated and classify todo tools ahead of the write branch. Entirely within the adapter boundary — no contract, decider, projector, or migration changes, since turn.plan.updated already flows end to end for five other providers.

Notes on the choices:

  • Uses the v2 SDK's todo.updated event, not the tool-call input the upstream issue suggests sniffing. The event is structured, already in the subscribed Event union, and survives OpenCode changing its tool internals.
  • Cancelled todos are dropped, not reported completed. Pylon's plan contract has no cancelled state, and claiming finished work that was abandoned is worse than omitting it.
  • Unknown statuses settle to pending — the SDK types Todo.status as a bare string.
  • A fingerprint suppresses duplicates. OpenCode re-emits todo.updated on every mutation; without this each step transition would write a redundant plan activity. Same approach Grok and Prime already use.
  • Only the parent session's todos count. A delegated child session would otherwise overwrite the thread's plan.

Verification

vp test run apps/server/src/provider/Layers/OpenCodeAdapter.test.ts84 passed, including 4 new.

Each new test was mutation-tested rather than trusted for going green:

  • Disabling the todo.updated case → the 3 plan tests fail.
  • Removing the todo branch from classification → the classification test fails.

vp run -F t3 typecheck — 0 errors. vp lint clean on both changed files, verified live with a debugger probe rather than inferred from a silent exit 0.


Claude Opus 5 in Pylon.


View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

OpenCode threads never populated the Tasks tab, the turn plan summary, or the
sidebar working line. The adapter dropped OpenCode's `todo.updated` event off
the end of its event switch, so no `turn.plan.updated` was ever emitted --
OpenCode was the only provider without plan parity. Separately, `todowrite`
matched the `write` branch of tool classification and was filed as a file
change, inflating the work log's edit count.
Handle `todo.updated` from the v2 SDK, which already reaches the adapter and
already passes the parent-session gate, and classify todo tools ahead of the
write branch. Cancelled todos are dropped rather than reported completed, and
a fingerprint suppresses the duplicate emissions OpenCode sends on every
mutation.
@github-actionsgithub-actionsBot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.2 KiB−121 B (−0.9%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−2 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.3 KiB−119 B (−1.8%)7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB54.7 KiB−822 B (−1.4%)66.4 KiB
CodexLive turn messages990 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB−7 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−8 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.4 KiB+1 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: abe70a7 · PR result: 0e0afd0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

rynfar added a commit that referenced this pull request Aug 30, 2026
Also note in WATCH-1 that Pylon's own OpenCode task-progress fix is open as #178, so the row stays listed only because upstream #5760 is still open.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:Mvouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rynfar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(server): show OpenCode task progress in the composer - #178

Open
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity
Open

fix(server): show OpenCode task progress in the composer#178
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity

Conversation

@rynfar

@rynfarrynfar commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

OpenCode threads never showed task progress. The composer Tasks tab, the turn plan summary, and the sidebar working line were all permanently empty — OpenCode was the only provider in Pylon without plan parity:

AdapterPlan surface
Codexnative turn/plan/updated
ClaudeTodoWrite input + Claude Tasks ✅
CursorACP ext cursor/update_todos
GrokACP PlanUpdated
Prime (ACP + managed daemon)
OpenCodenone

Two causes, both in OpenCodeAdapter.ts:

  1. OpenCode emits a structured todo.updated event that was already arriving at the adapter and already passing the parent-session gate — it just fell off the end of the event switch, which has no default. No turn.plan.updated was ever emitted.
  2. toToolLifecycleItemType matched todowrite on its write branch and classified it as file_change, so every todo update was filed into the edit tool group and counted toward the edit total. OpenCode threads over-reported file edits.

Found while reviewing upstream pingdotgg/t3code#5760, which diagnoses the same bug in T3. That issue is still open with no upstream fix, so this is Pylon's own.

Fix

Handle todo.updated and classify todo tools ahead of the write branch. Entirely within the adapter boundary — no contract, decider, projector, or migration changes, since turn.plan.updated already flows end to end for five other providers.

Notes on the choices:

  • Uses the v2 SDK's todo.updated event, not the tool-call input the upstream issue suggests sniffing. The event is structured, already in the subscribed Event union, and survives OpenCode changing its tool internals.
  • Cancelled todos are dropped, not reported completed. Pylon's plan contract has no cancelled state, and claiming finished work that was abandoned is worse than omitting it.
  • Unknown statuses settle to pending — the SDK types Todo.status as a bare string.
  • A fingerprint suppresses duplicates. OpenCode re-emits todo.updated on every mutation; without this each step transition would write a redundant plan activity. Same approach Grok and Prime already use.
  • Only the parent session's todos count. A delegated child session would otherwise overwrite the thread's plan.

Verification

vp test run apps/server/src/provider/Layers/OpenCodeAdapter.test.ts84 passed, including 4 new.

Each new test was mutation-tested rather than trusted for going green:

  • Disabling the todo.updated case → the 3 plan tests fail.
  • Removing the todo branch from classification → the classification test fails.

vp run -F t3 typecheck — 0 errors. vp lint clean on both changed files, verified live with a debugger probe rather than inferred from a silent exit 0.


Claude Opus 5 in Pylon.


View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

OpenCode threads never populated the Tasks tab, the turn plan summary, or the
sidebar working line. The adapter dropped OpenCode's `todo.updated` event off
the end of its event switch, so no `turn.plan.updated` was ever emitted --
OpenCode was the only provider without plan parity. Separately, `todowrite`
matched the `write` branch of tool classification and was filed as a file
change, inflating the work log's edit count.
Handle `todo.updated` from the v2 SDK, which already reaches the adapter and
already passes the parent-session gate, and classify todo tools ahead of the
write branch. Cancelled todos are dropped rather than reported completed, and
a fingerprint suppresses the duplicate emissions OpenCode sends on every
mutation.
@github-actionsgithub-actionsBot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.2 KiB−121 B (−0.9%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−2 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.3 KiB−119 B (−1.8%)7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB54.7 KiB−822 B (−1.4%)66.4 KiB
CodexLive turn messages990 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB−7 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−8 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.4 KiB+1 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: abe70a7 · PR result: 0e0afd0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

rynfar added a commit that referenced this pull request Aug 30, 2026
Also note in WATCH-1 that Pylon's own OpenCode task-progress fix is open as #178, so the row stays listed only because upstream #5760 is still open.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:Mvouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rynfar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): show OpenCode task progress in the composer - #178

Open
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity
Open

fix(server): show OpenCode task progress in the composer#178
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity

Conversation

@rynfar

@rynfarrynfar commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

OpenCode threads never showed task progress. The composer Tasks tab, the turn plan summary, and the sidebar working line were all permanently empty — OpenCode was the only provider in Pylon without plan parity:

AdapterPlan surface
Codexnative turn/plan/updated
ClaudeTodoWrite input + Claude Tasks ✅
CursorACP ext cursor/update_todos
GrokACP PlanUpdated
Prime (ACP + managed daemon)
OpenCodenone

Two causes, both in OpenCodeAdapter.ts:

  1. OpenCode emits a structured todo.updated event that was already arriving at the adapter and already passing the parent-session gate — it just fell off the end of the event switch, which has no default. No turn.plan.updated was ever emitted.
  2. toToolLifecycleItemType matched todowrite on its write branch and classified it as file_change, so every todo update was filed into the edit tool group and counted toward the edit total. OpenCode threads over-reported file edits.

Found while reviewing upstream pingdotgg/t3code#5760, which diagnoses the same bug in T3. That issue is still open with no upstream fix, so this is Pylon's own.

Fix

Handle todo.updated and classify todo tools ahead of the write branch. Entirely within the adapter boundary — no contract, decider, projector, or migration changes, since turn.plan.updated already flows end to end for five other providers.

Notes on the choices:

  • Uses the v2 SDK's todo.updated event, not the tool-call input the upstream issue suggests sniffing. The event is structured, already in the subscribed Event union, and survives OpenCode changing its tool internals.
  • Cancelled todos are dropped, not reported completed. Pylon's plan contract has no cancelled state, and claiming finished work that was abandoned is worse than omitting it.
  • Unknown statuses settle to pending — the SDK types Todo.status as a bare string.
  • A fingerprint suppresses duplicates. OpenCode re-emits todo.updated on every mutation; without this each step transition would write a redundant plan activity. Same approach Grok and Prime already use.
  • Only the parent session's todos count. A delegated child session would otherwise overwrite the thread's plan.

Verification

vp test run apps/server/src/provider/Layers/OpenCodeAdapter.test.ts84 passed, including 4 new.

Each new test was mutation-tested rather than trusted for going green:

  • Disabling the todo.updated case → the 3 plan tests fail.
  • Removing the todo branch from classification → the classification test fails.

vp run -F t3 typecheck — 0 errors. vp lint clean on both changed files, verified live with a debugger probe rather than inferred from a silent exit 0.


Claude Opus 5 in Pylon.


View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

OpenCode threads never populated the Tasks tab, the turn plan summary, or the
sidebar working line. The adapter dropped OpenCode's `todo.updated` event off
the end of its event switch, so no `turn.plan.updated` was ever emitted --
OpenCode was the only provider without plan parity. Separately, `todowrite`
matched the `write` branch of tool classification and was filed as a file
change, inflating the work log's edit count.
Handle `todo.updated` from the v2 SDK, which already reaches the adapter and
already passes the parent-session gate, and classify todo tools ahead of the
write branch. Cancelled todos are dropped rather than reported completed, and
a fingerprint suppresses the duplicate emissions OpenCode sends on every
mutation.
@github-actionsgithub-actionsBot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.2 KiB−121 B (−0.9%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−2 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.3 KiB−119 B (−1.8%)7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB54.7 KiB−822 B (−1.4%)66.4 KiB
CodexLive turn messages990 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB−7 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−8 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.4 KiB+1 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: abe70a7 · PR result: 0e0afd0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

rynfar added a commit that referenced this pull request Aug 30, 2026
Also note in WATCH-1 that Pylon's own OpenCode task-progress fix is open as #178, so the row stays listed only because upstream #5760 is still open.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:Mvouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rynfar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): show OpenCode task progress in the composer - #178

Open
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity
Open

fix(server): show OpenCode task progress in the composer#178
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity

Conversation

@rynfar

@rynfarrynfar commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

OpenCode threads never showed task progress. The composer Tasks tab, the turn plan summary, and the sidebar working line were all permanently empty — OpenCode was the only provider in Pylon without plan parity:

AdapterPlan surface
Codexnative turn/plan/updated
ClaudeTodoWrite input + Claude Tasks ✅
CursorACP ext cursor/update_todos
GrokACP PlanUpdated
Prime (ACP + managed daemon)
OpenCodenone

Two causes, both in OpenCodeAdapter.ts:

  1. OpenCode emits a structured todo.updated event that was already arriving at the adapter and already passing the parent-session gate — it just fell off the end of the event switch, which has no default. No turn.plan.updated was ever emitted.
  2. toToolLifecycleItemType matched todowrite on its write branch and classified it as file_change, so every todo update was filed into the edit tool group and counted toward the edit total. OpenCode threads over-reported file edits.

Found while reviewing upstream pingdotgg/t3code#5760, which diagnoses the same bug in T3. That issue is still open with no upstream fix, so this is Pylon's own.

Fix

Handle todo.updated and classify todo tools ahead of the write branch. Entirely within the adapter boundary — no contract, decider, projector, or migration changes, since turn.plan.updated already flows end to end for five other providers.

Notes on the choices:

  • Uses the v2 SDK's todo.updated event, not the tool-call input the upstream issue suggests sniffing. The event is structured, already in the subscribed Event union, and survives OpenCode changing its tool internals.
  • Cancelled todos are dropped, not reported completed. Pylon's plan contract has no cancelled state, and claiming finished work that was abandoned is worse than omitting it.
  • Unknown statuses settle to pending — the SDK types Todo.status as a bare string.
  • A fingerprint suppresses duplicates. OpenCode re-emits todo.updated on every mutation; without this each step transition would write a redundant plan activity. Same approach Grok and Prime already use.
  • Only the parent session's todos count. A delegated child session would otherwise overwrite the thread's plan.

Verification

vp test run apps/server/src/provider/Layers/OpenCodeAdapter.test.ts84 passed, including 4 new.

Each new test was mutation-tested rather than trusted for going green:

  • Disabling the todo.updated case → the 3 plan tests fail.
  • Removing the todo branch from classification → the classification test fails.

vp run -F t3 typecheck — 0 errors. vp lint clean on both changed files, verified live with a debugger probe rather than inferred from a silent exit 0.


Claude Opus 5 in Pylon.


View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

OpenCode threads never populated the Tasks tab, the turn plan summary, or the
sidebar working line. The adapter dropped OpenCode's `todo.updated` event off
the end of its event switch, so no `turn.plan.updated` was ever emitted --
OpenCode was the only provider without plan parity. Separately, `todowrite`
matched the `write` branch of tool classification and was filed as a file
change, inflating the work log's edit count.
Handle `todo.updated` from the v2 SDK, which already reaches the adapter and
already passes the parent-session gate, and classify todo tools ahead of the
write branch. Cancelled todos are dropped rather than reported completed, and
a fingerprint suppresses the duplicate emissions OpenCode sends on every
mutation.
@github-actionsgithub-actionsBot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.2 KiB−121 B (−0.9%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−2 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.3 KiB−119 B (−1.8%)7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB54.7 KiB−822 B (−1.4%)66.4 KiB
CodexLive turn messages990 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB−7 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−8 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.4 KiB+1 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: abe70a7 · PR result: 0e0afd0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

rynfar added a commit that referenced this pull request Aug 30, 2026
Also note in WATCH-1 that Pylon's own OpenCode task-progress fix is open as #178, so the row stays listed only because upstream #5760 is still open.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:Mvouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rynfar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(server): show OpenCode task progress in the composer - #178

Open
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity
Open

fix(server): show OpenCode task progress in the composer#178
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity

Conversation

@rynfar

@rynfarrynfar commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

OpenCode threads never showed task progress. The composer Tasks tab, the turn plan summary, and the sidebar working line were all permanently empty — OpenCode was the only provider in Pylon without plan parity:

AdapterPlan surface
Codexnative turn/plan/updated
ClaudeTodoWrite input + Claude Tasks ✅
CursorACP ext cursor/update_todos
GrokACP PlanUpdated
Prime (ACP + managed daemon)
OpenCodenone

Two causes, both in OpenCodeAdapter.ts:

  1. OpenCode emits a structured todo.updated event that was already arriving at the adapter and already passing the parent-session gate — it just fell off the end of the event switch, which has no default. No turn.plan.updated was ever emitted.
  2. toToolLifecycleItemType matched todowrite on its write branch and classified it as file_change, so every todo update was filed into the edit tool group and counted toward the edit total. OpenCode threads over-reported file edits.

Found while reviewing upstream pingdotgg/t3code#5760, which diagnoses the same bug in T3. That issue is still open with no upstream fix, so this is Pylon's own.

Fix

Handle todo.updated and classify todo tools ahead of the write branch. Entirely within the adapter boundary — no contract, decider, projector, or migration changes, since turn.plan.updated already flows end to end for five other providers.

Notes on the choices:

  • Uses the v2 SDK's todo.updated event, not the tool-call input the upstream issue suggests sniffing. The event is structured, already in the subscribed Event union, and survives OpenCode changing its tool internals.
  • Cancelled todos are dropped, not reported completed. Pylon's plan contract has no cancelled state, and claiming finished work that was abandoned is worse than omitting it.
  • Unknown statuses settle to pending — the SDK types Todo.status as a bare string.
  • A fingerprint suppresses duplicates. OpenCode re-emits todo.updated on every mutation; without this each step transition would write a redundant plan activity. Same approach Grok and Prime already use.
  • Only the parent session's todos count. A delegated child session would otherwise overwrite the thread's plan.

Verification

vp test run apps/server/src/provider/Layers/OpenCodeAdapter.test.ts84 passed, including 4 new.

Each new test was mutation-tested rather than trusted for going green:

  • Disabling the todo.updated case → the 3 plan tests fail.
  • Removing the todo branch from classification → the classification test fails.

vp run -F t3 typecheck — 0 errors. vp lint clean on both changed files, verified live with a debugger probe rather than inferred from a silent exit 0.


Claude Opus 5 in Pylon.


View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

OpenCode threads never populated the Tasks tab, the turn plan summary, or the
sidebar working line. The adapter dropped OpenCode's `todo.updated` event off
the end of its event switch, so no `turn.plan.updated` was ever emitted --
OpenCode was the only provider without plan parity. Separately, `todowrite`
matched the `write` branch of tool classification and was filed as a file
change, inflating the work log's edit count.
Handle `todo.updated` from the v2 SDK, which already reaches the adapter and
already passes the parent-session gate, and classify todo tools ahead of the
write branch. Cancelled todos are dropped rather than reported completed, and
a fingerprint suppresses the duplicate emissions OpenCode sends on every
mutation.
@github-actionsgithub-actionsBot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.2 KiB−121 B (−0.9%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−2 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.3 KiB−119 B (−1.8%)7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB54.7 KiB−822 B (−1.4%)66.4 KiB
CodexLive turn messages990 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB−7 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−8 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.4 KiB+1 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: abe70a7 · PR result: 0e0afd0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

rynfar added a commit that referenced this pull request Aug 30, 2026
Also note in WATCH-1 that Pylon's own OpenCode task-progress fix is open as #178, so the row stays listed only because upstream #5760 is still open.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:Mvouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rynfar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): show OpenCode task progress in the composer - #178

Open
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity
Open

fix(server): show OpenCode task progress in the composer#178
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity

Conversation

@rynfar

@rynfarrynfar commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

OpenCode threads never showed task progress. The composer Tasks tab, the turn plan summary, and the sidebar working line were all permanently empty — OpenCode was the only provider in Pylon without plan parity:

AdapterPlan surface
Codexnative turn/plan/updated
ClaudeTodoWrite input + Claude Tasks ✅
CursorACP ext cursor/update_todos
GrokACP PlanUpdated
Prime (ACP + managed daemon)
OpenCodenone

Two causes, both in OpenCodeAdapter.ts:

  1. OpenCode emits a structured todo.updated event that was already arriving at the adapter and already passing the parent-session gate — it just fell off the end of the event switch, which has no default. No turn.plan.updated was ever emitted.
  2. toToolLifecycleItemType matched todowrite on its write branch and classified it as file_change, so every todo update was filed into the edit tool group and counted toward the edit total. OpenCode threads over-reported file edits.

Found while reviewing upstream pingdotgg/t3code#5760, which diagnoses the same bug in T3. That issue is still open with no upstream fix, so this is Pylon's own.

Fix

Handle todo.updated and classify todo tools ahead of the write branch. Entirely within the adapter boundary — no contract, decider, projector, or migration changes, since turn.plan.updated already flows end to end for five other providers.

Notes on the choices:

  • Uses the v2 SDK's todo.updated event, not the tool-call input the upstream issue suggests sniffing. The event is structured, already in the subscribed Event union, and survives OpenCode changing its tool internals.
  • Cancelled todos are dropped, not reported completed. Pylon's plan contract has no cancelled state, and claiming finished work that was abandoned is worse than omitting it.
  • Unknown statuses settle to pending — the SDK types Todo.status as a bare string.
  • A fingerprint suppresses duplicates. OpenCode re-emits todo.updated on every mutation; without this each step transition would write a redundant plan activity. Same approach Grok and Prime already use.
  • Only the parent session's todos count. A delegated child session would otherwise overwrite the thread's plan.

Verification

vp test run apps/server/src/provider/Layers/OpenCodeAdapter.test.ts84 passed, including 4 new.

Each new test was mutation-tested rather than trusted for going green:

  • Disabling the todo.updated case → the 3 plan tests fail.
  • Removing the todo branch from classification → the classification test fails.

vp run -F t3 typecheck — 0 errors. vp lint clean on both changed files, verified live with a debugger probe rather than inferred from a silent exit 0.


Claude Opus 5 in Pylon.


View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

OpenCode threads never populated the Tasks tab, the turn plan summary, or the
sidebar working line. The adapter dropped OpenCode's `todo.updated` event off
the end of its event switch, so no `turn.plan.updated` was ever emitted --
OpenCode was the only provider without plan parity. Separately, `todowrite`
matched the `write` branch of tool classification and was filed as a file
change, inflating the work log's edit count.
Handle `todo.updated` from the v2 SDK, which already reaches the adapter and
already passes the parent-session gate, and classify todo tools ahead of the
write branch. Cancelled todos are dropped rather than reported completed, and
a fingerprint suppresses the duplicate emissions OpenCode sends on every
mutation.
@github-actionsgithub-actionsBot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.2 KiB−121 B (−0.9%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−2 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.3 KiB−119 B (−1.8%)7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB54.7 KiB−822 B (−1.4%)66.4 KiB
CodexLive turn messages990 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB−7 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−8 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.4 KiB+1 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: abe70a7 · PR result: 0e0afd0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

rynfar added a commit that referenced this pull request Aug 30, 2026
Also note in WATCH-1 that Pylon's own OpenCode task-progress fix is open as #178, so the row stays listed only because upstream #5760 is still open.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:Mvouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rynfar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(server): show OpenCode task progress in the composer - #178

Open
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity
Open

fix(server): show OpenCode task progress in the composer#178
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity

Conversation

@rynfar

@rynfarrynfar commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

OpenCode threads never showed task progress. The composer Tasks tab, the turn plan summary, and the sidebar working line were all permanently empty — OpenCode was the only provider in Pylon without plan parity:

AdapterPlan surface
Codexnative turn/plan/updated
ClaudeTodoWrite input + Claude Tasks ✅
CursorACP ext cursor/update_todos
GrokACP PlanUpdated
Prime (ACP + managed daemon)
OpenCodenone

Two causes, both in OpenCodeAdapter.ts:

  1. OpenCode emits a structured todo.updated event that was already arriving at the adapter and already passing the parent-session gate — it just fell off the end of the event switch, which has no default. No turn.plan.updated was ever emitted.
  2. toToolLifecycleItemType matched todowrite on its write branch and classified it as file_change, so every todo update was filed into the edit tool group and counted toward the edit total. OpenCode threads over-reported file edits.

Found while reviewing upstream pingdotgg/t3code#5760, which diagnoses the same bug in T3. That issue is still open with no upstream fix, so this is Pylon's own.

Fix

Handle todo.updated and classify todo tools ahead of the write branch. Entirely within the adapter boundary — no contract, decider, projector, or migration changes, since turn.plan.updated already flows end to end for five other providers.

Notes on the choices:

  • Uses the v2 SDK's todo.updated event, not the tool-call input the upstream issue suggests sniffing. The event is structured, already in the subscribed Event union, and survives OpenCode changing its tool internals.
  • Cancelled todos are dropped, not reported completed. Pylon's plan contract has no cancelled state, and claiming finished work that was abandoned is worse than omitting it.
  • Unknown statuses settle to pending — the SDK types Todo.status as a bare string.
  • A fingerprint suppresses duplicates. OpenCode re-emits todo.updated on every mutation; without this each step transition would write a redundant plan activity. Same approach Grok and Prime already use.
  • Only the parent session's todos count. A delegated child session would otherwise overwrite the thread's plan.

Verification

vp test run apps/server/src/provider/Layers/OpenCodeAdapter.test.ts84 passed, including 4 new.

Each new test was mutation-tested rather than trusted for going green:

  • Disabling the todo.updated case → the 3 plan tests fail.
  • Removing the todo branch from classification → the classification test fails.

vp run -F t3 typecheck — 0 errors. vp lint clean on both changed files, verified live with a debugger probe rather than inferred from a silent exit 0.


Claude Opus 5 in Pylon.


View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

OpenCode threads never populated the Tasks tab, the turn plan summary, or the
sidebar working line. The adapter dropped OpenCode's `todo.updated` event off
the end of its event switch, so no `turn.plan.updated` was ever emitted --
OpenCode was the only provider without plan parity. Separately, `todowrite`
matched the `write` branch of tool classification and was filed as a file
change, inflating the work log's edit count.
Handle `todo.updated` from the v2 SDK, which already reaches the adapter and
already passes the parent-session gate, and classify todo tools ahead of the
write branch. Cancelled todos are dropped rather than reported completed, and
a fingerprint suppresses the duplicate emissions OpenCode sends on every
mutation.
@github-actionsgithub-actionsBot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.2 KiB−121 B (−0.9%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−2 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.3 KiB−119 B (−1.8%)7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB54.7 KiB−822 B (−1.4%)66.4 KiB
CodexLive turn messages990 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB−7 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−8 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.4 KiB+1 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: abe70a7 · PR result: 0e0afd0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

rynfar added a commit that referenced this pull request Aug 30, 2026
Also note in WATCH-1 that Pylon's own OpenCode task-progress fix is open as #178, so the row stays listed only because upstream #5760 is still open.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:Mvouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rynfar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(server): show OpenCode task progress in the composer - #178

Open
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity
Open

fix(server): show OpenCode task progress in the composer#178
rynfar wants to merge 1 commit into
pylonfrom
fix/opencode-plan-parity

Conversation

@rynfar

@rynfarrynfar commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Problem

OpenCode threads never showed task progress. The composer Tasks tab, the turn plan summary, and the sidebar working line were all permanently empty — OpenCode was the only provider in Pylon without plan parity:

AdapterPlan surface
Codexnative turn/plan/updated
ClaudeTodoWrite input + Claude Tasks ✅
CursorACP ext cursor/update_todos
GrokACP PlanUpdated
Prime (ACP + managed daemon)
OpenCodenone

Two causes, both in OpenCodeAdapter.ts:

  1. OpenCode emits a structured todo.updated event that was already arriving at the adapter and already passing the parent-session gate — it just fell off the end of the event switch, which has no default. No turn.plan.updated was ever emitted.
  2. toToolLifecycleItemType matched todowrite on its write branch and classified it as file_change, so every todo update was filed into the edit tool group and counted toward the edit total. OpenCode threads over-reported file edits.

Found while reviewing upstream pingdotgg/t3code#5760, which diagnoses the same bug in T3. That issue is still open with no upstream fix, so this is Pylon's own.

Fix

Handle todo.updated and classify todo tools ahead of the write branch. Entirely within the adapter boundary — no contract, decider, projector, or migration changes, since turn.plan.updated already flows end to end for five other providers.

Notes on the choices:

  • Uses the v2 SDK's todo.updated event, not the tool-call input the upstream issue suggests sniffing. The event is structured, already in the subscribed Event union, and survives OpenCode changing its tool internals.
  • Cancelled todos are dropped, not reported completed. Pylon's plan contract has no cancelled state, and claiming finished work that was abandoned is worse than omitting it.
  • Unknown statuses settle to pending — the SDK types Todo.status as a bare string.
  • A fingerprint suppresses duplicates. OpenCode re-emits todo.updated on every mutation; without this each step transition would write a redundant plan activity. Same approach Grok and Prime already use.
  • Only the parent session's todos count. A delegated child session would otherwise overwrite the thread's plan.

Verification

vp test run apps/server/src/provider/Layers/OpenCodeAdapter.test.ts84 passed, including 4 new.

Each new test was mutation-tested rather than trusted for going green:

  • Disabling the todo.updated case → the 3 plan tests fail.
  • Removing the todo branch from classification → the classification test fails.

vp run -F t3 typecheck — 0 errors. vp lint clean on both changed files, verified live with a debugger probe rather than inferred from a silent exit 0.


Claude Opus 5 in Pylon.


View with [code]smithAutofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

OpenCode threads never populated the Tasks tab, the turn plan summary, or the
sidebar working line. The adapter dropped OpenCode's `todo.updated` event off
the end of its event switch, so no `turn.plan.updated` was ever emitted --
OpenCode was the only provider without plan parity. Separately, `todowrite`
matched the `write` branch of tool classification and was filed as a file
change, inflating the work log's edit count.
Handle `todo.updated` from the v2 SDK, which already reaches the adapter and
already passes the parent-session gate, and classify todo tools ahead of the
write branch. Cancelled todos are dropped rather than reported completed, and
a fingerprint suppresses the duplicate emissions OpenCode sends on every
mutation.
@github-actionsgithub-actionsBot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Aug 29, 2026
@github-actions

Copy link
Copy Markdown

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire13.3 KiB13.2 KiB−121 B (−0.9%)15.1 KiB
CodexThread snapshot wire6.9 KiB6.9 KiB−2 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.4 KiB6.3 KiB−119 B (−1.8%)7.8 KiB
CodexLive turn WebSocket decoded55.5 KiB54.7 KiB−822 B (−1.4%)66.4 KiB
CodexLive turn messages990 (0.0%)21
ClaudeTotal thread wire13.3 KiB13.3 KiB−7 B (−0.1%)15.1 KiB
ClaudeThread snapshot wire6.9 KiB6.9 KiB−8 B (−0.1%)7.3 KiB
ClaudeLive turn WebSocket wire6.4 KiB6.4 KiB+1 B (+0.0%)7.8 KiB
ClaudeLive turn WebSocket decoded56.4 KiB56.4 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages10100 (0.0%)21

Baseline: abe70a7 · PR result: 0e0afd0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 109.4 KiB
  • Claude decoded thread snapshot: 110.1 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

rynfar added a commit that referenced this pull request Aug 30, 2026
Also note in WATCH-1 that Pylon's own OpenCode task-progress fix is open as #178, so the row stays listed only because upstream #5760 is still open.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:Mvouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@rynfar