mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
* 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 #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 #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 #12639
* chore(a2a): follow-ups changelog
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
3.7 KiB
3.7 KiB
Task C1 — History hidrata memoryHits (PR-C, Refs #12639)
Commit: da8014442debd2eca7a326472ab704aa86aefba1
Branch: feat/orch-fase3-c · 2 arquivos, +164/-16
O que mudou
src/app/api/a2a/tasks/[id]/route.ts
- Novas constantes
MEMORY_HITS_EVENT_TYPE = "memory_hits"eMEMORY_HIT_FIELDS = ["id","key","type","snippet"], interfaceMemoryHit. - Nova função
parseMemoryHits(dataJson):safeJsonParse(try/catch já existente) →Array.isArray→ filtro por item exigindo os 4 campos comostring. Nunca lança; payload ruim vira lista vazia. reconstituteHistoricalTask:listA2ATaskEvents(row.id)agora é lido uma vez emeventRows; os eventosmemory_hitssão extraídos paramemoryHitse filtrados fora deevents;metadatapassou de{}fixo paramemoryHits.length > 0 ? { memoryHits } : {}.
tests/unit/a2a-history-route.test.ts (+104 linhas, 365 no total — teto 1200 ok)
7 testes novos:
- hidrata
metadata.memoryHitscom 2 hits e o evento de memória NÃO aparece emevents(timeline fica["submitted","completed"]). - descarta entradas malformadas (
sem snippet,keyobjeto,null, string, número) e mantém só a válida. 3–7. tabela de payloads ruins ({not-json, string JSON pura, objeto JSON, array 100% inválido, array vazio) ⇒ HTTP 200,metadata{}, e o evento de memória continua fora da timeline.
Gates (saída real)
$ node --import tsx/esm --test tests/unit/a2a-history-route.test.ts # ANTES do fix (C1.2)
✔ 10 pré-existentes ✖ 7 novos (falha típica: metadata {} esperado ≠ { memoryHits: [...] } / timeline extra 'completed')
$ node --import tsx/esm --test tests/unit/a2a-history-route.test.ts # DEPOIS (C1.4)
ℹ tests 17
ℹ pass 17
ℹ fail 0
$ node --import tsx/esm --test tests/unit/db-a2a-tasks.test.ts
ℹ tests 7
ℹ pass 7
ℹ fail 0
$ npm run typecheck:core
npm notice run tsc --pretty false -p tsconfig.typecheck-core.json
(sem erros, exit 0)
$ npx eslint 'src/app/api/a2a/tasks/[id]/route.ts' tests/unit/a2a-history-route.test.ts
eslint exit=0
$ git commit (husky ativo, sem --no-verify)
lint-staged: prettier + eslint --fix ✔
[docs-sync] PASS
[t11:any-budget] PASS
[tracked-artifacts] OK
Divergências entre plano e código real
Nenhuma divergência material. Confirmado no código:
event_typeexato ="memory_hits"(src/lib/a2a/taskExecution.ts:143,appendA2ATaskEvent(task.id, "memory_hits", JSON.stringify(hits))).- Shape de
listA2ATaskEvents={ event_type, data_json, created_at }(src/lib/db/a2aTasks.ts,A2ATaskHistoryEventRow) — semidexposto, ordenaçãoORDER BY id ASC. STATE_EVENT_PREFIX = "state:"continua governando só os eventos de transição; omemory_hitscaía noelsee herdavarow.state, o que o fazia aparecer como uma transição duplicada na timeline — daí o filtro ser obrigatório, não cosmético.
Observações (não são divergência):
- A validação implementada é espelho exata da que já existe no drawer (
DrawerMemory/MEMORY_HIT_FIELDSemOrchestrationDrawer.tsx:275), incluindo o motivo dos 4 campos:type/key/snippetsão renderizados como React children. - Zod não foi usado: esta rota não valida body (é GET sem query schema) e a leitura é de JSON persistido, coberto pelo
safeJsonParsejá existente no arquivo. Hard Rule #12 intocada — o catch da rota continua usandosanitizeErrorMessage. - Hard Rule #5 respeitada: nenhum SQL na rota; tudo por
src/lib/db/a2aTasks.ts.
Trabalho paralelo
src/lib/a2a/taskExecution.ts e tests/unit/a2a-memory-hits.test.ts (Task C2) aparecem como M no git status — não foram tocados nem adicionados por esta task. O commit contém exclusivamente os 2 arquivos da C1.