mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-09-08 22:21:15 +00:00
* refractor(proactive): upgrade proactive feature with disentangled job and steps * refactor(proactive): apply audit fixes - rename read-side job 'proactive' -> 'proactive_read' (less confusing vs the refresh pipeline) - drop dedicated agent_wrapper.proactive; extraction reuses the default wrapper - simplify schema: remove unused ProactiveExtractOutput/TopicUpdate, drop resource_paths - extract no longer scans resource/ directly (daily notes already carry resource content) - update tests and docs accordingly * feat(proactive): strict extract-output gate and prompt total budget - parse_extract_reply now requires a contract section (follow_ups/extends/updates as a list); non-empty replies with misspelled section names trigger the existing one-shot retry instead of silently checkpointing changed files - pack_paths gains max_total_chars; extract packs newest daily material first, keeps the first file on overflow, and records omitted files in a trailer (default budget 300000 chars, configurable via max_total_chars) - tests: schema gate unit, schema-error retry e2e, budget unit + e2e * feat(proactive): add scenario-card plan step and generative agenda step * feat(proactive): digest-personal profile personalization and leaner LLM contract - extract/plan/agenda now draw a user profile block from <digest_dir>/personal/*.md (frontmatter description + body excerpt, per-file budget, profile.md fallback) - all daily access honours the configured daily_dir (prompt paths parameterized, config-driven fallbacks) so workspaces using e.g. memory/ work unchanged - schema trim: drop dead fields errors/material_paths, carry_forward_all -> count - shrink LLM output contract: new topics emit title/reason/confidence/paths only; keywords removed end-to-end, evidence derived from paths[0] (updates keep it) * fix(proactive): skip checkpoint when extract reply stays unusable after retry Two consecutive unparseable replies now short-circuit the round without checkpointing, so the same material is retried next round instead of being silently consumed (closes the residual audit #1 gap: the structural gate detected schema-wrong output but a double failure still checkpointed). * fix(proactive): replace running bool with reference-counted job activity tracker for the idle gate * refactor(proactive): remove job activity tracking and idle gate, restore job tree to upstream * fix(proactive): address second audit round (readonly reader, mtime checkpoint, wider fallbacks, profile containment, horizon content, expiry boundary) * refactor(dream): strip interests.yaml ownership from dream, proactive is now the sole writer * refactor(dream): separate proactive topic generation * ci: update renamed auto dream smoke test * fix(proactive): complete refresh migration and docs --------- Co-authored-by: jinli.yl <jinli.yl@alibaba-inc.com>
109 lines
4.8 KiB
Python
109 lines
4.8 KiB
Python
"""Dream catalog persistence step."""
|
|
|
|
from pathlib import Path
|
|
|
|
from ...base_step import BaseStep
|
|
from ....components import R
|
|
from ....schema import DreamState, FileNode
|
|
from .utils import state_from_context, store_state, workspace_dir
|
|
|
|
|
|
@R.register("dream_finish_step")
|
|
class DreamFinishStep(BaseStep):
|
|
"""Persist dream catalog and render final auto-dream response."""
|
|
|
|
def __init__(self, persist: bool = True, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.persist = persist
|
|
|
|
async def execute(self):
|
|
assert self.context is not None
|
|
state = state_from_context(self)
|
|
workspace = Path(state.workspace).resolve() if state.workspace else workspace_dir(self)
|
|
if self.file_catalog is None:
|
|
raise RuntimeError("dream_finish_step requires file_catalog")
|
|
|
|
failed_paths = set(state.failed_paths)
|
|
checkpoint = [p for p in state.changed_paths if p not in failed_paths]
|
|
day_index_paths = [f"{state.daily_dir}/{day}.md" for day in (state.dates or [state.date]) if day]
|
|
supplemental = [p for p in day_index_paths if p and p not in failed_paths]
|
|
upsert_paths = list(dict.fromkeys([*checkpoint, *supplemental]))
|
|
self.logger.info(
|
|
f"[{self.name}] start changed={len(state.changed_paths)} failed_paths={len(state.failed_paths)} "
|
|
f"checkpoint={len(checkpoint)} day_indexes={len(day_index_paths)} "
|
|
f"deleted={len(state.deleted_paths)} persist={self.persist}",
|
|
)
|
|
upserts = self._nodes(workspace, upsert_paths)
|
|
if upserts:
|
|
self.logger.info(f"[{self.name}] catalog upsert start nodes={len(upserts)}")
|
|
await self.file_catalog.upsert(upserts)
|
|
self.logger.info(f"[{self.name}] catalog upsert done nodes={len(upserts)}")
|
|
if self.persist and (upserts or state.deleted_paths):
|
|
self.logger.info(
|
|
f"[{self.name}] catalog dump start upserts={len(upserts)} deleted={len(state.deleted_paths)}",
|
|
)
|
|
await self.file_catalog.dump()
|
|
self.logger.info(f"[{self.name}] catalog dump done")
|
|
|
|
state.checkpoint_paths = [n.path for n in upserts if n.path in checkpoint]
|
|
state.summary = render_summary(state)
|
|
store_state(self, state)
|
|
self.context.response.success = not state.errors
|
|
self.context.response.answer = state.summary
|
|
self.context.response.metadata["modified"] = bool(state.modified_paths)
|
|
self.logger.info(
|
|
f"[{self.name}] finish success={self.context.response.success} "
|
|
f"checkpointed={len(state.checkpoint_paths)} failed_units={len(state.failed_units)} "
|
|
f"errors={len(state.errors)}",
|
|
)
|
|
return self.context.response
|
|
|
|
@staticmethod
|
|
def _nodes(workspace: Path, paths: list[str]) -> list[FileNode]:
|
|
out: list[FileNode] = []
|
|
for rel in paths:
|
|
try:
|
|
out.append(FileNode(path=rel, st_mtime=(workspace / rel).stat().st_mtime))
|
|
except OSError:
|
|
continue
|
|
return out
|
|
|
|
|
|
def render_summary(state: DreamState) -> str:
|
|
"""Render a concise user-facing summary."""
|
|
dates = ", ".join(state.dates or [state.date])
|
|
lines = [
|
|
("AutoDream completed with warnings" if state.warnings else "AutoDream completed"),
|
|
"",
|
|
f"- Date: {state.date}",
|
|
f"- Scan window: {dates}",
|
|
(
|
|
f"- Files: {state.files_scanned} scanned, {state.files_changed} changed, "
|
|
f"{state.files_unchanged} unchanged, {state.files_deleted} deleted"
|
|
),
|
|
f"- Extracted: {len(state.units)} unit(s)",
|
|
(
|
|
f"- Integrated: {len(state.integrate_results)} ok, {len(state.skipped_units)} skipped, "
|
|
f"{len(state.failed_units)} failed"
|
|
),
|
|
f"- Catalog: checkpointed {len(state.checkpoint_paths)} changed path(s)",
|
|
]
|
|
if state.nodes_created:
|
|
lines.append(f"- Created: {', '.join(state.nodes_created)}")
|
|
if state.nodes_updated:
|
|
lines.append(f"- Updated: {', '.join(state.nodes_updated)}")
|
|
if state.integrate_results:
|
|
lines.extend(["", "Changes:"])
|
|
for item in state.integrate_results:
|
|
action = str(item.get("action") or "").strip() or "UPDATED"
|
|
target = str(item.get("target_path") or "").strip() or "(unknown target)"
|
|
note = str(item.get("note") or "").strip()
|
|
suffix = f": {note}" if note else ""
|
|
lines.append(f"- [{target}][{action}]{suffix}")
|
|
if state.failed_paths:
|
|
lines.append(f"- Failed paths: {', '.join(state.failed_paths)}")
|
|
if state.warnings:
|
|
lines.append(f"- Warnings: {'; '.join(state.warnings)}")
|
|
if state.errors:
|
|
lines.append(f"- Errors: {'; '.join(state.errors)}")
|
|
return "\n".join(lines)
|