fix: complete 0.5.1 demo workflows

This commit is contained in:
2026-09-18 09:11:53 +08:00
parent 1cee7b892f
commit 32bc2ddccb
13 changed files with 376 additions and 33 deletions
+2 -4
View File
@@ -6,7 +6,7 @@ import pytest
from app.config import BACKEND_DIR
from app.container import build_container
from app.contracts import ModelCapability, PluginCommandContext, ToolCall
from app.contracts import ModelCapability, ToolCall
from app.agent.tools import ToolExecutionContext
from app.extensions.archive import install_zip
@@ -37,7 +37,7 @@ def test_analysis_ignores_metadata_and_code_and_keeps_line_numbers():
assert many['truncated'] and many['summary']['tasks'] == 205 and len(many['tasks']) == 200
def test_zip_install_real_mcp_tool_command_and_skill(tmp_path):
def test_zip_install_real_declarative_tool_and_skill(tmp_path):
builder = load(ROOT / 'build_packages.py')
output = tmp_path / 'dist'
catalog = builder.build(output)
@@ -53,8 +53,6 @@ def test_zip_install_real_mcp_tool_command_and_skill(tmp_path):
result = await runtime.tools.execute(ToolCall(tool_call_id='community-test', name='markdown-workbench.inspect_markdown', arguments={'text': sample}), ToolExecutionContext(run_id='community-test'))
assert result.success, result.error_message
assert result.output['summary']['issues'] == 2
command = await runtime.plugins.execute_command('markdown-workbench.inspect-selection', {}, PluginCommandContext(selection=sample))
assert '1 项未完成任务' in command.effect.payload.message
assert runtime.skills.enable('note-reviewer').status == 'ready'
config = runtime.skills.build_agent_configuration('note-reviewer', [ModelCapability.chat, ModelCapability.tool_calling])
assert 'notes.read' in config.allowed_tools
+124
View File
@@ -149,6 +149,130 @@ def test_desktop_revision_conflict_recovers_marker_matched_note(monkeypatch):
assert result is recovered
def test_transcript_export_recovers_link_from_another_vault(monkeypatch):
from app.contracts import TranscriptNoteRequest
from app.services import note_service
from app.services.media_notes import create_transcript_note
text_attachment()
async def scenario():
job = await jobs.create_transcription("lecture.txt")
options = TranscriptNoteRequest(title="跨库课程")
original = await create_transcript_note(job.job_id, options)
with closing(connect()) as conn:
conn.execute(
"UPDATE media_notes SET note_id=? WHERE job_id=?",
("note-from-another-vault", job.job_id),
)
conn.commit()
real_get_note = note_service.get_note
async def get_note(note_id):
if note_id == "note-from-another-vault":
return None
return await real_get_note(note_id)
monkeypatch.setattr(note_service, "get_note", get_note)
recovered = await create_transcript_note(job.job_id, options)
assert recovered.note_id == original.note_id
with closing(connect()) as conn:
linked = conn.execute(
"SELECT note_id FROM media_notes WHERE job_id=?",
(job.job_id,),
).fetchone()[0]
assert linked == original.note_id
asyncio.run(scenario())
def test_artifact_host_writes_use_distinct_child_operations(monkeypatch):
from app import host_bridge
from app.contracts import TranscriptArtifactsRequest
from app.services import media_notes
operations = []
text_attachment()
job = asyncio.run(jobs.create_transcription("lecture.txt"))
async def transcript(_job_id, _options):
operations.append(host_bridge.operation_id.get())
return SimpleNamespace(note_id="transcript-note")
async def knowledge(*_args):
return "<!-- knowledge-note:test -->\n# Knowledge"
async def create(*_args, **_kwargs):
operations.append(host_bridge.operation_id.get())
return SimpleNamespace(note_id="knowledge-note")
monkeypatch.setattr(media_notes, "create_transcript_note", transcript)
monkeypatch.setattr(media_notes, "_knowledge_markdown", knowledge)
monkeypatch.setattr(media_notes, "_create_note", create)
token = host_bridge.operation_id.set("11111111-1111-4111-8111-111111111111")
try:
result = asyncio.run(media_notes.create_transcript_artifacts(
job.job_id,
TranscriptArtifactsRequest(
title="Transcript", knowledge_title="Knowledge",
provider_id="mock", model="mock-1",
),
))
finally:
host_bridge.operation_id.reset(token)
assert result["transcript"].note_id == "transcript-note"
assert result["knowledge_note"].note_id == "knowledge-note"
assert len(operations) == 2
assert operations[0] != operations[1]
assert all(operation and operation != "11111111-1111-4111-8111-111111111111" for operation in operations)
def test_course_note_blocks_are_recomposed_with_markdown_and_plot_tools(monkeypatch):
from app.container import container
from app.services.media_notes import _compose_course_blocks
names = []
original = container.tools.execute
async def execute(call, context):
names.append(call.name)
return await original(call, context)
monkeypatch.setattr(container.tools, "execute", execute)
markdown = """## 算法
```python
left += 1
```
```mermaid
flowchart LR
A --> B
```
```function_plot
domain: -4, 4
range: -1, 8
y = x^2
```"""
rendered = asyncio.run(_compose_course_blocks(markdown, "media-test"))
assert names == ["markdown.compose", "markdown.compose", "function_plot.compose"]
assert "```python\nleft += 1\n```" in rendered
assert "```mermaid\nflowchart LR" in rendered
assert "```function-plot\ndomain: -4, 4" in rendered
def test_course_note_rejects_invalid_function_plot():
from app.services.media_notes import _compose_course_blocks
with pytest.raises(ApiError) as invalid:
asyncio.run(_compose_course_blocks(
"```function-plot\ndomain: -4, 4\ny = __import__('os')\n```",
"media-test",
))
assert invalid.value.code == "KNOWLEDGE_NOTE_VISUAL_INVALID"
def test_local_only_export_and_rebuild_keep_local_embedding_policy(monkeypatch):
from types import SimpleNamespace
from app.contracts import TranscriptNoteRequest, IndexRebuildRequest
+28 -1
View File
@@ -3,7 +3,7 @@ import json
from pathlib import Path
import pytest
from pydantic import TypeAdapter, ValidationError
from pydantic import BaseModel, TypeAdapter, ValidationError
from app.agent import ToolRegistry
from app.config import BACKEND_DIR, get_settings
@@ -91,6 +91,33 @@ def test_echo_command_returns_none_for_empty_message() -> None:
assert populated.payload.message == "hello"
def test_markdown_inspector_returns_line_based_findings() -> None:
class Arguments(BaseModel):
text: str
markdown = "# 课程\n### 跳级\n## 重复\n## 重复\n- [ ] 复习\n```python\nprint(1)"
result = run(
DeclarativePluginHost().execute(
"inspect_markdown", Arguments(text=markdown), None
)
)
assert result["summary"] == {
"lines": 7,
"characters": len(markdown),
"headings": 4,
"tasks": 1,
"open_tasks": 1,
"issues": 3,
}
assert [issue["type"] for issue in result["issues"]] == [
"heading_level_jump",
"duplicate_heading",
"unclosed_code_fence",
]
assert [issue["line"] for issue in result["issues"]] == [2, 4, 6]
@pytest.mark.parametrize(
("effect_type", "payload"),
[