Repository navigation
fix(dashboard): orchestration canvas fase 3 — follow-ups da review final (#12639) - #12988
Merged
Merged
Conversation
`GET /api/a2a/tasks/[id]` falls back to the persisted history row once a task
leaves the in-memory TTL window, and `reconstituteHistoricalTask` hard-coded
`metadata: {}` — so the drawer's "Memory used" section vanished for any
historical task, even though `executeA2ATaskWithState` had already written a
`memory_hits` event with the hits.
The fallback now reads that event: `data_json` is parsed and, when it yields at
least one well-formed hit, exposed as `metadata.memoryHits`. The event itself is
filtered out of `events` — it is observability, not a state transition, and
without the filter it leaked into the timeline as a duplicate of the row's
current state.
Reading is defensive throughout, mirroring `DrawerMemory`'s own validation: the
payload is caller-influenced and unvalidated end to end, so `JSON.parse` runs
inside `safeJsonParse`, non-arrays are rejected, and each entry must carry `id`,
`key`, `type` and `snippet` as strings (a non-string field would be rendered as
a React child and take the drawer down). Malformed input degrades to
`metadata: {}` and a 200 — never a 500.
Refs #12639
collectMemoryHits() runs BEFORE the skill handler and had no deadline at all, so a slow memory backend delayed the start of every A2A task — the HTTP genericBackend alone defaults to a 30s timeout. The search now races a MEMORY_RECALL_TIMEOUT_MS (1500ms) deadline. Overshooting degrades exactly like any other recall failure: empty hits, a warn log, and the task proceeds normally (best-effort contract unchanged, nothing propagates). The deadline timer is cleared in a finally on BOTH paths so no handle is left holding the event loop open, and MemoryHitsDeps.timeoutMs makes it injectable so the tests cost milliseconds instead of 1.5s of wall clock. Refs #12639
The drawer's "Repeat" for a Conductor task dropped the runner/model pinning and left the operator staring at the finished run: - `hubTaskSchema` now parses the hub's `requirements` (`.catch(null)` so an odd shape never fails the whole task parse), and `ConductorTaskDetail` exposes `cli`/`model` (`null` when the hub sends none). - `repeatReqForConductor` carries `cli`/`model` when present and OMITS them otherwise — the route's Zod takes both as optional strings, so a `null` would 400. The two fields are independent. - `performAction` reads the response body once and returns it, so the repeat can report the CANVAS id of the created task (`task_id` / `data.id` / `result.task.id`, each with its node prefix). `OrchestrationPageClient` then refetches and focuses it via `?node=`; History keeps its current behavior. - `conductor-routes-auth.test.ts` covers the creation route through its `ROUTES` array; the duplicated source assertion left `conductor-create-route.test.ts`. Refs #12639
Changelog fragment for the five items PR-C delivers from #12639. The sixth item on the issue — an authenticated panel path for A2A task creation — stays deliberately out of scope and is recorded as such in a comment on the issue rather than silently dropped: the JSON-RPC endpoint accepts API keys only, and widening that endpoint's auth surface to serve a UI convenience is the operator's call, not the implementation's. Closes #12639
This was referenced Sep 8, 2026
…fase3-c # Conflicts: # src/app/(dashboard)/dashboard/orchestration/OrchestrationPageClient.tsx # src/app/(dashboard)/dashboard/orchestration/drawer/useDrawerDetail.ts
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…nal (cheaper-inference#12639) (cheaper-inference#12988) * feat(api): hydrate memoryHits from the persisted history event `GET /api/a2a/tasks/[id]` falls back to the persisted history row once a task leaves the in-memory TTL window, and `reconstituteHistoricalTask` hard-coded `metadata: {}` — so the drawer's "Memory used" section vanished for any historical task, even though `executeA2ATaskWithState` had already written a `memory_hits` event with the hits. The fallback now reads that event: `data_json` is parsed and, when it yields at least one well-formed hit, exposed as `metadata.memoryHits`. The event itself is filtered out of `events` — it is observability, not a state transition, and without the filter it leaked into the timeline as a duplicate of the row's current state. Reading is defensive throughout, mirroring `DrawerMemory`'s own validation: the payload is caller-influenced and unvalidated end to end, so `JSON.parse` runs inside `safeJsonParse`, non-arrays are rejected, and each entry must carry `id`, `key`, `type` and `snippet` as strings (a non-string field would be rendered as a React child and take the drawer down). Malformed input degrades to `metadata: {}` and a 200 — never a 500. Refs cheaper-inference#12639 * fix(a2a): bound the memory recall with its own deadline collectMemoryHits() runs BEFORE the skill handler and had no deadline at all, so a slow memory backend delayed the start of every A2A task — the HTTP genericBackend alone defaults to a 30s timeout. The search now races a MEMORY_RECALL_TIMEOUT_MS (1500ms) deadline. Overshooting degrades exactly like any other recall failure: empty hits, a warn log, and the task proceeds normally (best-effort contract unchanged, nothing propagates). The deadline timer is cleared in a finally on BOTH paths so no handle is left holding the event loop open, and MemoryHitsDeps.timeoutMs makes it injectable so the tests cost milliseconds instead of 1.5s of wall clock. Refs cheaper-inference#12639 * fix(dashboard): carry conductor requirements and focus the repeated task The drawer's "Repeat" for a Conductor task dropped the runner/model pinning and left the operator staring at the finished run: - `hubTaskSchema` now parses the hub's `requirements` (`.catch(null)` so an odd shape never fails the whole task parse), and `ConductorTaskDetail` exposes `cli`/`model` (`null` when the hub sends none). - `repeatReqForConductor` carries `cli`/`model` when present and OMITS them otherwise — the route's Zod takes both as optional strings, so a `null` would 400. The two fields are independent. - `performAction` reads the response body once and returns it, so the repeat can report the CANVAS id of the created task (`task_id` / `data.id` / `result.task.id`, each with its node prefix). `OrchestrationPageClient` then refetches and focuses it via `?node=`; History keeps its current behavior. - `conductor-routes-auth.test.ts` covers the creation route through its `ROUTES` array; the duplicated source assertion left `conductor-create-route.test.ts`. Refs cheaper-inference#12639 * chore(a2a): follow-ups changelog Changelog fragment for the five items PR-C delivers from cheaper-inference#12639. The sixth item on the issue — an authenticated panel path for A2A task creation — stays deliberately out of scope and is recorded as such in a comment on the issue rather than silently dropped: the JSON-RPC endpoint accepts API keys only, and widening that endpoint's auth surface to serve a UI convenience is the operator's call, not the implementation's. Closes cheaper-inference#12639
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fase 3 do Orchestration Canvas, PR-C: os follow-ups da review final da Fase 2, issue #12639. Cinco dos seis itens; o sexto fica fora por decisão de escopo, explicado abaixo e registrado na issue.
Spec:
_tasks/superpowers/specs/2026-09-03-orchestration-canvas-fase3-design.md· Plano:_tasks/superpowers/plans/2026-09-03-orchestration-canvas-fase3.md.1. History hidrata
memoryHits(da801444)reconstituteHistoricalTaskdevolviametadata: {}fixo e nunca lia o eventomemory_hitspersistido, então a seção "Memória" do drawer só existia dentro da janela de TTL em memória. Agora odata_jsondo evento é parseado parametadata.memoryHits, com a mesma validação defensiva que oDrawerMemoryaplica:Array.isArray, e cada item precisa terid/key/type/snippetcomo string. Payload malformado degrada parametadata: {}e HTTP 200 — nunca 500.Defeito visível que apareceu ao implementar: sem filtrar o
memory_hitsda lista de eventos, ele caía noelsedoSTATE_EVENT_PREFIXe herdavarow.state, aparecendo na timeline como uma transição de estado duplicada. O filtro conserta isso, não é só arrumação.2. Deadline próprio no recall de memória (
3ef0aef1)collectMemoryHitsnão tinha deadline nenhum e roda ANTES do handler da skill — um backend HTTP lento (ogenericBackendsozinho tem default de 30s) atrasava o início da task. Agora corre contraMEMORY_RECALL_TIMEOUT_MS = 1500numPromise.race, comclearTimeoutnumfinallynos dois caminhos. Contrato best-effort intacto: estouro ⇒ lista vazia, a task segue, nada propaga.Achado do mutation-check: a primeira versão tinha
timer.unref?.()junto doclearTimeout. Removendo oclearTimeoutpara verificar, o teste do "timer pendurado" continuava passando —process.getActiveResourcesInfo()não reporta timerunrefado, ou seja ounrefmascarava exatamente o defeito que o teste deveria pegar. Ounrefsaiu; refeito o mutation-check, o teste agora falha sem oclearTimeout.3.
requirements.cli/modelno repeat do Conductor · 4. foco na task nova · 5. teste de auth (a7d430fe)hubTaskSchemapassou a parsearrequirementseConductorTaskDetailganhoucli/model(nullquando o hub não envia), então repetir uma execução do Conductor a fixa no mesmo perfil de runner e modelo em vez de deixá-la derivar para o que a frota escolher.performActionpassou a devolver o corpo já parseado, enewNodeIdFrommonta o id no formato que o canvas indexa —conductor:task:<id>,cloud-agent:<id>,a2a:<id>. Um repeat bem-sucedido a partir da aba Agents foca a execução criada; da aba History o comportamento fica como está (as execuções de lá não são endereçáveis no snapshot vivo).approve/cancelseguem devolvendo boolean de propósito: a resposta deles ecoa o id da MESMA task e navegaria para onde já se está.O teste de auth de
POST /api/conductor/tasksentrou no arrayROUTEScompartilhado (regex do proxy estendido comcreateConductorTask\() e a duplicata saiu deconductor-create-route.test.ts. As quatro asserções são as mesmas — cobertura preservada, não enfraquecida.Divergências entre plano e código, resolvidas pelo código
setParams({ node: <id> })com o id cru. O canvas indexa por id prefixado — daí onewNodeIdFrom. O casoa2a:nem estava no plano; o repeat do drawer também atende A2A (enveloperesult.task.id).repeatReqForConductorpassaundefined, nãonull, quando o campo falta: o Zod real da rota éz.string().optional()enulldaria 400. Mesma classe de erro que a Fase 2 cometeu ao parafrasear contrato em vez de abrir o arquivo..catch(null)norequirements, que o plano não pedia: o mesmo schema parseia a lista emgetFleetSnapshot, então umrequirementsde formato inesperado numa única task derrubaria o snapshot inteiro paraoffline.conductor-routes-auth.test.tsnão falhava antes — é de-duplicação e ampliação de cobertura, não prova de defeito. Registrado para não passar por TDD o que não é.Fora de escopo, com motivo
O sexto item da issue — caminho de criação A2A autenticado pelo painel — fica fora. O endpoint JSON-RPC aceita apenas chave de API, e alargar a superfície de autenticação dele para resolver uma conveniência de UI é decisão do operador, não da implementação. Está no spec (§4) e comentado na issue; segue como limitação documentada no changelog da Fase 2.
Gates
typecheck:corelimpo ·check:dashboard-typecheckOK (206 erros pré-existentes, dentro do baseline) ·check:cyclesOK ·client-bundle-no-server-only-106921/1 ·vitest tests/unit/ui/210 arquivos / 1200 testes · suítes a2a + conductor 44/44.Os vermelhos abaixo vêm do tip de
release/v3.8.51, não desta PR — nenhum dos arquivos envolvidos é tocado aqui.API Route Typecheckopen-sse/executors/glm.tsTS2554Merge integritySKILL.mdgerado desatualizado (Generated: 1 · Unchanged: 45)Docs Gates/check:docs-alli18n:checkdocs/mudaram desde a última traduçãotests/unit/zai-web-silent-empty-repro.test.tsbad_gatewaysem atualizar o testelintopen-sse/services/combo.ts,tlsClientBase.ts,useProviderConnections.ts,catalogCache.tsCloses #12639
Atualização sobre os vermelhos herdados (reproduzidos no tip limpo): as quatro shards de
Unit Tests fast-pathtambém estão vermelhas na base. Prova de isolamento: a #12983 toca só UI do dashboard, i18n e changelog, e apresenta exatamente o mesmo conjunto de falhas da #12988, que toca rotas A2A/Conductor. Rodando no checkout principal emd6f31501, sem nenhuma alteração aplicada:Inventário por shard e o resto da evidência estão em #12732 (comment). Nenhum dos arquivos envolvidos é tocado por esta PR.