mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
feat(kanban): surface final_result for Done cards; show run summary when task.result is empty
This commit is contained in:
parent
e4ea0a0ed7
commit
deae8e3b4d
3 changed files with 115 additions and 4 deletions
37
plugins/kanban/dashboard/dist/index.js
vendored
37
plugins/kanban/dashboard/dist/index.js
vendored
|
|
@ -3428,10 +3428,39 @@
|
|||
onAddChild: props.onAddChild,
|
||||
onRemoveChild: props.onRemoveChild,
|
||||
}),
|
||||
t.result ? h("div", { className: "hermes-kanban-section" },
|
||||
h("div", { className: "hermes-kanban-section-head" }, tx(i18n, "result", "Result")),
|
||||
h(MarkdownBlock, { source: t.result, enabled: props.renderMarkdown }),
|
||||
) : null,
|
||||
(function () {
|
||||
var finalResult = t.final_result || t.result || t.latest_summary || null;
|
||||
var isDone = t.status === "done";
|
||||
var isParent = (t.link_counts && t.link_counts.children > 0);
|
||||
if (finalResult) {
|
||||
var label = t.result
|
||||
? tx(i18n, "result", "Result")
|
||||
: tx(i18n, "finalResult", "Final Result (run summary)");
|
||||
return h("div", { className: "hermes-kanban-section" },
|
||||
h("div", { className: "hermes-kanban-section-head" }, label),
|
||||
h(MarkdownBlock, { source: finalResult, enabled: props.renderMarkdown }),
|
||||
);
|
||||
}
|
||||
if (isDone && isParent) {
|
||||
return h("div", { className: "hermes-kanban-section" },
|
||||
h("div", { className: "hermes-kanban-section-head" }, tx(i18n, "result", "Result")),
|
||||
h("div", { className: "hermes-kanban-done-no-result hermes-kanban-done-parent-note" },
|
||||
tx(i18n, "doneParentNote",
|
||||
"This card is an orchestrator / parent task. The substantive results are in the child cards listed in Dependencies below."),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (isDone) {
|
||||
return h("div", { className: "hermes-kanban-section" },
|
||||
h("div", { className: "hermes-kanban-section-head" }, tx(i18n, "result", "Result")),
|
||||
h("div", { className: "hermes-kanban-done-no-result" },
|
||||
tx(i18n, "doneNoResult",
|
||||
"No final result was recorded. Check Run History, Logs, or Child Tasks for the worker output."),
|
||||
),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})(),
|
||||
h(AttachmentsSection, {
|
||||
attachments: attachments,
|
||||
boardSlug: props.boardSlug,
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ def _task_dict(
|
|||
# ``task_runs.summary`` (the kanban-worker pattern) instead of
|
||||
# ``tasks.result``. ``None`` when no run has produced a summary yet.
|
||||
d["latest_summary"] = latest_summary
|
||||
d["final_result"] = task.result or latest_summary or None
|
||||
# Keep body short on list endpoints; full body comes from /tasks/:id.
|
||||
return d
|
||||
|
||||
|
|
|
|||
|
|
@ -2362,3 +2362,84 @@ def test_dashboard_failed_card_highlight_class_exists():
|
|||
assert "hermes-kanban-card--failed" in js
|
||||
assert "hermes-kanban-card--failed" in css
|
||||
assert "failedIds" in js
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Final result visibility for Done cards
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_task_dict_final_result_uses_tasks_result_first(client):
|
||||
"""When tasks.result is set, final_result equals it (top priority)."""
|
||||
r = client.post(
|
||||
"/api/plugins/kanban/tasks",
|
||||
json={"title": "Task with explicit result"},
|
||||
)
|
||||
task_id = r.json()["task"]["id"]
|
||||
client.patch(
|
||||
f"/api/plugins/kanban/tasks/{task_id}",
|
||||
json={"status": "done", "result": "The final answer is 42.", "summary": "short handoff"},
|
||||
)
|
||||
r = client.get(f"/api/plugins/kanban/tasks/{task_id}")
|
||||
assert r.status_code == 200
|
||||
data = r.json()["task"]
|
||||
assert data["result"] == "The final answer is 42."
|
||||
assert data["final_result"] == "The final answer is 42."
|
||||
|
||||
|
||||
def test_task_dict_final_result_falls_back_to_latest_summary(client):
|
||||
"""When tasks.result is None but a run summary exists, final_result returns it."""
|
||||
conn = kb.connect()
|
||||
task_id = kb.create_task(conn, title="Task with only run summary")
|
||||
kb.claim_task(conn, task_id)
|
||||
kb.complete_task(conn, task_id, summary="Report written to /output/report.md")
|
||||
conn.close()
|
||||
|
||||
r = client.get(f"/api/plugins/kanban/tasks/{task_id}")
|
||||
assert r.status_code == 200
|
||||
data = r.json()["task"]
|
||||
assert data["status"] == "done"
|
||||
assert not data["result"]
|
||||
assert data["final_result"] == "Report written to /output/report.md"
|
||||
|
||||
|
||||
def test_task_dict_final_result_none_when_nothing_recorded(client):
|
||||
"""When neither tasks.result nor any run summary exists, final_result is None."""
|
||||
r = client.post(
|
||||
"/api/plugins/kanban/tasks",
|
||||
json={"title": "Task with no result at all"},
|
||||
)
|
||||
task_id = r.json()["task"]["id"]
|
||||
r = client.get(f"/api/plugins/kanban/tasks/{task_id}")
|
||||
assert r.status_code == 200
|
||||
assert r.json()["task"]["final_result"] is None
|
||||
|
||||
|
||||
def test_board_tasks_include_final_result_field(client):
|
||||
"""final_result must appear on board-level task cards too."""
|
||||
conn = kb.connect()
|
||||
task_id = kb.create_task(conn, title="Board card with summary only")
|
||||
kb.claim_task(conn, task_id)
|
||||
kb.complete_task(conn, task_id, summary="Done: see attachment")
|
||||
conn.close()
|
||||
|
||||
r = client.get("/api/plugins/kanban/board")
|
||||
assert r.status_code == 200
|
||||
done_col = next(c for c in r.json()["columns"] if c["name"] == "done")
|
||||
card = next((t for t in done_col["tasks"] if t["id"] == task_id), None)
|
||||
assert card is not None
|
||||
assert "Done: see attachment" in card["final_result"]
|
||||
|
||||
|
||||
def test_dashboard_done_final_result_section_rendered_from_summary():
|
||||
"""Frontend must render Final Result section from run summary when task.result is empty."""
|
||||
repo_root = Path(__file__).resolve().parents[2]
|
||||
dist = (repo_root / "plugins" / "kanban" / "dashboard" / "dist" / "index.js").read_text()
|
||||
assert "final_result" in dist
|
||||
assert "Final Result (run summary)" in dist
|
||||
assert "No final result was recorded" in dist
|
||||
assert "orchestrator" in dist or "parent task" in dist
|
||||
# ---------------------------------------------------------------------------
|
||||
# Final result visibility for Done cards
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue