Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions tests/tools/test_kanban_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion tools/kanban_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down