mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-10-16 04:33:12 +00:00
fix(stat): put mtime in the answer where the job description promises it (#566)
* fix(stat): put mtime in the answer where the job description promises it
The ``stat`` job is advertised to agents as "Stat path (size, mtime, exists,
is_dir, is_file)", but the answer only ever carried the path, type and size —
``mtime`` lived solely in metadata, and MCP returns only ``response.answer``.
So an agent asking how fresh a file is got no answer, even though the tool
description told it the field was there.
Render mtime in both branches of the answer:
stat: topics/n.md (file, 20 bytes, mtime 2026-09-21T15:50:52.880316)
stat: topics (dir, mtime 2026-09-21T15:50:52.880316)
The value is hoisted into a local so the answer and ``metadata["mtime"]`` are
guaranteed to come from the same ``stat()`` call rather than two reads. The
module docstring claimed the agent receives "size, mtime, mime type, and ...
frontmatter"; it now states that size and mtime are in the answer while mime
and frontmatter stay in metadata, matching reality.
Metadata is unchanged, so programmatic consumers see no difference. This is
the same answer-completeness class as #564, found while auditing which
advertised job fields actually reach the LLM.
* docs(stat): say which consumers actually see mime and frontmatter
Review feedback on #566: the module docstring told the agent it gets
"mime type and ... the parsed frontmatter alongside in metadata", which
reads as though an agent can use those fields to decide whether to
download or parse a file. It cannot: the MCP service returns only
`response.answer` (`mcp_tools.py` -> `return response.answer`), so
`metadata["mime"]` and `metadata["frontmatter"]` reach direct `Response`
consumers but never an MCP caller.
Spell the split out instead of implying one audience: the answer carries
path, type, size and mtime, and an agent that needs the frontmatter has
to read the file rather than stat it. The envelope description below is
unchanged, since it already lists the full metadata shape.
This commit is contained in:
parent
d865776edd
commit
29b83529ef
2 changed files with 39 additions and 7 deletions
|
|
@ -1,9 +1,11 @@
|
|||
"""``file_stat`` — peek at file metadata under the workspace without copying it.
|
||||
|
||||
Cheap inspection alternative to ``file_download``: the agent gets
|
||||
size, mtime, mime type, and (for markdown files) the parsed
|
||||
frontmatter — enough to decide whether to download / parse / skip
|
||||
without paying the copy cost.
|
||||
Cheap inspection alternative to ``file_download``. The answer carries the
|
||||
path, type, size and mtime, and the answer is all an MCP caller receives:
|
||||
MCP returns only ``response.answer``. ``mime`` and, for markdown files,
|
||||
the parsed frontmatter stay in ``metadata`` with the rest of the envelope,
|
||||
so they reach direct ``Response`` consumers but never an MCP caller — an
|
||||
agent that needs the frontmatter must read the file, not stat it.
|
||||
|
||||
Returns a uniform envelope:
|
||||
|
||||
|
|
@ -68,12 +70,13 @@ class StatStep(BaseStep):
|
|||
return
|
||||
|
||||
st = target.stat()
|
||||
mtime = datetime.fromtimestamp(st.st_mtime).isoformat()
|
||||
payload: dict = {
|
||||
"path": path,
|
||||
"absolute_path": str(target),
|
||||
"exists": True,
|
||||
"type": "dir" if target.is_dir() else "file",
|
||||
"mtime": datetime.fromtimestamp(st.st_mtime).isoformat(),
|
||||
"mtime": mtime,
|
||||
"ctime": datetime.fromtimestamp(st.st_ctime).isoformat(),
|
||||
}
|
||||
if target != original_target:
|
||||
|
|
@ -90,9 +93,9 @@ class StatStep(BaseStep):
|
|||
except Exception:
|
||||
meta = {}
|
||||
payload["frontmatter"] = meta
|
||||
answer = f"stat: {path} (file, {st.st_size} bytes)"
|
||||
answer = f"stat: {path} (file, {st.st_size} bytes, mtime {mtime})"
|
||||
else:
|
||||
answer = f"stat: {path} (dir)"
|
||||
answer = f"stat: {path} (dir, mtime {mtime})"
|
||||
|
||||
self.context.response.success = True
|
||||
self.context.response.answer = answer
|
||||
|
|
|
|||
|
|
@ -150,6 +150,35 @@ def test_stat_directory_fallback():
|
|||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_stat_answer_reports_size_and_mtime():
|
||||
"""The answer carries size and mtime, not just the path and type.
|
||||
|
||||
The ``stat`` job description advertises "size, mtime, exists, is_dir,
|
||||
is_file", but only ``answer`` reaches an MCP caller — mtime used to live
|
||||
solely in metadata, so an agent could not tell how fresh a file was.
|
||||
"""
|
||||
|
||||
async def run():
|
||||
with tempfile.TemporaryDirectory() as tmp, temp_chdir(tmp):
|
||||
store = await _make_store({"topics/n.md": "---\nname: T\n---\nbody"})
|
||||
step = crud_stat.StatStep(file_store=store)
|
||||
await step(path="topics/n.md")
|
||||
answer = step.context.response.answer
|
||||
mtime = _metadata(step)["mtime"]
|
||||
assert answer.startswith("stat: topics/n.md (file, ")
|
||||
assert "bytes, mtime " in answer
|
||||
assert answer.endswith(f"{mtime})")
|
||||
|
||||
(Path(tmp) / "topics").mkdir(parents=True, exist_ok=True)
|
||||
step = crud_stat.StatStep(file_store=store)
|
||||
await step(path="topics")
|
||||
answer = step.context.response.answer
|
||||
assert answer == f"stat: topics (dir, mtime {_metadata(step)['mtime']})"
|
||||
await store.close()
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_stat_directory_wins_over_same_name_index():
|
||||
"""stat prefers an existing directory over its same-name ``.md`` index.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue