Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion crates/ov_cli/src/base_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,4 +934,4 @@ impl<'a> FileUploader<'a> {
.map(|s| s.to_string())
.ok_or_else(|| Error::Parse("Missing temp_file_id in response".to_string()))
}
}
}
14 changes: 12 additions & 2 deletions crates/ov_cli/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,11 +366,21 @@ impl HttpClient {
self.post("/api/v1/fs/mkdir", &body).await
}

pub async fn rm(&self, uri: &str, recursive: bool) -> Result<serde_json::Value> {
let params = vec![
pub async fn rm(
&self,
uri: &str,
recursive: bool,
wait: bool,
timeout: Option<f64>,
) -> Result<serde_json::Value> {
let mut params = vec![
("uri".to_string(), uri.to_string()),
("recursive".to_string(), recursive.to_string()),
("wait".to_string(), wait.to_string()),
];
if let Some(timeout) = timeout {
params.push(("timeout".to_string(), timeout.to_string()));
}
self.delete("/api/v1/fs", &params).await
}

Expand Down
4 changes: 3 additions & 1 deletion crates/ov_cli/src/commands/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,10 +385,12 @@ pub async fn rm(
client: &HttpClient,
uri: &str,
recursive: bool,
wait: bool,
timeout: Option<f64>,
output_format: OutputFormat,
compact: bool,
) -> Result<()> {
let result = client.rm(uri, recursive).await?;
let result = client.rm(uri, recursive, wait, timeout).await?;

let message = if let Some(count) = result
.get("estimated_deleted_count")
Expand Down
19 changes: 17 additions & 2 deletions crates/ov_cli/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1427,9 +1427,24 @@ pub async fn handle_mkdir(uri: String, description: Option<String>, ctx: CliCont
.await
}

pub async fn handle_rm(uri: String, recursive: bool, ctx: CliContext) -> Result<()> {
pub async fn handle_rm(
uri: String,
recursive: bool,
wait: bool,
timeout: Option<f64>,
ctx: CliContext,
) -> Result<()> {
let client = ctx.get_client();
commands::filesystem::rm(&client, &uri, recursive, ctx.output_format, ctx.compact).await
commands::filesystem::rm(
&client,
&uri,
recursive,
wait,
timeout,
ctx.output_format,
ctx.compact,
)
.await
}

pub async fn handle_mv(from_uri: String, to_uri: String, ctx: CliContext) -> Result<()> {
Expand Down
5 changes: 5 additions & 0 deletions crates/ov_cli/src/help_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ const COMMAND_HELP_SPECS: &[CommandHelpSpec] = &[
label: "ov rm viking://scratch --recursive",
description: "Remove a directory subtree.",
},
HelpItem {
label: "ov rm viking://resources/images/foo --recursive --wait",
description: "Remove a subtree and wait for generated overviews to refresh.",
},
],
next_steps: &[
HelpItem {
Expand Down Expand Up @@ -2675,6 +2679,7 @@ mod tests {
for args in [
["ov", "add-resource", "--help"],
["ov", "add-skill", "--help"],
["ov", "rm", "--help"],
["ov", "write", "--help"],
] {
let rendered = strip_ansi(
Expand Down
13 changes: 12 additions & 1 deletion crates/ov_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,12 @@ enum Commands {
/// Remove recursively
#[arg(short, long, help_heading = "Common options")]
recursive: bool,
/// Wait until semantic refresh is complete
#[arg(long, help_heading = "Common options")]
wait: bool,
/// Wait timeout in seconds (only used with --wait)
#[arg(long, value_name = "seconds", help_heading = "Common options")]
timeout: Option<f64>,
},
/// [Data] Move or rename resource
#[command(alias = "rename")]
Expand Down Expand Up @@ -2778,7 +2784,12 @@ async fn main() {
level_limit,
} => handlers::handle_tree(uri, abs_limit, all, node_limit, level_limit, ctx).await,
Commands::Mkdir { uri, description } => handlers::handle_mkdir(uri, description, ctx).await,
Commands::Rm { uri, recursive } => handlers::handle_rm(uri, recursive, ctx).await,
Commands::Rm {
uri,
recursive,
wait,
timeout,
} => handlers::handle_rm(uri, recursive, wait, timeout, ctx).await,
Commands::Mv { from_uri, to_uri } => handlers::handle_mv(from_uri, to_uri, ctx).await,
Commands::Stat { uri } => handlers::handle_stat(uri, ctx).await,
Commands::AddMemory { content } => handlers::handle_add_memory(content, ctx).await,
Expand Down
2 changes: 1 addition & 1 deletion crates/ov_cli/src/tui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ impl App {
}
};

match client.rm(&selected_uri, is_dir).await {
match client.rm(&selected_uri, is_dir, false, None).await {
Ok(_) => {
self.set_status_message(format!("Deleted: {}", selected_uri));

Expand Down
33 changes: 15 additions & 18 deletions crates/ragfs/src/plugins/s3fs/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
//! Supports AWS S3 and S3-compatible services (MinIO, LocalStack, TOS).

use crate::core::{ConfigValue, Error, Result};
use aws_sdk_s3::error::SdkError;
use aws_sdk_s3::config::{BehaviorVersion, Credentials, Region};
use aws_sdk_s3::error::ProvideErrorMetadata;
use aws_sdk_s3::error::SdkError;
use aws_sdk_s3::operation::{RequestId, RequestIdExt};
use aws_sdk_s3::primitives::ByteStream;
use aws_sdk_s3::Client;
Expand Down Expand Up @@ -526,7 +526,11 @@ impl S3Client {
.send()
.await
.map_err(|e| {
format_sdk_s3_error("DeleteObject", &format!("bucket={} key={key}", self.bucket), &e)
format_sdk_s3_error(
"DeleteObject",
&format!("bucket={} key={key}", self.bucket),
&e,
)
})?;

Ok(())
Expand Down Expand Up @@ -725,16 +729,13 @@ impl S3Client {
req = req.continuation_token(token);
}

let resp = req
.send()
.await
.map_err(|e| {
format_sdk_s3_error(
"ListObjectsV2",
&format!("bucket={} prefix={prefix}", self.bucket),
&e,
)
})?;
let resp = req.send().await.map_err(|e| {
format_sdk_s3_error(
"ListObjectsV2",
&format!("bucket={} prefix={prefix}", self.bucket),
&e,
)
})?;

for obj in resp.contents() {
let key = obj.key().unwrap_or("");
Expand Down Expand Up @@ -1048,12 +1049,8 @@ mod tests {

#[test]
fn test_format_generic_s3_error_includes_operation_bucket_key_and_raw_error() {
let err = format_generic_s3_error(
"PutObject",
"test-bucket",
"tenant/a.txt",
"service error",
);
let err =
format_generic_s3_error("PutObject", "test-bucket", "tenant/a.txt", "service error");

match err {
Error::Internal(message) => {
Expand Down
7 changes: 5 additions & 2 deletions docs/en/api/02-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ This endpoint is the core entry point for resource management, supporting adding
3. Call the corresponding Parser to parse content
4. Build the directory tree and write to AGFS
5. Wait for semantic processing completion when `wait=true`; with `wait=false`, return a `task_id` for queue tracking
6. Set up scheduled update task if `watch_interval` is specified
6. If `reason` is non-empty, append it to the fixed resource reason session and commit through the normal memory extraction pipeline so suitable user memories can reference the resource URI
7. Set up scheduled update task if `watch_interval` is specified

**Code Entry Points**:
- `openviking/client/local.py:LocalClient.add_resource` - SDK entry (embedded)
Expand All @@ -146,7 +147,7 @@ This endpoint is the core entry point for resource management, supporting adding
| to | string | No | - | Target Viking URI (exact location). Mutually exclusive with `parent` |
| parent | string | No | - | Parent Viking URI (resource placed under this directory). Mutually exclusive with `to` |
| create_parent | bool | No | False | Automatically create parent directory if it does not exist (server-side flag) |
| reason | string | No | "" | Reason for adding the resource (for documentation and relevance improvement, experimental feature) |
| reason | string | No | "" | Reason for adding the resource. When non-empty, OpenViking runs it through the normal session memory extraction pipeline with the resource URI and records resource references in the resulting memory |
| instruction | string | No | "" | Processing instructions for semantic extraction (experimental feature) |
| wait | bool | No | False | Whether to wait for semantic processing and vectorization to complete before returning |
| timeout | float | No | None | Timeout in seconds, only effective when `wait=True` |
Expand All @@ -168,6 +169,8 @@ This endpoint is the core entry point for resource management, supporting adding
- Raw HTTP calls for local files require first uploading via [temp_upload](#temp_upload) to obtain `temp_file_id`
- When `to` is specified and the target already exists, triggers incremental update
- Only Git repository sources use full background import when `wait=false`; OpenViking performs repository preflight and target planning before returning the `task_id`.
- Memory generated from `reason` is extracted through the same pipeline as `session.commit`. It uses `reason`, the resource URI, available source name, and available directory abstract; it does not inspect or expand the full resource content. OpenViking writes to existing memory types such as `entities`, `events`, or `preferences`, not a dedicated resource memory directory.
- When deleting a resource, OpenViking scans the self or peer memories targeted by the current context before deletion, removes the matching resource URI and content introduced by that `reason`, and refreshes the semantic index for the affected memories.
- Other sources with `wait=false` finish source parsing, target resolution, and AGFS writes before returning. Only semantic and embedding queues continue asynchronously.
- When `watch_interval > 0`, the watch task binds to `to` if provided; otherwise it binds to the `root_uri` returned by this import. If no stable `root_uri` is available, the request fails and asks for an explicit `to`.
- Feishu/Lark app-token imports do not pass `args.feishu_access_token`. OpenViking keeps the existing app credential flow and the SDK obtains an app/tenant token from `app_id` and `app_secret`. This mode supports both one-time imports and `watch_interval > 0`.
Expand Down
2 changes: 2 additions & 0 deletions docs/en/api/03-filesystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ openviking rm viking://resources/old.md [--recursive]

The `estimated_deleted_count` field (for recursive deletes) contains the estimated number of items (files and directories) deleted (from vector index). The CLI will display this information in output.

When deleting `viking://resources/...`, the response may include `memory_cleanup`, indicating that user memories referencing that resource URI were cleaned up before deletion.

---

### mv()
Expand Down
1 change: 1 addition & 0 deletions docs/en/api/05-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ Commit a session. Message archiving (Phase 1) completes immediately. Summary gen
- Rapid consecutive commits on the same session are accepted; each request gets its own `task_id`.
- Background Phase 2 work is serialized by archive order: archive `N+1` waits until archive `N` writes `.done`.
- If an earlier archive failed and left no `.done`, later commit requests fail with `FAILED_PRECONDITION` until that failure is resolved.
- If committed messages contain durable facts, judgments, preferences, or events that mention `viking://resources/...`, memory extraction preserves the resource as a markdown link and records it in `MEMORY_FIELDS.resource_refs`.

**Code Entries:**
- `openviking/session/session.py:Session.commit_async()` - Core implementation
Expand Down
8 changes: 6 additions & 2 deletions docs/zh/api/02-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ URL/文件 Parser TreeBuilder AGFS Summarizer/Vector
3. 调用对应 Parser 解析内容
4. 构建目录树并写入 AGFS
5. `wait=true` 时等待语义处理完成;`wait=false` 时返回 `task_id` 用于队列跟踪
6. 如指定 `--watch-interval`,设置定时更新任务
6. 如果 `reason` 非空,将其追加到固定的资源 reason session 并 commit,复用常规记忆抽取链路,让合适的用户记忆引用该资源 URI
7. 如指定 `--watch-interval`,设置定时更新任务

**代码入口**:
- `openviking/client/local.py:LocalClient.add_resource` - SDK 入口(嵌入式)
Expand All @@ -141,7 +142,7 @@ URL/文件 Parser TreeBuilder AGFS Summarizer/Vector
| to | string | 否 | - | 目标 Viking URI(精确位置)。与 `parent` 互斥 |
| parent | string | 否 | - | 父级 Viking URI(资源放入此目录下)。与 `to` 互斥 |
| create_parent | bool | 否 | False | 如果父目录不存在,自动创建父目录(服务端标志) |
| reason | string | 否 | "" | 添加资源的原因(用于文档化和相关性提升,实验特性) |
| reason | string | 否 | "" | 添加资源的原因;非空时会随资源 URI 进入常规 session 记忆抽取链路,并在生成的记忆中记录资源引用 |
| instruction | string | 否 | "" | 语义提取的处理指令(实验特性) |
| wait | bool | 否 | False | 是否等待语义处理和向量化完成才返回 |
| timeout | float | 否 | None | 超时时间(秒),仅 `wait=true` 时生效 |
Expand All @@ -161,6 +162,8 @@ URL/文件 Parser TreeBuilder AGFS Summarizer/Vector
- `user_id` 和 `peer_id` 路径片段必须是安全的单段标识,例如 `alice` 或 `web-visitor-alice`。包含路径分隔符、`.`、`..`、`:` 或 `+` 的值会被拒绝。
- `path` 和 `temp_file_id` 不能同时指定,上传本地文件需要先通过 [temp_upload](#temp_upload) 上传获取 `temp_file_id`,在 SDK 和 CLI 中已经封装好。
- 只有 Git 仓库来源在 `wait=false` 时使用完整后台导入;OpenViking 会先完成仓库 preflight 和目标规划,再返回 `task_id`。
- `reason` 触发的记忆生成复用 `session.commit` 的抽取链路,只使用 `reason`、资源 URI、可用的资源名称和目录摘要,不会读取或展开完整资源正文;系统会写入 `entities`、`events`、`preferences` 等已有记忆类型,不创建独立的资源记忆目录。
- 删除资源时,系统会在删除前扫描本次上下文对应的 self 或 peer 记忆中的 `resource_refs`,清理对应资源 URI 和由该 `reason` 引入的内容,并重新刷新相关记忆的语义索引。
- 其他来源在 `wait=false` 时会在响应前完成来源解析、目标解析和 AGFS 写入,仅 semantic 与 embedding 队列继续异步处理。
- `watch_interval > 0` 时,如果指定了 `to`,监控任务绑定该目标;如果未指定 `to`,监控任务绑定本次导入返回的 `root_uri`。如果无法得到稳定 `root_uri`,请求会报错并要求显式传 `to`。
- 飞书/Lark 应用 token 导入不传 `args.feishu_access_token`。OpenViking 保持原有应用凭证流程,由 SDK 使用 `app_id` 和 `app_secret` 自动获取 app/tenant token。该模式支持一次性导入和 `watch_interval > 0`。
Expand Down Expand Up @@ -434,6 +437,7 @@ task_id uuid-xxx
| `errors` | array | 处理过程中的错误列表 |
| `warnings` | array | (可选)处理过程中的警告列表(仅在 `strict=False` 时可能出现) |
| `queue_status` | object | (可选,仅当 `wait=true` 时)队列处理状态,包含 `pending`、`processing`、`completed` 计数 |
| `memory_linking` | object | (可选,仅当 `reason` 触发记忆生成时)本次资源 URI 与用户记忆的关联结果 |

对于 `wait=false` 的 Git 仓库来源,后台任务的 `task_type="add_resource"`,`resource_id` 等于返回的 `root_uri`。运行中的任务记录可能包含 `stage`;完成后的任务 `result` 会包含带有 semantic 和 embedding 汇总的 `queue_status`。

Expand Down
2 changes: 2 additions & 0 deletions docs/zh/api/03-filesystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,8 @@ openviking rm viking://resources/old.md [--recursive]

`estimated_deleted_count` 字段(递归删除时)包含删除的项目(文件和目录)估计数量(来自向量索引)。CLI 会在输出中显示此信息。

删除 `viking://resources/...` 时,响应可能包含 `memory_cleanup`,表示删除前已清理引用该资源 URI 的用户记忆。

---

### mv()
Expand Down
1 change: 1 addition & 0 deletions docs/zh/api/05-sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,7 @@ await client.session_used(
- 同一 session 的多次快速连续 commit 会被接受;每次请求都会拿到独立的 `task_id`
- 后台 Phase 2 会按 archive 顺序串行推进:`archive_N+1` 会等待 `archive_N` 写出 `.done` 后再继续
- 如果更早的 archive 已失败且没有 `.done`,后续 commit 会直接返回错误,直到该失败被处理
- 如果提交的消息中包含带 `viking://resources/...` 的长期事实、评价、偏好或事件,记忆抽取会把资源保留为 markdown 链接,并写入 `MEMORY_FIELDS.resource_refs`

**代码入口**
- `openviking/session/session.py:Session.commit_async()` - 核心实现
Expand Down
10 changes: 8 additions & 2 deletions openviking/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,10 +503,16 @@ async def ls(self, uri: str, **kwargs) -> List[Any]:
show_all_hidden=show_all_hidden,
)

async def rm(self, uri: str, recursive: bool = False) -> None:
async def rm(
self,
uri: str,
recursive: bool = False,
wait: bool = False,
timeout: Optional[float] = None,
) -> None:
"""Remove resource"""
await self._ensure_initialized()
await self._client.rm(uri, recursive=recursive)
await self._client.rm(uri, recursive=recursive, wait=wait, timeout=timeout)

async def grep(
self,
Expand Down
16 changes: 14 additions & 2 deletions openviking/client/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,21 @@ async def mkdir(self, uri: str, description: Optional[str] = None) -> None:
"""Create directory."""
await self._service.fs.mkdir(uri, ctx=self._ctx, description=description)

async def rm(self, uri: str, recursive: bool = False) -> None:
async def rm(
self,
uri: str,
recursive: bool = False,
wait: bool = False,
timeout: Optional[float] = None,
) -> None:
"""Remove resource."""
await self._service.fs.rm(uri, ctx=self._ctx, recursive=recursive)
await self._service.fs.rm(
uri,
ctx=self._ctx,
recursive=recursive,
wait=wait,
timeout=timeout,
)

async def mv(self, from_uri: str, to_uri: str) -> None:
"""Move resource."""
Expand Down
5 changes: 5 additions & 0 deletions openviking/prompts/templates/memory/events.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,14 @@ enabled: true
# upsert 表示新增或更新(默认行为)
operation_mode: "add_only"
content_template: |
{% set resource_event_content = extract_context.get_resource_event_content(ranges, summary) %}
{% if resource_event_content %}
{{ resource_event_content }}
{% else %}
Summary: {{ summary }}
{{extract_context.get_first_message_time_with_weekday_from_ranges(ranges|default(''))|default('N/A')}} ChatLog:
{{ extract_context.get_event_content(ranges, summary, 0) }}
{% endif %}
embedding_template: |-
EventName: {{ event_name }}
Goal: {{ goal }}
Expand Down
Loading
Loading