diff --git a/tests/tools/test_kanban_tools.py b/tests/tools/test_kanban_tools.py index 476d14dd325c..5c851a2f6d36 100644 --- a/tests/tools/test_kanban_tools.py +++ b/tests/tools/test_kanban_tools.py @@ -132,6 +132,41 @@ def test_complete_happy_path(worker_env): conn.close() +def test_complete_reports_durable_scratch_artifact_paths(worker_env): + """The transcript-facing tool result must name the surviving attachment. + + Desktop's Artifacts browser indexes tool results. Returning only the + original scratch path leaves it indexing a file that completion deletes. + """ + from hermes_cli import kanban_db as kb + from tools import kanban_tools as kt + + conn = kb.connect() + try: + task = kb.get_task(conn, worker_env) + assert task is not None + workspace = kb.resolve_workspace(task) + kb.set_workspace_path(conn, worker_env, workspace) + finally: + conn.close() + + artifact = workspace / "result.txt" + artifact.write_text("survives\n") + + result = json.loads(kt._handle_complete({ + "summary": "saved result", + "artifacts": [str(artifact)], + })) + + assert result["ok"] is True + assert len(result["artifacts"]) == 1 + persisted = result["artifacts"][0] + assert persisted != str(artifact) + assert persisted.endswith(f"/attachments/{worker_env}/result.txt") + assert os.path.isfile(persisted) + assert not workspace.exists() + + def test_complete_retry_with_empty_created_cards_succeeds(worker_env): """After a phantom rejection, retrying kanban_complete with created_cards=[] (the documented escape hatch) must complete the diff --git a/tools/kanban_tools.py b/tools/kanban_tools.py index fdf846d7353a..5386eaacac9a 100644 --- a/tools/kanban_tools.py +++ b/tools/kanban_tools.py @@ -799,7 +799,20 @@ def _handle_complete(args: dict, **kw) -> str: f"could not complete {tid} (unknown id or already terminal)" ) run = kb.latest_run(conn, tid) - return _ok(task_id=tid, run_id=run.id if run else None) + persisted_artifacts: list[str] = [] + if run is not None and isinstance(run.metadata, dict): + raw_artifacts = run.metadata.get("artifacts") + if isinstance(raw_artifacts, (list, tuple)): + persisted_artifacts = [ + str(path).strip() + for path in raw_artifacts + if str(path).strip() + ] + return _ok( + task_id=tid, + run_id=run.id if run else None, + artifacts=persisted_artifacts, + ) finally: conn.close() except ValueError as e: