From aa302716e20f79e0aa7a13904509569b8f14b665 Mon Sep 17 00:00:00 2001 From: qin-ctx Date: Wed, 14 Oct 2026 15:26:57 +0800 Subject: [PATCH 1/2] =?UTF-8?q?refactor(api)!:=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E7=AD=89=E5=BE=85=E4=B8=93=E7=94=A8=20timeout=20=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保留 wait 和客户端 HTTP 超时配置,同步服务端、SDK、CLI、MCP、插件与文档。 BREAKING CHANGE: 等待相关接口和命令不再支持单独的 timeout / --timeout 参数。 --- .../custom/session_contention_benchmark.py | 32 ++----- bot/vikingbot/compile/service.py | 9 +- bot/vikingbot/openviking_mount/ov_server.py | 6 -- crates/ov_cli/README.md | 4 +- crates/ov_cli/README_CN.md | 4 +- crates/ov_cli/src/client.rs | 27 +----- crates/ov_cli/src/commands/content.rs | 2 - crates/ov_cli/src/commands/filesystem.rs | 3 +- crates/ov_cli/src/commands/resources.rs | 2 - crates/ov_cli/src/commands/skills.rs | 2 +- crates/ov_cli/src/commands/system.rs | 3 +- crates/ov_cli/src/config.rs | 13 --- crates/ov_cli/src/handlers.rs | 17 +--- crates/ov_cli/src/help_ui.rs | 15 ++-- crates/ov_cli/src/main.rs | 88 ++++--------------- crates/ov_cli/src/openviking_assets.rs | 46 +--------- crates/ov_cli/src/tui/app.rs | 2 +- docs/en/api/02-resources.md | 3 +- docs/en/api/03-filesystem.md | 1 - docs/en/api/04-skills.md | 9 +- docs/en/api/07-system.md | 21 ++--- docs/en/api/12-content.md | 2 - docs/en/api/99-api-doc-writing-guide.md | 2 +- docs/en/getting-started/02-quickstart.md | 2 +- .../en/guides/02-volcengine-purchase-guide.md | 2 +- docs/en/guides/06-mcp-integration.md | 4 +- docs/en/guides/09-ovpack.md | 2 +- docs/en/guides/15-snapshot.md | 4 +- docs/en/guides/18-openviking-assets.md | 13 ++- docs/zh/api/02-resources.md | 5 +- docs/zh/api/03-filesystem.md | 1 - docs/zh/api/04-skills.md | 9 +- docs/zh/api/07-system.md | 21 ++--- docs/zh/api/12-content.md | 2 - docs/zh/api/99-api-doc-writing-guide.md | 2 +- docs/zh/getting-started/02-quickstart.md | 2 +- .../zh/guides/02-volcengine-purchase-guide.md | 2 +- docs/zh/guides/06-mcp-integration.md | 4 +- docs/zh/guides/09-ovpack.md | 2 +- docs/zh/guides/15-snapshot.md | 4 +- docs/zh/guides/18-openviking-assets.md | 9 +- examples/cloud/bob.py | 4 +- examples/hermes-plugin/__init__.py | 3 +- .../src/langchain_openviking/store.py | 4 - .../src/langchain_openviking/testing.py | 1 - .../src/langchain_openviking/tools.py | 10 --- examples/openclaw-plugin/client.ts | 25 +----- .../docs/openviking-openclaw-plugin-guide.md | 4 +- .../docs/openviking-plugin-reference.md | 12 ++- .../docs/openviking-websocket-rpc-api.md | 8 +- .../plugin/openviking-command-args.ts | 8 +- .../plugin/openviking-command-definitions.ts | 2 - .../plugin/openviking-import-runtime.ts | 4 - .../plugin/openviking-import-tools.ts | 4 - .../skills/install-openviking-memory/SKILL.md | 2 - .../openviking-context-database/SKILL.md | 4 +- .../tests/ut/architecture-boundaries.test.ts | 17 +--- .../tests/ut/client-adapters.test.ts | 6 +- .../openclaw-plugin/tests/ut/client.test.ts | 28 +----- .../openclaw-plugin/tests/ut/tools.test.ts | 7 +- .../tests/fixtures/mcp-tools-list.json | 8 -- examples/skills/ov-add-paper/SKILL.md | 4 +- .../references/openviking-ingest.md | 8 +- .../skills/ov-resources/docs/add-resource.md | 4 - examples/snapshot/snapshot_example.py | 3 +- openviking/connector/delegate.py | 1 - openviking/eval/ragas/pipeline.py | 7 +- openviking/eval/ragas/rag_eval.py | 2 - openviking/server/mcp_endpoint.py | 6 +- openviking/server/routers/content.py | 4 - openviking/server/routers/filesystem.py | 3 +- openviking/server/routers/resources.py | 6 -- openviking/server/routers/skills.py | 2 - openviking/server/routers/system.py | 11 +-- openviking/server/skill_ingest.py | 2 - openviking/service/fs_service.py | 22 +---- .../service/resource_memory_link_service.py | 7 +- openviking/service/resource_service.py | 66 +++----------- openviking/storage/content_write.py | 39 ++------ .../storage/queuefs/add_resource_msg.py | 2 - .../storage/queuefs/add_resource_processor.py | 1 - openviking_cli/exceptions.py | 8 -- sdk/go/examples/basic_usage/main.go | 10 +-- sdk/go/filesystem.go | 5 -- sdk/go/helpers.go | 4 - sdk/go/resources.go | 1 - sdk/go/skills.go | 2 - sdk/go/system.go | 5 +- sdk/go/types.go | 11 --- sdk/python/openviking_sdk/client.py | 57 +++--------- .../tests/test_async_client_behaviors.py | 24 +---- sdk/typescript/src/client.ts | 12 +-- sdk/typescript/src/types.ts | 1 - tests/api_test/api/client.py | 13 +-- tests/api_test/filesystem/test_fs_cp.py | 4 +- .../test_compressor_v3_case_extraction.py | 2 +- tests/server/test_api_resources.py | 37 +++----- tests/server/test_api_skills.py | 2 +- tests/server/test_content_batch_write.py | 3 +- tests/server/test_filesystem_router.py | 2 +- tests/server/test_request_wait_tracking.py | 20 ++--- tests/server/test_skill_failure_accounting.py | 6 +- tests/server/test_skill_privacy_lock.py | 13 +-- .../server/test_skill_update_cancellation.py | 22 ++--- tests/server/test_skill_update_retry.py | 18 ++-- tests/service/test_fs_service.py | 6 +- .../test_content_write_processing_mode.py | 4 - web-studio/src/gen/ov-client/sdk.gen.ts | 8 +- web-studio/src/gen/ov-client/types.gen.ts | 28 +----- 109 files changed, 243 insertions(+), 849 deletions(-) diff --git a/benchmark/custom/session_contention_benchmark.py b/benchmark/custom/session_contention_benchmark.py index e7ee9588d2..baed739ecf 100644 --- a/benchmark/custom/session_contention_benchmark.py +++ b/benchmark/custom/session_contention_benchmark.py @@ -216,11 +216,9 @@ async def system_status(self) -> Any: ... async def observer_queue(self) -> Any: ... - async def add_resource( - self, *, path: str, to: str, reason: str, wait: bool, timeout: Optional[float] - ) -> Any: ... + async def add_resource(self, *, path: str, to: str, reason: str, wait: bool) -> Any: ... - async def wait_processed(self, timeout: Optional[float]) -> Any: ... + async def wait_processed(self) -> Any: ... async def find(self, *, query: str, target_uri: str, limit: int) -> Any: ... @@ -278,19 +276,16 @@ async def system_status(self) -> Any: async def observer_queue(self) -> Any: return await self._client._get_queue_status() - async def add_resource( - self, *, path: str, to: str, reason: str, wait: bool, timeout: Optional[float] - ) -> Any: + async def add_resource(self, *, path: str, to: str, reason: str, wait: bool) -> Any: return await self._client.add_resource( path=path, to=to, wait=wait, - timeout=timeout, options={"reason": reason, "strict": False}, ) - async def wait_processed(self, timeout: Optional[float]) -> Any: - return await self._client.wait_processed(timeout=timeout) + async def wait_processed(self) -> Any: + return await self._client.wait_processed() async def find(self, *, query: str, target_uri: str, limit: int) -> Any: result = await self._client.find(query=query, target_uri=target_uri, limit=limit) @@ -353,21 +348,14 @@ async def system_status(self) -> AdapterResult: async def observer_queue(self) -> AdapterResult: return await self._run(["observer", "queue"]) - async def add_resource( - self, *, path: str, to: str, reason: str, wait: bool, timeout: Optional[float] - ) -> AdapterResult: + async def add_resource(self, *, path: str, to: str, reason: str, wait: bool) -> AdapterResult: args = ["add-resource", path, "--to", to, "--reason", reason] if wait: args.append("--wait") - if timeout is not None: - args.extend(["--timeout", str(timeout)]) return await self._run(args, operation_timeout=max(self._config.timeout, 60.0)) - async def wait_processed(self, timeout: Optional[float]) -> AdapterResult: - args = ["wait"] - if timeout is not None: - args.extend(["--timeout", str(timeout)]) - return await self._run(args, operation_timeout=timeout or self._config.timeout) + async def wait_processed(self) -> AdapterResult: + return await self._run(["wait"]) async def find(self, *, query: str, target_uri: str, limit: int) -> AdapterResult: return await self._run(["find", query, "--uri", target_uri, "-n", str(limit)]) @@ -660,7 +648,6 @@ async def worker(worker_id: int) -> None: to=uri, reason="OpenViking server load benchmark", wait=False, - timeout=None, ), resource_uri=resource_uri, worker_id=worker_id, @@ -683,7 +670,7 @@ async def worker(worker_id: int) -> None: adapter, scenario, "wait_processed", - lambda: adapter.wait_processed(timeout=self.config.drain_timeout), + lambda: asyncio.wait_for(adapter.wait_processed(), timeout=self.config.drain_timeout), ) self.recorder.add_phase( PhaseMetadata( @@ -903,7 +890,6 @@ async def _mixed_worker( to=uri, reason="OpenViking mixed load benchmark", wait=False, - timeout=None, ), resource_uri=resource_uri, worker_id=worker_id, diff --git a/bot/vikingbot/compile/service.py b/bot/vikingbot/compile/service.py index ab5137a171..5b0f7f25f2 100644 --- a/bot/vikingbot/compile/service.py +++ b/bot/vikingbot/compile/service.py @@ -910,7 +910,6 @@ async def resolve_wiki_uri(uri: str) -> bool: root_uri=request.to, operations=rendered.operations, wait=False, - timeout=300.0, ) except OpenVikingError as exc: if exc.code == "CONFLICT": @@ -1090,14 +1089,13 @@ async def source_batches(): result = {"created": [], "updated": [], "unchanged": []} if skill_target: result = await self._write_skill_bundle( - client=client, target_uri=request.to, rendered=rendered, timeout=300.0 + client=client, target_uri=request.to, rendered=rendered ) elif rendered.operations: result = await client.batch_write( root_uri=request.to, operations=rendered.operations, wait=False, - timeout=300.0, skip_conflicts=True, ) conflicts = result.get("conflicts", []) @@ -1280,7 +1278,6 @@ async def _write_skill_bundle( client: VikingClient, target_uri: str, rendered: RenderedBundle, - timeout: float, ) -> dict[str, list[str]]: """Publish one validated package, retaining untouched existing attachments. @@ -1362,11 +1359,11 @@ async def _write_skill_bundle( ) if exists: published = await client.update_skill( - skill_name, str(skill_dir), target_uri=target_uri, wait=True, timeout=timeout + skill_name, str(skill_dir), target_uri=target_uri, wait=True ) else: published = await client.add_skill( - str(skill_dir), target_uri=target_uri, wait=True, timeout=timeout + str(skill_dir), target_uri=target_uri, wait=True ) result["updated" if exists else "created"].append( str(published.get("root_uri") or published.get("uri") or root_uri) diff --git a/bot/vikingbot/openviking_mount/ov_server.py b/bot/vikingbot/openviking_mount/ov_server.py index b921ed69ff..72ce2b572f 100644 --- a/bot/vikingbot/openviking_mount/ov_server.py +++ b/bot/vikingbot/openviking_mount/ov_server.py @@ -633,13 +633,11 @@ async def add_skill( *, target_uri: str, wait: bool = True, - timeout: Optional[float] = None, ) -> Dict[str, Any]: return await self.client.add_skill( path, options={"target_uri": target_uri}, wait=wait, - timeout=timeout, ) async def update_skill( @@ -649,14 +647,12 @@ async def update_skill( *, target_uri: str, wait: bool = True, - timeout: Optional[float] = None, ) -> Dict[str, Any]: return await self.client.update_skill( skill_name, path, options={"target_uri": target_uri}, wait=wait, - timeout=timeout, ) async def batch_write( @@ -665,7 +661,6 @@ async def batch_write( root_uri: str, operations: List[Dict[str, Any]], wait: bool = True, - timeout: Optional[float] = None, skip_conflicts: bool = False, ) -> Dict[str, Any]: """Publish a bundle, optionally retaining conflicting targets and reporting their URIs.""" @@ -673,7 +668,6 @@ async def batch_write( root_uri=root_uri, operations=operations, wait=wait, - timeout=timeout, options={"skip_conflicts": skip_conflicts}, ) diff --git a/crates/ov_cli/README.md b/crates/ov_cli/README.md index 72a57e75f6..ef0ce70068 100644 --- a/crates/ov_cli/README.md +++ b/crates/ov_cli/README.md @@ -256,11 +256,11 @@ Some command help may also show the long `--output json` form. `-o json` is the ```bash # Add URL and wait for processing -ov add-resource https://example.com/docs --wait --timeout 60 +ov add-resource https://example.com/docs --wait # Add a local directory with filters ov add-resource ./dir \ - --wait --timeout 600 \ + --wait \ --ignore-dirs "node_modules,dist" \ --include "*.md,*.py" \ --exclude "*.tmp,*.log" diff --git a/crates/ov_cli/README_CN.md b/crates/ov_cli/README_CN.md index 85a80da455..2375620ff7 100644 --- a/crates/ov_cli/README_CN.md +++ b/crates/ov_cli/README_CN.md @@ -255,11 +255,11 @@ ov -o json config list ```bash # 添加 URL 并等待处理完成 -ov add-resource https://example.com/docs --wait --timeout 60 +ov add-resource https://example.com/docs --wait # 添加本地目录并过滤文件 ov add-resource ./dir \ - --wait --timeout 600 \ + --wait \ --ignore-dirs "node_modules,dist" \ --include "*.md,*.py" \ --exclude "*.tmp,*.log" diff --git a/crates/ov_cli/src/client.rs b/crates/ov_cli/src/client.rs index 2484267baf..7cbcb22d6b 100644 --- a/crates/ov_cli/src/client.rs +++ b/crates/ov_cli/src/client.rs @@ -386,13 +386,12 @@ impl HttpClient { content: &str, mode: &str, wait: bool, - timeout: Option, processing_mode: &str, tags: Vec, tag_mode: &str, acl: Option, ) -> Result { - let mut body = Self::build_write_body(uri, content, mode, wait, timeout, processing_mode); + let mut body = Self::build_write_body(uri, content, mode, wait, processing_mode); add_resource_tag_fields(&mut body, &tags, tag_mode); if let Some(acl) = acl { body["acl"] = acl; @@ -463,7 +462,6 @@ impl HttpClient { content: &str, mode: &str, wait: bool, - timeout: Option, processing_mode: &str, ) -> Value { let mut body = serde_json::json!({ @@ -471,7 +469,6 @@ impl HttpClient { "content": content, "mode": mode, "wait": wait, - "timeout": timeout, "processing_mode": processing_mode, }); compact_request_body(&mut body); @@ -683,16 +680,12 @@ impl HttpClient { uri: &str, recursive: bool, wait: bool, - timeout: Option, ) -> Result { - let mut params = vec![ + let 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", ¶ms).await } @@ -870,7 +863,6 @@ impl HttpClient { reason: &str, instruction: &str, wait: bool, - timeout: Option, strict: bool, ignore_dirs: Option, include: Option, @@ -942,7 +934,6 @@ impl HttpClient { "reason": reason, "instruction": instruction, "wait": wait, - "timeout": timeout, "strict": strict, "ignore_dirs": ignore_dirs, "include": include, @@ -978,7 +969,6 @@ impl HttpClient { "reason": reason, "instruction": instruction, "wait": wait, - "timeout": timeout, "strict": strict, "ignore_dirs": ignore_dirs, "include": include, @@ -1002,7 +992,6 @@ impl HttpClient { "reason": reason, "instruction": instruction, "wait": wait, - "timeout": timeout, "strict": strict, "ignore_dirs": ignore_dirs, "include": include, @@ -1024,7 +1013,6 @@ impl HttpClient { "reason": reason, "instruction": instruction, "wait": wait, - "timeout": timeout, "strict": strict, "ignore_dirs": ignore_dirs, "include": include, @@ -1208,7 +1196,6 @@ impl HttpClient { name: &str, data: &str, wait: bool, - timeout: Option, show_progress: bool, verbose: bool, source_metadata: Option, @@ -1238,7 +1225,6 @@ impl HttpClient { let mut body = serde_json::json!({ "temp_file_id": temp_file_id, "wait": wait, - "timeout": timeout, }); if let Some(source_metadata) = source_metadata.clone() { body["source_metadata"] = source_metadata; @@ -1255,7 +1241,6 @@ impl HttpClient { let mut body = serde_json::json!({ "temp_file_id": temp_file_id, "wait": wait, - "timeout": timeout, }); if let Some(source_metadata) = source_metadata.clone() { body["source_metadata"] = source_metadata; @@ -1266,7 +1251,6 @@ impl HttpClient { let mut body = serde_json::json!({ "data": data, "wait": wait, - "timeout": timeout, }); if let Some(source_metadata) = source_metadata.clone() { body["source_metadata"] = source_metadata; @@ -1278,7 +1262,6 @@ impl HttpClient { let mut body = serde_json::json!({ "data": data, "wait": wait, - "timeout": timeout, }); if let Some(source_metadata) = source_metadata { body["source_metadata"] = source_metadata; @@ -2066,7 +2049,6 @@ mod tests { "", "", false, - None, false, None, None, @@ -2103,7 +2085,6 @@ mod tests { "", "", false, - None, false, None, None, @@ -2266,7 +2247,6 @@ mod tests { "updated", "replace", true, - Some(3.0), "semantic_and_vectors", ); @@ -2277,7 +2257,6 @@ mod tests { "content": "updated", "mode": "replace", "wait": true, - "timeout": 3.0, }) ); assert!(body.get("regenerate_semantics").is_none()); @@ -2291,7 +2270,6 @@ mod tests { "updated", "replace", true, - None, "semantic_and_vectors", ); @@ -2305,7 +2283,6 @@ mod tests { "updated", "replace", true, - None, "vectors_only", ); diff --git a/crates/ov_cli/src/commands/content.rs b/crates/ov_cli/src/commands/content.rs index 0f3383a281..2cfcaeeb13 100644 --- a/crates/ov_cli/src/commands/content.rs +++ b/crates/ov_cli/src/commands/content.rs @@ -45,7 +45,6 @@ pub async fn write( content: &str, mode: &str, wait: bool, - timeout: Option, processing_mode: &str, tags: Vec, tag_mode: &str, @@ -59,7 +58,6 @@ pub async fn write( content, mode, wait, - timeout, processing_mode, tags, tag_mode, diff --git a/crates/ov_cli/src/commands/filesystem.rs b/crates/ov_cli/src/commands/filesystem.rs index fb6cad29df..e867b5aa42 100644 --- a/crates/ov_cli/src/commands/filesystem.rs +++ b/crates/ov_cli/src/commands/filesystem.rs @@ -932,11 +932,10 @@ pub async fn rm( uri: &str, recursive: bool, wait: bool, - timeout: Option, output_format: OutputFormat, compact: bool, ) -> Result<()> { - let result = client.rm(uri, recursive, wait, timeout).await?; + let result = client.rm(uri, recursive, wait).await?; let message = if let Some(count) = result .get("estimated_deleted_count") diff --git a/crates/ov_cli/src/commands/resources.rs b/crates/ov_cli/src/commands/resources.rs index 42a7bd7dff..3a530772db 100644 --- a/crates/ov_cli/src/commands/resources.rs +++ b/crates/ov_cli/src/commands/resources.rs @@ -13,7 +13,6 @@ pub async fn add_resource( reason: String, instruction: String, wait: bool, - timeout: Option, strict: bool, ignore_dirs: Option, include: Option, @@ -40,7 +39,6 @@ pub async fn add_resource( &reason, &instruction, wait, - timeout, strict, ignore_dirs, include, diff --git a/crates/ov_cli/src/commands/skills.rs b/crates/ov_cli/src/commands/skills.rs index 07c82a314a..88c542c092 100644 --- a/crates/ov_cli/src/commands/skills.rs +++ b/crates/ov_cli/src/commands/skills.rs @@ -195,7 +195,7 @@ pub async fn update( if data.is_empty() { return Err(Error::Client("No update source supplied.".into())); } - client.skill_update(&name, data, wait, None, false, false, None, parent).await + client.skill_update(&name, data, wait, false, false, None, parent).await } else { Err(Error::Client(format!("Skill '{}' has no recorded Git source; supply a local path or Git URL interactively.", name))) } diff --git a/crates/ov_cli/src/commands/system.rs b/crates/ov_cli/src/commands/system.rs index 037cfae04d..5c6975fb9b 100644 --- a/crates/ov_cli/src/commands/system.rs +++ b/crates/ov_cli/src/commands/system.rs @@ -8,12 +8,11 @@ use serde_json::json; pub async fn wait( client: &HttpClient, - timeout: Option, output_format: OutputFormat, compact: bool, ) -> Result<()> { let response: serde_json::Value = client - .post("/api/v1/system/wait", &json!({ "timeout": timeout })) + .post("/api/v1/system/wait", &json!({})) .await?; output_success(&response, output_format, compact); Ok(()) diff --git a/crates/ov_cli/src/config.rs b/crates/ov_cli/src/config.rs index f4e4de4797..704998d585 100644 --- a/crates/ov_cli/src/config.rs +++ b/crates/ov_cli/src/config.rs @@ -151,19 +151,6 @@ pub(crate) fn timeout_is_valid(timeout: f64) -> bool { timeout > 0.0 && std::time::Duration::try_from_secs_f64(timeout).is_ok() } -pub(crate) fn parse_positive_timeout(value: &str) -> std::result::Result { - let timeout = value - .parse::() - .map_err(|_| format!("invalid timeout {value:?}: expected a positive number of seconds"))?; - if timeout_is_valid(timeout) { - Ok(timeout) - } else { - Err(format!( - "invalid timeout {value:?}: expected a positive finite number of seconds" - )) - } -} - fn default_output_format() -> String { "table".to_string() } diff --git a/crates/ov_cli/src/handlers.rs b/crates/ov_cli/src/handlers.rs index 8bf132c1fd..e56f4679b3 100644 --- a/crates/ov_cli/src/handlers.rs +++ b/crates/ov_cli/src/handlers.rs @@ -22,7 +22,6 @@ pub async fn handle_add_resource( reason: String, instruction: String, wait: bool, - timeout: Option, strict_mode: bool, ignore_dirs: Option, include: Option, @@ -86,11 +85,6 @@ pub async fn handle_add_resource( reject_manifest_run_keys(args)?; } - let effective_timeout = if wait { - timeout.unwrap_or(60.0).max(ctx.config.timeout) - } else { - ctx.config.timeout - }; let auth = ctx.config.effective_auth(ctx.sudo); let client = client::HttpClient::new( &ctx.config.url, @@ -98,7 +92,7 @@ pub async fn handle_add_resource( auth.account, auth.user, ctx.config.effective_actor_peer_id(), - effective_timeout, + ctx.config.timeout, ctx.profile.unwrap_or(ctx.config.profile), ctx.config.effective_extra_headers(), ) @@ -113,7 +107,6 @@ pub async fn handle_add_resource( reason, instruction, wait, - timeout, strict, effective_ignore_dirs, effective_include, @@ -420,8 +413,8 @@ use crate::SystemCommands; pub async fn handle_system(cmd: SystemCommands, ctx: CliContext) -> Result<()> { let client = ctx.get_client(); match cmd { - SystemCommands::Wait { timeout } => { - commands::system::wait(&client, timeout, ctx.output_format, ctx.compact).await + SystemCommands::Wait => { + commands::system::wait(&client, ctx.output_format, ctx.compact).await } SystemCommands::Status => { commands::system::status(&client, ctx.output_format, ctx.compact).await @@ -1387,7 +1380,6 @@ pub async fn handle_write( from_file: Option, mode: String, wait: bool, - timeout: Option, processing_mode: String, tags: Vec, tag_mode: String, @@ -1411,7 +1403,6 @@ pub async fn handle_write( &payload, &mode, wait, - timeout, &processing_mode, tags, &tag_mode, @@ -1877,7 +1868,6 @@ pub async fn handle_rm( uri: String, recursive: bool, wait: bool, - timeout: Option, ctx: CliContext, ) -> Result<()> { let client = ctx.get_client(); @@ -1886,7 +1876,6 @@ pub async fn handle_rm( &uri, recursive, wait, - timeout, ctx.output_format, ctx.compact, ) diff --git a/crates/ov_cli/src/help_ui.rs b/crates/ov_cli/src/help_ui.rs index 17f4015f13..222e781b00 100644 --- a/crates/ov_cli/src/help_ui.rs +++ b/crates/ov_cli/src/help_ui.rs @@ -908,8 +908,8 @@ const COMMAND_HELP_SPECS: &[CommandHelpSpec] = &[ path: &["wait"], purpose: "Wait for queued async processing to complete.", examples: &[HelpItem { - label: "ov wait --timeout 120", - description: "Wait up to two minutes.", + label: "ov wait", + description: "Wait for queued processing to complete.", }], next_steps: &[ HelpItem { @@ -2967,22 +2967,19 @@ mod tests { } #[test] - fn curated_help_lists_timeout_for_waiting_commands() { + fn curated_help_omits_timeout_for_waiting_commands() { for args in [ ["ov", "add-resource", "--help"], ["ov", "rm", "--help"], ["ov", "write", "--help"], + ["ov", "wait", "--help"], ] { let rendered = strip_ansi( &render_command_help_request(&os_args(&args)).expect("help should render"), ); assert!( - rendered.contains("--timeout "), - "missing timeout option in:\n{rendered}" - ); - assert!( - rendered.contains("seconds"), - "missing timeout description in:\n{rendered}" + !rendered.contains("--timeout"), + "unexpected timeout option in:\n{rendered}" ); } } diff --git a/crates/ov_cli/src/main.rs b/crates/ov_cli/src/main.rs index 00630c3edf..ca40f8e2a2 100644 --- a/crates/ov_cli/src/main.rs +++ b/crates/ov_cli/src/main.rs @@ -433,14 +433,6 @@ enum Commands { /// Wait until processing is complete #[arg(long, help_heading = "Common options")] wait: bool, - /// Request timeout in seconds. Used with --wait and by Manifest private Git imports - #[arg( - long, - value_parser = config::parse_positive_timeout, - value_name = "seconds", - help_heading = "Common options" - )] - timeout: Option, /// Enable strict mode for directory scanning (fail if any unsupported files found) #[arg( long = "strict", @@ -733,14 +725,6 @@ enum Commands { /// 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_parser = config::parse_positive_timeout, - value_name = "seconds", - help_heading = "Common options" - )] - timeout: Option, }, /// [Data] Copy a file or directory Cp { @@ -847,14 +831,6 @@ enum Commands { help_heading = "Advanced options" )] processing_mode: String, - /// Optional wait timeout in seconds - #[arg( - long, - value_parser = config::parse_positive_timeout, - value_name = "seconds", - help_heading = "Common options" - )] - timeout: Option, /// Comma-separated k=v retrieval tags to write with the content #[arg(long = "tags", value_delimiter = ',')] tags: Vec, @@ -1311,16 +1287,7 @@ enum Commands { // --- Status & Observability --- /// [Status] Wait for queued async processing to complete - Wait { - /// Wait timeout in seconds - #[arg( - long, - value_parser = config::parse_positive_timeout, - value_name = "seconds", - help_heading = "Common options" - )] - timeout: Option, - }, + Wait, /// [Status] Track async resource processing tasks Task { #[command(subcommand)] @@ -1569,11 +1536,7 @@ pub(crate) enum SnapshotCmd { #[derive(Subcommand)] enum SystemCommands { /// Wait for queued async processing to complete - Wait { - /// Wait timeout in seconds - #[arg(long, value_parser = config::parse_positive_timeout, value_name = "seconds")] - timeout: Option, - }, + Wait, /// Show component status Status, /// Quick health check @@ -3340,7 +3303,6 @@ async fn main() { reason, instruction, wait, - timeout, tags, tag_mode, acl, @@ -3373,7 +3335,6 @@ async fn main() { processing_mode, external_connector: run.external_connector, }, - timeout, ctx, ) .await @@ -3389,7 +3350,6 @@ async fn main() { reason, instruction, wait, - timeout, strict_mode, ignore_dirs, include, @@ -3537,9 +3497,9 @@ async fn main() { on_conflict, vector_mode, } => handlers::handle_restore(file_path, on_conflict, vector_mode, ctx).await, - Commands::Wait { timeout } => { + Commands::Wait => { let client = ctx.get_client(); - commands::system::wait(&client, timeout, ctx.output_format, ctx.compact).await + commands::system::wait(&client, ctx.output_format, ctx.compact).await } Commands::Task { action } => match action { TaskCommands::Status { task_id } => { @@ -3708,8 +3668,7 @@ async fn main() { uri, recursive, wait, - timeout, - } => handlers::handle_rm(uri, recursive, wait, timeout, ctx).await, + } => handlers::handle_rm(uri, recursive, wait, ctx).await, Commands::Cp { from_uri, to_uri, @@ -3821,7 +3780,6 @@ async fn main() { mode, wait, processing_mode, - timeout, tags, tag_mode, acl, @@ -3839,7 +3797,6 @@ async fn main() { from_file, effective_mode, wait, - timeout, processing_mode, tags, tag_mode, @@ -5254,37 +5211,28 @@ mod tests { } #[test] - fn all_timeout_options_require_positive_finite_seconds() { - let command_prefixes = [ - vec!["ov", "add-resource", "https://example.com", "--timeout"], - vec!["ov", "rm", "viking://resources/item", "--timeout"], + fn waiting_commands_reject_timeout_option() { + for mut args in [ + vec!["ov", "add-resource", "https://example.com"], + vec!["ov", "rm", "viking://resources/item"], vec![ "ov", "write", "viking://resources/item", "--content", "value", - "--timeout", ], - vec!["ov", "wait", "--timeout"], - vec!["ov", "system", "wait", "--timeout"], - ]; - - for prefix in command_prefixes { - for invalid in ["0", "-1", "inf", "NaN", "1e300"] { - let mut args = prefix.clone(); - args.push(invalid); - assert!( - Cli::try_parse_from(&args).is_err(), - "{args:?} should reject an invalid timeout" - ); - } - - let mut args = prefix; - args.push("0.1"); + vec!["ov", "wait"], + vec!["ov", "system", "wait"], + ] { assert!( Cli::try_parse_from(&args).is_ok(), - "{args:?} should accept a positive finite timeout" + "{args:?} should be accepted" + ); + args.extend(["--timeout", "60"]); + assert!( + Cli::try_parse_from(&args).is_err(), + "{args:?} should reject --timeout" ); } } diff --git a/crates/ov_cli/src/openviking_assets.rs b/crates/ov_cli/src/openviking_assets.rs index 1b292944b7..38394c3b8a 100644 --- a/crates/ov_cli/src/openviking_assets.rs +++ b/crates/ov_cli/src/openviking_assets.rs @@ -23,30 +23,11 @@ use crate::output::OutputFormat; pub const STATE_PROTOCOL: &str = "openviking-assets-state/1"; const DEFAULT_CATALOG_FILENAME: &str = "catalog.yaml"; const CREDENTIALS_ENV: &str = "OPENVIKING_ASSETS_CREDENTIALS_FILE"; -const NATIVE_AUTH_REQUEST_TIMEOUT_SECONDS: f64 = 300.0; fn client_err(msg: impl Into) -> Error { Error::Client(msg.into()) } -fn manifest_request_timeout( - wait: bool, - requested_timeout: Option, - configured_timeout: f64, - has_native_auth: bool, -) -> f64 { - let default_timeout = if has_native_auth { - NATIVE_AUTH_REQUEST_TIMEOUT_SECONDS - } else if wait { - 60.0 - } else { - 20.0 - }; - requested_timeout - .unwrap_or(default_timeout) - .max(configured_timeout) -} - // The server owns OpenViking Assets syntax parsing and semantic validation. // The CLI receives only the resolved execution plan. #[derive(Debug, Clone, Deserialize)] @@ -840,7 +821,6 @@ pub async fn apply_manifest_core( struct HttpSubmitter { client: crate::client::HttpClient, wait: bool, - timeout: Option, processing_mode: String, external_connector: bool, } @@ -884,7 +864,6 @@ impl Submitter for HttpSubmitter { "", "", self.wait, - self.timeout, false, None, None, @@ -1033,7 +1012,6 @@ pub async fn handle_manifest_apply( manifest: String, catalog_file: Option, options: ManifestRunOptions, - timeout: Option, ctx: CliContext, ) -> Result<()> { let manifest_path = PathBuf::from(&manifest); @@ -1065,9 +1043,7 @@ pub async fn handle_manifest_apply( }) .transpose()?; - let effective_timeout = - manifest_request_timeout(options.wait, timeout, ctx.config.timeout, false); - let client = ctx.get_client_with_timeout(Some(effective_timeout)); + let client = ctx.get_client(); let mut request = json!({ "manifest_yaml": manifest_yaml, "manifest_label": manifest_path.display().to_string(), @@ -1079,11 +1055,6 @@ pub async fn handle_manifest_apply( let resolved: ResolveResponse = client .post("/api/v1/openviking-assets/resolve", &request) .await?; - let has_native_auth = - !options.external_connector && resolved.assets.iter().any(|asset| asset.auth_ref.is_some()); - let effective_timeout = - manifest_request_timeout(options.wait, timeout, ctx.config.timeout, has_native_auth); - let client = ctx.get_client_with_timeout(Some(effective_timeout)); let json_mode = matches!(ctx.output_format, OutputFormat::Json); let mut emit = |event: Value| { @@ -1098,7 +1069,6 @@ pub async fn handle_manifest_apply( let submitter = HttpSubmitter { client, wait: options.wait, - timeout, processing_mode: options.processing_mode.clone(), external_connector: options.external_connector, }; @@ -1148,20 +1118,6 @@ mod tests { assert_eq!(effective_add_type(true, "tos"), Some("tos".to_string())); } - #[test] - fn native_auth_requests_get_a_long_default_and_honor_explicit_timeout() { - assert_eq!(manifest_request_timeout(false, None, 60.0, false), 60.0); - assert_eq!(manifest_request_timeout(false, None, 60.0, true), 300.0); - assert_eq!( - manifest_request_timeout(false, Some(900.0), 60.0, true), - 900.0 - ); - assert_eq!( - manifest_request_timeout(false, Some(120.0), 180.0, true), - 180.0 - ); - } - #[test] fn build_args_uses_manifest_commit_as_authoritative_selector() { let (_dir, _manifest, _catalog, mut assets) = workspace(); diff --git a/crates/ov_cli/src/tui/app.rs b/crates/ov_cli/src/tui/app.rs index fd0e0f0fb1..15aa8c775c 100644 --- a/crates/ov_cli/src/tui/app.rs +++ b/crates/ov_cli/src/tui/app.rs @@ -624,7 +624,7 @@ impl App { } }; - match client.rm(&selected_uri, is_dir, false, None).await { + match client.rm(&selected_uri, is_dir, false).await { Ok(_) => { self.set_status_message(format!("Deleted: {}", selected_uri)); diff --git a/docs/en/api/02-resources.md b/docs/en/api/02-resources.md index 2964d892e2..183a075b70 100644 --- a/docs/en/api/02-resources.md +++ b/docs/en/api/02-resources.md @@ -83,7 +83,7 @@ Source Input -> Parse -> Resource Tree Build -> Persistence -> Semantic Processi OpenViking first checks whether the source and request parameters qualify for Connector ingestion. Connector runs ingestion outside the standard pipeline. Otherwise, formats selected by [`parser_api`](../guides/01-configuration.md#parser-api) use the external Understanding parser; remaining formats use built-in parsers. External parsing replaces only the Parse stage and returns to TreeBuilder and the standard storage/semantic pipeline. Feishu documents already normalized to Markdown by an accessor bypass external parsing. -Connector requires an exact `to` target and does not support `wait=true`. A `tos://` source cannot fall back to a built-in accessor. Git may fall back when Connector does not apply, except when Connector-only credentials were supplied. For supported standard-pipeline requests, `wait=true` waits for work belonging to this import, not unrelated work in the global queues; a timeout does not imply that the import was cancelled. +Connector requires an exact `to` target and does not support `wait=true`. A `tos://` source cannot fall back to a built-in accessor. Git may fall back when Connector does not apply, except when Connector-only credentials were supplied. For supported standard-pipeline requests, `wait=true` waits for work belonging to this import, not unrelated work in the global queues; an HTTP request timeout does not imply that the import was cancelled. There is no per-import `timeout` parameter; configure HTTP timeouts on the client. #### Stage 1: Parse - Uses `UnifiedResourceProcessor` to parse content based on resource type @@ -187,7 +187,6 @@ This endpoint is the core entry point for resource management. It supports vario | 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` | | strict | bool | No | False | Whether to use strict mode | | ignore_dirs | string | No | None | Directory names to ignore (comma-separated) | | include | string | No | None | File patterns to include (glob) | diff --git a/docs/en/api/03-filesystem.md b/docs/en/api/03-filesystem.md index 7af3faa1b0..6287e0a503 100644 --- a/docs/en/api/03-filesystem.md +++ b/docs/en/api/03-filesystem.md @@ -596,7 +596,6 @@ Invalid URI formats, unsupported schemes, and internal scopes such as `temp` or |-----------|------|----------|---------|-------------| | uri | str | Yes | - | Viking URI to remove | | wait | bool | No | False | Wait for the semantic refresh after deletion | -| timeout | float | No | None | Refresh timeout in seconds when `wait=true` | | recursive | bool | No | False | Remove directory recursively | diff --git a/docs/en/api/04-skills.md b/docs/en/api/04-skills.md index 51e23d7f43..611ad2c78c 100644 --- a/docs/en/api/04-skills.md +++ b/docs/en/api/04-skills.md @@ -189,7 +189,6 @@ This endpoint stores Skill instructions and supporting files, then generates sum | data | Any | No | - | Inline SKILL.md, structured/MCP data, or Git URL; choose this or `temp_file_id` | | temp_file_id | str | No | - | Temporary upload file ID (from `temp_upload`); choose this or `data` | | wait | bool | No | False | Wait for skill processing to complete | -| timeout | float | No | None | Timeout in seconds, only effective when `wait=true` | | telemetry | TelemetryRequest | No | False | Whether to return telemetry data | | target_uri | str | No | Target resolution order | Skills root, such as `viking://~/skills` or `viking://agent/skills` | | skills | List[str] | No | `[]` | Select directory names from a directory/Git collection; empty or `["*"]` selects all | @@ -228,7 +227,7 @@ Local `.json` files are not automatically decoded into structured Skills. Parse Single-Skill imports return a `task_id` by default. Query the [Task API](17-tasks.md) and search after the task reaches `completed`. With `wait=true`, the call waits for queue processing and returns `queue_status`. Multiple imports return an `installed` array with independent task information per result; the batch is not atomic. `list_only=true` returns source `skills` and `total` without creating import tasks. -HTTP fields and SDK parameters do not have identical names or nesting. Python `add_skill(data, wait=False, timeout=None, options=None)` / `update_skill(skill_name, data, ...)` take `target_uri` and `telemetry` inside `options`. Put fields such as `skills`, `list_only`, `source_metadata` (add), and `from_source` (update) in `options["extra"]`. TypeScript uses `targetUri` and `extra`; Go uses `TargetURI` and `Extra`. TypeScript local path uploads require Node.js. +HTTP fields and SDK parameters do not have identical names or nesting. Python `add_skill(data, wait=False, options=None)` / `update_skill(skill_name, data, ...)` take `target_uri` and `telemetry` inside `options`. Put fields such as `skills`, `list_only`, `source_metadata` (add), and `from_source` (update) in `options["extra"]`. TypeScript uses `targetUri` and `extra`; Go uses `TargetURI` and `Extra`. TypeScript local path uploads require Node.js. ```python # Preview a local collection without writing to OpenViking @@ -488,7 +487,7 @@ If skill parsing or processing fails synchronously, raw HTTP returns the standar The Python HTTP SDK raises the corresponding mapped exception for this response. -Error codes depend on the failure stage: invalid names/targets use `INVALID_ARGUMENT` / `INVALID_URI`, missing Skills use `NOT_FOUND`, oversized integrity manifests use `RESOURCE_EXHAUSTED`, and wait timeouts use `DEADLINE_EXCEEDED`. Success with `wait=false` only confirms the synchronous import stage; inspect task status for background failures. Standard responses may include optional `telemetry` / `profile`, but have no generic `time` field. SDKs normally return the `result` object directly. +Error codes depend on the failure stage: invalid names/targets use `INVALID_ARGUMENT` / `INVALID_URI`, missing Skills use `NOT_FOUND`, oversized integrity manifests use `RESOURCE_EXHAUSTED`. Success with `wait=false` only confirms the synchronous import stage; inspect task status for background failures. Standard responses may include optional `telemetry` / `profile`, but have no generic `time` field. SDKs normally return the `result` object directly. ## Skill Management Operations @@ -704,12 +703,12 @@ Missing names/descriptions and invalid YAML produce errors. Directory/name misma | `from_source` | `false` | Refresh from recorded Git source; cannot be combined with data/temp_file_id | | `target_uri` | Unspecified | Skills root to try first; see read resolution fallback | | `source_metadata` | Generated automatically | Updated source tracking | -| `wait` / `timeout` | `false` / `null` | Wait for background processing and timeout in seconds | +| `wait` | `false` | Wait for background processing | | `telemetry` | `false` | Telemetry configuration | Supply new content/an upload, or set `from_source=true`. The new name must match `skill_name` in the URL; update is neither rename nor a partial patch. The server parses and checks the new package before backing up and replacing the old one, and attempts restoration on synchronous failure. Include all auxiliary files that should remain in the replacement package. -Background processing starts after the update's files, privacy configuration, and task setup are ready. When an update needs restoration, including a `wait=true` timeout, it cancels that update's summary and indexing work, waits for started writes to exit, and restores the original files, index, and privacy configuration. Restoration failures are reported. The timeout response may arrive after `timeout` while cancellation settles; it does not wait for the entire package to finish processing. Background failures after a successful `wait=false` response do not trigger restoration. A timeout when adding a new Skill still only ends the wait. +Background processing starts after the update's files, privacy configuration, and task setup are ready. When an update fails or is cancelled while waiting, it cancels that update's summary and indexing work, waits for started writes to exit, and restores the original files, index, and privacy configuration. Restoration failures are reported. Background failures after a successful `wait=false` response do not trigger restoration. **Python SDK** diff --git a/docs/en/api/07-system.md b/docs/en/api/07-system.md index d87aefa607..b656b4eaf3 100644 --- a/docs/en/api/07-system.md +++ b/docs/en/api/07-system.md @@ -331,7 +331,7 @@ ov system consistency viking://resources/my-project #### 1. API Implementation Overview -Wait until the processing queues are drained, or until the timeout. This check is not scoped to the caller’s last request and does not cover every asynchronous API. Check the returned `error_count` and `errors`; empty queues do not prove that every operation succeeded. To confirm one import or commit, poll its [task ID](17-tasks.md) instead. +Wait until the processing queues are drained. This check is not scoped to the caller’s last request and does not cover every asynchronous API. Check the returned `error_count` and `errors`; empty queues do not prove that every operation succeeded. To confirm one import or commit, poll its [task ID](17-tasks.md) instead. **Code Entry Points**: - `openviking/server/routers/system.py:wait_processed` - HTTP route @@ -342,9 +342,7 @@ Wait until the processing queues are drained, or until the timeout. This check i **Parameters** -| Parameter | Type | Required | Default | Description | -|-----------|------|----------|---------|-------------| -| timeout | float | No | None | Timeout in seconds. None means wait indefinitely | +None. HTTP requests use the client timeout configuration. #### 3. Usage Examples @@ -357,32 +355,27 @@ POST /api/v1/system/wait ```bash curl -X POST http://localhost:1933/api/v1/system/wait \ -H "Content-Type: application/json" \ - -H "X-API-Key: your-key" \ - -d '{ - "timeout": 60.0 - }' + -H "X-API-Key: your-key" ``` **Python SDK** ```python # Wait for the current processing queues to drain -status = client.wait_processed(timeout=60.0) +status = client.wait_processed() print(status) # Inspect each queue’s error_count and errors ``` **TypeScript SDK** ```typescript -console.log(await client.waitProcessed(60)); +console.log(await client.waitProcessed()); ``` **Go SDK** ```go -status, err := client.WaitProcessed(ctx, &openviking.WaitProcessedOptions{ - Timeout: openviking.Float64(60), -}) +status, err := client.WaitProcessed(ctx) if err != nil { return err } @@ -392,7 +385,7 @@ fmt.Println(status) **CLI** ```bash -ov system wait --timeout 60 +ov system wait ``` **Response Example** diff --git a/docs/en/api/12-content.md b/docs/en/api/12-content.md index 470eda6c3a..f2af2eab09 100644 --- a/docs/en/api/12-content.md +++ b/docs/en/api/12-content.md @@ -218,7 +218,6 @@ Write a file and automatically refresh related semantics and vectors. | content | str | Yes | - | New content to write | | mode | str | No | `replace` | `replace` overwrites an existing file or creates a missing file; `append` appends to an existing file or creates a missing file; `create` is a compatibility alias for `replace` | | wait | bool | No | `false` | Wait for background semantic/vector refresh | -| timeout | float | No | `null` | Timeout in seconds when `wait=true` | | tags | string[] | No | Unset | Explicit retrieval tags for the written file, for example `["team=search", "env=prod"]` | | tag_mode | string | No | `replace` | Tag update mode: `replace` overwrites tags, `append` merges by key, and `clear` removes existing tags without requiring `tags` | @@ -346,7 +345,6 @@ Write multiple files below one Resource or Memory directory, then refresh the af | `root_uri` | string | Yes | - | Existing Resource or Memory directory containing every target | | `operations` | array | Yes | - | File writes to validate and apply | | `wait` | boolean | No | `true` | Wait for semantic/vector refresh | -| `timeout` | number | No | `null` | Refresh timeout in seconds when `wait=true` | | `telemetry` | boolean/object | No | `false` | Include operation telemetry | | `processing_mode` | string | No | `semantic_and_vectors` | One processing mode for the whole batch: `semantic_and_vectors` or `vectors_only` | diff --git a/docs/en/api/99-api-doc-writing-guide.md b/docs/en/api/99-api-doc-writing-guide.md index b7231248d8..18215e0640 100644 --- a/docs/en/api/99-api-doc-writing-guide.md +++ b/docs/en/api/99-api-doc-writing-guide.md @@ -148,7 +148,7 @@ Use the following sections for each API. Add implementation details after the re #### 3. Usage Examples -For operations that return background tasks, default examples should submit a task and query its status. Avoid explicitly setting `wait` or `timeout`, and do not use global `wait_processed()` as a substitute for task status. Keep supported parameters and their actual defaults in the full reference. When later operations depend on asynchronous output, show polling by `task_id` until `completed` and handle `failed` and `cancelled`; do not just remove waiting parameters and immediately read the result. +For operations that return background tasks, default examples should submit a task and query its status. Avoid explicitly setting `wait`, and do not use global `wait_processed()` as a substitute for task status. Keep supported parameters and their actual defaults in the full reference. When later operations depend on asynchronous output, show polling by `task_id` until `completed` and handle `failed` and `cancelled`; do not just remove waiting parameters and immediately read the result. When an operation presents all transports together, prefer this order: - Python SDK example diff --git a/docs/en/getting-started/02-quickstart.md b/docs/en/getting-started/02-quickstart.md index 240a064d88..b0e9b9f59c 100644 --- a/docs/en/getting-started/02-quickstart.md +++ b/docs/en/getting-started/02-quickstart.md @@ -76,7 +76,7 @@ Release owners rotate in this order: qin-ctx, zhoujh01, ZaynJarvis, t0saki. Import it into a new resource directory: ```bash -ov add-resource ./ov-release-rotation.md --to viking://resources/ov-release-rotation --wait --timeout 120 +ov add-resource ./ov-release-rotation.md --to viking://resources/ov-release-rotation --wait ``` The CLI uploads the local file automatically. `--wait` waits for processing; continue after the command succeeds. Without it, save the returned `task_id` and use `ov task status ` until the task is `completed`. See [Background Tasks](../api/17-tasks.md). diff --git a/docs/en/guides/02-volcengine-purchase-guide.md b/docs/en/guides/02-volcengine-purchase-guide.md index 3a7a3fdba9..8c2700d901 100644 --- a/docs/en/guides/02-volcengine-purchase-guide.md +++ b/docs/en/guides/02-volcengine-purchase-guide.md @@ -180,7 +180,7 @@ openviking-server In another terminal, configure the CLI and create `quickstart.md` using the [Quick Start](../getting-started/02-quickstart.md). Import it into an unused target: ```bash -ov add-resource ./quickstart.md --to viking://resources/model-check --wait --timeout 120 +ov add-resource ./quickstart.md --to viking://resources/model-check --wait ov find "Who owns the backup process?" --uri viking://resources/model-check ``` diff --git a/docs/en/guides/06-mcp-integration.md b/docs/en/guides/06-mcp-integration.md index 175f7b06f8..8505ad5441 100644 --- a/docs/en/guides/06-mcp-integration.md +++ b/docs/en/guides/06-mcp-integration.md @@ -132,8 +132,8 @@ The built-in MCP tools are listed below. Use the connected server's `tools/list` | `list` | List entries under a `viking://` directory | `uri`, `recursive`, `offset`, `limit`, `sort_by`, `sort_order` (optional) | | `tree` | Show the recursive directory tree under a `viking://` URI, indented by depth — use when you need a full picture of the file tree (prefer `list` for a single level, `glob` for filename patterns) | `uri` (optional), `level_limit` (default 3), `node_limit` (default 1000), `offset`, `limit`, `include_abstract` (optional — also show each directory's summary; for a skill directory that is its name and description) | | `remember` | Submit messages for long-term memory extraction; returns the background extraction `task_id` right away | `messages` (list of `{role, content}`) | -| `write` | Write text to a `viking://` file (create/overwrite/append). Parent directories are created automatically; use `read` first to see current content before overwriting, and prefer `edit` for changing part of an existing file. Skill packages are not maintained this way: the caller's own `skills/` subtree is refused, and a write under `viking://agent/skills` produces a plain file that skips skill installation — use `add_skill` | `uri`, `content`, `mode` (optional: `replace` default — overwrites or creates if missing; `append` — appends or creates if missing; `create` — compatibility alias for `replace`), `wait` (optional, block until re-indexed), `timeout` (optional), `acl` (optional) | -| `edit` | Replace an exact string with new text in an existing `viking://` file — for targeted changes instead of a full rewrite. The file is left unchanged if `old_string` is not found, or matches multiple times while `replace_all` is false. Editing a file inside a skill package does not re-run skill installation — use `add_skill` | `uri`, `old_string`, `new_string`, `replace_all` (optional), `wait` (optional, block until re-indexed), `timeout` (optional) | +| `write` | Write text to a `viking://` file (create/overwrite/append). Parent directories are created automatically; use `read` first to see current content before overwriting, and prefer `edit` for changing part of an existing file. Skill packages are not maintained this way: the caller's own `skills/` subtree is refused, and a write under `viking://agent/skills` produces a plain file that skips skill installation — use `add_skill` | `uri`, `content`, `mode` (optional: `replace` default — overwrites or creates if missing; `append` — appends or creates if missing; `create` — compatibility alias for `replace`), `wait` (optional, block until re-indexed), `acl` (optional) | +| `edit` | Replace an exact string with new text in an existing `viking://` file — for targeted changes instead of a full rewrite. The file is left unchanged if `old_string` is not found, or matches multiple times while `replace_all` is false. Editing a file inside a skill package does not re-run skill installation — use `add_skill` | `uri`, `old_string`, `new_string`, `replace_all` (optional), `wait` (optional, block until re-indexed) | | `add_resource` | Add a local file or URL as a resource (local files trigger a progressive upload flow) | `path`, `temp_file_id` (optional), `description` (optional), `watch_interval` (optional, minutes — auto-refresh cadence for remote URLs), `processing_mode` (optional: `semantic_and_vectors` default, or `vectors_only` to skip VLM semantic understanding and only vectorize current files), `to` (optional, target `viking://resources/...` URI; if omitted when `watch_interval > 0`, the watch auto-binds to the resource's created URI), `args` (optional parser-specific options, including `{"parse_mode":"no_split"}` to parse each source document into one Markdown body, `{"feishu_access_token":"u-..."}` for one-time Feishu user-token imports, or access/refresh tokens plus an optional `feishu_app_id` / `feishu_app_secret` pair for Feishu user-token watches), `acl` (optional) | | `add_skill` | Create, install, or replace an agent skill. New skills pass the full SKILL.md text; Git and GitHub tree URLs install every skill in the source unless `skills` names some; a local SKILL.md, directory, or zip returns a signed upload URL like `add_resource` | `data` (SKILL.md text) or `path` (Git URL or local path), `skills` (optional), `target_uri` (optional; `viking://agent/skills` shares with the account), `list_only` (optional) | | `list_watches` | List watch tasks (auto-refresh subscriptions) visible to the current agent. Each entry shows target URI, refresh interval (minutes), active/paused status, and next scheduled execution time | none | diff --git a/docs/en/guides/09-ovpack.md b/docs/en/guides/09-ovpack.md index 8b117348f2..7be1daf89f 100644 --- a/docs/en/guides/09-ovpack.md +++ b/docs/en/guides/09-ovpack.md @@ -213,7 +213,7 @@ def migrate_project(): vector_mode="auto", ) print(imported_uri) - print(client.wait_processed(timeout=120)) + print(client.wait_processed()) print(client.check_consistency(uri=imported_uri)) finally: client.close() diff --git a/docs/en/guides/15-snapshot.md b/docs/en/guides/15-snapshot.md index 450d6efc9e..2348f9a062 100644 --- a/docs/en/guides/15-snapshot.md +++ b/docs/en/guides/15-snapshot.md @@ -177,7 +177,7 @@ client.write( content="# Guide\n\nv1 content\n", mode="create", ) -print(client.wait_processed(timeout=120)) +print(client.wait_processed()) v1 = client.snapshot.commit(message="v1 initial import", paths=[root]) if not v1.get("commit_oid"): raise RuntimeError(f"No snapshot created: {v1}") @@ -189,7 +189,7 @@ client.write( content="# Guide\n\nv2 content\n", mode="replace", ) -print(client.wait_processed(timeout=120)) +print(client.wait_processed()) v2 = client.snapshot.commit(message="v2 update", paths=[root]) # 3. Walk history diff --git a/docs/en/guides/18-openviking-assets.md b/docs/en/guides/18-openviking-assets.md index 00163fc82e..49bc8b6729 100644 --- a/docs/en/guides/18-openviking-assets.md +++ b/docs/en/guides/18-openviking-assets.md @@ -300,17 +300,17 @@ deployments and restrict local access to the credentials file. When the effective `watch_interval` is positive, OpenViking stores an HTTPS Git token resolved from `auth_ref` in the Watch task's private, repository-bound authentication state. The token is not written to Manifest State, ordinary ingestion queues, or watch API/MCP/CLI responses. A zero -interval keeps the token request-local. Git PATs have no generic refresh flow, so recreate the -Watch when a token expires or is revoked. +interval keeps the token in the import task's private authentication state for background execution. +Git PATs have no generic refresh flow, so recreate the Watch when a token expires or is revoked. Private watch state is stored in `viking://resources/.watch_tasks.json`. It is encrypted at rest when VikingFS file encryption is enabled; otherwise the server-side control file and its backup contain plaintext token state. Restrict server storage access and enable encryption in production. -Native credential imports wait for clone and parse before the server returns a task, even without -`--wait`; the CLI therefore uses a 300-second request timeout by default for these assets. Use -`--timeout ` for a larger repository. The token is carried in the HTTPS request body, so -keep diagnostic request-body dumping disabled in production. +Native credential imports return a `task_id` after permission preflight and enqueueing; clone and +parse run in the background. `--wait` waits for task completion. HTTP requests use the client timeout +configuration; `add-resource` has no `--timeout` option. The token is carried in the HTTPS request body, +so keep diagnostic request-body dumping disabled in production. Omit `auth_ref` when the target service already has the SSH keys or other authentication needed to access the repository. @@ -415,7 +415,6 @@ Options used with `--manifest`: | `-m, --manifest ` | Manifest file. | | `--args ` | Manifest-run options, comma-separated; supported keys below. | | `--wait` | Wait for each resource to finish processing. | -| `--timeout ` | HTTP request timeout. Native private Git imports honor it even without `--wait`; their default is 300 seconds. | | `--watch-interval ` | Override the refresh interval for all assets. | Run options supported by `--args`: diff --git a/docs/zh/api/02-resources.md b/docs/zh/api/02-resources.md index 55b96a3e59..6755b83335 100644 --- a/docs/zh/api/02-resources.md +++ b/docs/zh/api/02-resources.md @@ -83,7 +83,7 @@ URL/文件 Parser TreeBuilder AGFS Summarizer/Vector OpenViking 先检查来源和请求参数是否满足 Connector 接入条件。Connector 在标准流水线之外完成导入。其余请求中,[`parser_api`](../guides/01-configuration.md#parser-api) 选中的格式交给外部 Understanding 解析,剩余格式使用内置 Parser。外部解析只替换 Parse 阶段,结果仍进入 TreeBuilder 和后续存储、语义处理。已经由 Accessor 归一化为 Markdown 的飞书文档跳过外部解析。 -Connector 要求精确的 `to` 目标,不支持 `wait=true`。`tos://` 来源不能回退到内置 Accessor。Git 未命中 Connector 时可以回退,但携带 Connector 专用凭证时禁止回退。对于支持等待的标准流水线请求,`wait=true` 只等待本次导入的工作,不等待全局队列中的无关任务;等待超时不代表导入已取消。 +Connector 要求精确的 `to` 目标,不支持 `wait=true`。`tos://` 来源不能回退到内置 Accessor。Git 未命中 Connector 时可以回退,但携带 Connector 专用凭证时禁止回退。对于支持等待的标准流水线请求,`wait=true` 只等待本次导入的工作,不等待全局队列中的无关任务;HTTP 请求超时不代表导入已取消。 #### 阶段 1:源解析 (Parse) - 使用 `UnifiedResourceProcessor` 根据资源类型解析内容 @@ -186,8 +186,7 @@ Connector 要求精确的 `to` 目标,不支持 `wait=true`。`tos://` 来源 | source_name | string | 否 | None | 来源显示名;上传文件省略时使用原始文件名 | | reason | string | 否 | "" | 添加资源的原因;非空时会随资源 URI 进入常规 session 记忆抽取链路,并在生成的记忆中记录资源引用 | | instruction | string | 否 | "" | 语义提取的处理指令(实验特性) | -| wait | bool | 否 | False | 是否等待语义处理和向量化完成才返回 | -| timeout | float | 否 | None | 超时时间(秒),仅 `wait=true` 时生效 | +| wait | bool | 否 | False | 是否等待语义处理和向量化完成才返回;不支持单独的 `timeout` 参数,HTTP 超时使用客户端配置 | | strict | bool | 否 | False | 是否使用严格模式 | | ignore_dirs | string | 否 | None | 要忽略的目录名(逗号分隔) | | include | string | 否 | None | 包含的文件模式(glob) | diff --git a/docs/zh/api/03-filesystem.md b/docs/zh/api/03-filesystem.md index 7a6e125bd3..164a49babd 100644 --- a/docs/zh/api/03-filesystem.md +++ b/docs/zh/api/03-filesystem.md @@ -631,7 +631,6 @@ URI 格式非法、scheme 不支持,或使用 `temp`、`queue` 等内部作用 |------|------|------|--------|------| | uri | str | 是 | - | 要删除的 Viking URI | | wait | bool | 否 | False | 等待删除后的语义刷新完成 | -| timeout | float | 否 | None | `wait=true` 时的刷新超时,单位秒 | | recursive | bool | 否 | False | 递归删除目录 | diff --git a/docs/zh/api/04-skills.md b/docs/zh/api/04-skills.md index 4b69b10ee4..69b0c73a4c 100644 --- a/docs/zh/api/04-skills.md +++ b/docs/zh/api/04-skills.md @@ -189,7 +189,6 @@ This tool wraps the MCP tool `search-web`. Call this when the user needs functio | data | Any | 否 | - | 内联 SKILL.md、结构化/MCP 数据或 Git URL;与 `temp_file_id` 选择一个 | | temp_file_id | string | 否 | - | 临时上传文件 ID(通过 `temp_upload` 获取);与 `data` 选择一个 | | wait | bool | 否 | False | 是否等待技能处理完成 | -| timeout | float | 否 | None | 超时时间(秒),仅 `wait=True` 时生效 | | telemetry | TelemetryRequest | 否 | False | 是否返回遥测数据 | | target_uri | string | 否 | 按目标优先级解析 | 目标 skills 根,例如 `viking://~/skills` 或 `viking://agent/skills` | | skills | List[str] | 否 | `[]` | 从目录/Git 集合按目录名选择 Skill;空列表或 `["*"]` 选择全部 | @@ -229,7 +228,7 @@ This tool wraps the MCP tool `search-web`. Call this when the user needs functio 单个 Skill 导入默认返回 `task_id`。通过 [任务 API](17-tasks.md) 查询状态,任务为 `completed` 后再检索;`wait=true` 则等待队列处理并返回 `queue_status`。多 Skill 导入返回 `installed` 数组,每个结果独立携带任务信息,不保证批次原子性。`list_only=true` 只返回来源中的 `skills` 与 `total`,不创建导入任务。 -HTTP 字段和 SDK 参数并非同名同层级:Python `add_skill(data, wait=False, timeout=None, options=None)` / `update_skill(skill_name, data, ...)` 将 `target_uri`、`telemetry` 放在 `options` 中;`skills`、`list_only`、`source_metadata`(新增)及 `from_source`(更新)等字段放在 `options["extra"]` 中。TypeScript 使用 `targetUri` 和 `extra`,Go 使用 `TargetURI` 和 `Extra`。TypeScript 的本地路径自动上传仅适用于 Node.js。 +HTTP 字段和 SDK 参数并非同名同层级:Python `add_skill(data, wait=False, options=None)` / `update_skill(skill_name, data, ...)` 将 `target_uri`、`telemetry` 放在 `options` 中;`skills`、`list_only`、`source_metadata`(新增)及 `from_source`(更新)等字段放在 `options["extra"]` 中。TypeScript 使用 `targetUri` 和 `extra`,Go 使用 `TargetURI` 和 `Extra`。TypeScript 的本地路径自动上传仅适用于 Node.js。 ```python # 预览本地集合;不写入 OpenViking @@ -488,7 +487,7 @@ task_id uuid-xxx Python HTTP SDK 会把该响应映射为对应异常(`ProcessingError`)。 -具体错误码取决于失败阶段,例如名称或目标参数无效为 `INVALID_ARGUMENT` / `INVALID_URI`,找不到 Skill 为 `NOT_FOUND`,完整性清单超限为 `RESOURCE_EXHAUSTED`,等待超时为 `DEADLINE_EXCEEDED`。`wait=false` 返回成功只表示同步导入阶段完成,后台失败需通过任务状态确认。标准响应可能带可选 `telemetry` / `profile`,没有通用 `time` 字段;SDK 通常直接返回 `result` 对象。 +具体错误码取决于失败阶段,例如名称或目标参数无效为 `INVALID_ARGUMENT` / `INVALID_URI`,找不到 Skill 为 `NOT_FOUND`,完整性清单超限为 `RESOURCE_EXHAUSTED`。`wait=false` 返回成功只表示同步导入阶段完成,后台失败需通过任务状态确认。标准响应可能带可选 `telemetry` / `profile`,没有通用 `time` 字段;SDK 通常直接返回 `result` 对象。 ## 技能管理操作 @@ -706,12 +705,12 @@ curl -X POST http://localhost:1933/api/v1/skills/find \ | `from_source` | `false` | 根据已记录的 Git 来源更新;不能与 data/temp_file_id 同用 | | `target_uri` | 未指定 | 优先查找的 skills 根,参见读取接口的回退规则 | | `source_metadata` | 自动生成 | 更新来源信息 | -| `wait` / `timeout` | `false` / `null` | 是否等待后台处理及超时秒数 | +| `wait` | `false` | 是否等待后台处理 | | `telemetry` | `false` | 遥测配置 | 必须提供新内容/上传包,或设 `from_source=true`。新内容的名称必须与 URL 中的 `skill_name` 一致;更新不是重命名或局部 patch。先解析并检查新包,再备份替换;同步失败会尝试恢复旧包。需保留的辅助文件应随新包一起提交。 -更新的文件、隐私配置和任务准备完成后才启动后台。需要恢复旧包时,包括 `wait=true` 超时,先取消本次摘要和索引任务,确认已开始的写入退出,再恢复原文件、索引及隐私配置;恢复失败会明确报告。超时响应可能晚于 `timeout`,但只等待取消收尾,不继续处理完整包。`wait=false` 已成功返回后的后台失败不自动恢复;新增 Skill 的等待超时仍只结束等待。 +更新的文件、隐私配置和任务准备完成后才启动后台。更新失败或等待期间被取消时,先取消本次摘要和索引任务,确认已开始的写入退出,再恢复原文件、索引及隐私配置;恢复失败会明确报告。`wait=false` 已成功返回后的后台失败不自动恢复。 **Python SDK**: diff --git a/docs/zh/api/07-system.md b/docs/zh/api/07-system.md index 420fac892e..62ecc611f5 100644 --- a/docs/zh/api/07-system.md +++ b/docs/zh/api/07-system.md @@ -322,7 +322,7 @@ ov system consistency viking://resources/my-project #### 1. API 实现介绍 -等待处理队列排空,或在超时后结束。该检查不限定为调用方刚提交的请求,也不覆盖所有异步 API。需检查返回的 `error_count` 和 `errors`,队列排空不代表所有操作成功。确认某次导入或 commit 的结果,应查询它的[任务 ID](17-tasks.md)。 +等待处理队列排空。该检查不限定为调用方刚提交的请求,也不覆盖所有异步 API。需检查返回的 `error_count` 和 `errors`,队列排空不代表所有操作成功。确认某次导入或 commit 的结果,应查询它的[任务 ID](17-tasks.md)。 **代码入口**: - `openviking/server/routers/system.py:wait_processed` - HTTP 路由 @@ -333,9 +333,7 @@ ov system consistency viking://resources/my-project **参数** -| 参数 | 类型 | 必填 | 默认值 | 说明 | -|------|------|------|--------|------| -| timeout | float | 否 | None | 超时时间(秒),None 表示无限等待 | +无参数。HTTP 请求超时使用客户端配置。 #### 3. 使用示例 @@ -348,32 +346,27 @@ POST /api/v1/system/wait ```bash curl -X POST http://localhost:1933/api/v1/system/wait \ -H "Content-Type: application/json" \ - -H "X-API-Key: your-key" \ - -d '{ - "timeout": 60.0 - }' + -H "X-API-Key: your-key" ``` **Python SDK** ```python # 等待当前处理队列排空 -status = client.wait_processed(timeout=60.0) +status = client.wait_processed() print(status) # 检查各队列的 error_count 和 errors ``` **TypeScript SDK** ```typescript -console.log(await client.waitProcessed(60)); +console.log(await client.waitProcessed()); ``` **Go SDK** ```go -status, err := client.WaitProcessed(ctx, &openviking.WaitProcessedOptions{ - Timeout: openviking.Float64(60), -}) +status, err := client.WaitProcessed(ctx) if err != nil { return err } @@ -383,7 +376,7 @@ fmt.Println(status) **CLI** ```bash -ov system wait --timeout 60 +ov system wait ``` **响应示例** diff --git a/docs/zh/api/12-content.md b/docs/zh/api/12-content.md index 4991f5db88..71566e406d 100644 --- a/docs/zh/api/12-content.md +++ b/docs/zh/api/12-content.md @@ -218,7 +218,6 @@ ov read viking://resources/docs/api.md | content | str | 是 | - | 要写入的新内容 | | mode | str | 否 | `replace` | `replace` 覆盖已有文件、缺失时创建;`append` 追加已有文件、缺失时创建;`create` 是 `replace` 的兼容别名 | | wait | bool | 否 | `false` | 是否等待后台语义/向量刷新完成 | -| timeout | float | 否 | `null` | 当 `wait=true` 时的超时时间(秒) | | tags | string[] | 否 | 未设置 | 写入文件的显式检索标签,例如 `["team=search", "env=prod"]` | | tag_mode | string | 否 | `replace` | 标签更新方式:`replace` 覆盖、`append` 按 key 合并、`clear` 清空已有标签且不要求传 `tags` | @@ -346,7 +345,6 @@ ov write viking://resources/docs/api.md \ | `root_uri` | string | 是 | - | 包含所有写入目标的已存在 Resource 或 Memory 目录 | | `operations` | array | 是 | - | 要校验并执行的文件写入 | | `wait` | boolean | 否 | `true` | 是否等待语义/向量刷新 | -| `timeout` | number | 否 | `null` | `wait=true` 时的刷新超时时间(秒) | | `telemetry` | boolean/object | 否 | `false` | 是否返回操作遥测信息 | | `processing_mode` | string | 否 | `semantic_and_vectors` | 整批统一的处理模式:`semantic_and_vectors` 或 `vectors_only` | diff --git a/docs/zh/api/99-api-doc-writing-guide.md b/docs/zh/api/99-api-doc-writing-guide.md index ae740078ce..88513bdd1c 100644 --- a/docs/zh/api/99-api-doc-writing-guide.md +++ b/docs/zh/api/99-api-doc-writing-guide.md @@ -147,7 +147,7 @@ API 文档按模块组织,每个模块一个文件,使用两位数字序号 #### 3. 使用示例 -对于返回后台任务的操作,默认示例优先使用“提交任务 → 查询状态”,尽量不主动设置 `wait`、`timeout`,也不使用全局 `wait_processed()` 代替任务状态。完整参数参考应保留受支持参数及其真实默认值。后续操作依赖异步产物时,应展示按 `task_id` 轮询至 `completed`,并处理 `failed`、`cancelled`;不能仅删除等待参数后立即读取结果。 +对于返回后台任务的操作,默认示例优先使用“提交任务 → 查询状态”,尽量不主动设置 `wait`,也不使用全局 `wait_processed()` 代替任务状态。完整参数参考应保留受支持参数及其真实默认值。后续操作依赖异步产物时,应展示按 `task_id` 轮询至 `completed`,并处理 `failed`、`cancelled`;不能仅删除等待参数后立即读取结果。 当一个接口并列展示所有调用方式时,建议按以下顺序提供: - Python SDK 示例 diff --git a/docs/zh/getting-started/02-quickstart.md b/docs/zh/getting-started/02-quickstart.md index 60f13eb180..83b8e07e5f 100644 --- a/docs/zh/getting-started/02-quickstart.md +++ b/docs/zh/getting-started/02-quickstart.md @@ -76,7 +76,7 @@ OpenViking 每周五发布一个新版本。 将它导入新的资源目录: ```bash -ov add-resource ./ov-release-rotation.md --to viking://resources/ov-release-rotation --wait --timeout 120 +ov add-resource ./ov-release-rotation.md --to viking://resources/ov-release-rotation --wait ``` CLI 会自动上传本地文件。`--wait` 等待处理完成,命令成功后再继续。若省略该参数,保存返回的 `task_id`,用 `ov task status ` 查询到 `completed` 后再使用结果。详见[后台任务](../api/17-tasks.md)。 diff --git a/docs/zh/guides/02-volcengine-purchase-guide.md b/docs/zh/guides/02-volcengine-purchase-guide.md index c9feda58ac..a2e881cc8a 100644 --- a/docs/zh/guides/02-volcengine-purchase-guide.md +++ b/docs/zh/guides/02-volcengine-purchase-guide.md @@ -182,7 +182,7 @@ openviking-server 另开终端,按[快速开始](../getting-started/02-quickstart.md)配置 CLI 并创建其中的 `quickstart.md`,然后导入到未使用的目录: ```bash -ov add-resource ./quickstart.md --to viking://resources/model-check --wait --timeout 120 +ov add-resource ./quickstart.md --to viking://resources/model-check --wait ov find "谁负责备份流程?" --uri viking://resources/model-check ``` diff --git a/docs/zh/guides/06-mcp-integration.md b/docs/zh/guides/06-mcp-integration.md index ff1d6fd18c..b8acb303d4 100644 --- a/docs/zh/guides/06-mcp-integration.md +++ b/docs/zh/guides/06-mcp-integration.md @@ -128,8 +128,8 @@ claude mcp add --transport http openviking \ | `list` | 列出 `viking://` 目录下的条目 | `uri`, `recursive`(可选) | | `tree` | 以缩进形式展示 `viking://` URI 下的递归目录树——当需要全面了解文件树结构时使用(单层列表用 `list`,按文件名查找用 `glob`) | `uri`(可选), `level_limit`(默认 3), `node_limit`(默认 1000), `include_abstract`(可选——同时展示每个目录的摘要;skill 目录的摘要就是它的名字和描述) | | `remember` | 提交消息做长期记忆提取;立即返回后台任务的 `task_id`,提取结果可能什么都不保留 | `messages`(`{role, content}` 列表) | -| `write` | 向 `viking://` 文件写入文本(创建/覆盖/追加)。自动创建缺失的父目录;覆盖前请先用 `read` 查看当前内容;只改文件局部时优先用 `edit`。skill 包不要用它维护:调用方自己的 `skills/` 子树会被拒绝,写 `viking://agent/skills` 则生成绕过安装流程的普通文件,请改用 `add_skill` | `uri`, `content`, `mode`(可选:默认 `replace` — 覆盖或在缺失时创建,`append` — 追加或在缺失时创建,`create` — `replace` 的兼容别名), `wait`(可选,阻塞直到重建索引完成), `timeout`(可选), `acl`(可选) | -| `edit` | 在已有 `viking://` 文件中把精确字符串替换为新文本——用于局部修改,避免整文件重写。若 `old_string` 找不到、或匹配多处且 `replace_all` 为 false,则编辑失败且文件保持不变。编辑 skill 包内的文件不会重新触发 skill 安装流程,请改用 `add_skill` | `uri`, `old_string`, `new_string`, `replace_all`(可选), `wait`(可选,阻塞直到重建索引完成), `timeout`(可选) | +| `write` | 向 `viking://` 文件写入文本(创建/覆盖/追加)。自动创建缺失的父目录;覆盖前请先用 `read` 查看当前内容;只改文件局部时优先用 `edit`。skill 包不要用它维护:调用方自己的 `skills/` 子树会被拒绝,写 `viking://agent/skills` 则生成绕过安装流程的普通文件,请改用 `add_skill` | `uri`, `content`, `mode`(可选:默认 `replace` — 覆盖或在缺失时创建,`append` — 追加或在缺失时创建,`create` — `replace` 的兼容别名), `wait`(可选,阻塞直到重建索引完成), `acl`(可选) | +| `edit` | 在已有 `viking://` 文件中把精确字符串替换为新文本——用于局部修改,避免整文件重写。若 `old_string` 找不到、或匹配多处且 `replace_all` 为 false,则编辑失败且文件保持不变。编辑 skill 包内的文件不会重新触发 skill 安装流程,请改用 `add_skill` | `uri`, `old_string`, `new_string`, `replace_all`(可选), `wait`(可选,阻塞直到重建索引完成) | | `add_resource` | 添加本地文件或 URL 作为资源(本地文件触发渐进式上传流) | `path`, `temp_file_id`(可选), `description`(可选), `watch_interval`(可选,分钟数 — 远程 URL 的自动刷新周期), `processing_mode`(可选:默认 `semantic_and_vectors`;传 `vectors_only` 时跳过 VLM 语义理解,只向量化当前文件), `to`(可选,目标 `viking://resources/...` URI;`watch_interval > 0` 时若省略 `to`,watch 将自动绑定到本次 add 创建的资源 URI), `args`(可选,特定 parser 参数,包括 `{"parse_mode":"no_split"}` 用于正常解析但每个源文档只生成一个 Markdown 正文、飞书一次性用户 token 导入使用 `{"feishu_access_token":"u-..."}`,或飞书用户 token watch 使用 access/refresh token,并可选传入 `feishu_app_id` / `feishu_app_secret`), `acl`(可选) | | `add_skill` | 新建、安装或替换 agent skill。新 skill 直接传完整 SKILL.md 文本;Git 与 GitHub tree URL 默认安装源里的全部 skill,可用 `skills` 挑选;本地 SKILL.md、目录或 zip 会和 `add_resource` 一样返回签名上传 URL | `data`(SKILL.md 文本)或 `path`(Git URL 或本地路径), `skills`(可选), `target_uri`(可选;`viking://agent/skills` 表示账户共享), `list_only`(可选) | | `list_watches` | 列出当前 Agent 可见的 watch 任务(自动刷新订阅),每行显示目标 URI、刷新间隔(分钟)、active/paused 状态以及下一次调度时间 | 无 | diff --git a/docs/zh/guides/09-ovpack.md b/docs/zh/guides/09-ovpack.md index 9533748a88..f2569ad0ab 100644 --- a/docs/zh/guides/09-ovpack.md +++ b/docs/zh/guides/09-ovpack.md @@ -196,7 +196,7 @@ def migrate_project(): vector_mode="auto", ) print(imported_uri) - print(client.wait_processed(timeout=120)) + print(client.wait_processed()) print(client.check_consistency(uri=imported_uri)) finally: client.close() diff --git a/docs/zh/guides/15-snapshot.md b/docs/zh/guides/15-snapshot.md index 7bca6fe3cf..7ba6b915a3 100644 --- a/docs/zh/guides/15-snapshot.md +++ b/docs/zh/guides/15-snapshot.md @@ -177,7 +177,7 @@ client.write( content="# Guide\n\nv1 content\n", mode="create", ) -print(client.wait_processed(timeout=120)) +print(client.wait_processed()) v1 = client.snapshot.commit(message="v1 initial import", paths=[root]) if not v1.get("commit_oid"): raise RuntimeError(f"No snapshot created: {v1}") @@ -189,7 +189,7 @@ client.write( content="# Guide\n\nv2 content\n", mode="replace", ) -print(client.wait_processed(timeout=120)) +print(client.wait_processed()) v2 = client.snapshot.commit(message="v2 update", paths=[root]) # 3. 查看历史 diff --git a/docs/zh/guides/18-openviking-assets.md b/docs/zh/guides/18-openviking-assets.md index 353bb09cb3..c98c9b55b8 100644 --- a/docs/zh/guides/18-openviking-assets.md +++ b/docs/zh/guides/18-openviking-assets.md @@ -281,16 +281,16 @@ export OPENVIKING_ASSETS_CREDENTIALS_FILE=/secure/path/assets-credentials.yaml 当最终 `watch_interval` 大于 `0` 时,OpenViking 会把通过 `auth_ref` 解析出的 HTTPS Git token 保存到 Watch task 私有且与仓库 URL 绑定的鉴权状态中。token 不会写入 Manifest -State、普通入库队列或 Watch API/MCP/CLI 返回。周期为 `0` 时,token 仍只在本次请求内使用。 +State、普通入库队列或 Watch API/MCP/CLI 返回。周期为 `0` 时,token 保存在本次导入任务的私有鉴权状态中供后台执行使用。 Git PAT 没有通用刷新流程,token 过期或被撤销后需要重建 Watch。 Watch 私有状态保存在 `viking://resources/.watch_tasks.json`。启用 VikingFS 文件加密时会 静态加密;否则服务端控制文件及其备份包含明文 token 状态。生产环境应限制服务端存储访问并 启用加密。 -即使没有指定 `--wait`,原生凭据导入也需要等 clone 和 parse 完成后,服务端才会返回 task; -因此 CLI 对这类资产默认使用 300 秒请求超时,大仓库可通过 `--timeout <秒>` 调大。token -会放在 HTTPS 请求体中传输,生产环境应保持诊断请求体 dump 关闭。 +原生凭据导入在权限预检和任务入队后返回 `task_id`,clone 和 parse 在后台执行; +指定 `--wait` 才等待任务完成。HTTP 请求超时统一使用客户端配置,`add-resource` 不支持 +`--timeout`。token 会放在 HTTPS 请求体中传输,生产环境应保持诊断请求体 dump 关闭。 如果目标服务已经具备访问仓库所需的 SSH key 或其他认证配置,可以不设置 `auth_ref`。 @@ -391,7 +391,6 @@ ov add-resource --manifest manifest.yaml --args skip_failed:true | `-m, --manifest ` | Manifest 文件。 | | `--args ` | Manifest 运行选项,多个选项用逗号分隔,支持的键见下表。 | | `--wait` | 等待每个资源处理完成。 | -| `--timeout ` | HTTP 请求超时。原生私有 Git 即使没有 `--wait` 也会使用该值,默认 300 秒。 | | `--watch-interval ` | 覆盖全部资产的更新周期。 | `--args` 支持的运行选项: diff --git a/examples/cloud/bob.py b/examples/cloud/bob.py index d214f6a3f7..e015ccff39 100644 --- a/examples/cloud/bob.py +++ b/examples/cloud/bob.py @@ -120,7 +120,7 @@ def main(): bob_uri = result.get("root_uri", "") print(f" URI: {bob_uri}") print(" 等待处理...") - client.wait_processed(timeout=120) + client.wait_processed() print(" 完成") # ── 5. 创建会话,模拟入职学习 ── @@ -163,7 +163,7 @@ def main(): client.commit_session(session.session_id) print(" commit 完成") time.sleep(2) - client.wait_processed(timeout=120) + client.wait_processed() print(" 记忆向量化完成") # ── 7. 回顾自己的记忆 ── diff --git a/examples/hermes-plugin/__init__.py b/examples/hermes-plugin/__init__.py index 7462db7f06..2e338061cb 100644 --- a/examples/hermes-plugin/__init__.py +++ b/examples/hermes-plugin/__init__.py @@ -557,7 +557,6 @@ def _str(description: str, **extra) -> dict: "parent": _str("Optional parent viking:// URI. Cannot be used with to."), "instruction": _str("Optional processing instruction for semantic extraction."), "wait": {"type": "boolean", "description": "Whether to wait for processing to complete."}, - "timeout": {"type": "number", "description": "Timeout in seconds when wait is true."}, }, ["url"], ) @@ -3364,7 +3363,7 @@ def _tool_add_resource(self, args: dict) -> str: if args.get("to") and args.get("parent"): return tool_error("Cannot specify both 'to' and 'parent'") payload: Dict[str, Any] = { - key: args[key] for key in ("reason", "to", "parent", "instruction", "wait", "timeout") if key in args and args[key] not in {None, ""} + key: args[key] for key in ("reason", "to", "parent", "instruction", "wait") if key in args and args[key] not in {None, ""} } parsed_url = urlparse(url) diff --git a/examples/langchain/src/langchain_openviking/store.py b/examples/langchain/src/langchain_openviking/store.py index 377e883397..4c06444dbd 100644 --- a/examples/langchain/src/langchain_openviking/store.py +++ b/examples/langchain/src/langchain_openviking/store.py @@ -81,7 +81,6 @@ def __init__( root_uri: str = "viking://~/memories/langgraph_store", index: bool | list[str] | None = None, wait: bool = True, - timeout: float | None = None, search_fetch_limit: int = 50, auto_initialize: bool = True, ): @@ -101,7 +100,6 @@ def __init__( self.root_uri: str = root_uri.rstrip("/") self.index: bool | list[str] | None = index self.wait: bool = wait - self.timeout: float | None = timeout self.search_fetch_limit: int = search_fetch_limit self._client_cache: Any = None @@ -340,7 +338,6 @@ def _write_create(self, uri: str, content: str) -> None: content=content, mode="create", wait=self.wait, - timeout=self.timeout, ) def _write_replace(self, uri: str, content: str) -> None: @@ -351,7 +348,6 @@ def _write_replace(self, uri: str, content: str) -> None: content=content, mode="replace", wait=self.wait, - timeout=self.timeout, ) def _remove(self, uri: str) -> None: diff --git a/examples/langchain/src/langchain_openviking/testing.py b/examples/langchain/src/langchain_openviking/testing.py index c9f10d25b1..faf1bd9f18 100644 --- a/examples/langchain/src/langchain_openviking/testing.py +++ b/examples/langchain/src/langchain_openviking/testing.py @@ -154,7 +154,6 @@ def write( content: str, mode: str = "replace", wait: bool = False, - timeout: float | None = None, **_: Any, ) -> dict[str, Any]: if mode == "create" and uri in self.records: diff --git a/examples/langchain/src/langchain_openviking/tools.py b/examples/langchain/src/langchain_openviking/tools.py index 85a0805bde..87d764b0e4 100644 --- a/examples/langchain/src/langchain_openviking/tools.py +++ b/examples/langchain/src/langchain_openviking/tools.py @@ -406,10 +406,6 @@ def viking_add_resource( bool, Field(description="Whether to wait for resource ingestion to finish before returning."), ] = False, - timeout: Annotated[ - float | None, - Field(description="Optional wait timeout in seconds for resource ingestion."), - ] = None, ) -> str: """Import an explicit resource into OpenViking. @@ -432,7 +428,6 @@ def viking_add_resource( reason=reason, instruction=instruction, wait=wait, - timeout=timeout, ) except Exception as exc: if _is_probably_local_path(path) and "direct host filesystem paths" in str(exc): @@ -458,10 +453,6 @@ def viking_add_skill( bool, Field(description="Whether to wait for skill registration to finish before returning."), ] = False, - timeout: Annotated[ - float | None, - Field(description="Optional wait timeout in seconds for skill registration."), - ] = None, ) -> str: """Register a reusable OpenViking skill for trusted admin workflows.""" @@ -470,7 +461,6 @@ def viking_add_skill( "add_skill", data=data, wait=wait, - timeout=timeout, ) return stringify(result, max_chars=8_000) diff --git a/examples/openclaw-plugin/client.ts b/examples/openclaw-plugin/client.ts index a57513be14..190813afc2 100644 --- a/examples/openclaw-plugin/client.ts +++ b/examples/openclaw-plugin/client.ts @@ -207,7 +207,6 @@ export type AddResourceInput = { reason?: string; instruction?: string; wait?: boolean; - timeout?: number; strict?: boolean; ignoreDirs?: string; include?: string; @@ -230,7 +229,6 @@ export type AddSkillInput = { path?: string; data?: unknown; wait?: boolean; - timeout?: number; }; export type AddSkillResult = { @@ -241,22 +239,12 @@ export type AddSkillResult = { queue_status?: unknown; }; -const DEFAULT_WAIT_REQUEST_TIMEOUT_MS = 120_000; export const DEFAULT_PHASE2_POLL_TIMEOUT_MS = 300_000; -const WAIT_REQUEST_TIMEOUT_BUFFER_MS = 5_000; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -function resolveWaitRequestTimeoutMs(defaultTimeoutMs: number, waitTimeoutSeconds?: number): number { - const requestedMs = - typeof waitTimeoutSeconds === "number" && Number.isFinite(waitTimeoutSeconds) && waitTimeoutSeconds > 0 - ? Math.ceil(waitTimeoutSeconds * 1000) + WAIT_REQUEST_TIMEOUT_BUFFER_MS - : DEFAULT_WAIT_REQUEST_TIMEOUT_MS; - return Math.max(defaultTimeoutMs, requestedMs); -} - export class OpenVikingClient { private readonly transport: HttpTransport; private readonly configuredHeaders: Record; @@ -678,7 +666,6 @@ export class OpenVikingClient { reason: input.reason ?? "", instruction: input.instruction ?? "", wait: input.wait ?? false, - timeout: input.timeout, strict: input.strict ?? false, ignore_dirs: input.ignoreDirs, include: input.include, @@ -689,8 +676,6 @@ export class OpenVikingClient { } let packagedSource: Awaited> | undefined; - const requestTimeoutMs = - input.wait ? resolveWaitRequestTimeoutMs(this.timeoutMs, input.timeout) : undefined; try { packagedSource = await this.resourcePackager.prepareResourceSource(pathOrUrl); if (packagedSource.kind === "remote") { @@ -704,7 +689,7 @@ export class OpenVikingClient { return this.request( "/api/v1/resources", { method: "POST", body: JSON.stringify(body) }, - requestTimeoutMs, + undefined, actorPeerId, ); } finally { @@ -721,11 +706,8 @@ export class OpenVikingClient { const body: Record = { wait: input.wait ?? false, - timeout: input.timeout, }; let packagedSource: Awaited> | undefined; - const requestTimeoutMs = - input.wait ? resolveWaitRequestTimeoutMs(this.timeoutMs, input.timeout) : undefined; try { if (hasPath) { const skillPath = input.path!.trim(); @@ -740,7 +722,7 @@ export class OpenVikingClient { return this.request( "/api/v1/skills", { method: "POST", body: JSON.stringify(body) }, - requestTimeoutMs, + undefined, actorPeerId, ); } finally { @@ -850,7 +832,6 @@ export class OpenVikingClient { options?: { wait?: boolean; enableWorkingMemory?: boolean; - timeoutMs?: number; /** * WM v2: number of most-recent messages to keep live after commit. * Forwarded as `keep_recent_count` in the POST body. 0 (default) @@ -907,7 +888,7 @@ export class OpenVikingClient { } // Client-side poll until Phase 2 finishes - const deadline = this.now() + (options.timeoutMs ?? DEFAULT_PHASE2_POLL_TIMEOUT_MS); + const deadline = this.now() + DEFAULT_PHASE2_POLL_TIMEOUT_MS; const pollInterval = 500; while (this.now() < deadline) { await this.sleep(pollInterval); diff --git a/examples/openclaw-plugin/docs/openviking-openclaw-plugin-guide.md b/examples/openclaw-plugin/docs/openviking-openclaw-plugin-guide.md index 33333e80de..7c7e2ab1ab 100644 --- a/examples/openclaw-plugin/docs/openviking-openclaw-plugin-guide.md +++ b/examples/openclaw-plugin/docs/openviking-openclaw-plugin-guide.md @@ -284,8 +284,8 @@ transformContext auto recall 流程: | API | 官方用途 | 当前插件映射 | 关键参数 / 返回 | | --- | --- | --- | --- | | `POST /api/v1/resources/temp_upload` | 临时上传本地文件 | `/add-resource`、opt-in `add_resource`、`add_skill` 的本地文件/目录路径 | 插件本地目录会先 zip,再上传,服务端返回 `temp_file_id`:`client.ts:533`、`client.ts:552` | -| `POST /api/v1/resources` | 导入文件、目录、URL、Git 仓库等 resource | `/add-resource` 命令;`add_resource` 工具仅在 `enableAddResourceTool=true` 时注册 | body 官方字段包括 `path`/`temp_file_id`、`target`/插件兼容 `to`、`parent`、`reason`、`instruction`、`wait`、`timeout`、`strict`、`ignore_dirs`、`include`、`exclude`;返回 `root_uri`、`source_path`、`errors`、`queue_status`:`client.ts:609` | -| `POST /api/v1/skills` | 导入 skill,支持 dict、MCP tool、SKILL.md 字符串、文件/目录 | `add_skill` 工具、`/add-skill` 命令 | body: `data` 或 `temp_file_id`、`wait`、`timeout`;返回 `uri`/`skill_uri`、`name`、`auxiliary_files`、`queue_status`:`client.ts:663` | +| `POST /api/v1/resources` | 导入文件、目录、URL、Git 仓库等 resource | `/add-resource` 命令;`add_resource` 工具仅在 `enableAddResourceTool=true` 时注册 | body 官方字段包括 `path`/`temp_file_id`、`target`/插件兼容 `to`、`parent`、`reason`、`instruction`、`wait`、`strict`、`ignore_dirs`、`include`、`exclude`;返回 `root_uri`、`source_path`、`errors`、`queue_status`:`client.ts:609` | +| `POST /api/v1/skills` | 导入 skill,支持 dict、MCP tool、SKILL.md 字符串、文件/目录 | `add_skill` 工具、`/add-skill` 命令 | body: `data` 或 `temp_file_id`、`wait`;返回 `uri`/`skill_uri`、`name`、`auxiliary_files`、`queue_status`:`client.ts:663` | | `POST /api/v1/pack/export` | 导出 `.ovpack` | 暂未封装 | 官方 API Overview 有列出;当前插件没有 pack 管理工具 | | `POST /api/v1/pack/import` | 导入 `.ovpack` | 暂未封装 | 官方 API Overview 有列出;当前插件没有 pack 管理工具 | diff --git a/examples/openclaw-plugin/docs/openviking-plugin-reference.md b/examples/openclaw-plugin/docs/openviking-plugin-reference.md index b744b9572c..cb87ce6456 100644 --- a/examples/openclaw-plugin/docs/openviking-plugin-reference.md +++ b/examples/openclaw-plugin/docs/openviking-plugin-reference.md @@ -326,7 +326,7 @@ Manifest 中声明了 runtime slash alias: 用途:手动把文件、目录、URL、Git 仓库或 OpenClaw media attachment 导入 OpenViking resources。 ```text -/add-resource [--to URI] [--parent URI] [--reason TEXT] [--instruction TEXT] [--wait] [--timeout SEC] +/add-resource [--to URI] [--parent URI] [--reason TEXT] [--instruction TEXT] [--wait] ``` 参数: @@ -339,7 +339,6 @@ Manifest 中声明了 runtime slash alias: | `--reason TEXT` | 否 | 导入原因。 | | `--instruction TEXT` | 否 | 导入处理指令。 | | `--wait` | 否 | 等待服务端处理完成。 | -| `--timeout SEC` | 否 | `--wait` 时的等待超时秒数。 | 示例: @@ -354,7 +353,7 @@ Manifest 中声明了 runtime slash alias: 用途:手动导入 `SKILL.md` 文件或 skill 目录。 ```text -/add-skill [--wait] [--timeout SEC] +/add-skill [--wait] ``` 参数: @@ -363,12 +362,11 @@ Manifest 中声明了 runtime slash alias: | --- | --- | --- | | `` | 是 | `SKILL.md` 文件或 skill 目录。 | | `--wait` | 否 | 等待服务端处理完成。 | -| `--timeout SEC` | 否 | `--wait` 时的等待超时秒数。 | 示例: ```text -/add-skill ./skills/my-skill --wait --timeout 30 +/add-skill ./skills/my-skill --wait ``` ### 6.3 `/ov-search` @@ -431,7 +429,7 @@ Manifest 中声明了 runtime slash alias: | Tool | 参数 | 用途 | | --- | --- | --- | -| `add_skill` | `source?`、`data?`、`wait?`、`timeout?` | 导入或注册 OpenViking agent skill。 | +| `add_skill` | `source?`、`data?`、`wait?` | 导入或注册 OpenViking agent skill。 | | `ov_search` | `query`、`uri?`、`limit?` | 搜索 OpenViking resources 和 skills。 | | `ov_read` | `uri` | 读取精确 `viking://...` OpenViking URI 的完整内容。 | | `ov_multi_read` | `uris` | 一次读取多个精确 `viking://...` URI,适合 overview + 同级切片。 | @@ -450,7 +448,7 @@ Manifest 中声明了 runtime slash alias: | Tool | 参数 | 默认 | 开启方式 | 用途 | | --- | --- | --- | --- | --- | -| `add_resource` | `source`、`to?`、`parent?`、`reason?`、`instruction?`、`wait?`、`timeout?` | 不注册 | `enableAddResourceTool=true`,且被 `enabledTools` 选中 | 允许 Agent 导入资源。 | +| `add_resource` | `source`、`to?`、`parent?`、`reason?`、`instruction?`、`wait?` | 不注册 | `enableAddResourceTool=true`,且被 `enabledTools` 选中 | 允许 Agent 导入资源。 | 安全边界:搜索、读取、trace 和资源消费路径应优先使用 `ov_search` / `ov_read`,不要让 Agent 在检索阶段自动导入新资源,除非用户明确授权。 diff --git a/examples/openclaw-plugin/docs/openviking-websocket-rpc-api.md b/examples/openclaw-plugin/docs/openviking-websocket-rpc-api.md index 8b80547b8f..ce10d7896d 100644 --- a/examples/openclaw-plugin/docs/openviking-websocket-rpc-api.md +++ b/examples/openclaw-plugin/docs/openviking-websocket-rpc-api.md @@ -258,8 +258,7 @@ Recall semantic memories and resources. Current semantic recall target types are "sessionKey": "main", "args": { "source": "/absolute/path/to/my-skill", - "wait": true, - "timeout": 120 + "wait": true } } } @@ -290,7 +289,6 @@ Recall semantic memories and resources. Current semantic recall target types are | `source` | string | 二选一 | 本地 `SKILL.md` 文件或 skill 目录 | | `data` | any | 二选一 | 原始 `SKILL.md` 内容或 MCP tool dict | | `wait` | boolean | 否 | 是否等待服务端处理完成 | -| `timeout` | number | 否 | `wait=true` 时的超时时间,单位秒 | ### `add_resource` @@ -311,8 +309,7 @@ Recall semantic memories and resources. Current semantic recall target types are "parent": "viking://resources/project-docs", "reason": "导入项目文档", "instruction": "保留 API 示例和配置说明", - "wait": true, - "timeout": 300 + "wait": true } } } @@ -328,7 +325,6 @@ Recall semantic memories and resources. Current semantic recall target types are | `reason` | string | 否 | 导入原因或说明 | | `instruction` | string | 否 | 服务端处理指令 | | `wait` | boolean | 否 | 是否等待服务端处理完成 | -| `timeout` | number | 否 | `wait=true` 时的超时时间,单位秒 | ### `ov_archive_search` diff --git a/examples/openclaw-plugin/plugin/openviking-command-args.ts b/examples/openclaw-plugin/plugin/openviking-command-args.ts index 022ca72795..152b467622 100644 --- a/examples/openclaw-plugin/plugin/openviking-command-args.ts +++ b/examples/openclaw-plugin/plugin/openviking-command-args.ts @@ -5,14 +5,12 @@ export type AddResourceCommandArgs = { reason?: string; instruction?: string; wait?: boolean; - timeout?: number; }; export type AddSkillCommandArgs = { source?: string; data?: unknown; wait?: boolean; - timeout?: number; }; export type OVSearchCommandArgs = { @@ -136,7 +134,7 @@ export function parseAddResourceCommandArgs(args: string): AddResourceCommandArg const source = parsed.positionals.length <= 1 ? parsed.positionals[0] : parsed.positionals.join(" ").trim(); if (!source) { - throw new Error("Usage: /add-resource [--to URI] [--parent URI] [--reason TEXT] [--instruction TEXT] [--wait] [--timeout SEC]"); + throw new Error("Usage: /add-resource [--to URI] [--parent URI] [--reason TEXT] [--instruction TEXT] [--wait]"); } const to = getStringFlag(parsed.flags, "to"); const parent = getStringFlag(parsed.flags, "parent"); @@ -150,7 +148,6 @@ export function parseAddResourceCommandArgs(args: string): AddResourceCommandArg reason: getStringFlag(parsed.flags, "reason"), instruction: getStringFlag(parsed.flags, "instruction"), wait: getBoolFlag(parsed.flags, "wait"), - timeout: getNumberFlag(parsed.flags, "timeout"), }; } @@ -159,7 +156,7 @@ export function parseAddSkillCommandArgs(args: string): AddSkillCommandArgs { const source = parsed.positionals.length <= 1 ? parsed.positionals[0] : parsed.positionals.join(" ").trim(); if (!source) { - throw new Error("Usage: /add-skill [--wait] [--timeout SEC]"); + throw new Error("Usage: /add-skill [--wait]"); } if (parsed.flags.has("to") || parsed.flags.has("parent") || parsed.flags.has("reason") || parsed.flags.has("instruction")) { throw new Error("--to, --parent, --reason, and --instruction are resource-only options."); @@ -167,7 +164,6 @@ export function parseAddSkillCommandArgs(args: string): AddSkillCommandArgs { return { source, wait: getBoolFlag(parsed.flags, "wait"), - timeout: getNumberFlag(parsed.flags, "timeout"), }; } diff --git a/examples/openclaw-plugin/plugin/openviking-command-definitions.ts b/examples/openclaw-plugin/plugin/openviking-command-definitions.ts index 6608257e3e..ab77e872db 100644 --- a/examples/openclaw-plugin/plugin/openviking-command-definitions.ts +++ b/examples/openclaw-plugin/plugin/openviking-command-definitions.ts @@ -21,14 +21,12 @@ export type AddResourceCommandInput = { reason?: string; instruction?: string; wait?: boolean; - timeout?: number; }; export type AddSkillCommandInput = { source?: string; data?: unknown; wait?: boolean; - timeout?: number; }; export type OVSearchCommandInput = { diff --git a/examples/openclaw-plugin/plugin/openviking-import-runtime.ts b/examples/openclaw-plugin/plugin/openviking-import-runtime.ts index eee6970f38..baa56e9464 100644 --- a/examples/openclaw-plugin/plugin/openviking-import-runtime.ts +++ b/examples/openclaw-plugin/plugin/openviking-import-runtime.ts @@ -12,14 +12,12 @@ export type OpenVikingImportRuntimeResourceInput = { reason?: string; instruction?: string; wait?: boolean; - timeout?: number; }; export type OpenVikingImportRuntimeSkillInput = { source?: string; data?: unknown; wait?: boolean; - timeout?: number; }; export type OpenVikingImportRuntimeToolResult = { @@ -90,7 +88,6 @@ export function createOpenVikingImportRuntime(deps: OpenVikingImportRuntimeDeps) reason: input.reason, instruction: input.instruction, wait: input.wait, - timeout: input.timeout, }, agentId); const addSkillOpenViking = (input: OpenVikingImportRuntimeSkillInput, agentId?: string) => @@ -98,7 +95,6 @@ export function createOpenVikingImportRuntime(deps: OpenVikingImportRuntimeDeps) path: input.source, data: input.data, wait: input.wait, - timeout: input.timeout, }, agentId); return { addResourceOpenViking, addSkillOpenViking }; diff --git a/examples/openclaw-plugin/plugin/openviking-import-tools.ts b/examples/openclaw-plugin/plugin/openviking-import-tools.ts index 9ae24d35db..72e8db87a5 100644 --- a/examples/openclaw-plugin/plugin/openviking-import-tools.ts +++ b/examples/openclaw-plugin/plugin/openviking-import-tools.ts @@ -66,7 +66,6 @@ export function registerOpenVikingImportTools(deps: OpenVikingImportToolsDeps): reason: Type.Optional(Type.String({ description: "Reason or note for adding this resource" })), instruction: Type.Optional(Type.String({ description: "Processing instruction for semantic extraction" })), wait: Type.Optional(Type.Boolean({ description: "Wait for processing to complete" })), - timeout: Type.Optional(Type.Number({ description: "Timeout in seconds when wait is true" })), }), async execute(_toolCallId: string, params: Record) { if (deps.isBypassedSession(ctx)) { @@ -81,7 +80,6 @@ export function registerOpenVikingImportTools(deps: OpenVikingImportToolsDeps): reason: typeof params.reason === "string" ? params.reason : undefined, instruction: typeof params.instruction === "string" ? params.instruction : undefined, wait: typeof params.wait === "boolean" ? params.wait : undefined, - timeout: typeof params.timeout === "number" ? params.timeout : undefined, }, session.actorPeerId); return { content: [{ type: "text" as const, text: formatResourceImportText(result) }], @@ -107,7 +105,6 @@ export function registerOpenVikingImportTools(deps: OpenVikingImportToolsDeps): source: Type.Optional(Type.String({ description: "Local SKILL.md path or skill directory path" })), data: Type.Optional(Type.Any({ description: "Raw SKILL.md content or MCP tool dict" })), wait: Type.Optional(Type.Boolean({ description: "Wait for processing to complete" })), - timeout: Type.Optional(Type.Number({ description: "Timeout in seconds when wait is true" })), }), async execute(_toolCallId: string, params: Record) { if (deps.isBypassedSession(ctx)) { @@ -119,7 +116,6 @@ export function registerOpenVikingImportTools(deps: OpenVikingImportToolsDeps): path: typeof params.source === "string" ? params.source : undefined, data: params.data, wait: typeof params.wait === "boolean" ? params.wait : undefined, - timeout: typeof params.timeout === "number" ? params.timeout : undefined, }, session.actorPeerId); return { content: [{ type: "text" as const, text: formatSkillImportText(result) }], diff --git a/examples/openclaw-plugin/skills/install-openviking-memory/SKILL.md b/examples/openclaw-plugin/skills/install-openviking-memory/SKILL.md index f07c33b9c3..162f99dce1 100644 --- a/examples/openclaw-plugin/skills/install-openviking-memory/SKILL.md +++ b/examples/openclaw-plugin/skills/install-openviking-memory/SKILL.md @@ -543,7 +543,6 @@ The agent-visible `add_resource` tool is disabled by default (`enableAddResource | `reason` | No | Reason or note for import. | | `instruction` | No | Processing instruction for semantic extraction. | | `wait` | No | Wait for processing completion. | -| `timeout` | No | Timeout in seconds when `wait=true`. | ### `add_skill` — Import Agent Skill @@ -552,7 +551,6 @@ The agent-visible `add_resource` tool is disabled by default (`enableAddResource | `source` | No | Local `SKILL.md` path or skill directory. Exactly one of `source` or `data` is required. | | `data` | No | Raw `SKILL.md` content or MCP tool dict. Exactly one of `source` or `data` is required. | | `wait` | No | Wait for processing completion. | -| `timeout` | No | Timeout in seconds when `wait=true`. | Agent Skill best practice: verify frontmatter has a precise `name`, a trigger-oriented `description`, useful `tags`, explicit scope boundaries, and concrete execution steps. Do not include secrets in skill content. diff --git a/examples/openclaw-plugin/skills/openviking-context-database/SKILL.md b/examples/openclaw-plugin/skills/openviking-context-database/SKILL.md index 478d194e5a..18b9e12657 100644 --- a/examples/openclaw-plugin/skills/openviking-context-database/SKILL.md +++ b/examples/openclaw-plugin/skills/openviking-context-database/SKILL.md @@ -190,7 +190,6 @@ This agent tool is disabled by default. Prefer manual `/add-resource` for resour | `reason` | No | Reason/note for import. | | `instruction` | No | Processing instruction for semantic extraction. | | `wait` | No | Wait for processing completion. | -| `timeout` | No | Timeout in seconds when `wait=true`. | The current OpenClaw tool exposes the parameters above. The underlying client also supports server-facing resource options such as `strict`, `ignore_dirs`, `include`, `exclude`, and `preserve_structure` for command/internal paths; do not pass them to the tool unless the registered schema exposes them. @@ -203,7 +202,6 @@ Import Agent Skills into `viking://~/skills/...`. | `source` | No | Local `SKILL.md` path or skill directory. Exactly one of `source` or `data` is required. | | `data` | No | Raw `SKILL.md` content or an MCP tool dict. Exactly one of `source` or `data` is required. | | `wait` | No | Wait for processing completion. | -| `timeout` | No | Timeout in seconds when `wait=true`. | Agent Skill best practice: a skill should have precise frontmatter (`name`, trigger-oriented `description`, useful `tags`), clear scope boundaries, explicit “when not to use” guidance if needed, and executable steps with concrete parameters. Keep secrets out of skill content. @@ -258,7 +256,7 @@ The plugin refuses to read/search a tool-result ref from another session. ```text /add-resource ./README.md --to viking://resources/openviking-readme --wait -/add-skill ./skills/install-openviking-memory --wait --timeout=30 +/add-skill ./skills/install-openviking-memory --wait /ov-search "OpenViking install" --uri viking://resources/openviking-readme --limit=5 /ov-recall-trace --turn latest --source auto_recall --include-content ``` diff --git a/examples/openclaw-plugin/tests/ut/architecture-boundaries.test.ts b/examples/openclaw-plugin/tests/ut/architecture-boundaries.test.ts index 1069f0fd40..c54cf6ceda 100644 --- a/examples/openclaw-plugin/tests/ut/architecture-boundaries.test.ts +++ b/examples/openclaw-plugin/tests/ut/architecture-boundaries.test.ts @@ -461,7 +461,7 @@ describe("architecture boundaries", () => { it("keeps OpenViking error response client tests on the injected transport seam", () => { const clientTestSource = readFileSync(join(rootDir, "tests/ut/client.test.ts"), "utf8"); const start = clientTestSource.indexOf('it("surfaces OpenViking error responses"'); - const end = clientTestSource.indexOf(' it("uses an extended request timeout for wait=true imports"', start); + const end = clientTestSource.indexOf(' it.each([false, true])("uses configured request timeout for imports (wait=%s)"', start); expect(start).toBeGreaterThanOrEqual(0); expect(end).toBeGreaterThan(start); @@ -471,21 +471,6 @@ describe("architecture boundaries", () => { expect(errorResponseBlock).toMatch(/new OpenVikingClient\([\s\S]*\{\s*transport\s*\}/); }); - it("keeps import timeout client tests on the injected transport seam", () => { - const clientTestSource = readFileSync(join(rootDir, "tests/ut/client.test.ts"), "utf8"); - const waitStart = clientTestSource.indexOf('it("uses an extended request timeout for wait=true imports"'); - const waitEnd = clientTestSource.indexOf(' it("still uses the default request timeout for non-wait imports"', waitStart); - const nonWaitEnd = clientTestSource.indexOf(' it("keeps polling wait=true commit long enough for slow Phase 2 completion"', waitEnd); - - expect(waitStart).toBeGreaterThanOrEqual(0); - expect(waitEnd).toBeGreaterThan(waitStart); - expect(nonWaitEnd).toBeGreaterThan(waitEnd); - - const timeoutBlock = clientTestSource.slice(waitStart, nonWaitEnd); - expect(timeoutBlock).not.toMatch(/vi\.stubGlobal\("fetch"/); - expect(timeoutBlock).toMatch(/new OpenVikingClient\([\s\S]*\{\s*transport\s*\}/); - }); - it("keeps commit polling client tests on the injected transport seam", () => { const clientTestSource = readFileSync(join(rootDir, "tests/ut/client.test.ts"), "utf8"); const start = clientTestSource.indexOf('it("keeps polling wait=true commit long enough for slow Phase 2 completion"'); diff --git a/examples/openclaw-plugin/tests/ut/client-adapters.test.ts b/examples/openclaw-plugin/tests/ut/client-adapters.test.ts index 2d08aa3d00..9a64462c90 100644 --- a/examples/openclaw-plugin/tests/ut/client-adapters.test.ts +++ b/examples/openclaw-plugin/tests/ut/client-adapters.test.ts @@ -132,7 +132,7 @@ describe("OpenVikingClient adapter seams", () => { }, }); - const result = await client.commitSession("session-1", { wait: true, timeoutMs: 2_000, agentId: "runtime-agent" }); + const result = await client.commitSession("session-1", { wait: true, agentId: "runtime-agent" }); expect(result).toMatchObject({ status: "completed", @@ -157,11 +157,11 @@ describe("OpenVikingClient adapter seams", () => { transport, now: () => now, sleep: async (ms) => { - now += ms; + now += 150_000; }, }); - const result = await client.commitSession("slow-session", { wait: true, timeoutMs: 1_000 }); + const result = await client.commitSession("slow-session", { wait: true }); expect(result).toMatchObject({ status: "timeout", task_id: "task-timeout" }); expect(transport.mock.calls.map(([url]) => url)).toEqual([ diff --git a/examples/openclaw-plugin/tests/ut/client.test.ts b/examples/openclaw-plugin/tests/ut/client.test.ts index 0117b739c0..4a1665796a 100644 --- a/examples/openclaw-plugin/tests/ut/client.test.ts +++ b/examples/openclaw-plugin/tests/ut/client.test.ts @@ -304,31 +304,7 @@ describe("OpenVikingClient resource and skill import", () => { ); }); - it("uses an extended request timeout for wait=true imports", async () => { - vi.useFakeTimers(); - const transport = vi.fn((_url: string, init?: RequestInit) => new Promise((resolve, reject) => { - init?.signal?.addEventListener("abort", () => reject(new Error("aborted"))); - setTimeout(() => { - resolve(okResponse({ root_uri: "viking://resources/site", status: "success" })); - }, 20_000); - })); - - const client = new OpenVikingClient("http://127.0.0.1:1933", "", "agent", 15_000, "", "", undefined, false, true, { transport }); - const pending = client.addResource({ - pathOrUrl: "https://example.com/docs", - wait: true, - timeout: 60, - }); - - await vi.advanceTimersByTimeAsync(20_000); - - await expect(pending).resolves.toMatchObject({ - root_uri: "viking://resources/site", - status: "success", - }); - }); - - it("still uses the default request timeout for non-wait imports", async () => { + it.each([false, true])("uses configured request timeout for imports (wait=%s)", async (wait) => { vi.useFakeTimers(); const transport = vi.fn((_url: string, init?: RequestInit) => new Promise((resolve, reject) => { init?.signal?.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError"))); @@ -340,7 +316,7 @@ describe("OpenVikingClient resource and skill import", () => { const client = new OpenVikingClient("http://127.0.0.1:1933", "", "agent", 15_000, "", "", undefined, false, true, { transport }); const pending = client.addResource({ pathOrUrl: "https://example.com/docs", - wait: false, + wait, }); const assertion = expect(pending).rejects.toThrow(/aborted/i); diff --git a/examples/openclaw-plugin/tests/ut/tools.test.ts b/examples/openclaw-plugin/tests/ut/tools.test.ts index 456ed94bab..8ed50f620d 100644 --- a/examples/openclaw-plugin/tests/ut/tools.test.ts +++ b/examples/openclaw-plugin/tests/ut/tools.test.ts @@ -875,7 +875,7 @@ describe("Tool: add_resource, add_skill, and ov_search (registration)", () => { expect(props).toHaveProperty("source"); expect(props).toHaveProperty("data"); expect(props).toHaveProperty("wait"); - expect(props).toHaveProperty("timeout"); + expect(props).not.toHaveProperty("timeout"); expect(props).not.toHaveProperty("to"); expect(props).not.toHaveProperty("parent"); }); @@ -1386,10 +1386,9 @@ describe("OpenViking import command parsing", () => { }); it("parses add-skill flags", () => { - expect(parseAddSkillCommandArgs("./skills/demo --wait --timeout=30")).toMatchObject({ + expect(parseAddSkillCommandArgs("./skills/demo --wait")).toMatchObject({ source: "./skills/demo", wait: true, - timeout: 30, }); }); @@ -2290,7 +2289,6 @@ describe("Plugin registration", () => { const result = await tool.execute("tc-add-skill", { data: "name: demo\n", wait: true, - timeout: 30, }) as ToolResult; expect(result.content[0]!.text).toContain("Imported OpenViking skill"); @@ -2300,7 +2298,6 @@ describe("Plugin registration", () => { expect(body).toMatchObject({ data: "name: demo\n", wait: true, - timeout: 30, }); }); diff --git a/examples/pi-coding-agent-extension/tests/fixtures/mcp-tools-list.json b/examples/pi-coding-agent-extension/tests/fixtures/mcp-tools-list.json index f59c4e05de..48a26a65c5 100644 --- a/examples/pi-coding-agent-extension/tests/fixtures/mcp-tools-list.json +++ b/examples/pi-coding-agent-extension/tests/fixtures/mcp-tools-list.json @@ -400,10 +400,6 @@ "default": false, "title": "Wait", "type": "boolean" - }, - "timeout": { - "type": "number", - "title": "Timeout" } }, "required": [ @@ -440,10 +436,6 @@ "default": false, "title": "Wait", "type": "boolean" - }, - "timeout": { - "type": "number", - "title": "Timeout" } }, "required": [ diff --git a/examples/skills/ov-add-paper/SKILL.md b/examples/skills/ov-add-paper/SKILL.md index 625a29ea8a..b1065586c1 100644 --- a/examples/skills/ov-add-paper/SKILL.md +++ b/examples/skills/ov-add-paper/SKILL.md @@ -64,7 +64,7 @@ python3 scripts/validate_ara.py Then ingest with the OV CLI: ```bash -ov add-resource --to viking://resources/papers/ --wait --timeout 300 +ov add-resource --to viking://resources/papers/ --wait ``` Before ingest, use `ov -o json stat ` to check whether the target already exists. After ingest, verify with `ov -o json stat ` and `ov tree `. @@ -79,7 +79,7 @@ Before ingest, use `ov -o json stat ` to check whether the target al - Do not use `ov add-skill`; this skill creates paper resources, not OV skills. - Do not silently skip `ov add-resource`; if ingestion fails, report the command, error, and recovery path. -- If `ov add-resource --wait` exits with a connection error after creating the target, do not immediately retry the same URI. Run `ov stat`, `ov wait --timeout `, `ov observer queue`, and `ov tree` to determine whether ingestion completed. +- If `ov add-resource --wait` exits with a connection error after creating the target, do not immediately retry the same URI. Run `ov stat`, `ov wait`, `ov observer queue`, and `ov tree` to determine whether ingestion completed. - Do not invent claims, evidence, source refs, code, numbers, or research history. - Do not overwrite an explicit existing OV target unless the user asked for that target. - Mark unsupported or unreadable content as unavailable instead of filling it in. diff --git a/examples/skills/ov-add-paper/references/openviking-ingest.md b/examples/skills/ov-add-paper/references/openviking-ingest.md index 9094a3a176..bf78cd7055 100644 --- a/examples/skills/ov-add-paper/references/openviking-ingest.md +++ b/examples/skills/ov-add-paper/references/openviking-ingest.md @@ -14,12 +14,6 @@ ov tree viking://resources/papers/ If the user did not provide a target URI, derive a stable slug from the paper title, arXiv ID, DOI, or file stem. -Use `--timeout 300` or a larger value for medium-sized papers: - -```bash -ov add-resource --to viking://resources/papers/ --wait --timeout 300 -``` - The `stat` preflight should return NOT_FOUND for a new target. If it succeeds, ask before overwriting or choose a different target. ## Required Preconditions @@ -46,7 +40,7 @@ If `ov add-resource --wait` exits with a connection error after the target appea ```bash ov -o json stat viking://resources/papers/ -ov wait --timeout 300 +ov wait ov observer queue ov tree viking://resources/papers/ ``` diff --git a/examples/skills/ov-resources/docs/add-resource.md b/examples/skills/ov-resources/docs/add-resource.md index 7287dd01d4..ec66a04ea7 100644 --- a/examples/skills/ov-resources/docs/add-resource.md +++ b/examples/skills/ov-resources/docs/add-resource.md @@ -63,9 +63,6 @@ Semantic processing runs asynchronously. Use `--wait` to block until complete: # Wait for completion ov add-resource ./docs --wait -# Wait with timeout -ov add-resource ./docs --wait --timeout 60 - # Fire and forget (default) ov add-resource ./docs ``` @@ -145,7 +142,6 @@ With `--wait`, the response includes `queue_status` with `pending`, `processing` | `--reason` | Reason for adding (experimental) | | `--instruction` | Processing instructions (experimental) | | `--wait` | Block until semantic processing completes | -| `--timeout` | Timeout in seconds when `--wait` is used | | `--strict` | Use strict mode | | `--ignore-dirs` | Directory names to ignore (comma-separated) | | `--include` | File patterns to include (glob) | diff --git a/examples/snapshot/snapshot_example.py b/examples/snapshot/snapshot_example.py index ddf888f09c..8e5ace9873 100644 --- a/examples/snapshot/snapshot_example.py +++ b/examples/snapshot/snapshot_example.py @@ -37,13 +37,12 @@ def write_text(client: Any, uri: str, content: str, mode: str) -> None: content=content, mode=mode, wait=True, - timeout=WAIT_TIMEOUT, ) print(f"write: {uri} (mode={result.get('mode')}, bytes={result.get('written_bytes')})") def remove_resource(client: Any, uri: str) -> None: - client.rm(uri=uri, wait=True, timeout=WAIT_TIMEOUT) + client.rm(uri=uri, wait=True) print(f"rm: {uri}") diff --git a/openviking/connector/delegate.py b/openviking/connector/delegate.py index 7fce0deb29..d0382071ba 100644 --- a/openviking/connector/delegate.py +++ b/openviking/connector/delegate.py @@ -884,7 +884,6 @@ async def _monitor( ctx=ctx, reason=reason, source_name=None, - timeout=None, ) for key in ("memory_linking", "warnings"): if key in link_result: diff --git a/openviking/eval/ragas/pipeline.py b/openviking/eval/ragas/pipeline.py index fb2cde55b3..7cade95c30 100644 --- a/openviking/eval/ragas/pipeline.py +++ b/openviking/eval/ragas/pipeline.py @@ -68,7 +68,6 @@ def add_documents( self, docs_dirs: List[Union[str, Path]], wait: bool = True, - timeout: float = 300, ) -> List[str]: """ Add document directories/files to OpenViking. @@ -76,7 +75,6 @@ def add_documents( Args: docs_dirs: List of document directory or file paths wait: Whether to wait for processing - timeout: Timeout for waiting Returns: List of root URIs for added resources @@ -94,7 +92,6 @@ def add_documents( result = client.add_resource( str(path), wait=wait, - timeout=timeout, ) if result and "root_uri" in result: @@ -110,7 +107,6 @@ def add_code_repos( self, code_dirs: List[Union[str, Path]], wait: bool = True, - timeout: float = 300, ) -> List[str]: """ Add code repositories to OpenViking. @@ -118,12 +114,11 @@ def add_code_repos( Args: code_dirs: List of code repository paths (local or git URLs) wait: Whether to wait for processing - timeout: Timeout for waiting Returns: List of root URIs for added resources """ - return self.add_documents(code_dirs, wait=wait, timeout=timeout) + return self.add_documents(code_dirs, wait=wait) def query( self, diff --git a/openviking/eval/ragas/rag_eval.py b/openviking/eval/ragas/rag_eval.py index c6a4c7520e..7f38896b6c 100644 --- a/openviking/eval/ragas/rag_eval.py +++ b/openviking/eval/ragas/rag_eval.py @@ -119,7 +119,6 @@ async def initialize(self): result = client.add_resource( str(path), wait=True, - timeout=300, ) if result and "root_uri" in result: logger.info(f"Added: {result['root_uri']}") @@ -137,7 +136,6 @@ async def initialize(self): result = client.add_resource( str(path), wait=True, - timeout=300, ) if result and "root_uri" in result: logger.info(f"Added: {result['root_uri']}") diff --git a/openviking/server/mcp_endpoint.py b/openviking/server/mcp_endpoint.py index aa21803d9e..769da2964e 100644 --- a/openviking/server/mcp_endpoint.py +++ b/openviking/server/mcp_endpoint.py @@ -1200,7 +1200,6 @@ async def write( content: str, mode: Literal["replace", "append", "create"] = "replace", wait: bool = False, - timeout: Optional[float] = None, acl: Optional[AclSpec] = None, ) -> str: """Write text to a viking:// file. Use this for files you author yourself (notes, profiles, state), the same way you would use a working directory. To change part of an existing file, prefer the edit tool over a full rewrite. To store a file, document, URL, or repo the user gives you, use add_resource; for a skill, use add_skill. Do not copy its text into a file here instead. @@ -1221,7 +1220,7 @@ async def write( uri = _resolve_mcp_workspace_uri(uri, ctx) result = await service.fs.write( - uri=uri, content=content, ctx=ctx, mode=mode, wait=wait, timeout=timeout, acl=acl + uri=uri, content=content, ctx=ctx, mode=mode, wait=wait, acl=acl ) written = result.get("written_bytes", 0) message = ( @@ -1238,7 +1237,6 @@ async def edit( new_string: str, replace_all: bool = False, wait: bool = False, - timeout: Optional[float] = None, ) -> str: """Replace an exact string with new text in an existing viking:// file. Use this for targeted changes instead of rewriting the whole file with the write tool. old_string must match the file's current content exactly, including indentation and newlines; use the read tool first to see it. The edit fails and the file is left unchanged if old_string is not found, or if it matches more than once and replace_all is false (pass more surrounding context to make it unique, or set replace_all=true to replace every occurrence). Pass new_string="" to delete old_string. @@ -1275,7 +1273,7 @@ async def edit( if updated == current: return f"No changes: {uri} already matches the requested edit." result = await service.fs.write( - uri=uri, content=updated, ctx=ctx, mode="replace", wait=wait, timeout=timeout + uri=uri, content=updated, ctx=ctx, mode="replace", wait=wait ) written = result.get("written_bytes", 0) message = f"Edited {result.get('uri', uri)} ({written} bytes written)." diff --git a/openviking/server/routers/content.py b/openviking/server/routers/content.py index cacf55da06..eb212d023f 100644 --- a/openviking/server/routers/content.py +++ b/openviking/server/routers/content.py @@ -45,7 +45,6 @@ class WriteContentRequest(BaseModel): content: str mode: str = "replace" wait: bool = False - timeout: float | None = None telemetry: TelemetryRequest = False processing_mode: ProcessingMode = DEFAULT_PROCESSING_MODE tags: list[str] | None = None @@ -79,7 +78,6 @@ class BatchWriteRequest(BaseModel): root_uri: str operations: list[BatchWriteOperation] wait: bool = True - timeout: float | None = None telemetry: TelemetryRequest = False # Preserve conflicting target files and return their URI/reason while writing other files. skip_conflicts: bool = False @@ -253,7 +251,6 @@ async def write( ctx=_ctx, mode=request.mode, wait=request.wait, - timeout=request.timeout, processing_mode=request.processing_mode, tags=request.tags, tag_mode=request.tag_mode, @@ -288,7 +285,6 @@ async def batch_write( operations=operations, ctx=_ctx, wait=request.wait, - timeout=request.timeout, skip_conflicts=request.skip_conflicts, processing_mode=request.processing_mode, ), diff --git a/openviking/server/routers/filesystem.py b/openviking/server/routers/filesystem.py index 720d0cbfa5..d3768cae52 100644 --- a/openviking/server/routers/filesystem.py +++ b/openviking/server/routers/filesystem.py @@ -300,7 +300,6 @@ async def rm( uri: str = Query(..., description="Viking URI"), recursive: bool = Query(False, description="Remove recursively"), wait: bool = Query(False, description="Wait for semantic refresh to complete"), - timeout: Optional[float] = Query(None, description="Wait timeout in seconds"), _ctx: RequestContext = Depends(get_request_context), ): """Remove resource.""" @@ -308,7 +307,7 @@ async def rm( # Resolve path variables uri = validate_request_viking_uri(resolve_path_variables(uri), _ctx) try: - result = await service.fs.rm(uri, ctx=_ctx, recursive=recursive, wait=wait, timeout=timeout) + result = await service.fs.rm(uri, ctx=_ctx, recursive=recursive, wait=wait) except AGFSNotFoundError: raise NotFoundError(uri, "file") except AGFSClientError as e: diff --git a/openviking/server/routers/resources.py b/openviking/server/routers/resources.py index b4f1612fbf..d8ddd960e2 100644 --- a/openviking/server/routers/resources.py +++ b/openviking/server/routers/resources.py @@ -52,7 +52,6 @@ class AddResourceRequest(BaseModel): Provides hints for how the resource should be processed. wait: Whether to wait for semantic extraction and vectorization to complete. Default is False (async processing). - timeout: Timeout in seconds when wait=True. None means no timeout. strict: Whether to use strict mode for processing. Default is True. internal_task: Whether to hide this task from the default task list. ignore_dirs: Comma-separated list of directory names to ignore during parsing. @@ -88,7 +87,6 @@ class AddResourceRequest(BaseModel): reason: str = "" instruction: str = "" wait: bool = False - timeout: Optional[float] = None strict: bool = False internal_task: bool = False source_name: Optional[str] = None @@ -144,7 +142,6 @@ class AddSkillRequest(BaseModel): HTTP requests do not treat strings as host filesystem paths. temp_file_id: Temporary upload id returned by /api/v1/resources/temp_upload. wait: Whether to wait for skill processing to complete. - timeout: Timeout in seconds when wait=True. """ model_config = ConfigDict(extra="forbid") @@ -154,7 +151,6 @@ class AddSkillRequest(BaseModel): skills: list[str] = Field(default_factory=list) list_only: bool = False wait: bool = False - timeout: Optional[float] = None source_metadata: Optional[Dict[str, Any]] = None target_uri: Optional[str] = None telemetry: TelemetryRequest = False @@ -333,7 +329,6 @@ async def _add() -> dict[str, Any]: reason=request.reason, instruction=request.instruction, wait=request.wait, - timeout=request.timeout, acl=request.acl, tags=request.tags, tag_mode=request.tag_mode, @@ -409,7 +404,6 @@ async def _add() -> dict[str, Any]: names=request.skills, list_only=request.list_only, wait=request.wait, - timeout=request.timeout, target_uri=target_uri, source_metadata=source_metadata, allow_local_path_resolution=allow_local_path_resolution, diff --git a/openviking/server/routers/skills.py b/openviking/server/routers/skills.py index 09659519fe..5ed325154c 100644 --- a/openviking/server/routers/skills.py +++ b/openviking/server/routers/skills.py @@ -69,7 +69,6 @@ class UpdateSkillRequest(BaseModel): temp_file_id: Optional[str] = None from_source: bool = False wait: bool = False - timeout: Optional[float] = None source_metadata: Optional[Dict[str, Any]] = None telemetry: TelemetryRequest = False target_uri: Optional[str] = None @@ -983,7 +982,6 @@ async def release_update_locks() -> None: data=preparation, ctx=_ctx, wait=request.wait, - timeout=request.timeout, allow_local_path_resolution=False, source_path_hint=source_path_hint, apply_privacy=False, diff --git a/openviking/server/routers/system.py b/openviking/server/routers/system.py index 78b62f4d61..3442547414 100644 --- a/openviking/server/routers/system.py +++ b/openviking/server/routers/system.py @@ -2,8 +2,6 @@ # SPDX-License-Identifier: AGPL-3.0 """System endpoints for OpenViking HTTP Server.""" -from typing import Optional - from fastapi import APIRouter, Depends, Request from fastapi.responses import JSONResponse from pydantic import BaseModel @@ -202,12 +200,6 @@ async def system_status( ) -class WaitRequest(BaseModel): - """Request model for wait.""" - - timeout: Optional[float] = None - - class ConsistencyRequest(BaseModel): """Request model for filesystem/vector-index consistency checks.""" @@ -216,12 +208,11 @@ class ConsistencyRequest(BaseModel): @router.post("/api/v1/system/wait", tags=["system"]) async def wait_processed( - request: WaitRequest, _ctx: RequestContext = Depends(get_request_context), ): """Wait for all processing to complete.""" service = get_service() - result = await service.resources.wait_processed(timeout=request.timeout) + result = await service.resources.wait_processed() return Response(status="ok", result=result) diff --git a/openviking/server/skill_ingest.py b/openviking/server/skill_ingest.py index 33921c5c3b..b590049e78 100644 --- a/openviking/server/skill_ingest.py +++ b/openviking/server/skill_ingest.py @@ -29,7 +29,6 @@ async def install_skills( names: Optional[list[str]] = None, list_only: bool = False, wait: bool = False, - timeout: Optional[float] = None, target_uri: str = "", source_metadata: Optional[dict[str, Any]] = None, allow_local_path_resolution: bool = False, @@ -58,7 +57,6 @@ async def _install(skill_data=skill_data, skill_source=skill_source): data=skill_data, ctx=ctx, wait=wait, - timeout=timeout, allow_local_path_resolution=isinstance(skill_data, Path), source_path_hint=source_path_hint, target_uri=target_uri, diff --git a/openviking/service/fs_service.py b/openviking/service/fs_service.py index 72888bd8ba..ede621c2da 100644 --- a/openviking/service/fs_service.py +++ b/openviking/service/fs_service.py @@ -52,7 +52,6 @@ from openviking.utils.path_safety import validate_safe_viking_uri_path from openviking.utils.tags import normalize_search_tags from openviking_cli.exceptions import ( - DeadlineExceededError, InvalidArgumentError, NotFoundError, NotInitializedError, @@ -456,7 +455,6 @@ async def rm( ctx: RequestContext, recursive: bool = False, wait: bool = False, - timeout: Optional[float] = None, ) -> Optional[Dict[str, Any]]: """Remove resource.""" viking_fs = self._ensure_initialized() @@ -508,7 +506,7 @@ async def rm( ctx=ctx, ) if refresh_parent_uri and wait and refresh_action is not FreshnessAction.MARK_PENDING: - queue_status = await self._wait_for_refresh(timeout=timeout) + queue_status = await self._wait_for_refresh() finally: if request_registered: get_request_wait_tracker().cleanup(telemetry_id) @@ -718,20 +716,12 @@ async def _enqueue_copy_refresh( raise return "queued" - async def _wait_for_refresh(self, *, timeout: Optional[float]) -> Dict[str, Any]: + async def _wait_for_refresh(self) -> Dict[str, Any]: telemetry_id = get_current_telemetry().telemetry_id if telemetry_id: - try: - await get_request_wait_tracker().wait_for_request(telemetry_id, timeout=timeout) - except TimeoutError as exc: - raise DeadlineExceededError("queue processing", timeout) from exc + await get_request_wait_tracker().wait_for_request(telemetry_id) return get_request_wait_tracker().build_queue_status(telemetry_id) - try: - return build_queue_status_payload( - await get_queue_manager().wait_complete(timeout=timeout) - ) - except TimeoutError as exc: - raise DeadlineExceededError("queue processing", timeout) from exc + return build_queue_status_payload(await get_queue_manager().wait_complete()) async def cp( self, @@ -1219,7 +1209,6 @@ async def write( ctx: RequestContext, mode: str = "replace", wait: bool = False, - timeout: Optional[float] = None, processing_mode: str = "semantic_and_vectors", tags: Optional[List[str]] = None, tag_mode: str = "replace", @@ -1234,7 +1223,6 @@ async def write( ctx=ctx, mode=mode, wait=wait, - timeout=timeout, processing_mode=processing_mode, tags=tags, tag_mode=tag_mode, @@ -1248,7 +1236,6 @@ async def batch_write( operations: list[dict[str, Any]], ctx: RequestContext, wait: bool = True, - timeout: Optional[float] = None, skip_conflicts: bool = False, processing_mode: str = "semantic_and_vectors", ) -> Dict[str, Any]: @@ -1260,7 +1247,6 @@ async def batch_write( operations=operations, ctx=ctx, wait=wait, - timeout=timeout, skip_conflicts=skip_conflicts, processing_mode=processing_mode, ) diff --git a/openviking/service/resource_memory_link_service.py b/openviking/service/resource_memory_link_service.py index 7a80c1e405..d512fd68df 100644 --- a/openviking/service/resource_memory_link_service.py +++ b/openviking/service/resource_memory_link_service.py @@ -152,7 +152,6 @@ async def on_resource_added( resource_uri: str, reason: str, source_name: Optional[str] = None, - timeout: Optional[float] = None, ) -> Dict[str, Any]: """Bridge add-resource reason extraction through normal session commit.""" reason = (reason or "").strip() @@ -210,7 +209,6 @@ async def on_resource_added( task_result = await self._wait_for_commit_task( task_id=str(task_id), ctx=ctx, - timeout=timeout, ) return { "status": "success", @@ -227,7 +225,6 @@ async def on_resource_deleted( resource_uri: str, memory_uris: Sequence[str], recursive: bool = False, - timeout: Optional[float] = None, ) -> Dict[str, Any]: """Bridge resource deletion through normal session commit for mutable memories.""" if not resource_uri: @@ -280,7 +277,6 @@ async def on_resource_deleted( task_result = await self._wait_for_commit_task( task_id=str(task_id), ctx=ctx, - timeout=timeout, ) return { "status": "success", @@ -335,7 +331,6 @@ async def _wait_for_commit_task( *, task_id: str, ctx: RequestContext, - timeout: Optional[float], ) -> Dict[str, Any]: from openviking.service.task_tracker import TaskStatus, get_task_tracker @@ -343,7 +338,7 @@ async def _wait_for_commit_task( task_id, account_id=ctx.account_id, user_id=ctx.user.user_id, - timeout=(_RESOURCE_REASON_COMMIT_TIMEOUT_SECONDS if timeout is None else timeout), + timeout=_RESOURCE_REASON_COMMIT_TIMEOUT_SECONDS, poll_interval=0.1, ) if task.status == TaskStatus.COMPLETED: diff --git a/openviking/service/resource_service.py b/openviking/service/resource_service.py index bdab1f0217..acce5d5000 100644 --- a/openviking/service/resource_service.py +++ b/openviking/service/resource_service.py @@ -87,7 +87,6 @@ from openviking.utils.skill_processor import SkillProcessingPreparation, SkillProcessor from openviking_cli.exceptions import ( ConflictError, - DeadlineExceededError, FailedPreconditionError, InternalError, InvalidArgumentError, @@ -842,7 +841,6 @@ async def save_response(entry: str, response_id: str) -> None: reason=msg.reason, instruction=msg.instruction, defer_post_processing=False, - timeout=msg.timeout, build_index=msg.build_index, summarize=msg.summarize, processing_mode=msg.processing_mode, @@ -1218,7 +1216,6 @@ async def _enqueue_source_plan( create_parent: bool, reason: str, instruction: str, - timeout: Optional[float], build_index: bool, summarize: bool, processing_mode: ProcessingMode, @@ -1295,7 +1292,6 @@ async def _enqueue_source_plan( lock_handoff=lock_handoff, reason=reason, instruction=instruction, - timeout=timeout, build_index=build_index, summarize=summarize, processing_mode=processing_mode, @@ -1514,7 +1510,6 @@ async def add_resource( reason: str = "", instruction: str = "", wait: bool = False, - timeout: Optional[float] = None, build_index: bool = True, summarize: bool = False, processing_mode: ProcessingMode = DEFAULT_PROCESSING_MODE, @@ -1549,7 +1544,6 @@ async def add_resource( reason=reason, instruction=instruction, wait=wait, - timeout=timeout, build_index=build_index, summarize=summarize, processing_mode=processing_mode, @@ -1576,7 +1570,6 @@ async def refresh_resource( parent: Optional[str] = None, reason: str = "", instruction: str = "", - timeout: Optional[float] = None, build_index: bool = True, summarize: bool = False, processing_mode: ProcessingMode = DEFAULT_PROCESSING_MODE, @@ -1598,7 +1591,6 @@ async def refresh_resource( reason=reason, instruction=instruction, wait=False, - timeout=timeout, build_index=build_index, summarize=summarize, processing_mode=processing_mode, @@ -1623,7 +1615,6 @@ async def _submit_resource_ingestion( reason: str = "", instruction: str = "", wait: bool = False, - timeout: Optional[float] = None, build_index: bool = True, summarize: bool = False, processing_mode: ProcessingMode = DEFAULT_PROCESSING_MODE, @@ -1674,7 +1665,6 @@ async def _submit_resource_ingestion( reason: Reason for adding the resource instruction: Processing instruction for semantic extraction wait: Whether to wait for semantic extraction and vectorization to complete - timeout: Wait timeout in seconds build_index: Whether to build vector index immediately (default: True) summarize: Whether to generate summary (default: False) processing_mode: Post-ingest processing mode for semantic/vector work @@ -1973,7 +1963,6 @@ async def apply_connector_acl(new_states=None): create_parent=target_create_parent, reason=reason, instruction=instruction, - timeout=timeout, build_index=build_index, summarize=summarize, processing_mode=processing_mode, @@ -2000,7 +1989,6 @@ async def apply_connector_acl(new_states=None): reason=reason, instruction=instruction, defer_post_processing=True, - timeout=timeout, build_index=build_index, summarize=summarize, processing_mode=processing_mode, @@ -2025,17 +2013,11 @@ async def apply_connector_acl(new_states=None): from openviking.service.task_tracker import TaskStatus, get_task_tracker task_id = result["task_id"] - try: - task = await get_task_tracker().wait( - task_id, - account_id=ctx.account_id, - user_id=ctx.user.user_id, - timeout=timeout, - ) - except TimeoutError as exc: - raise DeadlineExceededError( - "waiting for resource import", timeout, task_id=task_id - ) from exc + task = await get_task_tracker().wait( + task_id, + account_id=ctx.account_id, + user_id=ctx.user.user_id, + ) if task.status == TaskStatus.COMPLETED: completed = dict(task.result) @@ -2063,7 +2045,6 @@ async def _execute_resource_ingestion( parent: Optional[str] = None, reason: str = "", instruction: str = "", - timeout: Optional[float] = None, build_index: bool = True, summarize: bool = False, processing_mode: ProcessingMode = DEFAULT_PROCESSING_MODE, @@ -2194,7 +2175,6 @@ async def _execute_resource_ingestion( lock_handoff=lock_handoff, reason=reason, instruction=instruction, - timeout=timeout, build_index=build_index, summarize=summarize, processing_mode=processing_mode, @@ -2259,7 +2239,6 @@ async def _link_resource_reason_memory( ctx: RequestContext, reason: str, source_name: Optional[str], - timeout: Optional[float] = None, ) -> None: if not self._resource_memory_link_service: return @@ -2274,7 +2253,6 @@ async def _link_resource_reason_memory( resource_uri=root_uri, reason=reason, source_name=source_name, - timeout=timeout, ) result["memory_linking"] = link_result except Exception as exc: @@ -2437,7 +2415,6 @@ async def add_skill( data: Any, ctx: RequestContext, wait: bool = False, - timeout: Optional[float] = None, allow_local_path_resolution: bool = True, source_path_hint: Optional[str] = None, apply_privacy: bool = True, @@ -2452,7 +2429,6 @@ async def add_skill( Args: data: Skill data (directory path, file path, string, or dict) wait: Whether to wait for vectorization to complete - timeout: Wait timeout in seconds target_uri: Optional root URI override (e.g. ``viking://agent/skills``). Returns: @@ -2540,20 +2516,12 @@ async def create_skill_task() -> None: if wait: wait_start = time.perf_counter() - try: - if telemetry_id: - await request_wait_tracker.wait_for_request(telemetry_id, timeout=timeout) - status = request_wait_tracker.build_queue_status(telemetry_id) - else: - qm = get_queue_manager() - status = build_queue_status_payload(await qm.wait_complete(timeout=timeout)) - except TimeoutError as exc: - get_current_telemetry().set_error( - "resource_service.wait_complete", - "DEADLINE_EXCEEDED", - str(exc), - ) - raise DeadlineExceededError("queue processing", timeout) from exc + if telemetry_id: + await request_wait_tracker.wait_for_request(telemetry_id) + status = request_wait_tracker.build_queue_status(telemetry_id) + else: + qm = get_queue_manager() + status = build_queue_status_payload(await qm.wait_complete()) get_current_telemetry().set( "queue.wait.duration_ms", round((time.perf_counter() - wait_start) * 1000, 3), @@ -2582,7 +2550,7 @@ async def create_skill_task() -> None: except BaseException as exc: if task is not None and not monitor_started: if processing_started and not request_wait_tracker.is_complete(telemetry_id): - # A standalone add retains its original timeout behavior. + # A cancelled standalone request leaves processing in the background. # An update's caller separately cancels before restoring. monitor_started = True asyncio.create_task( @@ -2651,20 +2619,14 @@ async def summarize( self._ensure_initialized() return await self._resource_processor.summarize(resource_uris, ctx, **kwargs) - async def wait_processed(self, timeout: Optional[float] = None) -> Dict[str, Any]: + async def wait_processed(self) -> Dict[str, Any]: """Wait for all queued processing to complete. - Args: - timeout: Wait timeout in seconds - Returns: Queue status """ qm = get_queue_manager() - try: - status = await qm.wait_complete(timeout=timeout) - except TimeoutError as exc: - raise DeadlineExceededError("queue processing", timeout) from exc + status = await qm.wait_complete() return { name: { "processed": s.processed, diff --git a/openviking/storage/content_write.py b/openviking/storage/content_write.py index a6d8cfa146..b31ec54aff 100644 --- a/openviking/storage/content_write.py +++ b/openviking/storage/content_write.py @@ -85,7 +85,6 @@ from openviking.utils.tags import normalize_search_tags from openviking_cli.exceptions import ( ConflictError, - DeadlineExceededError, InvalidArgumentError, NotFoundError, OpenVikingError, @@ -206,7 +205,6 @@ async def write( ctx: RequestContext, mode: str = "replace", wait: bool = False, - timeout: Optional[float] = None, processing_mode: ProcessingMode = DEFAULT_PROCESSING_MODE, tags: list[str] | None = None, tag_mode: str = "replace", @@ -252,7 +250,6 @@ async def write( mode=mode, response_mode=response_mode, wait=wait, - timeout=timeout, ctx=ctx, telemetry_id=telemetry_id, ingest_options=ingest_options, @@ -266,7 +263,6 @@ async def write( response_mode=response_mode, context_type=context_type, wait=wait, - timeout=timeout, ctx=ctx, telemetry_id=telemetry_id, processing_mode=processing_mode, @@ -280,7 +276,6 @@ async def batch_write( operations: list[dict[str, Any]], ctx: RequestContext, wait: bool = True, - timeout: Optional[float] = None, skip_conflicts: bool = False, processing_mode: ProcessingMode = DEFAULT_PROCESSING_MODE, ) -> Dict[str, Any]: @@ -517,7 +512,6 @@ async def batch_write( # wait after both legacy special paths and RNFV # plans have enqueued their work. wait=False, - timeout=timeout, telemetry_id=telemetry_id, file_md5s=file_md5s, file_abstracts=file_abstracts, @@ -557,8 +551,6 @@ async def batch_write( ) queue_status = None else: - if isinstance(exc, DeadlineExceededError): - raise cause = str(exc).strip() or type(exc).__name__ raise OpenVikingError( "Content is already at the requested state, but semantic/index " @@ -594,7 +586,7 @@ async def batch_write( await self._viking_fs._async_agfs.pathlock_release(lease) lock_settled = True queue_status = ( - await self._wait_for_request(telemetry_id=telemetry_id, timeout=timeout) + await self._wait_for_request(telemetry_id=telemetry_id) if wait and request_registered else refresh_outcome.queue_status if refresh_outcome is not None @@ -781,7 +773,6 @@ async def _refresh_batch( refresh_kinds: dict[str, str], ctx: RequestContext, wait: bool, - timeout: Optional[float], telemetry_id: str, file_md5s: dict[str, str] | None = None, file_abstracts: dict[str, str] | None = None, @@ -858,7 +849,6 @@ async def _refresh_batch( ) queue_status = await self._wait_for_request( telemetry_id=telemetry_id, - timeout=timeout, ) self._raise_refresh_errors(queue_status) return _BatchRefreshOutcome( @@ -1460,7 +1450,6 @@ async def _write_direct_with_refresh( response_mode: str, context_type: str, wait: bool, - timeout: Optional[float], ctx: RequestContext, telemetry_id: str, processing_mode: ProcessingMode = DEFAULT_PROCESSING_MODE, @@ -1478,7 +1467,6 @@ async def _write_direct_with_refresh( response_mode=response_mode, context_type=context_type, wait=wait, - timeout=timeout, ctx=ctx, telemetry_id=telemetry_id, ingest_options=ingest_options, @@ -1586,7 +1574,7 @@ async def _write_direct_with_refresh( lock_settled = True queue_status = ( - await self._wait_for_request(telemetry_id=telemetry_id, timeout=timeout) + await self._wait_for_request(telemetry_id=telemetry_id) if wait else None ) @@ -1702,7 +1690,6 @@ async def _write_abstract_overview_with_refresh( response_mode: str, context_type: str, wait: bool, - timeout: Optional[float], ctx: RequestContext, telemetry_id: str, ingest_options: IngestOptions | None = None, @@ -1736,7 +1723,7 @@ async def _write_abstract_overview_with_refresh( await self._viking_fs._async_agfs.pathlock_release(lease) lock_released = True queue_status = ( - await self._wait_for_request(telemetry_id=telemetry_id, timeout=timeout) + await self._wait_for_request(telemetry_id=telemetry_id) if wait else None ) @@ -2012,27 +1999,20 @@ def _prepare_abstract_overview_content( except AbstractOverviewFormatError as exc: raise InvalidArgumentError(str(exc)) from exc - async def _wait_for_queues(self, *, timeout: Optional[float]) -> Dict[str, Any]: + async def _wait_for_queues(self) -> Dict[str, Any]: queue_manager = get_queue_manager() - try: - status = await queue_manager.wait_complete(timeout=timeout) - except TimeoutError as exc: - raise DeadlineExceededError("queue processing", timeout) from exc + status = await queue_manager.wait_complete() return build_queue_status_payload(status) async def _wait_for_request( self, *, telemetry_id: str, - timeout: Optional[float], ) -> Dict[str, Any]: if not telemetry_id: - return await self._wait_for_queues(timeout=timeout) + return await self._wait_for_queues() tracker = get_request_wait_tracker() - try: - await tracker.wait_for_request(telemetry_id, timeout=timeout) - except TimeoutError as exc: - raise DeadlineExceededError("queue processing", timeout) from exc + await tracker.wait_for_request(telemetry_id) return tracker.build_queue_status(telemetry_id) async def _write_memory_with_refresh( @@ -2044,7 +2024,6 @@ async def _write_memory_with_refresh( mode: str, response_mode: str, wait: bool, - timeout: Optional[float], ctx: RequestContext, telemetry_id: str, ingest_options: IngestOptions | None = None, @@ -2097,9 +2076,9 @@ async def _write_memory_with_refresh( queue_status = None if embedding_requested and wait: queue_status = ( - await self._wait_for_request(telemetry_id=telemetry_id, timeout=timeout) + await self._wait_for_request(telemetry_id=telemetry_id) if telemetry_id - else await self._wait_for_queues(timeout=timeout) + else await self._wait_for_queues() ) vector_status = self._queue_work_status( requested=embedding_requested, diff --git a/openviking/storage/queuefs/add_resource_msg.py b/openviking/storage/queuefs/add_resource_msg.py index fcd5b64afd..e9b16364be 100644 --- a/openviking/storage/queuefs/add_resource_msg.py +++ b/openviking/storage/queuefs/add_resource_msg.py @@ -34,7 +34,6 @@ class AddResourceMsg: bypass_acl: bool = False reason: str = "" instruction: str = "" - timeout: Optional[float] = None build_index: bool = True summarize: bool = False strict: bool = False @@ -181,7 +180,6 @@ def from_dict(cls, data: Dict[str, Any]) -> "AddResourceMsg": else None, reason=str(data.get("reason", "")), instruction=str(data.get("instruction", "")), - timeout=float(data["timeout"]) if data.get("timeout") is not None else None, build_index=bool(data.get("build_index", True)), summarize=bool(data.get("summarize", False)), strict=bool(data.get("strict", False)), diff --git a/openviking/storage/queuefs/add_resource_processor.py b/openviking/storage/queuefs/add_resource_processor.py index f763b967ed..214a151efb 100644 --- a/openviking/storage/queuefs/add_resource_processor.py +++ b/openviking/storage/queuefs/add_resource_processor.py @@ -341,7 +341,6 @@ async def _set_stage(stage: str) -> None: ctx=ctx, reason=msg.reason, source_name=msg.source_name, - timeout=msg.timeout, ) await self._record_watch_execution(msg, "completed") await tracker.complete( diff --git a/openviking_cli/exceptions.py b/openviking_cli/exceptions.py index 133ae1300c..b0d20616cb 100644 --- a/openviking_cli/exceptions.py +++ b/openviking_cli/exceptions.py @@ -159,19 +159,11 @@ def __init__( self, operation: str = "operation", timeout: Optional[float] = None, - *, - task_id: Optional[str] = None, ): message = f"{operation.capitalize()} timed out" if timeout: message += f" after {timeout}s" details = {"operation": operation, "timeout": timeout} - if task_id is not None: - message += ( - ". This timeout only stops waiting; it does not cancel or fail the background task. " - f"Check its status with 'ov task status {task_id}'." - ) - details["task_id"] = task_id super().__init__(message, code="DEADLINE_EXCEEDED", details=details) diff --git a/sdk/go/examples/basic_usage/main.go b/sdk/go/examples/basic_usage/main.go index 6bb36d96ea..e97e035711 100644 --- a/sdk/go/examples/basic_usage/main.go +++ b/sdk/go/examples/basic_usage/main.go @@ -60,9 +60,7 @@ func main() { fmt.Printf(" resource=%v\n", resource) fmt.Println("3. Wait for background processing") - waitResult, err := client.WaitProcessed(ctx, &openviking.WaitProcessedOptions{ - Timeout: openviking.Float64(120), - }) + waitResult, err := client.WaitProcessed(ctx) if err != nil { fmt.Printf(" wait warning: %v\n", err) } else { @@ -184,8 +182,7 @@ func main() { fmt.Printf(" validation=%v\n", validation) skill, err := client.AddSkill(ctx, skillDir, &openviking.AddSkillOptions{ - Wait: true, - Timeout: openviking.Float64(120), + Wait: true, }) if err != nil { log.Fatal(err) @@ -214,8 +211,7 @@ func main() { updateDemoSkill(skillDir, skillName, "Updated by Go SDK smoke test.") updatedSkill, err := client.UpdateSkill(ctx, skillName, skillDir, &openviking.UpdateSkillOptions{ - Wait: true, - Timeout: openviking.Float64(120), + Wait: true, SourceMetadata: map[string]any{ "type": "example", "operation": "basic_usage", diff --git a/sdk/go/filesystem.go b/sdk/go/filesystem.go index bb2a8776d5..4c307df40b 100644 --- a/sdk/go/filesystem.go +++ b/sdk/go/filesystem.go @@ -193,9 +193,6 @@ func (c *Client) Remove(ctx context.Context, uri string, opts *RemoveOptions) er query.Set("uri", NormalizeURI(uri)) queryBool(query, "recursive", opts.Recursive) queryBool(query, "wait", opts.Wait) - if opts.Timeout != nil { - queryFloat(query, "timeout", *opts.Timeout) - } return c.doJSON(ctx, http.MethodDelete, "/api/v1/fs", query, nil, nil) } @@ -282,7 +279,6 @@ func (c *Client) Write(ctx context.Context, uri string, content string, opts *Wr "mode": mode, "wait": opts.Wait, } - setFloatPtr(payload, "timeout", opts.Timeout) setAny(payload, "telemetry", opts.Telemetry) setString(payload, "processing_mode", opts.ProcessingMode) if opts.ACL != nil { @@ -324,7 +320,6 @@ func (c *Client) BatchWrite( } if opts != nil { setAny(payload, "wait", opts.Wait) - setFloatPtr(payload, "timeout", opts.Timeout) setAny(payload, "telemetry", opts.Telemetry) setString(payload, "processing_mode", opts.ProcessingMode) if err := mergeExtra(payload, opts.Extra); err != nil { diff --git a/sdk/go/helpers.go b/sdk/go/helpers.go index f688e9b15e..b01a3cb7fe 100644 --- a/sdk/go/helpers.go +++ b/sdk/go/helpers.go @@ -118,10 +118,6 @@ func queryInt(values url.Values, key string, value int) { values.Set(key, strconv.Itoa(value)) } -func queryFloat(values url.Values, key string, value float64) { - values.Set(key, strconv.FormatFloat(value, 'f', -1, 64)) -} - func normalizeTarget(target any) any { switch v := target.(type) { case nil: diff --git a/sdk/go/resources.go b/sdk/go/resources.go index cdc3efd455..2a83b83249 100644 --- a/sdk/go/resources.go +++ b/sdk/go/resources.go @@ -32,7 +32,6 @@ func (c *Client) AddResource(ctx context.Context, path string, opts *AddResource setString(payload, "ignore_dirs", opts.IgnoreDirs) setString(payload, "include", opts.Include) setString(payload, "exclude", opts.Exclude) - setFloatPtr(payload, "timeout", opts.Timeout) if opts.PreserveStructure != nil { payload["preserve_structure"] = *opts.PreserveStructure } diff --git a/sdk/go/skills.go b/sdk/go/skills.go index 7314be7a4a..a85c243164 100644 --- a/sdk/go/skills.go +++ b/sdk/go/skills.go @@ -17,7 +17,6 @@ func (c *Client) AddSkill(ctx context.Context, data any, opts *AddSkillOptions) payload := map[string]any{ "wait": opts.Wait, } - setFloatPtr(payload, "timeout", opts.Timeout) setAny(payload, "telemetry", opts.Telemetry) setAny(payload, "target_uri", opts.TargetURI) if err := c.attachSkillData(ctx, payload, data); err != nil { @@ -120,7 +119,6 @@ func (c *Client) UpdateSkill(ctx context.Context, skillName string, data any, op payload := map[string]any{ "wait": opts.Wait, } - setFloatPtr(payload, "timeout", opts.Timeout) setAny(payload, "source_metadata", opts.SourceMetadata) setAny(payload, "telemetry", opts.Telemetry) setAny(payload, "target_uri", opts.TargetURI) diff --git a/sdk/go/system.go b/sdk/go/system.go index d17bcf505c..85f33d5016 100644 --- a/sdk/go/system.go +++ b/sdk/go/system.go @@ -11,11 +11,8 @@ import ( ) // WaitProcessed waits until all queued processing completes. -func (c *Client) WaitProcessed(ctx context.Context, opts *WaitProcessedOptions) (map[string]any, error) { +func (c *Client) WaitProcessed(ctx context.Context) (map[string]any, error) { payload := map[string]any{} - if opts != nil && opts.Timeout != nil { - payload["timeout"] = *opts.Timeout - } var result map[string]any err := c.doJSON(ctx, http.MethodPost, "/api/v1/system/wait", nil, payload, &result) return result, err diff --git a/sdk/go/types.go b/sdk/go/types.go index a40c540d03..22dc3fdcf0 100644 --- a/sdk/go/types.go +++ b/sdk/go/types.go @@ -33,7 +33,6 @@ type AddResourceOptions struct { Reason string Instruction string Wait bool - Timeout *float64 Strict bool IgnoreDirs string Include string @@ -52,7 +51,6 @@ type AddResourceOptions struct { // AddSkillOptions controls AddSkill. type AddSkillOptions struct { Wait bool - Timeout *float64 Telemetry any Extra map[string]any // TargetURI scopes the operation to a skills root such as @@ -141,7 +139,6 @@ type GetSkillOptions struct { // UpdateSkillOptions controls UpdateSkill. type UpdateSkillOptions struct { Wait bool - Timeout *float64 SourceMetadata map[string]any Telemetry any TargetURI any @@ -153,11 +150,6 @@ type DeleteSkillOptions struct { TargetURI any } -// WaitProcessedOptions controls WaitProcessed. -type WaitProcessedOptions struct { - Timeout *float64 `json:"timeout,omitempty"` -} - // ObserverStatusOptions controls observer status formatting. type ObserverStatusOptions struct { Format string @@ -239,7 +231,6 @@ type TreePage struct { type RemoveOptions struct { Recursive bool Wait bool - Timeout *float64 } // WriteOptions controls Write. @@ -247,7 +238,6 @@ type WriteOptions struct { ACL *ACLSpec Mode string Wait bool - Timeout *float64 Telemetry any ProcessingMode string Tags []string @@ -269,7 +259,6 @@ type BatchWriteOperation struct { // BatchWriteOptions controls BatchWrite. type BatchWriteOptions struct { Wait *bool - Timeout *float64 Telemetry any ProcessingMode string Extra map[string]any diff --git a/sdk/python/openviking_sdk/client.py b/sdk/python/openviking_sdk/client.py index b5afc34c1b..c01669d003 100644 --- a/sdk/python/openviking_sdk/client.py +++ b/sdk/python/openviking_sdk/client.py @@ -483,12 +483,6 @@ async def _request(self, method: str, url: str, **kwargs: Any) -> httpx.Response retry_headers[GATEWAY_TOKEN_HEADER] = self._gateway_token return await self._send_http_request(method, url, retry_headers, request_kwargs) - def _wait_request_kwargs(self, *, wait: bool, timeout: Optional[float]) -> Dict[str, Any]: - if not wait or timeout is None: - return {} - read_timeout = max(self._timeout, timeout + 30.0) - return {"timeout": httpx.Timeout(self._timeout, read=read_timeout)} - async def close(self) -> None: if self._http: try: @@ -717,7 +711,6 @@ async def add_resource( to: Optional[str] = None, parent: Optional[str] = None, wait: bool = False, - timeout: Optional[float] = None, options: Optional[AddResourceOptions] = None, ) -> Dict[str, Any]: option_values = dict(options or {}) @@ -744,7 +737,6 @@ async def add_resource( "to": to, "parent": parent, "wait": wait, - "timeout": timeout, }, protected={"path", "temp_file_id", "source_name"}, ) @@ -794,7 +786,6 @@ async def add_skill( self, data: Any, wait: bool = False, - timeout: Optional[float] = None, options: Optional[AddSkillOptions] = None, ) -> Dict[str, Any]: option_values = dict(options or {}) @@ -803,7 +794,7 @@ async def add_skill( request_data = self._build_options_payload( option_values, AddSkillOptions, - fixed={"wait": wait, "timeout": timeout}, + fixed={"wait": wait}, protected={"data", "temp_file_id"}, ) if isinstance(data, str): @@ -905,7 +896,6 @@ async def update_skill( skill_name: str, data: Any, wait: bool = False, - timeout: Optional[float] = None, options: Optional[UpdateSkillOptions] = None, ) -> Dict[str, Any]: option_values = dict(options or {}) @@ -914,7 +904,7 @@ async def update_skill( request_data = self._build_options_payload( option_values, UpdateSkillOptions, - fixed={"wait": wait, "timeout": timeout}, + fixed={"wait": wait}, protected={"data", "temp_file_id"}, ) if isinstance(data, str): @@ -1041,13 +1031,11 @@ async def trigger_watch( ) return self._handle_response(response) - async def wait_processed(self, timeout: Optional[float] = None) -> Dict[str, Any]: - http_timeout = timeout if timeout else 600.0 + async def wait_processed(self) -> Dict[str, Any]: response = await self._request( "POST", "/api/v1/system/wait", - json={"timeout": timeout}, - timeout=http_timeout, + json={}, ) return self._handle_response(response) @@ -1269,11 +1257,8 @@ async def rm( uri: str, recursive: bool = False, wait: bool = False, - timeout: Optional[float] = None, ) -> None: params = {"uri": VikingURI.normalize(uri), "recursive": recursive, "wait": wait} - if timeout is not None: - params["timeout"] = timeout response = await self._request("DELETE", "/api/v1/fs", params=params) self._handle_response(response) @@ -1340,7 +1325,6 @@ async def write( content: str, mode: str = "replace", wait: bool = False, - timeout: Optional[float] = None, options: Optional[WriteOptions] = None, ) -> Dict[str, Any]: payload = self._build_options_payload( @@ -1351,7 +1335,6 @@ async def write( "content": content, "mode": mode, "wait": wait, - "timeout": timeout, }, ) response = await self._request( @@ -1366,7 +1349,6 @@ async def batch_write( root_uri: str, operations: List[Dict[str, Any]], wait: bool = True, - timeout: Optional[float] = None, options: Optional[BatchWriteOptions] = None, ) -> Dict[str, Any]: """Apply multiple content writes, then refresh semantics once.""" @@ -1382,14 +1364,12 @@ async def batch_write( "root_uri": VikingURI.normalize(root_uri), "operations": normalized_operations, "wait": wait, - "timeout": timeout, }, ) response = await self._request( "POST", "/api/v1/content/batch-write", json=payload, - **self._wait_request_kwargs(wait=wait, timeout=timeout), ) return self._handle_response_data(response).get("result", {}) @@ -2368,7 +2348,6 @@ def add_resource( to: Optional[str] = None, parent: Optional[str] = None, wait: bool = False, - timeout: Optional[float] = None, options: Optional[AddResourceOptions] = None, ) -> Dict[str, Any]: return run_async( @@ -2377,7 +2356,6 @@ def add_resource( to=to, parent=parent, wait=wait, - timeout=timeout, options=options, ) ) @@ -2394,12 +2372,9 @@ def add_skill( self, data: Any, wait: bool = False, - timeout: Optional[float] = None, options: Optional[AddSkillOptions] = None, ) -> Dict[str, Any]: - return run_async( - self._async_client.add_skill(data, wait=wait, timeout=timeout, options=options) - ) + return run_async(self._async_client.add_skill(data, wait=wait, options=options)) def list_skills( self, @@ -2475,13 +2450,10 @@ def update_skill( skill_name: str, data: Any, wait: bool = False, - timeout: Optional[float] = None, options: Optional[UpdateSkillOptions] = None, ) -> Dict[str, Any]: return run_async( - self._async_client.update_skill( - skill_name, data, wait=wait, timeout=timeout, options=options - ) + self._async_client.update_skill(skill_name, data, wait=wait, options=options) ) def delete_skill( @@ -2542,8 +2514,8 @@ def trigger_watch( ) -> Dict[str, Any]: return run_async(self._async_client.trigger_watch(task_id, to_uri=to_uri)) - def wait_processed(self, timeout: Optional[float] = None) -> Dict[str, Any]: - return run_async(self._async_client.wait_processed(timeout)) + def wait_processed(self) -> Dict[str, Any]: + return run_async(self._async_client.wait_processed()) def ls( self, @@ -2717,9 +2689,8 @@ def rm( uri: str, recursive: bool = False, wait: bool = False, - timeout: Optional[float] = None, ) -> None: - run_async(self._async_client.rm(uri, recursive=recursive, wait=wait, timeout=timeout)) + run_async(self._async_client.rm(uri, recursive=recursive, wait=wait)) def mv(self, from_uri: str, to_uri: str) -> None: run_async(self._async_client.mv(from_uri, to_uri)) @@ -2745,13 +2716,10 @@ def write( content: str, mode: str = "replace", wait: bool = False, - timeout: Optional[float] = None, options: Optional[WriteOptions] = None, ) -> Dict[str, Any]: return run_async( - self._async_client.write( - uri, content, mode=mode, wait=wait, timeout=timeout, options=options - ) + self._async_client.write(uri, content, mode=mode, wait=wait, options=options) ) def batch_write( @@ -2759,13 +2727,10 @@ def batch_write( root_uri: str, operations: List[Dict[str, Any]], wait: bool = True, - timeout: Optional[float] = None, options: Optional[BatchWriteOptions] = None, ) -> Dict[str, Any]: return run_async( - self._async_client.batch_write( - root_uri, operations, wait=wait, timeout=timeout, options=options - ) + self._async_client.batch_write(root_uri, operations, wait=wait, options=options) ) def set_tags( diff --git a/sdk/python/tests/test_async_client_behaviors.py b/sdk/python/tests/test_async_client_behaviors.py index 521de8eb94..5b9ef9b76f 100644 --- a/sdk/python/tests/test_async_client_behaviors.py +++ b/sdk/python/tests/test_async_client_behaviors.py @@ -1122,7 +1122,6 @@ def test_sync_add_resource_accepts_and_forwards_declared_add_type(): "to": "viking://resources/feishu", "parent": None, "wait": False, - "timeout": None, "options": {"add_type": "feishu"}, } @@ -1598,13 +1597,13 @@ async def test_ls_and_tree_preserve_query_options_and_pagination(): @pytest.mark.asyncio -async def test_rm_uses_delete_request_with_timeout_when_provided(): +async def test_rm_uses_delete_request_with_wait(): client = AsyncHTTPClient(url="http://localhost:1933") fake_http = SimpleNamespace(request=AsyncMock(return_value=object())) client._http = fake_http client._handle_response = lambda _response: None - await client.rm("/resources/demo.md", recursive=True, wait=True, timeout=5.0) + await client.rm("/resources/demo.md", recursive=True, wait=True) fake_http.request.assert_awaited_once_with( "DELETE", @@ -1613,29 +1612,10 @@ async def test_rm_uses_delete_request_with_timeout_when_provided(): "uri": "viking://resources/demo.md", "recursive": True, "wait": True, - "timeout": 5.0, }, ) -@pytest.mark.asyncio -async def test_batch_write_http_timeout_outlives_server_wait_timeout(): - client = AsyncHTTPClient(url="http://localhost:1933", timeout=180.0) - client._request = AsyncMock(return_value=object()) - client._handle_response_data = lambda _response: {"result": {}} - - await client.batch_write( - "viking://resources/wiki", - [], - wait=True, - timeout=300.0, - ) - - request_timeout = client._request.await_args.kwargs["timeout"] - assert request_timeout.read == 330.0 - assert request_timeout.connect == 180.0 - - @pytest.mark.asyncio async def test_batch_write_serializes_processing_mode_and_operation_metadata(): client = AsyncHTTPClient(url="http://localhost:1933") diff --git a/sdk/typescript/src/client.ts b/sdk/typescript/src/client.ts index 2ac0d64740..3447a891c8 100644 --- a/sdk/typescript/src/client.ts +++ b/sdk/typescript/src/client.ts @@ -149,7 +149,6 @@ export class OpenVikingClient { reason: options.reason, instruction: options.instruction, wait: options.wait, - timeout: options.timeout, strict: options.strict, ignore_dirs: options.ignoreDirs, include: options.include, @@ -187,7 +186,6 @@ export class OpenVikingClient { ): Promise { const body: JsonObject = compact({ wait: options.wait ?? false, - timeout: options.timeout, telemetry: options.telemetry, target_uri: options.targetUri, }); @@ -279,7 +277,6 @@ export class OpenVikingClient { ): Promise { const body: JsonObject = compact({ wait: options.wait ?? false, - timeout: options.timeout, source_metadata: options.sourceMetadata, target_uri: options.targetUri, telemetry: options.telemetry, @@ -615,14 +612,13 @@ export class OpenVikingClient { /** Remove a resource or directory. */ remove( uri: string, - options: { recursive?: boolean; wait?: boolean; timeout?: number } = {}, + options: { recursive?: boolean; wait?: boolean } = {}, ): Promise { return this.request("DELETE", "/api/v1/fs", { query: { uri: normalizeURI(uri), recursive: options.recursive ?? false, wait: options.wait ?? false, - timeout: options.timeout, }, }); } @@ -684,7 +680,6 @@ export class OpenVikingClient { ? (options.tagMode ?? "replace") : undefined, wait: options.wait, - timeout: options.timeout, telemetry: options.telemetry, }); return this.request("POST", "/api/v1/content/write", { @@ -711,7 +706,6 @@ export class OpenVikingClient { }), ), wait: options.wait, - timeout: options.timeout, telemetry: options.telemetry, processing_mode: options.processingMode, }); @@ -1085,9 +1079,9 @@ export class OpenVikingClient { }); } /** Wait for queued processing to finish. */ - waitProcessed(timeout?: number): Promise { + waitProcessed(): Promise { return this.request("POST", "/api/v1/system/wait", { - body: compact({ timeout }), + body: {}, }); } /** Check the raw server health endpoint. */ diff --git a/sdk/typescript/src/types.ts b/sdk/typescript/src/types.ts index 722b29d174..181611a0ee 100644 --- a/sdk/typescript/src/types.ts +++ b/sdk/typescript/src/types.ts @@ -74,7 +74,6 @@ export interface RequestOptions { /** Options shared by asynchronous processing APIs. */ export interface WaitOptions { wait?: boolean; - timeout?: number; telemetry?: unknown; } /** Writable direct grants and inheritance mode. */ diff --git a/tests/api_test/api/client.py b/tests/api_test/api/client.py index 76afebae8d..9bb18cdeb8 100644 --- a/tests/api_test/api/client.py +++ b/tests/api_test/api/client.py @@ -394,7 +394,6 @@ def fs_write( content: str, mode: str = "replace", wait: bool = False, - timeout: Optional[float] = None, ) -> requests.Response: endpoint = "/api/v1/content/write" url = self._build_url(self.server_url, endpoint) @@ -404,8 +403,6 @@ def fs_write( "mode": mode, "wait": wait, } - if timeout is not None: - payload["timeout"] = timeout return self._request_with_retry("POST", url, json=payload) def fs_rm(self, uri: str, recursive: bool = False) -> requests.Response: @@ -503,12 +500,10 @@ def system_status(self) -> requests.Response: url = self._build_url(self.server_url, endpoint) return self._request_with_retry("GET", url) - def system_wait(self, timeout: Optional[float] = None) -> requests.Response: + def system_wait(self) -> requests.Response: endpoint = "/api/v1/system/wait" url = self._build_url(self.server_url, endpoint) payload = {} - if timeout is not None: - payload["timeout"] = timeout return self._request_with_retry("POST", url, json=payload) def observer_queue(self) -> requests.Response: @@ -610,16 +605,12 @@ def admin_regenerate_key(self, account_id: str, user_id: str) -> requests.Respon url = self._build_url(self.server_url, endpoint) return self._request_with_retry("POST", url, json={}, headers=self._admin_headers()) - def add_skill( - self, data: Any, wait: bool = False, timeout: Optional[float] = None - ) -> requests.Response: + def add_skill(self, data: Any, wait: bool = False) -> requests.Response: endpoint = "/api/v1/skills" url = self._build_url(self.server_url, endpoint) payload = {"data": data} if wait: payload["wait"] = wait - if timeout is not None: - payload["timeout"] = timeout return self._request_with_retry("POST", url, json=payload) def content_reindex( diff --git a/tests/api_test/filesystem/test_fs_cp.py b/tests/api_test/filesystem/test_fs_cp.py index b99ec2a177..cb5aaffed6 100644 --- a/tests/api_test/filesystem/test_fs_cp.py +++ b/tests/api_test/filesystem/test_fs_cp.py @@ -97,7 +97,7 @@ def test_cp_directory_requires_recursive_and_copies_tree(self, api_client): # Request-scoped write completion can precede ancestor refreshes. # Copy needs a lease on the whole source tree, so settle those first. - settled = api_client.system_wait(timeout=30) + settled = api_client.system_wait() assert settled.status_code == 200, settled.text copied = api_client.fs_cp(source, target, recursive=True) @@ -155,7 +155,7 @@ def test_cp_rejects_file_directory_type_conflicts(self, api_client, source_is_di assert api_client.fs_mkdir(directory_uri).status_code == 200 # mkdir queues a parent refresh; finish it before preparing files so # this test exercises type validation, not a transient refresh lock. - settled = api_client.system_wait(timeout=30) + settled = api_client.system_wait() assert settled.status_code == 200, settled.text for uri, content in ((file_uri, file_content), (child_uri, child_content)): write = api_client.fs_write(uri, content, mode="create", wait=True) diff --git a/tests/integration/test_compressor_v3_case_extraction.py b/tests/integration/test_compressor_v3_case_extraction.py index 30ddb91abd..9c76c8367c 100644 --- a/tests/integration/test_compressor_v3_case_extraction.py +++ b/tests/integration/test_compressor_v3_case_extraction.py @@ -222,7 +222,7 @@ def run_ingest( if wait_processed: _log("wait_processed: drain vectorization/semantic queues") - client.wait_processed(timeout=task_timeout) + client.wait_processed() session_info = client.get_session(session_id) _log(f"session info: {session_info}") diff --git a/tests/server/test_api_resources.py b/tests/server/test_api_resources.py index 7c7b7a5a0a..ab00e2f66b 100644 --- a/tests/server/test_api_resources.py +++ b/tests/server/test_api_resources.py @@ -161,36 +161,21 @@ async def test_add_resource_success( assert body["result"]["task_id"] -@pytest.mark.parametrize("timeout", [None, 0.0]) async def test_add_resource_with_wait( client: httpx.AsyncClient, sample_markdown_file, upload_temp_dir, - timeout, ): - resp = await client.post( - "/api/v1/resources", - json={ - "temp_file_id": sample_markdown_file.name, - "reason": "test resource", - "wait": True, - "timeout": timeout, - }, - ) - if timeout == 0.0: - assert resp.status_code == 504 - error = resp.json()["error"] - assert error["code"] == "DEADLINE_EXCEEDED" - task_id = error["details"]["task_id"] - assert error["details"]["timeout"] == timeout - assert "Waiting for resource import timed out" in error["message"] - assert "does not cancel or fail the background task" in error["message"] - assert f"ov task status {task_id}" in error["message"] - task = await _wait_task_terminal(client, task_id) - assert task["status"] == "completed" - assert "root_uri" in task["result"] - return + payload = { + "temp_file_id": sample_markdown_file.name, + "reason": "test resource", + "wait": True, + } + rejected = await client.post("/api/v1/resources", json={**payload, "timeout": 1.0}) + assert rejected.status_code == 400 + assert rejected.json()["error"]["code"] == "INVALID_ARGUMENT" + resp = await client.post("/api/v1/resources", json=payload) assert resp.status_code == 200 body = resp.json() assert body["status"] == "ok" @@ -829,7 +814,7 @@ async def test_add_resource_non_wait_auto_name_resolves_unique_root_uri( async def test_wait_processed_empty_queue(client: httpx.AsyncClient): resp = await client.post( "/api/v1/system/wait", - json={"timeout": 30.0}, + json={}, ) assert resp.status_code == 200 body = resp.json() @@ -847,7 +832,7 @@ async def test_wait_processed_after_add( ) resp = await client.post( "/api/v1/system/wait", - json={"timeout": 60.0}, + json={}, ) assert resp.status_code == 200 assert resp.json()["status"] == "ok" diff --git a/tests/server/test_api_skills.py b/tests/server/test_api_skills.py index 542348e10e..7644f06b77 100644 --- a/tests/server/test_api_skills.py +++ b/tests/server/test_api_skills.py @@ -803,7 +803,7 @@ async def test_mcp_skill_import_and_vector_rebuild_preserve_l1_frontmatter(clien "required": ["expression"], }, } - added = await client.post("/api/v1/skills", json={"data": tool, "wait": True, "timeout": 10}) + added = await client.post("/api/v1/skills", json={"data": tool, "wait": True}) assert added.status_code == 200, added.text root = added.json()["result"]["root_uri"] ctx = RequestContext(user=UserIdentifier.the_default_user(), role=Role.ROOT) diff --git a/tests/server/test_content_batch_write.py b/tests/server/test_content_batch_write.py index 6190fcce0c..d365f40ca0 100644 --- a/tests/server/test_content_batch_write.py +++ b/tests/server/test_content_batch_write.py @@ -1008,7 +1008,6 @@ async def embedding(**kwargs): }, ctx=ctx, wait=False, - timeout=None, telemetry_id="", ) assert len(semantic_calls) == 1 @@ -1060,7 +1059,7 @@ async def test_batch_write_api_upserts_files(client_with_resource): waited = await client.post( "/api/v1/system/wait", - json={"timeout": 30.0}, + json={}, ) assert waited.status_code == 200 diff --git a/tests/server/test_filesystem_router.py b/tests/server/test_filesystem_router.py index cada1da78c..9e9f8d4614 100644 --- a/tests/server/test_filesystem_router.py +++ b/tests/server/test_filesystem_router.py @@ -21,7 +21,7 @@ async def test_rm_preserves_memory_cleanup(monkeypatch): cleanup = {"status": "success", "memory_uris": ["viking://user/alice/memories/entities/a.md"]} - async def fake_rm(uri, ctx=None, recursive=False, wait=False, timeout=None): + async def fake_rm(uri, ctx=None, recursive=False, wait=False): return {"estimated_deleted_count": 1, "memory_cleanup": cleanup} monkeypatch.setattr( diff --git a/tests/server/test_request_wait_tracking.py b/tests/server/test_request_wait_tracking.py index 1a22327703..d4c1546302 100644 --- a/tests/server/test_request_wait_tracking.py +++ b/tests/server/test_request_wait_tracking.py @@ -25,7 +25,7 @@ def __init__(self, queue_status): def register_request(self, telemetry_id: str) -> None: self.registered_requests.append(telemetry_id) - async def wait_for_request(self, telemetry_id: str, timeout, poll_interval=None): + async def wait_for_request(self, telemetry_id: str, timeout=None, poll_interval=None): del poll_interval self.wait_calls.append((telemetry_id, timeout)) @@ -112,12 +112,11 @@ async def _fake_process_skill(**kwargs): data={"name": "demo", "content": "# Demo"}, ctx=ctx, wait=True, - timeout=9.0, ) assert result["queue_status"] == tracker.queue_status assert tracker.registered_requests == [telemetry.telemetry_id] - assert tracker.wait_calls == [(telemetry.telemetry_id, 9.0)] + assert tracker.wait_calls == [(telemetry.telemetry_id, None)] assert tracker.build_calls == [telemetry.telemetry_id] assert tracker.cleaned == [telemetry.telemetry_id] @@ -153,13 +152,12 @@ async def _fake_process_skill(**kwargs): data={"name": "demo", "content": "# Demo"}, ctx=ctx, wait=True, - timeout=9.0, ) assert result["root_uri"] == "viking://user/default/skills/demo" assert result["queue_status"] == tracker.queue_status assert tracker.registered_requests == [telemetry.telemetry_id] - assert tracker.wait_calls == [(telemetry.telemetry_id, 9.0)] + assert tracker.wait_calls == [(telemetry.telemetry_id, None)] assert tracker.build_calls == [telemetry.telemetry_id] assert tracker.cleaned == [telemetry.telemetry_id] @@ -190,8 +188,7 @@ async def _fake_enqueue_semantic_refresh(**kwargs): del kwargs return None - async def _explode_wait_for_queues(*, timeout): - del timeout + async def _explode_wait_for_queues(): raise AssertionError("global queue wait should not be used") monkeypatch.setattr(coordinator, "_enqueue_semantic_refresh", _fake_enqueue_semantic_refresh) @@ -203,12 +200,11 @@ async def _explode_wait_for_queues(*, timeout): content="updated", ctx=ctx, wait=True, - timeout=5.0, ) assert result["queue_status"] == tracker.queue_status assert tracker.registered_requests == [telemetry.telemetry_id] - assert tracker.wait_calls == [(telemetry.telemetry_id, 5.0)] + assert tracker.wait_calls == [(telemetry.telemetry_id, None)] assert tracker.build_calls == [telemetry.telemetry_id] assert tracker.cleaned == [telemetry.telemetry_id] assert result["semantic_status"] == "complete" @@ -241,8 +237,7 @@ async def _fake_enqueue_semantic_refresh(**kwargs): del kwargs return None - async def _explode_wait_for_queues(*, timeout): - del timeout + async def _explode_wait_for_queues(): raise AssertionError("global queue wait should not be used") monkeypatch.setattr(coordinator, "_enqueue_semantic_refresh", _fake_enqueue_semantic_refresh) @@ -254,12 +249,11 @@ async def _explode_wait_for_queues(*, timeout): content="updated", ctx=ctx, wait=True, - timeout=5.0, ) assert result["queue_status"] == tracker.queue_status assert tracker.registered_requests == [telemetry.telemetry_id] - assert tracker.wait_calls == [(telemetry.telemetry_id, 5.0)] + assert tracker.wait_calls == [(telemetry.telemetry_id, None)] assert tracker.build_calls == [telemetry.telemetry_id] assert tracker.cleaned == [telemetry.telemetry_id] assert result["semantic_status"] == "complete" diff --git a/tests/server/test_skill_failure_accounting.py b/tests/server/test_skill_failure_accounting.py index 07f3a1b649..9f4ee59fb2 100644 --- a/tests/server/test_skill_failure_accounting.py +++ b/tests/server/test_skill_failure_accounting.py @@ -30,7 +30,7 @@ async def fail_selected(self, file_path, *args, **kwargs): async def _assert_failed_package_settled(client, failed_paths): # Resource follow-up work may include a separate parent refresh. Wait for # the whole queue, including that work, before checking its final counts. - waited = await client.post("/api/v1/system/wait", json={"timeout": 5}) + waited = await client.post("/api/v1/system/wait", json={}) assert waited.status_code == 200, waited.text assert waited.json()["result"]["Semantic"]["error_count"] == 1 queue_manager = get_queue_manager() @@ -58,7 +58,7 @@ async def test_failed_skill_files_do_not_break_global_wait_or_followup_work( {"references/a.md": "First attachment", "references/b.md": "Second attachment"}, ) response = await client.post( - "/api/v1/skills", json={"temp_file_id": upload, "wait": True, "timeout": 5} + "/api/v1/skills", json={"temp_file_id": upload, "wait": True} ) assert response.status_code == 500, response.text # The request still reports every failed file, not merely the first failure. @@ -99,7 +99,7 @@ async def test_update_with_multiple_file_failures_restores_package_and_settles_q response = await client.put( f"/api/v1/skills/{name}", - json={"temp_file_id": upload, "wait": True, "timeout": 5}, + json={"temp_file_id": upload, "wait": True}, ) assert response.status_code == 500, response.text for path in failed_paths: diff --git a/tests/server/test_skill_privacy_lock.py b/tests/server/test_skill_privacy_lock.py index 7211d7632d..17662ed744 100644 --- a/tests/server/test_skill_privacy_lock.py +++ b/tests/server/test_skill_privacy_lock.py @@ -120,10 +120,10 @@ async def fail_metadata(*args, **kwargs): await asyncio.wait_for(asyncio.gather(*pending), 10) -async def test_update_timeout_restores_privacy_and_releases_both_locks( +async def test_update_cancellation_restores_privacy_and_releases_both_locks( client, service, monkeypatch ): - name = "privacy-update-timeout" + name = "privacy-update-cancellation" root = (await _add_skill(client, name, "Original"))["root_uri"] endpoint = f"/api/v1/privacy-configs/skill/{name}" seeded = await client.post(endpoint, json={"values": {"api_key": "old-value"}}) @@ -155,16 +155,17 @@ async def hold_summary(self, *args, **kwargs): updating = asyncio.create_task( client.put( f"/api/v1/skills/{name}", - json={"data": _skill_md(name, "Timed out replacement"), "wait": True, "timeout": 2}, + json={"data": _skill_md(name, "Cancelled replacement"), "wait": True}, ) ) try: await _wait_until(started.is_set) await _assert_locked(fs, root) await _assert_locked(fs, privacy_root) - failed = await asyncio.wait_for(asyncio.shield(updating), 5) - assert failed.status_code == 504, failed.text - assert cancelled.is_set(), "The timeout must cancel the actual background summary" + updating.cancel() + with pytest.raises(asyncio.CancelledError): + await asyncio.wait_for(asyncio.shield(updating), 5) + assert cancelled.is_set(), "Request cancellation must cancel the background summary" await _assert_unlocked(fs, root, ctx) await _assert_unlocked(fs, privacy_root, ctx) status, current, history = await _privacy_snapshot(client, endpoint) diff --git a/tests/server/test_skill_update_cancellation.py b/tests/server/test_skill_update_cancellation.py index 5741b34c57..81b716ade8 100644 --- a/tests/server/test_skill_update_cancellation.py +++ b/tests/server/test_skill_update_cancellation.py @@ -7,8 +7,6 @@ import threading import zipfile -import pytest - from openviking.service.task_tracker import get_task_tracker from openviking.service.task_work_index import get_task_context from openviking.storage.queuefs import get_queue_manager @@ -60,13 +58,12 @@ async def _indexed_record(client, uri, level): ] -@pytest.mark.parametrize("cancel_mode", ["timeout", "task"]) async def test_update_cancellation_restores_package_and_index( - client, tmp_path, monkeypatch, cancel_mode + client, tmp_path, monkeypatch ): from openviking.storage.queuefs import semantic_executor - name = "timeout-package-rollback" + name = "cancelled-package-rollback" old_upload = await _upload_package( client, tmp_path, @@ -138,7 +135,6 @@ async def hold_summary(self, file_path, *args, **kwargs): json={ "temp_file_id": new_upload, "wait": True, - "timeout": 2 if cancel_mode == "timeout" else 10, }, ) ) @@ -147,16 +143,14 @@ async def hold_summary(self, file_path, *args, **kwargs): async with asyncio.timeout(1): while not await _indexed_record(client, f"{root}/references/000-fast.md", 2): await asyncio.sleep(0.01) - if cancel_mode == "task": - assert len(task_owners) == 1 - owner = next(iter(task_owners.values())) - await get_task_tracker().cancel( - owner.task_id, account_id=owner.account_id, user_id=owner.user_id - ) + assert len(task_owners) == 1 + owner = next(iter(task_owners.values())) + await get_task_tracker().cancel( + owner.task_id, account_id=owner.account_id, user_id=owner.user_id + ) response = await asyncio.wait_for(asyncio.shield(updating), timeout=5) - expected_error = "DEADLINE_EXCEEDED" if cancel_mode == "timeout" else "PROCESSING_ERROR" assert response.status_code >= 400, response.text - assert response.json()["error"]["code"] == expected_error + assert response.json()["error"]["code"] == "PROCESSING_ERROR" assert cancelled == started assert not finished, "Rollback must not wait for blocked summaries to finish normally" assert len(started) < len(files), "Pending summaries should be skipped on cancellation" diff --git a/tests/server/test_skill_update_retry.py b/tests/server/test_skill_update_retry.py index e2a15381ef..403906697a 100644 --- a/tests/server/test_skill_update_retry.py +++ b/tests/server/test_skill_update_retry.py @@ -49,7 +49,7 @@ async def test_update_wait_true_retries_transient_failure_without_conflicting_wi response = await client.put( f"/api/v1/skills/{name}", - json={"data": _skill_md(name, "Replacement"), "wait": True, "timeout": 5}, + json={"data": _skill_md(name, "Replacement"), "wait": True}, ) assert response.status_code == 200, response.text @@ -59,14 +59,14 @@ async def test_update_wait_true_retries_transient_failure_without_conflicting_wi @pytest.mark.parametrize("enqueue_committed", [False, True]) -async def test_update_timeout_waits_for_retry_handoff_to_settle_before_restoring( +async def test_update_cancellation_waits_for_retry_handoff_to_settle_before_restoring( client, service, monkeypatch, enqueue_committed ): name = f"update-cancel-retry-enqueue-{enqueue_committed}" root = (await _add_skill(client, name, "Original"))["root_uri"] old_content = await _download(client, f"{root}/SKILL.md") # A published retry may already be running while its enqueue is returning. - # Keep that work pending so this case actually exercises a request timeout. + # Keep that work pending so this case actually exercises request cancellation. _fail_first_run(monkeypatch, root, block_retries=enqueue_committed) queue_manager = get_queue_manager() queue = queue_manager.get_queue(queue_manager.SEMANTIC) @@ -95,20 +95,22 @@ async def slow_retry_enqueue(msg): updating = asyncio.create_task( client.put( f"/api/v1/skills/{name}", - json={"data": _skill_md(name, "Replacement"), "wait": True, "timeout": 2}, + json={"data": _skill_md(name, "Replacement"), "wait": True}, ) ) try: await _wait_until(retry_started.is_set) - await asyncio.sleep(2.1) + updating.cancel() + await asyncio.sleep(0) assert not updating.done(), "Rollback must wait for the started retry enqueue" await _assert_locked(service.viking_fs, root) resume_enqueue.set() - response = await asyncio.wait_for(updating, 10) - assert response.status_code == 504, response.text + with pytest.raises(asyncio.CancelledError): + await asyncio.wait_for(updating, 10) assert await _download(client, f"{root}/SKILL.md") == old_content await _assert_unlocked(service.viking_fs, root) finally: resume_enqueue.set() if not updating.done(): - await asyncio.wait_for(updating, 10) + updating.cancel() + await asyncio.wait_for(asyncio.gather(updating, return_exceptions=True), 10) diff --git a/tests/service/test_fs_service.py b/tests/service/test_fs_service.py index 39cef518d5..4c11d2ac45 100644 --- a/tests/service/test_fs_service.py +++ b/tests/service/test_fs_service.py @@ -926,7 +926,6 @@ async def test_resource_rm_enqueues_parent_delete_refresh_and_waits(request_cont ctx=request_context, recursive=True, wait=True, - timeout=12.0, ) assert viking_fs.rm_calls == [{"uri": uri, "recursive": True, "ctx": request_context}] @@ -937,7 +936,7 @@ async def test_resource_rm_enqueues_parent_delete_refresh_and_waits(request_cont ctx=request_context, force_refresh=True, ) - service._wait_for_refresh.assert_awaited_once_with(timeout=12.0) + service._wait_for_refresh.assert_awaited_once_with() assert result["semantic_root_uri"] == "viking://resources/images/2026/06/10" assert result["semantic_status"] == "complete" assert result["queue_status"] == {"Semantic": {"pending_count": 0}} @@ -1499,14 +1498,13 @@ async def test_resource_rm_wait_registers_request_before_semantic_root( ctx=request_context, recursive=True, wait=True, - timeout=3, ) assert tracker.registered_requests == ["tm-fs-rm"] assert tracker.registered_roots assert tracker.registered_roots[0]["request_was_registered"] is True assert queue_manager.messages[0].recursive is False - assert tracker.wait_calls == [("tm-fs-rm", 3)] + assert tracker.wait_calls == [("tm-fs-rm", None)] assert tracker.cleaned == ["tm-fs-rm"] assert result["semantic_status"] == "complete" diff --git a/tests/storage/test_content_write_processing_mode.py b/tests/storage/test_content_write_processing_mode.py index e97ef1a037..1ed5b94f55 100644 --- a/tests/storage/test_content_write_processing_mode.py +++ b/tests/storage/test_content_write_processing_mode.py @@ -239,7 +239,6 @@ async def _commit(*args, **kwargs): response_mode="replace", context_type="resource", wait=False, - timeout=None, ctx=ctx, telemetry_id="", ingest_options=IngestOptions(), @@ -298,7 +297,6 @@ async def test_direct_write_hands_exact_lock_to_request_coordinator(monkeypatch, response_mode="replace", context_type="resource", wait=False, - timeout=None, ctx=ctx, telemetry_id=telemetry.telemetry_id, ingest_options=IngestOptions(), @@ -351,7 +349,6 @@ async def _plan(**kwargs): response_mode="replace", context_type="resource", wait=False, - timeout=None, ctx=ctx, telemetry_id="", ingest_options=IngestOptions(), @@ -444,7 +441,6 @@ async def _commit(*args, **kwargs): response_mode="append", context_type="resource", wait=False, - timeout=None, ctx=ctx, telemetry_id="", ingest_options=IngestOptions(), diff --git a/web-studio/src/gen/ov-client/sdk.gen.ts b/web-studio/src/gen/ov-client/sdk.gen.ts index 7a35b14d4a..c431e92339 100644 --- a/web-studio/src/gen/ov-client/sdk.gen.ts +++ b/web-studio/src/gen/ov-client/sdk.gen.ts @@ -63,14 +63,10 @@ export const getSystemStatus = (options?: * * Wait for all processing to complete. */ -export const postSystemWait = (options: Options) => (options.client ?? client).post({ +export const postSystemWait = (options?: Options) => (options?.client ?? client).post({ responseType: 'json', url: '/api/v1/system/wait', - ...options, - headers: { - 'Content-Type': 'application/json', - ...options.headers - } + ...options }); /** diff --git a/web-studio/src/gen/ov-client/types.gen.ts b/web-studio/src/gen/ov-client/types.gen.ts index 919e127981..e5802c5102 100644 --- a/web-studio/src/gen/ov-client/types.gen.ts +++ b/web-studio/src/gen/ov-client/types.gen.ts @@ -89,7 +89,6 @@ export type AddMessageRequest = { * Provides hints for how the resource should be processed. * wait: Whether to wait for semantic extraction and vectorization to complete. * Default is False (async processing). - * timeout: Timeout in seconds when wait=True. None means no timeout. * strict: Whether to use strict mode for processing. Default is True. * ignore_dirs: Comma-separated list of directory names to ignore during parsing. * include: Glob pattern for files to include during parsing. @@ -141,10 +140,6 @@ export type AddResourceRequest = { * Wait */ wait?: boolean; - /** - * Timeout - */ - timeout?: number | null; /** * Strict */ @@ -195,7 +190,6 @@ export type AddResourceRequest = { * string values as host filesystem paths. * temp_file_id: Temporary upload id returned by /api/v1/resources/temp_upload. * wait: Whether to wait for skill processing to complete. - * timeout: Timeout in seconds when wait=True. */ export type AddSkillRequest = { /** @@ -210,10 +204,6 @@ export type AddSkillRequest = { * Wait */ wait?: boolean; - /** - * Timeout - */ - timeout?: number | null; /** * Telemetry */ @@ -736,18 +726,6 @@ export type ValidationError = { }; }; -/** - * WaitRequest - * - * Request model for wait. - */ -export type WaitRequest = { - /** - * Timeout - */ - timeout?: number | null; -}; - /** * WriteContentRequest * @@ -770,10 +748,6 @@ export type WriteContentRequest = { * Wait */ wait?: boolean; - /** - * Timeout - */ - timeout?: number | null; /** * Telemetry */ @@ -852,7 +826,7 @@ export type GetSystemStatusResponses = { }; export type PostSystemWaitData = { - body: WaitRequest; + body?: never; headers?: { /** * X-Api-Key From f86008a0c2f7df89ad0536c9febaea5461b614d0 Mon Sep 17 00:00:00 2001 From: qin-ctx Date: Wed, 14 Oct 2026 15:44:32 +0800 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=E6=B8=85=E7=90=86=E7=AD=89?= =?UTF-8?q?=E5=BE=85=E9=80=82=E9=85=8D=E4=B8=8E=E8=BF=87=E6=97=B6=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/ov_cli/src/help_ui.rs | 18 -- crates/ov_cli/src/main.rs | 27 -- .../tests/ut/architecture-boundaries.test.ts | 26 -- .../openclaw-plugin/tests/ut/client.test.ts | 21 -- .../openclaw-plugin/tests/ut/tools.test.ts | 7 - openviking/service/fs_service.py | 12 +- openviking/service/resource_service.py | 11 +- openviking/storage/content_write.py | 14 +- openviking/storage/queuefs/queue_manager.py | 20 +- openviking/telemetry/request_wait_tracker.py | 6 +- openviking/telemetry/resource_summary.py | 14 - .../tests/test_async_client_behaviors.py | 20 -- tests/server/test_content_write_service.py | 44 +-- tests/server/test_request_wait_tracking.py | 284 +++--------------- tests/server/test_skill_privacy_lock.py | 2 +- .../server/test_skill_update_cancellation.py | 6 +- tests/service/test_fs_service.py | 59 +--- tests/service/test_resource_service_watch.py | 14 - tests/service/test_task_processing_time.py | 2 +- tests/telemetry/test_request_wait_tracker.py | 11 +- 20 files changed, 78 insertions(+), 540 deletions(-) diff --git a/crates/ov_cli/src/help_ui.rs b/crates/ov_cli/src/help_ui.rs index 222e781b00..201b2b5a53 100644 --- a/crates/ov_cli/src/help_ui.rs +++ b/crates/ov_cli/src/help_ui.rs @@ -2966,24 +2966,6 @@ mod tests { assert!(rendered.contains("Show full component tables")); } - #[test] - fn curated_help_omits_timeout_for_waiting_commands() { - for args in [ - ["ov", "add-resource", "--help"], - ["ov", "rm", "--help"], - ["ov", "write", "--help"], - ["ov", "wait", "--help"], - ] { - let rendered = strip_ansi( - &render_command_help_request(&os_args(&args)).expect("help should render"), - ); - assert!( - !rendered.contains("--timeout"), - "unexpected timeout option in:\n{rendered}" - ); - } - } - #[test] fn curated_help_lists_upload_filters_and_limit_aliases() { let add_resource = strip_ansi( diff --git a/crates/ov_cli/src/main.rs b/crates/ov_cli/src/main.rs index ca40f8e2a2..b633287319 100644 --- a/crates/ov_cli/src/main.rs +++ b/crates/ov_cli/src/main.rs @@ -5210,33 +5210,6 @@ mod tests { } } - #[test] - fn waiting_commands_reject_timeout_option() { - for mut args in [ - vec!["ov", "add-resource", "https://example.com"], - vec!["ov", "rm", "viking://resources/item"], - vec![ - "ov", - "write", - "viking://resources/item", - "--content", - "value", - ], - vec!["ov", "wait"], - vec!["ov", "system", "wait"], - ] { - assert!( - Cli::try_parse_from(&args).is_ok(), - "{args:?} should be accepted" - ); - args.extend(["--timeout", "60"]); - assert!( - Cli::try_parse_from(&args).is_err(), - "{args:?} should reject --timeout" - ); - } - } - #[test] fn all_node_limit_options_accept_zero_and_reject_negative_values() { let command_prefixes = [ diff --git a/examples/openclaw-plugin/tests/ut/architecture-boundaries.test.ts b/examples/openclaw-plugin/tests/ut/architecture-boundaries.test.ts index c54cf6ceda..46ea40120b 100644 --- a/examples/openclaw-plugin/tests/ut/architecture-boundaries.test.ts +++ b/examples/openclaw-plugin/tests/ut/architecture-boundaries.test.ts @@ -458,32 +458,6 @@ describe("architecture boundaries", () => { expect(mcpSkillBlock).toMatch(/new OpenVikingClient\([\s\S]*\{\s*transport\s*\}/); }); - it("keeps OpenViking error response client tests on the injected transport seam", () => { - const clientTestSource = readFileSync(join(rootDir, "tests/ut/client.test.ts"), "utf8"); - const start = clientTestSource.indexOf('it("surfaces OpenViking error responses"'); - const end = clientTestSource.indexOf(' it.each([false, true])("uses configured request timeout for imports (wait=%s)"', start); - - expect(start).toBeGreaterThanOrEqual(0); - expect(end).toBeGreaterThan(start); - - const errorResponseBlock = clientTestSource.slice(start, end); - expect(errorResponseBlock).not.toMatch(/vi\.stubGlobal\("fetch"/); - expect(errorResponseBlock).toMatch(/new OpenVikingClient\([\s\S]*\{\s*transport\s*\}/); - }); - - it("keeps commit polling client tests on the injected transport seam", () => { - const clientTestSource = readFileSync(join(rootDir, "tests/ut/client.test.ts"), "utf8"); - const start = clientTestSource.indexOf('it("keeps polling wait=true commit long enough for slow Phase 2 completion"'); - const end = clientTestSource.indexOf('});\n\ndescribe("OpenVikingClient tenant headers', start); - - expect(start).toBeGreaterThanOrEqual(0); - expect(end).toBeGreaterThan(start); - - const commitPollingBlock = clientTestSource.slice(start, end); - expect(commitPollingBlock).not.toMatch(/vi\.stubGlobal\("fetch"/); - expect(commitPollingBlock).toMatch(/new OpenVikingClient\([\s\S]*\{\s*transport\s*\}/); - }); - it("keeps tenant header client tests on the injected transport seam", () => { const clientTestSource = readFileSync(join(rootDir, "tests/ut/client.test.ts"), "utf8"); const start = clientTestSource.indexOf('describe("OpenVikingClient tenant headers'); diff --git a/examples/openclaw-plugin/tests/ut/client.test.ts b/examples/openclaw-plugin/tests/ut/client.test.ts index 4a1665796a..62330542d5 100644 --- a/examples/openclaw-plugin/tests/ut/client.test.ts +++ b/examples/openclaw-plugin/tests/ut/client.test.ts @@ -304,27 +304,6 @@ describe("OpenVikingClient resource and skill import", () => { ); }); - it.each([false, true])("uses configured request timeout for imports (wait=%s)", async (wait) => { - vi.useFakeTimers(); - const transport = vi.fn((_url: string, init?: RequestInit) => new Promise((resolve, reject) => { - init?.signal?.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError"))); - setTimeout(() => { - resolve(okResponse({ root_uri: "viking://resources/site", status: "success" })); - }, 20_000); - })); - - const client = new OpenVikingClient("http://127.0.0.1:1933", "", "agent", 15_000, "", "", undefined, false, true, { transport }); - const pending = client.addResource({ - pathOrUrl: "https://example.com/docs", - wait, - }); - const assertion = expect(pending).rejects.toThrow(/aborted/i); - - await vi.advanceTimersByTimeAsync(15_001); - - await assertion; - }); - it("keeps polling wait=true commit long enough for slow Phase 2 completion", async () => { vi.useFakeTimers(); const transport = vi.fn((url: string) => { diff --git a/examples/openclaw-plugin/tests/ut/tools.test.ts b/examples/openclaw-plugin/tests/ut/tools.test.ts index 8ed50f620d..6d0fdfbdab 100644 --- a/examples/openclaw-plugin/tests/ut/tools.test.ts +++ b/examples/openclaw-plugin/tests/ut/tools.test.ts @@ -1385,13 +1385,6 @@ describe("OpenViking import command parsing", () => { ).toThrow("Cannot specify both"); }); - it("parses add-skill flags", () => { - expect(parseAddSkillCommandArgs("./skills/demo --wait")).toMatchObject({ - source: "./skills/demo", - wait: true, - }); - }); - it("rejects resource-only flags for skill imports", () => { expect(() => parseAddSkillCommandArgs("./skills/demo --to viking://resources/nope"), diff --git a/openviking/service/fs_service.py b/openviking/service/fs_service.py index ede621c2da..5b1910751d 100644 --- a/openviking/service/fs_service.py +++ b/openviking/service/fs_service.py @@ -46,7 +46,6 @@ from openviking.storage.vikingdb_manager import VikingDBManagerProxy from openviking.telemetry import get_current_telemetry from openviking.telemetry.request_wait_tracker import get_request_wait_tracker -from openviking.telemetry.resource_summary import build_queue_status_payload from openviking.utils.embedding_utils import vectorize_directory_meta from openviking.utils.ingest_options import IngestOptions from openviking.utils.path_safety import validate_safe_viking_uri_path @@ -506,7 +505,9 @@ async def rm( ctx=ctx, ) if refresh_parent_uri and wait and refresh_action is not FreshnessAction.MARK_PENDING: - queue_status = await self._wait_for_refresh() + tracker = get_request_wait_tracker() + await tracker.wait_for_request(telemetry_id) + queue_status = tracker.build_queue_status(telemetry_id) finally: if request_registered: get_request_wait_tracker().cleanup(telemetry_id) @@ -716,13 +717,6 @@ async def _enqueue_copy_refresh( raise return "queued" - async def _wait_for_refresh(self) -> Dict[str, Any]: - telemetry_id = get_current_telemetry().telemetry_id - if telemetry_id: - await get_request_wait_tracker().wait_for_request(telemetry_id) - return get_request_wait_tracker().build_queue_status(telemetry_id) - return build_queue_status_payload(await get_queue_manager().wait_complete()) - async def cp( self, from_uri: str, diff --git a/openviking/service/resource_service.py b/openviking/service/resource_service.py index acce5d5000..308a0410f1 100644 --- a/openviking/service/resource_service.py +++ b/openviking/service/resource_service.py @@ -68,9 +68,6 @@ from openviking.storage.vikingdb_manager import VikingDBManager from openviking.telemetry import get_current_telemetry, register_telemetry, unregister_telemetry from openviking.telemetry.request_wait_tracker import get_request_wait_tracker -from openviking.telemetry.resource_summary import ( - build_queue_status_payload, -) from openviking.utils import is_git_repo_url, is_github_url, parse_code_hosting_url from openviking.utils.git_auth import ( GitHttpAuthConfig, @@ -2516,12 +2513,8 @@ async def create_skill_task() -> None: if wait: wait_start = time.perf_counter() - if telemetry_id: - await request_wait_tracker.wait_for_request(telemetry_id) - status = request_wait_tracker.build_queue_status(telemetry_id) - else: - qm = get_queue_manager() - status = build_queue_status_payload(await qm.wait_complete()) + await request_wait_tracker.wait_for_request(telemetry_id) + status = request_wait_tracker.build_queue_status(telemetry_id) get_current_telemetry().set( "queue.wait.duration_ms", round((time.perf_counter() - wait_start) * 1000, 3), diff --git a/openviking/storage/content_write.py b/openviking/storage/content_write.py index b31ec54aff..2c474fce85 100644 --- a/openviking/storage/content_write.py +++ b/openviking/storage/content_write.py @@ -76,7 +76,6 @@ from openviking.storage.viking_fs import VikingFS from openviking.telemetry import get_current_telemetry from openviking.telemetry.request_wait_tracker import get_request_wait_tracker -from openviking.telemetry.resource_summary import build_queue_status_payload from openviking.utils.content_hash import content_md5 from openviking.utils.embedding_utils import vectorize_directory_meta from openviking.utils.ingest_options import IngestOptions @@ -1999,18 +1998,11 @@ def _prepare_abstract_overview_content( except AbstractOverviewFormatError as exc: raise InvalidArgumentError(str(exc)) from exc - async def _wait_for_queues(self) -> Dict[str, Any]: - queue_manager = get_queue_manager() - status = await queue_manager.wait_complete() - return build_queue_status_payload(status) - async def _wait_for_request( self, *, telemetry_id: str, ) -> Dict[str, Any]: - if not telemetry_id: - return await self._wait_for_queues() tracker = get_request_wait_tracker() await tracker.wait_for_request(telemetry_id) return tracker.build_queue_status(telemetry_id) @@ -2075,11 +2067,7 @@ async def _write_memory_with_refresh( ) queue_status = None if embedding_requested and wait: - queue_status = ( - await self._wait_for_request(telemetry_id=telemetry_id) - if telemetry_id - else await self._wait_for_queues() - ) + queue_status = await self._wait_for_request(telemetry_id=telemetry_id) vector_status = self._queue_work_status( requested=embedding_requested, wait=wait, diff --git a/openviking/storage/queuefs/queue_manager.py b/openviking/storage/queuefs/queue_manager.py index b2ac17cdf8..1907850d1b 100644 --- a/openviking/storage/queuefs/queue_manager.py +++ b/openviking/storage/queuefs/queue_manager.py @@ -8,7 +8,6 @@ import asyncio import atexit import threading -import time import traceback from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Set, Union @@ -521,17 +520,8 @@ async def is_all_complete(self, queue_name: Optional[str] = None) -> bool: statuses = await self.check_status(queue_name) return all(s.is_complete for s in statuses.values()) - async def wait_complete( - self, - queue_name: Optional[str] = None, - timeout: Optional[float] = None, - poll_interval: float = 0.5, - ) -> Dict[str, QueueStatus]: - """Wait for completion and return final status.""" - start = time.time() - while True: - if await self.is_all_complete(queue_name): - return await self.check_status(queue_name) - if timeout and (time.time() - start) > timeout: - raise TimeoutError(f"Queue processing not complete after {timeout}s") - await asyncio.sleep(poll_interval) + async def wait_complete(self) -> Dict[str, QueueStatus]: + """Wait for all queues to complete and return their final status.""" + while not await self.is_all_complete(): + await asyncio.sleep(0.5) + return await self.check_status() diff --git a/openviking/telemetry/request_wait_tracker.py b/openviking/telemetry/request_wait_tracker.py index 1b5d30b92b..3e2d21363c 100644 --- a/openviking/telemetry/request_wait_tracker.py +++ b/openviking/telemetry/request_wait_tracker.py @@ -276,7 +276,6 @@ async def wait_for_request( self, telemetry_id: str, timeout: Optional[float] = None, - poll_interval: float = 0.05, ) -> None: if not telemetry_id: return @@ -286,12 +285,11 @@ async def wait_for_request( return if timeout is not None and (time.time() - start) > timeout: raise TimeoutError(f"Request processing not complete after {timeout}s") - await asyncio.sleep(poll_interval) + await asyncio.sleep(0.05) async def wait_for_embeddings( self, telemetry_id: str, - poll_interval: float = 0.05, *, stop_waiting: Optional[Callable[[], bool]] = None, ) -> None: @@ -308,7 +306,7 @@ async def wait_for_embeddings( # Leave this semantic delivery unacked for recovery rather than # waiting forever for embeddings still in the persistent queue. raise asyncio.CancelledError("Embedding worker stopped with queued work") - await asyncio.sleep(poll_interval) + await asyncio.sleep(0.05) def build_queue_status(self, telemetry_id: str) -> Dict[str, Dict[str, object]]: with self._lock: diff --git a/openviking/telemetry/resource_summary.py b/openviking/telemetry/resource_summary.py index 3ba2a44afe..4928d0c4b3 100644 --- a/openviking/telemetry/resource_summary.py +++ b/openviking/telemetry/resource_summary.py @@ -37,19 +37,6 @@ def _consume_semantic_tree_stats(telemetry_id: str, root_uri: str | None): return None -def build_queue_status_payload(status: Dict[str, Any]) -> Dict[str, Dict[str, Any]]: - """Convert queue status objects to response payload format.""" - return { - name: { - "processed": s.processed, - "requeue_count": getattr(s, "requeue_count", 0), - "error_count": s.error_count, - "errors": [{"message": e.message} for e in s.errors], - } - for name, s in status.items() - } - - def _queue_metrics(stats: Any) -> Dict[str, int]: if stats is None: return {"processed": 0, "requeue_count": 0, "error_count": 0} @@ -94,6 +81,5 @@ def record_resource_queue_metrics( __all__ = [ - "build_queue_status_payload", "record_resource_queue_metrics", ] diff --git a/sdk/python/tests/test_async_client_behaviors.py b/sdk/python/tests/test_async_client_behaviors.py index 5b9ef9b76f..77d431abff 100644 --- a/sdk/python/tests/test_async_client_behaviors.py +++ b/sdk/python/tests/test_async_client_behaviors.py @@ -1596,26 +1596,6 @@ async def test_ls_and_tree_preserve_query_options_and_pagination(): assert fake_http.get.await_args_list[1].kwargs["params"]["directories_only"] is True -@pytest.mark.asyncio -async def test_rm_uses_delete_request_with_wait(): - client = AsyncHTTPClient(url="http://localhost:1933") - fake_http = SimpleNamespace(request=AsyncMock(return_value=object())) - client._http = fake_http - client._handle_response = lambda _response: None - - await client.rm("/resources/demo.md", recursive=True, wait=True) - - fake_http.request.assert_awaited_once_with( - "DELETE", - "/api/v1/fs", - params={ - "uri": "viking://resources/demo.md", - "recursive": True, - "wait": True, - }, - ) - - @pytest.mark.asyncio async def test_batch_write_serializes_processing_mode_and_operation_metadata(): client = AsyncHTTPClient(url="http://localhost:1933") diff --git a/tests/server/test_content_write_service.py b/tests/server/test_content_write_service.py index ef73f29719..76672ac869 100644 --- a/tests/server/test_content_write_service.py +++ b/tests/server/test_content_write_service.py @@ -704,8 +704,8 @@ async def _fake_write_in_place(uri, content, *, mode, ctx, lock_handle=None, lea del uri, content, mode, ctx, lock_handle, lease_ref return b"updated" - async def _fail_wait_for_request(*, telemetry_id, timeout): - del telemetry_id, timeout + async def _fail_wait_for_request(*, telemetry_id): + del telemetry_id raise AssertionError("memory write should not wait for semantic refresh") async def _fake_refresh_schema_overview(**kwargs): @@ -826,12 +826,7 @@ async def _fake_write_in_place( write_calls.append((uri, content, mode, is_new_file)) return content - async def _fake_wait_for_queues(*, timeout): - del timeout - return None - monkeypatch.setattr(coordinator, "_write_in_place", _fake_write_in_place) - monkeypatch.setattr(coordinator, "_wait_for_queues", _fake_wait_for_queues) result = await coordinator.write( uri=file_uri, content="new content", mode="create", ctx=ctx, wait=True @@ -867,12 +862,7 @@ async def _fake_refresh_schema_overview(**kwargs): refresh_calls.append(kwargs) return None - async def _fake_wait_for_queues(*, timeout): - del timeout - return None - monkeypatch.setattr(coordinator, "_write_in_place", _fake_write_in_place) - monkeypatch.setattr(coordinator, "_wait_for_queues", _fake_wait_for_queues) monkeypatch.setattr( "openviking.storage.content_write.MemoryUpdater.refresh_schema_overview", _fake_refresh_schema_overview, @@ -940,12 +930,7 @@ async def _fake_write_in_place(uri, content, *, mode, ctx, lock_handle=None, lea del uri, content, mode, ctx, lock_handle, lease_ref return None - async def _fake_wait_for_queues(*, timeout): - del timeout - return None - monkeypatch.setattr(coordinator, "_write_in_place", _fake_write_in_place) - monkeypatch.setattr(coordinator, "_wait_for_queues", _fake_wait_for_queues) with pytest.raises(InvalidArgumentError): await coordinator.write(uri=file_uri, content="content", mode="create", ctx=ctx, wait=True) @@ -968,12 +953,7 @@ async def _fake_write_in_place( write_calls.append((uri, content, mode, is_new_file)) return content - async def _fake_wait_for_queues(*, timeout): - del timeout - return None - monkeypatch.setattr(coordinator, "_write_in_place", _fake_write_in_place) - monkeypatch.setattr(coordinator, "_wait_for_queues", _fake_wait_for_queues) result = await coordinator.write( uri=file_uri, content="nested content", mode="create", ctx=ctx, wait=True @@ -1002,12 +982,7 @@ async def _fake_write_in_place( del uri, mode, ctx, lock_handle, lease_ref, is_new_file return content - async def _fake_wait_for_queues(*, timeout): - del timeout - return None - monkeypatch.setattr(coordinator, "_write_in_place", _fake_write_in_place) - monkeypatch.setattr(coordinator, "_wait_for_queues", _fake_wait_for_queues) result = await coordinator.write( uri=file_uri, content="content", mode="create", ctx=ctx, wait=True @@ -1035,12 +1010,7 @@ async def _fake_refresh_schema_overview(**kwargs): refresh_calls.append(kwargs) return None - async def _fake_wait_for_queues(*, timeout): - del timeout - return None - monkeypatch.setattr(coordinator, "_write_in_place", _fake_write_in_place) - monkeypatch.setattr(coordinator, "_wait_for_queues", _fake_wait_for_queues) monkeypatch.setattr( "openviking.storage.content_write.MemoryUpdater.refresh_schema_overview", _fake_refresh_schema_overview, @@ -1116,12 +1086,7 @@ async def _fake_write_in_place(uri, content, *, mode, ctx, lock_handle=None, lea del uri, content, ctx, lock_handle, lease_ref return b"updated" - async def _fake_wait_for_queues(*, timeout): - del timeout - return None - monkeypatch.setattr(coordinator, "_write_in_place", _fake_write_in_place) - monkeypatch.setattr(coordinator, "_wait_for_queues", _fake_wait_for_queues) result = await coordinator.write( uri=file_uri, content="updated", ctx=ctx, mode="replace", wait=True @@ -1144,12 +1109,7 @@ async def _fake_write_in_place(uri, content, *, mode, ctx, lock_handle=None, lea del uri, content, ctx, lock_handle, lease_ref return b"previousappended" - async def _fake_wait_for_queues(*, timeout): - del timeout - return None - monkeypatch.setattr(coordinator, "_write_in_place", _fake_write_in_place) - monkeypatch.setattr(coordinator, "_wait_for_queues", _fake_wait_for_queues) result = await coordinator.write( uri=file_uri, content="appended", ctx=ctx, mode="append", wait=True diff --git a/tests/server/test_request_wait_tracking.py b/tests/server/test_request_wait_tracking.py index d4c1546302..0a87444a26 100644 --- a/tests/server/test_request_wait_tracking.py +++ b/tests/server/test_request_wait_tracking.py @@ -1,270 +1,58 @@ # Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd. # SPDX-License-Identifier: AGPL-3.0 -"""Tests for request-scoped wait behavior on write APIs.""" +"""Request-scoped waits must not depend on unrelated queue work.""" +import asyncio from types import SimpleNamespace -import pytest - from openviking.server.identity import RequestContext, Role -from openviking.storage.content_write import ContentWriteCoordinator +from openviking.service.resource_service import ResourceService +from openviking.service.task_store import PersistentTaskStore +from openviking.service.task_tracker import TaskTracker from openviking.telemetry.context import bind_telemetry from openviking.telemetry.operation import OperationTelemetry +from openviking.telemetry.request_wait_tracker import RequestWaitTracker from openviking_cli.session.user_id import UserIdentifier +from tests.test_task_tracker import _FakeAgfs -class _FakeRequestWaitTracker: - def __init__(self, queue_status): - self.queue_status = queue_status - self.registered_requests = [] - self.wait_calls = [] - self.build_calls = [] - self.cleaned = [] - - def register_request(self, telemetry_id: str) -> None: - self.registered_requests.append(telemetry_id) - - async def wait_for_request(self, telemetry_id: str, timeout=None, poll_interval=None): - del poll_interval - self.wait_calls.append((telemetry_id, timeout)) - - def build_queue_status(self, telemetry_id: str): - self.build_calls.append(telemetry_id) - return self.queue_status - - def cleanup(self, telemetry_id: str) -> None: - self.cleaned.append(telemetry_id) - - -class _ExplodingQueueManager: - async def wait_complete(self, *args, **kwargs): - raise AssertionError("global queue wait should not be used") - - -class _FakeVikingFS: - def __init__(self, file_uri: str, root_uri: str): - self._file_uri = file_uri - self._root_uri = root_uri - self.content = {file_uri: "original"} - self._async_agfs = SimpleNamespace( - pathlock_acquire_exact=lambda lock_path: SimpleNamespace(id="lock-1"), - pathlock_release=lambda lease: None, - ) - - async def stat(self, uri: str, ctx=None): - del ctx - if uri == self._file_uri: - return {"isDir": False} - if uri == self._root_uri: - return {"isDir": True} - raise AssertionError(f"unexpected stat uri: {uri}") - - def _uri_to_path(self, uri: str, ctx=None): - del ctx - return f"/fake/{uri.replace('://', '/').strip('/')}" - - async def delete_temp(self, temp_uri: str, ctx=None): - del temp_uri, ctx - return None - - async def read_file(self, uri: str, ctx=None): - del ctx - return self.content[uri] - - async def write_file(self, uri: str, content: str, ctx=None, lease_ref=None): - del ctx, lease_ref - self.content[uri] = content - - async def rm(self, uri: str, ctx=None, lock_handle=None, lease_ref=None): - del ctx, lock_handle, lease_ref - self.content.pop(uri, None) - - -@pytest.mark.asyncio -async def test_add_skill_wait_uses_request_tracker(service, monkeypatch): - tracker = _FakeRequestWaitTracker( - { - "Semantic": {"processed": 0, "error_count": 0, "errors": []}, - "Embedding": {"processed": 1, "error_count": 0, "errors": []}, - } - ) - ctx = RequestContext(user=service.user, role=Role.ROOT) - telemetry = OperationTelemetry(operation="resources.add_skill", enabled=True) - - async def _fake_process_skill(**kwargs): - del kwargs - return {"status": "success", "uri": "viking://user/default/skills/demo", "name": "demo"} - - monkeypatch.setattr(service.resources._skill_processor, "process_skill", _fake_process_skill) - monkeypatch.setattr( - "openviking.service.resource_service.get_queue_manager", - lambda: _ExplodingQueueManager(), - ) - monkeypatch.setattr( - "openviking.service.resource_service.get_request_wait_tracker", - lambda: tracker, - raising=False, - ) - - with bind_telemetry(telemetry): - result = await service.resources.add_skill( - data={"name": "demo", "content": "# Demo"}, - ctx=ctx, - wait=True, - ) - - assert result["queue_status"] == tracker.queue_status - assert tracker.registered_requests == [telemetry.telemetry_id] - assert tracker.wait_calls == [(telemetry.telemetry_id, None)] - assert tracker.build_calls == [telemetry.telemetry_id] - assert tracker.cleaned == [telemetry.telemetry_id] - - -@pytest.mark.asyncio -async def test_add_skill_wait_uses_request_tracker_when_telemetry_disabled(service, monkeypatch): - tracker = _FakeRequestWaitTracker( - { - "Semantic": {"processed": 0, "error_count": 0, "errors": []}, - "Embedding": {"processed": 1, "error_count": 0, "errors": []}, - } - ) - ctx = RequestContext(user=service.user, role=Role.ROOT) +async def test_add_skill_wait_uses_request_tracker_when_telemetry_disabled(monkeypatch): + tracker = RequestWaitTracker() telemetry = OperationTelemetry(operation="resources.add_skill", enabled=False) + tracker.register_request("unrelated") + tracker.register_embedding_root("unrelated", "pending-embedding") + ctx = RequestContext(user=UserIdentifier.the_default_user(), role=Role.ROOT) + root_uri = "viking://user/default/skills/demo" - async def _fake_process_skill(**kwargs): - del kwargs - return {"status": "success", "uri": "viking://user/default/skills/demo", "name": "demo"} + async def process_skill(**kwargs): + tracker.register_embedding_root(telemetry.telemetry_id, "skill-embedding") + tracker.mark_embedding_done(telemetry.telemetry_id, "skill-embedding") + return {"status": "success", "uri": root_uri, "name": "demo"} - monkeypatch.setattr(service.resources._skill_processor, "process_skill", _fake_process_skill) - monkeypatch.setattr( - "openviking.service.resource_service.get_queue_manager", - lambda: _ExplodingQueueManager(), + service = ResourceService( + resource_processor=object(), + skill_processor=SimpleNamespace(process_skill=process_skill), + viking_fs=object(), ) monkeypatch.setattr( - "openviking.service.resource_service.get_request_wait_tracker", - lambda: tracker, - raising=False, + "openviking.service.resource_service.get_request_wait_tracker", lambda: tracker ) + task_tracker = TaskTracker(store=PersistentTaskStore(_FakeAgfs())) + monkeypatch.setattr("openviking.service.task_tracker.get_task_tracker", lambda: task_tracker) with bind_telemetry(telemetry): - result = await service.resources.add_skill( - data={"name": "demo", "content": "# Demo"}, - ctx=ctx, - wait=True, + result = await asyncio.wait_for( + service.add_skill( + data={"name": "demo", "content": "# Demo"}, + ctx=ctx, + wait=True, + target_uri="viking://user/default/skills", + ), + 1, ) - assert result["root_uri"] == "viking://user/default/skills/demo" - assert result["queue_status"] == tracker.queue_status - assert tracker.registered_requests == [telemetry.telemetry_id] - assert tracker.wait_calls == [(telemetry.telemetry_id, None)] - assert tracker.build_calls == [telemetry.telemetry_id] - assert tracker.cleaned == [telemetry.telemetry_id] - - -@pytest.mark.asyncio -async def test_content_write_wait_uses_request_tracker(monkeypatch): - file_uri = "viking://resources/demo/doc.md" - root_uri = "viking://resources/demo" - ctx = RequestContext(user=UserIdentifier.the_default_user(), role=Role.USER) - telemetry = OperationTelemetry(operation="content.write", enabled=True) - tracker = _FakeRequestWaitTracker( - { - "Semantic": {"processed": 1, "error_count": 0, "errors": []}, - "Embedding": {"processed": 0, "error_count": 0, "errors": []}, - } - ) - coordinator = ContentWriteCoordinator( - viking_fs=_FakeVikingFS(file_uri=file_uri, root_uri=root_uri) - ) - - monkeypatch.setattr( - "openviking.storage.content_write.get_request_wait_tracker", - lambda: tracker, - raising=False, - ) - - async def _fake_enqueue_semantic_refresh(**kwargs): - del kwargs - return None - - async def _explode_wait_for_queues(): - raise AssertionError("global queue wait should not be used") - - monkeypatch.setattr(coordinator, "_enqueue_semantic_refresh", _fake_enqueue_semantic_refresh) - monkeypatch.setattr(coordinator, "_wait_for_queues", _explode_wait_for_queues) - - with bind_telemetry(telemetry): - result = await coordinator.write( - uri=file_uri, - content="updated", - ctx=ctx, - wait=True, - ) - - assert result["queue_status"] == tracker.queue_status - assert tracker.registered_requests == [telemetry.telemetry_id] - assert tracker.wait_calls == [(telemetry.telemetry_id, None)] - assert tracker.build_calls == [telemetry.telemetry_id] - assert tracker.cleaned == [telemetry.telemetry_id] - assert result["semantic_status"] == "complete" - assert result["vector_status"] == "complete" - - -@pytest.mark.asyncio -async def test_content_write_wait_uses_request_tracker_when_telemetry_disabled(monkeypatch): - file_uri = "viking://resources/demo/doc.md" - root_uri = "viking://resources/demo" - ctx = RequestContext(user=UserIdentifier.the_default_user(), role=Role.USER) - telemetry = OperationTelemetry(operation="content.write", enabled=False) - tracker = _FakeRequestWaitTracker( - { - "Semantic": {"processed": 1, "error_count": 0, "errors": []}, - "Embedding": {"processed": 0, "error_count": 0, "errors": []}, - } - ) - coordinator = ContentWriteCoordinator( - viking_fs=_FakeVikingFS(file_uri=file_uri, root_uri=root_uri) - ) - - monkeypatch.setattr( - "openviking.storage.content_write.get_request_wait_tracker", - lambda: tracker, - raising=False, - ) - - async def _fake_enqueue_semantic_refresh(**kwargs): - del kwargs - return None - - async def _explode_wait_for_queues(): - raise AssertionError("global queue wait should not be used") - - monkeypatch.setattr(coordinator, "_enqueue_semantic_refresh", _fake_enqueue_semantic_refresh) - monkeypatch.setattr(coordinator, "_wait_for_queues", _explode_wait_for_queues) - - with bind_telemetry(telemetry): - result = await coordinator.write( - uri=file_uri, - content="updated", - ctx=ctx, - wait=True, - ) - - assert result["queue_status"] == tracker.queue_status - assert tracker.registered_requests == [telemetry.telemetry_id] - assert tracker.wait_calls == [(telemetry.telemetry_id, None)] - assert tracker.build_calls == [telemetry.telemetry_id] - assert tracker.cleaned == [telemetry.telemetry_id] - assert result["semantic_status"] == "complete" - assert result["vector_status"] == "complete" - - -async def _return_true(handle, path): - del handle, path - return True - - -async def _return_none(handle): - del handle - return None + assert result["root_uri"] == root_uri + assert result["queue_status"]["Embedding"]["processed"] == 1 + assert not tracker.has_request(telemetry.telemetry_id) + assert not tracker.is_complete("unrelated") diff --git a/tests/server/test_skill_privacy_lock.py b/tests/server/test_skill_privacy_lock.py index 17662ed744..ad0df019bf 100644 --- a/tests/server/test_skill_privacy_lock.py +++ b/tests/server/test_skill_privacy_lock.py @@ -182,7 +182,7 @@ async def hold_summary(self, *args, **kwargs): assert shown.json()["result"]["description"] == "Original" finally: resume.set() - await get_queue_manager().wait_complete(timeout=5) + await asyncio.wait_for(get_queue_manager().wait_complete(), 5) if not updating.done(): updating.cancel() await asyncio.gather(updating, return_exceptions=True) diff --git a/tests/server/test_skill_update_cancellation.py b/tests/server/test_skill_update_cancellation.py index 81b716ade8..2213cf38f1 100644 --- a/tests/server/test_skill_update_cancellation.py +++ b/tests/server/test_skill_update_cancellation.py @@ -157,7 +157,7 @@ async def hold_summary(self, file_path, *args, **kwargs): # Releasing the fault after the response must not let stale work modify the restored pack. release.set() - await get_queue_manager().wait_complete(timeout=5) + await asyncio.wait_for(get_queue_manager().wait_complete(), 5) for path, content in old_contents.items(): assert await _download(client, f"{root}/{path}") == content for location, records in old_records.items(): @@ -168,7 +168,7 @@ async def hold_summary(self, file_path, *args, **kwargs): assert await _indexed_record(client, f"{root}/{path}", 2) == [] finally: release.set() - await get_queue_manager().wait_complete(timeout=5) + await asyncio.wait_for(get_queue_manager().wait_complete(), 5) if not updating.done(): updating.cancel() await asyncio.gather(updating, return_exceptions=True) @@ -209,4 +209,4 @@ async def fail_after_response(self, file_path, *args, **kwargs): assert shown.json()["result"]["description"] == "Replacement description" finally: release_failure.set() - await get_queue_manager().wait_complete(timeout=5) + await asyncio.wait_for(get_queue_manager().wait_complete(), 5) diff --git a/tests/service/test_fs_service.py b/tests/service/test_fs_service.py index 4c11d2ac45..878246fc61 100644 --- a/tests/service/test_fs_service.py +++ b/tests/service/test_fs_service.py @@ -267,8 +267,8 @@ def register_semantic_root(self, telemetry_id, root_id): } ) - async def wait_for_request(self, telemetry_id, timeout=None): - self.wait_calls.append((telemetry_id, timeout)) + async def wait_for_request(self, telemetry_id): + self.wait_calls.append(telemetry_id) def build_queue_status(self, telemetry_id): return { @@ -913,51 +913,22 @@ async def filter(self, **_kwargs): assert viking_fs.tree.await_args.kwargs["node_limit"] is None -@pytest.mark.asyncio -async def test_resource_rm_enqueues_parent_delete_refresh_and_waits(request_context): - viking_fs = _FakeVikingFS() - service = FSService(viking_fs=viking_fs) - service._enqueue_delete_refresh = AsyncMock() - service._wait_for_refresh = AsyncMock(return_value={"Semantic": {"pending_count": 0}}) - - uri = "viking://resources/images/2026/06/10/不二周助_jpeg" - result = await service.rm( - uri, - ctx=request_context, - recursive=True, - wait=True, - ) - - assert viking_fs.rm_calls == [{"uri": uri, "recursive": True, "ctx": request_context}] - service._enqueue_delete_refresh.assert_awaited_once_with( - root_uri="viking://resources/images/2026/06/10", - deleted_uri=uri, - context_type="resource", - ctx=request_context, - force_refresh=True, - ) - service._wait_for_refresh.assert_awaited_once_with() - assert result["semantic_root_uri"] == "viking://resources/images/2026/06/10" - assert result["semantic_status"] == "complete" - assert result["queue_status"] == {"Semantic": {"pending_count": 0}} - - @pytest.mark.asyncio async def test_resource_rm_reports_failed_semantic_status_when_wait_queue_has_errors( - request_context, + request_context, monkeypatch ): viking_fs = _FakeVikingFS() service = FSService(viking_fs=viking_fs) service._enqueue_delete_refresh = AsyncMock() - service._wait_for_refresh = AsyncMock( - return_value={ - "Semantic": { - "processed": 1, - "error_count": 1, - "errors": [{"message": "refresh failed"}], - } + tracker = _FakeWaitTracker() + tracker.build_queue_status = lambda _id: { + "Semantic": { + "processed": 1, + "error_count": 1, + "errors": [{"message": "refresh failed"}], } - ) + } + monkeypatch.setattr("openviking.service.fs_service.get_request_wait_tracker", lambda: tracker) result = await service.rm( "viking://resources/images/2026/06/10/不二周助_jpeg", @@ -989,13 +960,11 @@ async def test_resource_rm_without_wait_only_queues_refresh(request_context): viking_fs = _FakeVikingFS() service = FSService(viking_fs=viking_fs) service._enqueue_delete_refresh = AsyncMock() - service._wait_for_refresh = AsyncMock() uri = "viking://resources/images/2026/06/10/不二周助_jpeg" result = await service.rm(uri, ctx=request_context, recursive=True) service._enqueue_delete_refresh.assert_awaited_once() - service._wait_for_refresh.assert_not_awaited() assert result["semantic_status"] == "queued" @@ -1003,7 +972,6 @@ async def test_resource_rm_without_wait_only_queues_refresh(request_context): async def test_resource_scope_rm_does_not_refresh_global_root(request_context): service = FSService(viking_fs=_FakeVikingFS()) service._enqueue_delete_refresh = AsyncMock() - service._wait_for_refresh = AsyncMock() result = await service.rm( "viking://resources", @@ -1013,7 +981,6 @@ async def test_resource_scope_rm_does_not_refresh_global_root(request_context): ) service._enqueue_delete_refresh.assert_not_awaited() - service._wait_for_refresh.assert_not_awaited() assert "semantic_root_uri" not in result @@ -1504,9 +1471,11 @@ async def test_resource_rm_wait_registers_request_before_semantic_root( assert tracker.registered_roots assert tracker.registered_roots[0]["request_was_registered"] is True assert queue_manager.messages[0].recursive is False - assert tracker.wait_calls == [("tm-fs-rm", None)] + assert tracker.wait_calls == ["tm-fs-rm"] assert tracker.cleaned == ["tm-fs-rm"] + assert result["semantic_root_uri"] == "viking://resources/images/2026/06/10" assert result["semantic_status"] == "complete" + assert result["queue_status"]["Semantic"]["processed"] == 1 @pytest.mark.asyncio diff --git a/tests/service/test_resource_service_watch.py b/tests/service/test_resource_service_watch.py index 03addcfc24..74fbd90c0f 100644 --- a/tests/service/test_resource_service_watch.py +++ b/tests/service/test_resource_service_watch.py @@ -284,14 +284,7 @@ async def test_add_resource_forwards_tags_to_ingest_without_calling_set_tags( async def fake_set_tags(self, **kwargs): raise AssertionError("add_resource(tags=...) must write tags during ingest") - class FakeQueueManager: - async def wait_complete(self, timeout=None): - return {} - monkeypatch.setattr(ContentWriteCoordinator, "set_tags", fake_set_tags) - monkeypatch.setattr( - resource_service_module, "get_queue_manager", lambda: FakeQueueManager() - ) result = await resource_service.add_resource( path="/test/path", @@ -334,14 +327,7 @@ async def test_watch_task_persists_tag_policy_for_refresh( async def fake_set_tags(self, **kwargs): return {"tags_updated": True} - class FakeQueueManager: - async def wait_complete(self, timeout=None): - return {} - monkeypatch.setattr(ContentWriteCoordinator, "set_tags", fake_set_tags) - monkeypatch.setattr( - resource_service_module, "get_queue_manager", lambda: FakeQueueManager() - ) await resource_service.add_resource( path="/test/path", diff --git a/tests/service/test_task_processing_time.py b/tests/service/test_task_processing_time.py index 9ca2ca9f38..d3a161194a 100644 --- a/tests/service/test_task_processing_time.py +++ b/tests/service/test_task_processing_time.py @@ -139,7 +139,7 @@ async def test_downstream_wait_remains_worker_occupied_time(monkeypatch, method) index.register_active("task", worker) try: now[0] = 3 - waiter = asyncio.create_task(getattr(tracker, method)(request, poll_interval=0.001)) + waiter = asyncio.create_task(getattr(tracker, method)(request)) await asyncio.sleep(0.01) now[0] = 20 assert index.processing_seconds("task") == 20 diff --git a/tests/telemetry/test_request_wait_tracker.py b/tests/telemetry/test_request_wait_tracker.py index 5401656cfc..e72a13b44d 100644 --- a/tests/telemetry/test_request_wait_tracker.py +++ b/tests/telemetry/test_request_wait_tracker.py @@ -124,12 +124,17 @@ def test_request_wait_tracker_accumulates_queue_wait_and_execution_durations(): } -async def test_wait_for_request_timeout_keeps_existing_error(): +async def test_wait_for_request_timeout_preserves_pending_work(): tracker = RequestWaitTracker() telemetry_id = "tm_wait_timeout" tracker.register_request(telemetry_id) tracker.register_embedding_root(telemetry_id, "embedding-1") - with pytest.raises(TimeoutError, match="Request processing not complete after 0.01s"): - await tracker.wait_for_request(telemetry_id, timeout=0.01, poll_interval=0.001) + with pytest.raises(TimeoutError): + await tracker.wait_for_request(telemetry_id, timeout=0.01) + + assert not tracker.is_complete(telemetry_id) + tracker.mark_embedding_done(telemetry_id, "embedding-1") + await tracker.wait_for_request(telemetry_id) + assert tracker.build_queue_status(telemetry_id)["Embedding"]["processed"] == 1