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
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@
## 5. 🟦 可认领/未完成(供同事,注意文件区避免冲突)
- **🔴 #53 [#47-C] 时间线播放引擎**(连续解码 + cpal 音频 + A/V 同步 + MJPEG 回环传输)。子项 #63(cpal)/#64(MJPEG 传输)/#65(Lottie 烘焙)。最大未完成项,需专门会话 + 真机视觉验证。
- **#48 片段编辑收尾**:Delete/切割/片段右键菜单/Inspector 三段式/Toolbar 接线。
- **剩余 MCP 工具 stub**:媒体读取(inspect_media/get_transcript/search_media)+ import_media 需**拓宽 CoreHandle 接 MediaEngine**(注意:CoreHandle 现仅持 AppCore,MediaEngine 在 MediaState,需架构扩展);`generate_*`/upscale 需异步 GenClient + BYOK;add_captions 需端上 whisper
- **剩余隐藏能力**:`inspect_media/get_transcript/search_media/inspect_timeline/import_media/add_captions` 已接真实路径;生成/超分/Motion 六个线名在生产后端完成前不进入发现面。`inspect_media` 尚缺 Lottie;`generate_*`/upscale 仍需异步 GenClient + BYOK,Motion 仍需确定性渲染/导入事务
- **#49 项目内文件夹导入 + 嵌套文件夹浏览(剪映式)**:文件夹图标/双击进入/面包屑/拖出;DTO 加 folderId+folders;import_folder 镜像目录树。用户很想要。
- **#37 全局可复用素材库 + 收藏**(跨项目/分类/音效库/全库可见):**后端已并入 main** —— 存储层 `crates/opentake-media/src/library.rs`(#37-A/#54,PR #104,copy-on-favorite + SHA-256 内容寻址去重 + JSON manifest 原子写)+ Tauri 命令层 `src-tauri/src/library.rs`(#37-B/#55,PR #106,7 命令 list/favorite/unfavorite/categorize/rename/delete/import_to_project)。**前端 #37-C/#56 已并入 main**(PR #115:独立 `LibraryView` 全屏视图 + `libraryStore`/`libraryApi`,分类树/网格/搜索/排序/跨视图聚合/音效库;Home/TitleBar 入口;前端↔后端 7 命令契约已核实)。**#37 epic 收口**(后端 #104/#106 + 前端 #115)。剩:库→时间线拖拽(现用「导入当前项目」按钮)、媒体面板「星标→library_favorite」接线、收藏从 localStorage 迁后端。follow-up:`library.rs:322` remove() 静默吞 remove_file 错误,建议补 `tracing::warn!`;`library_delete` 与 `library_unfavorite` 现为纯别名,建议语义区分。
- **#39 提取音频星标 · #40 设置多分页+主页 1:1 · #34 motion dispatch · #27–30 进阶 B/C/D/E · #22–25 #12 follow-up · #35 bundle id 改名**。
- 冲突注意:我(#47/#48)动 opentake-render/opentake-media(decode/FrameProvider)/src-tauri(composite_frame、autosave)/web Preview+timeline;#36 动 agent+src-tauri(server 段);#37/#49 动 opentake-media(library/folders)+web media。**src-tauri/lib.rs、opentake-media 是多方交汇点,合并按 issue 顺序、各自小段、勤 rebase。**

## 6. MCP 配置(#36 落地后)
Streamable-HTTP `http://127.0.0.1:19789/mcp`(loopback+Origin 校验)。`claude mcp add --transport http opentake http://127.0.0.1:19789/mcp`;Cursor/Codex/Claude Desktop 同址。40 工具,返回附 context_signal。
Streamable-HTTP `http://127.0.0.1:19789/mcp`(loopback+Origin 校验)。`claude mcp add --transport http opentake http://127.0.0.1:19789/mcp`;Cursor/Codex/Claude Desktop 同址。当前发布 38 个真实路径工具,返回附 context_signal。

## 7. 压缩后立即执行
1. 读本文件 + `docs/architecture/PORT-1TO1-GAP.md`。2. `git -C OpenTake pull`(main)。3. 盘点 `gh issue list`,挑最高价值且可完整交付的:**首选 🔴 #53 播放引擎**(大,需专门会话),或 #48 片段编辑收尾、#49/#37 库与文件夹、剩余 MCP 工具 stub。4. 每项走 分支→写→自审→`cargo fmt`+clippy+test→真机/确定性验证→`gh run watch` 双绿→`--admin` 合并。5. 新依赖先读 `~/.cargo/registry/src` 真实源码核实 API(cosmic-text/rmcp 都这么做的),别照猜测写。
9 changes: 7 additions & 2 deletions crates/opentake-agent/src/chat/loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,8 @@ impl ChatLoop {
}

/// The tool catalog in the OpenAI function-calling shape. Built fresh per
/// turn (cheap; ~44 tools) so the model always sees the current schema.
/// turn (cheap; currently 38 live tools) so the model always sees the
/// current fail-closed catalog.
///
/// When the dispatcher lacks a media bridge, hide the bridge-dependent
/// tools instead of advertising tools that would only fail at runtime.
Expand All @@ -261,7 +262,10 @@ impl ChatLoop {
.copied()
.filter(|tool| {
self.dispatcher.has_media_bridge()
|| !matches!(tool, ToolName::InspectTimeline | ToolName::ImportMedia)
|| !matches!(
tool,
ToolName::InspectMedia | ToolName::InspectTimeline | ToolName::ImportMedia
)
})
.map(|tool| ToolSchema {
name: tool.as_str().to_string(),
Expand Down Expand Up @@ -603,6 +607,7 @@ mod tests {
let loop_ = build_loop(talking_head_timeline(), Arc::new(MemoryKeyStore::new()));
let tools = loop_.tool_catalog();
assert!(tools.iter().any(|t| t.name == "tighten_silences"));
assert!(!tools.iter().any(|t| t.name == "inspect_media"));
assert!(!tools.iter().any(|t| t.name == "inspect_timeline"));
assert!(!tools.iter().any(|t| t.name == "import_media"));
}
Expand Down
28 changes: 28 additions & 0 deletions crates/opentake-agent/src/mcp/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ fn safe_public_detail(kind: PublicErrorKind, private_detail: &str) -> Option<Str
PublicErrorKind::InvalidArguments(tool) => {
Some(safe_invalid_argument_detail(tool, private_detail))
}
PublicErrorKind::ResourceNotFound(tool) => Some(format!(
"{} could not resolve the referenced project resource.",
tool.as_str()
)),
PublicErrorKind::CapabilityUnavailable(tool) => Some(format!(
"{} cannot inspect this source in the current build or source state.",
tool.as_str()
)),
}
}

Expand Down Expand Up @@ -314,6 +322,26 @@ mod tests {
assert!(!wire.contains("expected i32"));
}

#[test]
fn typed_unavailable_error_exposes_only_fixed_recovery_contract() {
let private = "inspect_media: /Users/alice/private.mov is offline";
let result = ToolResult::public_error(
PublicErrorKind::CapabilityUnavailable(ToolName::InspectMedia),
private,
);
let value = safe_tool_result_for_llm(&result);
assert_eq!(value["code"], "MCP_CAPABILITY_UNAVAILABLE");
assert_eq!(
value["message"],
"This capability is unavailable for the referenced media."
);
assert_eq!(
value["details"],
"inspect_media cannot inspect this source in the current build or source state."
);
assert!(!value.to_string().contains("/Users/alice"));
}

#[test]
fn explicitly_public_marker_cannot_bypass_detail_guard() {
let private = "/Users/alice/private.mp4";
Expand Down
Loading
Loading