noah-code 0.5.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {noah_code-0.5.0 → noah_code-0.5.1}/PKG-INFO +5 -5
- noah_code-0.5.1/docs/releases/v0.5.1.md +36 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/pyproject.toml +5 -5
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/__init__.py +1 -1
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/agent.py +37 -1
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/nooa_compat.py +1 -1
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/workspace_tools.py +5 -3
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_agent_security.py +7 -2
- noah_code-0.5.1/tests/test_lean_reasoning.py +201 -0
- noah_code-0.5.1/tests/test_nooa_migration.py +28 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_textual_tui.py +5 -1
- {noah_code-0.5.0 → noah_code-0.5.1}/uv.lock +11 -11
- {noah_code-0.5.0 → noah_code-0.5.1}/.github/workflows/ci.yml +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/.github/workflows/release.yml +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/.gitignore +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/.pre-commit-config.yaml +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/README.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/assets/noah-in-action.svg +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/assets/noah-logo.svg +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/configuration.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/development.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/extensions.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/interactive-reference.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.1.0.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.1.1.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.2.0.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.2.1.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.2.2.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.2.3.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.2.4.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.3.0.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.4.0.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.4.1.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/releases/v0.5.0.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/reliability.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/docs/security.md +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/install.sh +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/__main__.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/agents.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/approvals.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/budget.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/checkpoints.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/cli.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/commands.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/composer.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/config.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/credentials.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/custom_commands.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/event_bridge.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/events.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/github.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/hooks.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/host.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/llm.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/llm_replies.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/macos_sandbox.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/mcp_setup.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/permissions.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/predict.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/project_notes.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/providers.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/redaction.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/runtime_state.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/secure_files.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/sessions.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/skills_setup.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/snapshots.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/steer.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/summarization.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/telemetry.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/themes.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tool_output.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/__init__.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/diff_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/git_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/github_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/lsp_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/media_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/memory_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/plan_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/process_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/question_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/task_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/tools/web_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/ui/__init__.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/ui/console.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/ui/protocol.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/ui/textual.css +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/ui/textual_app.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/updates.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/usage.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/workspace.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/src/noah_code/worktree.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/conftest.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_agents.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_approvals.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_budget.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_cache_context.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_checkpoints.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_cli.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_commands.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_compaction_eviction.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_composer.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_config.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_console_ui.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_credentials.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_custom_commands.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_diff_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_efficiency.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_event_bridge.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_event_bridge_and_shell.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_git_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_github.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_hooks.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_host.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_installer.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_llm.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_llm_replies.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_lsp_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_mcp_setup.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_permissions.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_plan_memory_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_process_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_project_notes.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_prompt_scorecard.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_providers.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_question_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_redaction.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_run_exit.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_runtime_state.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_sessions.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_skills_setup.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_snapshots.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_steer.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_summarization.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_task_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_telemetry.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_tool_output.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_updates.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_wave1_e2e.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_web_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_workspace_tools.py +0 -0
- {noah_code-0.5.0 → noah_code-0.5.1}/tests/test_worktree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: noah-code
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Noah Code terminal coding agent, built on NVIDIA OO Agents (NOOA)
|
|
5
5
|
Project-URL: Homepage, https://github.com/skundu42/noah-code
|
|
6
6
|
Project-URL: Documentation, https://github.com/skundu42/noah-code#readme
|
|
@@ -21,8 +21,8 @@ Classifier: Topic :: Software Development
|
|
|
21
21
|
Requires-Python: <3.14,>=3.12
|
|
22
22
|
Requires-Dist: click<9.0.0,>=8.1.0
|
|
23
23
|
Requires-Dist: litellm<1.99.0,>=1.96.0
|
|
24
|
-
Requires-Dist: nooa-cli==0.0.
|
|
25
|
-
Requires-Dist: nooa==0.0.
|
|
24
|
+
Requires-Dist: nooa-cli==0.0.10
|
|
25
|
+
Requires-Dist: nooa==0.0.10
|
|
26
26
|
Requires-Dist: packaging<27.0,>=24.0
|
|
27
27
|
Requires-Dist: pydantic<3.0.0,>=2.5.0
|
|
28
28
|
Requires-Dist: pyyaml<7.0.0,>=6.0.0
|
|
@@ -37,9 +37,9 @@ Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
|
37
37
|
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
38
38
|
Requires-Dist: types-pyyaml>=6.0.12.20260815; extra == 'dev'
|
|
39
39
|
Provides-Extra: mcp
|
|
40
|
-
Requires-Dist: nooa[mcp]==0.0.
|
|
40
|
+
Requires-Dist: nooa[mcp]==0.0.10; extra == 'mcp'
|
|
41
41
|
Provides-Extra: tracing
|
|
42
|
-
Requires-Dist: nooa[tracing]==0.0.
|
|
42
|
+
Requires-Dist: nooa[tracing]==0.0.10; extra == 'tracing'
|
|
43
43
|
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2.0.0,>=1.44.0; extra == 'tracing'
|
|
44
44
|
Description-Content-Type: text/markdown
|
|
45
45
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Noah Code v0.5.1
|
|
2
|
+
|
|
3
|
+
This release migrates Noah to NOOA 0.0.10 and preserves reasoning state in the default
|
|
4
|
+
lean execution mode.
|
|
5
|
+
|
|
6
|
+
## Reasoning continuity
|
|
7
|
+
|
|
8
|
+
- Structured reasoning returned alongside tool calls is retained in lean-mode history and
|
|
9
|
+
included in the next provider request. Noah keeps its compact tool transcript while gaining
|
|
10
|
+
upstream's reasoning-state support.
|
|
11
|
+
- Regression checks cover the follow-up provider request, multiple tool calls, and responses
|
|
12
|
+
without reasoning state.
|
|
13
|
+
|
|
14
|
+
## Runtime migration
|
|
15
|
+
|
|
16
|
+
- Pin NOOA and NOOA CLI, including MCP and tracing extras, to 0.0.10.
|
|
17
|
+
- Adopt upstream's shell framing fixes: malformed quotes and heredocs report syntax errors,
|
|
18
|
+
and commands that read stdin cannot consume the shell's internal control protocol.
|
|
19
|
+
- Carry upstream's configured error-output limits into Noah's macOS sandbox worker.
|
|
20
|
+
- Preserve Noah's background-task and recovery notifications after upstream's queue changes.
|
|
21
|
+
- Supply absolute file locations for upstream's updated edit anchors, keeping Noah's stale-edit
|
|
22
|
+
protections and workspace-relative display paths.
|
|
23
|
+
|
|
24
|
+
The migration also includes upstream's bounded, source-aware sandbox diagnostics and preservation
|
|
25
|
+
of MCP call timeouts during OAuth refresh.
|
|
26
|
+
|
|
27
|
+
## Upgrade
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
noah update
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Validation uses deterministic provider responses and runtime regressions. This release does not
|
|
34
|
+
claim measured live-provider quality improvements or general token savings.
|
|
35
|
+
|
|
36
|
+
**Full changelog:** https://github.com/skundu42/noah-code/compare/v0.5.0...v0.5.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "noah-code"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.1"
|
|
4
4
|
description = "Noah Code terminal coding agent, built on NVIDIA OO Agents (NOOA)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12,<3.14"
|
|
@@ -19,8 +19,8 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
dependencies = [
|
|
22
|
-
"nooa==0.0.
|
|
23
|
-
"nooa-cli==0.0.
|
|
22
|
+
"nooa==0.0.10",
|
|
23
|
+
"nooa-cli==0.0.10",
|
|
24
24
|
# LiteLLM 1.92.0-1.95.x intermittently shipped without macOS wheels,
|
|
25
25
|
# forcing a Rust source build. Wheels are back for every release since
|
|
26
26
|
# 1.96.0 (abi3 since 1.96.2), so the installer stays binary-only.
|
|
@@ -53,10 +53,10 @@ dev = [
|
|
|
53
53
|
"types-pyyaml>=6.0.12.20260815",
|
|
54
54
|
]
|
|
55
55
|
mcp = [
|
|
56
|
-
"nooa[mcp]==0.0.
|
|
56
|
+
"nooa[mcp]==0.0.10",
|
|
57
57
|
]
|
|
58
58
|
tracing = [
|
|
59
|
-
"nooa[tracing]==0.0.
|
|
59
|
+
"nooa[tracing]==0.0.10",
|
|
60
60
|
"opentelemetry-exporter-otlp-proto-http>=1.44.0,<2.0.0",
|
|
61
61
|
]
|
|
62
62
|
|
|
@@ -17,10 +17,14 @@ from typing import Annotated, Any, Literal
|
|
|
17
17
|
|
|
18
18
|
from nooa import Agent, Context, hidden, strategy
|
|
19
19
|
from nooa.config import CodeActConfig, PredictConfig
|
|
20
|
+
from nooa.context_blocks import RenderedMessage, ResolvedBlock, Role, ToolCallEvent
|
|
20
21
|
from nooa.interactive import InteractiveAgent, RespondReason, RespondResult
|
|
22
|
+
from nooa.runtime.channels import Channel
|
|
21
23
|
from nooa.runtime.restrictions import RESTRICTED_MODULES, RestrictionsConfig
|
|
22
24
|
from nooa.runtime.sandbox.config import FileRule, SandboxConfig, resolve_spec
|
|
23
25
|
from nooa.runtime.sandbox.executor import SandboxedExecutor
|
|
26
|
+
from nooa.runtime.sandbox.serialization import effective_error_limit
|
|
27
|
+
from nooa.storage.markers import nosnapshot
|
|
24
28
|
from nooa.strategies import CodeActStrategy
|
|
25
29
|
from nooa.strategies.codeact_lite import PlainCodeActBlockFormatter
|
|
26
30
|
from nooa.tools import TodoManager
|
|
@@ -284,6 +288,8 @@ class _MacOSPermissionSandboxedExecutor(_PermissionSandboxedExecutor):
|
|
|
284
288
|
cell_timeout: float | None,
|
|
285
289
|
framework_builtins: dict[str, Any] | None = None,
|
|
286
290
|
restrictions: Any = None,
|
|
291
|
+
max_error: int | None = None,
|
|
292
|
+
error_tail: int | None = None,
|
|
287
293
|
) -> None:
|
|
288
294
|
# NOOA probes specifically for Linux Landlock/seccomp. Preserve
|
|
289
295
|
# its worker and broker implementation, but install equivalent native
|
|
@@ -301,6 +307,8 @@ class _MacOSPermissionSandboxedExecutor(_PermissionSandboxedExecutor):
|
|
|
301
307
|
self._cell_timeout = cell_timeout
|
|
302
308
|
self._framework_builtins = framework_builtins or {}
|
|
303
309
|
self._restrictions = restrictions
|
|
310
|
+
self._max_error = effective_error_limit(max_error)
|
|
311
|
+
self._error_tail = error_tail
|
|
304
312
|
self._spec = resolve_spec(worker_config)
|
|
305
313
|
self._degraded: list[str] = []
|
|
306
314
|
# Forking a multithreaded Textual process can deadlock in inherited
|
|
@@ -347,6 +355,8 @@ class _MacOSPermissionSandboxedExecutor(_PermissionSandboxedExecutor):
|
|
|
347
355
|
),
|
|
348
356
|
"restrictions": self._restrictions,
|
|
349
357
|
"spec": self._spec,
|
|
358
|
+
"max_error": self._max_error,
|
|
359
|
+
"error_tail": self._error_tail,
|
|
350
360
|
}
|
|
351
361
|
)
|
|
352
362
|
except Exception:
|
|
@@ -580,9 +590,31 @@ class _PermissionCodeActStrategy(CodeActStrategy):
|
|
|
580
590
|
cell_timeout=self.config.cell_timeout,
|
|
581
591
|
framework_builtins=framework_builtins,
|
|
582
592
|
restrictions=self.config.restrictions,
|
|
593
|
+
max_error=runtime.truncation_config.capture.max_error,
|
|
594
|
+
error_tail=runtime.truncation_config.capture.tail,
|
|
583
595
|
)
|
|
584
596
|
|
|
585
597
|
|
|
598
|
+
class _ReasoningPlainCodeActBlockFormatter(PlainCodeActBlockFormatter):
|
|
599
|
+
"""Retain opaque reasoning state omitted by NOOA 0.0.10's plain formatter."""
|
|
600
|
+
|
|
601
|
+
def format(self, blocks: list[ResolvedBlock]) -> list[RenderedMessage]:
|
|
602
|
+
messages = super().format(blocks)
|
|
603
|
+
# Plain formatting preserves event order; IDs may repeat across turns.
|
|
604
|
+
reasoning_items = iter(
|
|
605
|
+
block.event.reasoning_items
|
|
606
|
+
for block in blocks
|
|
607
|
+
if block.role not in (Role.SYSTEM, Role.RUNTIME_EVENT)
|
|
608
|
+
and isinstance(block.event, ToolCallEvent)
|
|
609
|
+
)
|
|
610
|
+
for index, message in enumerate(messages):
|
|
611
|
+
if message.tool_call is not None:
|
|
612
|
+
reasoning = next(reasoning_items)
|
|
613
|
+
if reasoning:
|
|
614
|
+
messages[index] = message.model_copy(update={"reasoning_items": reasoning})
|
|
615
|
+
return messages
|
|
616
|
+
|
|
617
|
+
|
|
586
618
|
class _LeanPermissionCodeActStrategy(_PermissionCodeActStrategy):
|
|
587
619
|
"""Compact NOOA event rendering while retaining cross-turn history."""
|
|
588
620
|
|
|
@@ -594,7 +626,9 @@ class _LeanPermissionCodeActStrategy(_PermissionCodeActStrategy):
|
|
|
594
626
|
original = runtime.agent.render_config
|
|
595
627
|
event_format = nooa_compat.truncation_event_format(runtime.agent)
|
|
596
628
|
runtime.agent.render_config = original.model_copy(
|
|
597
|
-
update={
|
|
629
|
+
update={
|
|
630
|
+
"block_formatter": _ReasoningPlainCodeActBlockFormatter(event_format=event_format)
|
|
631
|
+
}
|
|
598
632
|
)
|
|
599
633
|
try:
|
|
600
634
|
return await super().execute(runtime, call)
|
|
@@ -630,6 +664,7 @@ class CodingAgent(InteractiveAgent):
|
|
|
630
664
|
|
|
631
665
|
workspace_root: str = "."
|
|
632
666
|
mode: Literal["build", "plan"] = "build"
|
|
667
|
+
_system_messages_in: Annotated[Channel, hidden, nosnapshot]
|
|
633
668
|
|
|
634
669
|
def __init__(
|
|
635
670
|
self,
|
|
@@ -656,6 +691,7 @@ class CodingAgent(InteractiveAgent):
|
|
|
656
691
|
|
|
657
692
|
llm = wrap_conversational_replies(llm)
|
|
658
693
|
super().__init__(llm=llm, storage=storage, **kwargs)
|
|
694
|
+
self._system_messages_in = self.queue_manager.queue("system_messages")
|
|
659
695
|
if cache_namespace:
|
|
660
696
|
# NOOA derives prompt_cache_key from this id. A durable Noah
|
|
661
697
|
# session therefore keeps provider cache affinity across restart.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Single seam for NOOA internals that have no public API yet.
|
|
2
2
|
|
|
3
3
|
Every private-attribute reach-through into the framework lives here so an
|
|
4
|
-
upgrade only requires auditing this one module. Pinned upstream: nooa==0.0.
|
|
4
|
+
upgrade only requires auditing this one module. Pinned upstream: nooa==0.0.10.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -343,7 +343,7 @@ class WorkspaceTools(Skill):
|
|
|
343
343
|
with resolved.open(encoding="utf-8") as stream:
|
|
344
344
|
content = stream.read()
|
|
345
345
|
total = len(content.splitlines(keepends=True))
|
|
346
|
-
return WorkspaceMatch(str(resolved), 1, total, content)
|
|
346
|
+
return WorkspaceMatch(str(resolved), 1, total, content, resolved_path=resolved)
|
|
347
347
|
|
|
348
348
|
@staticmethod
|
|
349
349
|
def _read_line_range(resolved: Path, lines: tuple[int, int]) -> WorkspaceMatch:
|
|
@@ -363,7 +363,7 @@ class WorkspaceTools(Skill):
|
|
|
363
363
|
else:
|
|
364
364
|
# The file ended before the range did; clamp like a full read.
|
|
365
365
|
end = min(end, last)
|
|
366
|
-
return WorkspaceMatch(str(resolved), start, end, "".join(collected))
|
|
366
|
+
return WorkspaceMatch(str(resolved), start, end, "".join(collected), resolved_path=resolved)
|
|
367
367
|
|
|
368
368
|
async def search(
|
|
369
369
|
self,
|
|
@@ -1322,7 +1322,9 @@ class WorkspaceTools(Skill):
|
|
|
1322
1322
|
if not candidate.is_absolute():
|
|
1323
1323
|
candidate = (base / candidate).resolve()
|
|
1324
1324
|
rewritten.append(
|
|
1325
|
-
WorkspaceMatch(
|
|
1325
|
+
WorkspaceMatch(
|
|
1326
|
+
str(candidate), match.start, match.end, match.text, resolved_path=candidate
|
|
1327
|
+
)
|
|
1326
1328
|
)
|
|
1327
1329
|
result.matches = rewritten
|
|
1328
1330
|
|
|
@@ -12,6 +12,8 @@ from types import SimpleNamespace
|
|
|
12
12
|
import pytest
|
|
13
13
|
from nooa.runtime.restrictions import RestrictionsConfig
|
|
14
14
|
from nooa.runtime.sandbox.config import SandboxConfig
|
|
15
|
+
from nooa.runtime.sandbox.errors import SandboxExecutionError
|
|
16
|
+
from nooa.runtime.sandbox.serialization import effective_error_limit
|
|
15
17
|
|
|
16
18
|
from noah_code.agent import (
|
|
17
19
|
_codeact_config,
|
|
@@ -75,6 +77,7 @@ def test_sandbox_broker_exposes_only_permission_gated_capabilities() -> None:
|
|
|
75
77
|
@pytest.mark.asyncio
|
|
76
78
|
async def test_sandbox_cannot_call_the_host_only_readonly_runner() -> None:
|
|
77
79
|
executor = object.__new__(_PermissionSandboxedExecutor)
|
|
80
|
+
executor._max_error = effective_error_limit(None)
|
|
78
81
|
executor._agent = SimpleNamespace(
|
|
79
82
|
ws=SimpleNamespace(run_trusted_readonly=lambda _command: "private host data")
|
|
80
83
|
)
|
|
@@ -125,8 +128,10 @@ async def test_macos_sandbox_blocks_unbrokered_file_and_network_access() -> None
|
|
|
125
128
|
|
|
126
129
|
assert safe.stdout.strip() == "2"
|
|
127
130
|
assert safe.error is None
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
for blocked in (blocked_file, blocked_network):
|
|
132
|
+
assert isinstance(blocked.error, SandboxExecutionError)
|
|
133
|
+
assert blocked.error.original_type == "PermissionError"
|
|
134
|
+
assert isinstance(blocked.error.original_error, PermissionError)
|
|
130
135
|
|
|
131
136
|
|
|
132
137
|
def test_spawn_safe_local_agent_drops_instance_file_descriptors() -> None:
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Compact history must replay opaque provider reasoning with its tool call."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from nooa.context_blocks import BlockMetadata, ResolvedBlock, Role, ToolCallEvent, ToolResult
|
|
11
|
+
from nooa.context_blocks.formatter import OpenAIProviderFormatter, ResponsesProviderFormatter
|
|
12
|
+
from nooa.interactive import RespondReason
|
|
13
|
+
from nooa.strategies.codeact_lite import PlainCodeActBlockFormatter
|
|
14
|
+
from nooa.unifiedllm import FakeLLMClient, LLMResponse, ToolCall
|
|
15
|
+
|
|
16
|
+
from noah_code import nooa_compat
|
|
17
|
+
from noah_code.agent import CodingAgent, _ReasoningPlainCodeActBlockFormatter
|
|
18
|
+
from noah_code.config import NoahCodeConfig
|
|
19
|
+
from noah_code.workspace import Workspace
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.parametrize(
|
|
23
|
+
"reasoning_items",
|
|
24
|
+
[None, [], [{"type": "reasoning", "id": "rs_fixture", "encrypted_content": "opaque-fixture"}]],
|
|
25
|
+
)
|
|
26
|
+
def test_lean_formatter_preserves_reasoning_without_changing_compact_history(reasoning_items):
|
|
27
|
+
blocks = [
|
|
28
|
+
ResolvedBlock(
|
|
29
|
+
key="instructions",
|
|
30
|
+
content="Stable instructions.",
|
|
31
|
+
metadata=BlockMetadata(static=True),
|
|
32
|
+
),
|
|
33
|
+
ResolvedBlock(key="task", content="Inspect the repository.", role=Role.USER),
|
|
34
|
+
]
|
|
35
|
+
for index in range(2):
|
|
36
|
+
call_id = f"call_{index}"
|
|
37
|
+
blocks.append(
|
|
38
|
+
ResolvedBlock(
|
|
39
|
+
key=call_id,
|
|
40
|
+
content="",
|
|
41
|
+
role=Role.ASSISTANT,
|
|
42
|
+
event=ToolCallEvent(
|
|
43
|
+
tool_call_id=call_id,
|
|
44
|
+
name="execute_python",
|
|
45
|
+
arguments={"code": f"print({index})"},
|
|
46
|
+
reasoning_items=reasoning_items if index == 0 else None,
|
|
47
|
+
result=ToolResult(tool_call_id=call_id, content=f"result {index}"),
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
baseline = PlainCodeActBlockFormatter().format(blocks)
|
|
53
|
+
rendered = _ReasoningPlainCodeActBlockFormatter().format(blocks)
|
|
54
|
+
|
|
55
|
+
assert [message.model_dump(exclude={"reasoning_items"}) for message in rendered] == [
|
|
56
|
+
message.model_dump(exclude={"reasoning_items"}) for message in baseline
|
|
57
|
+
]
|
|
58
|
+
assert [message.reasoning_items for message in rendered if message.tool_call] == [
|
|
59
|
+
reasoning_items or None,
|
|
60
|
+
None,
|
|
61
|
+
]
|
|
62
|
+
assert all(message.reasoning_items is None for message in rendered if not message.tool_call)
|
|
63
|
+
|
|
64
|
+
chat_messages = OpenAIProviderFormatter().format(rendered)
|
|
65
|
+
calls = [message for message in chat_messages if message.get("tool_calls")]
|
|
66
|
+
assert calls[0].get("reasoning_items") == (reasoning_items or None)
|
|
67
|
+
assert "reasoning_items" not in calls[1]
|
|
68
|
+
responses_messages = ResponsesProviderFormatter().format(rendered)
|
|
69
|
+
assert [message for message in responses_messages if message.get("type") == "reasoning"] == (
|
|
70
|
+
reasoning_items or []
|
|
71
|
+
)
|
|
72
|
+
if reasoning_items:
|
|
73
|
+
first_call = next(
|
|
74
|
+
i
|
|
75
|
+
for i, message in enumerate(responses_messages)
|
|
76
|
+
if message.get("type") == "function_call"
|
|
77
|
+
)
|
|
78
|
+
assert responses_messages[first_call - 1] == reasoning_items[0]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize("later_state", [None, [], [{"type": "reasoning", "id": "rs_later"}]])
|
|
82
|
+
def test_reused_tool_call_ids_keep_reasoning_with_the_original_event(later_state):
|
|
83
|
+
first_state = [{"type": "reasoning", "id": "rs_first"}]
|
|
84
|
+
blocks = [
|
|
85
|
+
ResolvedBlock(
|
|
86
|
+
key=f"event_{index}",
|
|
87
|
+
content="",
|
|
88
|
+
role=role,
|
|
89
|
+
event=ToolCallEvent(
|
|
90
|
+
tool_call_id="reused-call",
|
|
91
|
+
name="execute_python",
|
|
92
|
+
arguments={"code": "print('ok')"},
|
|
93
|
+
reasoning_items=state,
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
for index, (role, state) in enumerate(
|
|
97
|
+
[
|
|
98
|
+
(Role.SYSTEM, [{"type": "reasoning", "id": "not-rendered-system"}]),
|
|
99
|
+
(Role.RUNTIME_EVENT, [{"type": "reasoning", "id": "not-rendered-runtime"}]),
|
|
100
|
+
(Role.ASSISTANT, first_state),
|
|
101
|
+
(Role.ASSISTANT, later_state),
|
|
102
|
+
]
|
|
103
|
+
)
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
rendered = _ReasoningPlainCodeActBlockFormatter().format(blocks)
|
|
107
|
+
|
|
108
|
+
assert [message.reasoning_items for message in rendered if message.tool_call] == [
|
|
109
|
+
first_state,
|
|
110
|
+
later_state or None,
|
|
111
|
+
]
|
|
112
|
+
provider_messages = OpenAIProviderFormatter().format(rendered)
|
|
113
|
+
assert [
|
|
114
|
+
message.get("reasoning_items") for message in provider_messages if message.get("tool_calls")
|
|
115
|
+
] == [first_state, later_state or None]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.mark.asyncio
|
|
119
|
+
@pytest.mark.parametrize("with_reasoning", [False, True])
|
|
120
|
+
async def test_default_lean_agent_replays_reasoning_on_next_model_call(
|
|
121
|
+
tmp_path: Path,
|
|
122
|
+
with_reasoning: bool,
|
|
123
|
+
) -> None:
|
|
124
|
+
reasoning_items = [
|
|
125
|
+
{"type": "reasoning", "id": "rs_fixture", "encrypted_content": "opaque-fixture"}
|
|
126
|
+
]
|
|
127
|
+
first_assistant = {"role": "assistant", "content": "", "tool_calls": []}
|
|
128
|
+
if with_reasoning:
|
|
129
|
+
first_assistant["reasoning_items"] = reasoning_items
|
|
130
|
+
llm = FakeLLMClient(
|
|
131
|
+
scripted_responses=[
|
|
132
|
+
LLMResponse(
|
|
133
|
+
raw_response=None,
|
|
134
|
+
content="",
|
|
135
|
+
finish_reason="tool_calls",
|
|
136
|
+
assistant_message=first_assistant,
|
|
137
|
+
tool_calls=[
|
|
138
|
+
ToolCall(
|
|
139
|
+
id=f"call_{index}",
|
|
140
|
+
name="execute_python",
|
|
141
|
+
arguments=json.dumps({"code": f"print('result {index}')"}),
|
|
142
|
+
)
|
|
143
|
+
for index in range(2)
|
|
144
|
+
],
|
|
145
|
+
),
|
|
146
|
+
LLMResponse(
|
|
147
|
+
raw_response=None,
|
|
148
|
+
content="",
|
|
149
|
+
finish_reason="tool_calls",
|
|
150
|
+
assistant_message={"role": "assistant", "content": "", "tool_calls": []},
|
|
151
|
+
tool_calls=[
|
|
152
|
+
ToolCall(
|
|
153
|
+
id="call_done",
|
|
154
|
+
name="execute_python",
|
|
155
|
+
arguments=json.dumps(
|
|
156
|
+
{
|
|
157
|
+
"code": 'return_result(RespondReason.DONE, explanation="verified")',
|
|
158
|
+
}
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
],
|
|
162
|
+
),
|
|
163
|
+
]
|
|
164
|
+
)
|
|
165
|
+
agent = CodingAgent(
|
|
166
|
+
Workspace(root=tmp_path.resolve()),
|
|
167
|
+
NoahCodeConfig(auto_approve=True, unsafe_inprocess_code_execution=True),
|
|
168
|
+
llm=llm,
|
|
169
|
+
nested=True,
|
|
170
|
+
)
|
|
171
|
+
try:
|
|
172
|
+
nooa_compat.queue_user_message(agent, "Run both checks.")
|
|
173
|
+
wins = await agent.queue_manager.race()
|
|
174
|
+
notification: dict[str, list] = {}
|
|
175
|
+
for name, item in wins:
|
|
176
|
+
notification.setdefault(name, []).append(item)
|
|
177
|
+
result = await asyncio.wait_for(agent.handle(notification), timeout=10)
|
|
178
|
+
finally:
|
|
179
|
+
await agent.close_tools()
|
|
180
|
+
|
|
181
|
+
assert result.kind == RespondReason.DONE
|
|
182
|
+
assert llm.call_count == 2
|
|
183
|
+
# NOOA also includes a synthetic prefill call before the model's history.
|
|
184
|
+
calls = [
|
|
185
|
+
message
|
|
186
|
+
for message in llm.last_messages
|
|
187
|
+
if message.get("tool_calls") and message["tool_calls"][0]["id"].startswith("call_")
|
|
188
|
+
]
|
|
189
|
+
assert [message["tool_calls"][0]["id"] for message in calls] == ["call_0", "call_1"]
|
|
190
|
+
assert calls[0].get("reasoning_items") == (reasoning_items if with_reasoning else None)
|
|
191
|
+
assert "reasoning_items" not in calls[1]
|
|
192
|
+
results = [
|
|
193
|
+
message
|
|
194
|
+
for message in llm.last_messages
|
|
195
|
+
if message.get("role") == "tool" and message["tool_call_id"].startswith("call_")
|
|
196
|
+
]
|
|
197
|
+
assert [(message["tool_call_id"], message["content"]) for message in results] == [
|
|
198
|
+
("call_0", "result 0\n"),
|
|
199
|
+
("call_1", "result 1\n"),
|
|
200
|
+
]
|
|
201
|
+
assert all("opaque-fixture" not in str(message.get("content")) for message in llm.last_messages)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Runtime behaviors required from the pinned NOOA release."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from nooa.tools.shell_tools import ShellTools
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.asyncio
|
|
10
|
+
@pytest.mark.parametrize("command,expected_codes", [("printf 'unterminated", (1, 2)), ("cat", (0,))])
|
|
11
|
+
async def test_shell_commands_cannot_consume_the_control_protocol(
|
|
12
|
+
tmp_path: Path, command: str, expected_codes: tuple[int, ...]
|
|
13
|
+
) -> None:
|
|
14
|
+
shell = ShellTools(cwd=str(tmp_path))
|
|
15
|
+
try:
|
|
16
|
+
await shell.run("printf ready", timeout=2)
|
|
17
|
+
result = await shell.run(command, timeout=0.5)
|
|
18
|
+
# Bash 3 (macOS) and Bash 5 (Linux) use different syntax-error codes.
|
|
19
|
+
assert result.returncode in expected_codes
|
|
20
|
+
assert not result.timed_out
|
|
21
|
+
assert result.stdout == ""
|
|
22
|
+
if result.returncode:
|
|
23
|
+
assert "unexpected EOF" in result.stderr
|
|
24
|
+
recovered = await shell.run("printf recovered", timeout=2)
|
|
25
|
+
assert recovered.returncode == 0
|
|
26
|
+
assert recovered.stdout == "recovered"
|
|
27
|
+
finally:
|
|
28
|
+
await shell.close()
|
|
@@ -2291,7 +2291,11 @@ async def test_live_tool_output_stays_compact_until_expanded(tmp_path: Path) ->
|
|
|
2291
2291
|
meta={"activity_id": "tool-1", "stream": "stdout"},
|
|
2292
2292
|
)
|
|
2293
2293
|
)
|
|
2294
|
-
|
|
2294
|
+
# Stream output flushes on a separate timer after host events drain.
|
|
2295
|
+
output = app.query_one("#activity-output")
|
|
2296
|
+
async with asyncio.timeout(5):
|
|
2297
|
+
while "five" not in _log_text(output):
|
|
2298
|
+
await pilot.pause()
|
|
2295
2299
|
|
|
2296
2300
|
live = app.query_one("#live-activity")
|
|
2297
2301
|
assert live.styles.height.value == 6
|
|
@@ -1021,7 +1021,7 @@ wheels = [
|
|
|
1021
1021
|
|
|
1022
1022
|
[[package]]
|
|
1023
1023
|
name = "noah-code"
|
|
1024
|
-
version = "0.5.
|
|
1024
|
+
version = "0.5.1"
|
|
1025
1025
|
source = { editable = "." }
|
|
1026
1026
|
dependencies = [
|
|
1027
1027
|
{ name = "click" },
|
|
@@ -1058,10 +1058,10 @@ requires-dist = [
|
|
|
1058
1058
|
{ name = "click", specifier = ">=8.1.0,<9.0.0" },
|
|
1059
1059
|
{ name = "litellm", specifier = ">=1.96.0,<1.99.0" },
|
|
1060
1060
|
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=2.3.1" },
|
|
1061
|
-
{ name = "nooa", specifier = "==0.0.
|
|
1062
|
-
{ name = "nooa", extras = ["mcp"], marker = "extra == 'mcp'", specifier = "==0.0.
|
|
1063
|
-
{ name = "nooa", extras = ["tracing"], marker = "extra == 'tracing'", specifier = "==0.0.
|
|
1064
|
-
{ name = "nooa-cli", specifier = "==0.0.
|
|
1061
|
+
{ name = "nooa", specifier = "==0.0.10" },
|
|
1062
|
+
{ name = "nooa", extras = ["mcp"], marker = "extra == 'mcp'", specifier = "==0.0.10" },
|
|
1063
|
+
{ name = "nooa", extras = ["tracing"], marker = "extra == 'tracing'", specifier = "==0.0.10" },
|
|
1064
|
+
{ name = "nooa-cli", specifier = "==0.0.10" },
|
|
1065
1065
|
{ name = "opentelemetry-exporter-otlp-proto-http", marker = "extra == 'tracing'", specifier = ">=1.44.0,<2.0.0" },
|
|
1066
1066
|
{ name = "packaging", specifier = ">=24.0,<27.0" },
|
|
1067
1067
|
{ name = "pydantic", specifier = ">=2.5.0,<3.0.0" },
|
|
@@ -1079,7 +1079,7 @@ provides-extras = ["dev", "mcp", "tracing"]
|
|
|
1079
1079
|
|
|
1080
1080
|
[[package]]
|
|
1081
1081
|
name = "nooa"
|
|
1082
|
-
version = "0.0.
|
|
1082
|
+
version = "0.0.10"
|
|
1083
1083
|
source = { registry = "https://pypi.org/simple" }
|
|
1084
1084
|
dependencies = [
|
|
1085
1085
|
{ name = "httpx" },
|
|
@@ -1087,9 +1087,9 @@ dependencies = [
|
|
|
1087
1087
|
{ name = "openinference-instrumentation-litellm" },
|
|
1088
1088
|
{ name = "pydantic" },
|
|
1089
1089
|
]
|
|
1090
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1090
|
+
sdist = { url = "https://files.pythonhosted.org/packages/27/90/f07e44b57d10e3687caba9b18a1b2064e7c9602f85fdeb9c9b0a4843aeba/nooa-0.0.10.tar.gz", hash = "sha256:91d44a48610b116561d2ddc42ed80a7c4011c7324a67236300d2fe78182d3c78", size = 3504802, upload-time = "2026-09-04T06:04:36.719Z" }
|
|
1091
1091
|
wheels = [
|
|
1092
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1092
|
+
{ url = "https://files.pythonhosted.org/packages/02/84/ec56e26a39873862f046146bf677b1d259e7e923180f508747baa1076ed9/nooa-0.0.10-py3-none-any.whl", hash = "sha256:8a74d3e62bb4a216d6fcb1ba912b5d9e49ecc0bf8c2d685f808594a816cbe4ca", size = 1123235, upload-time = "2026-09-04T06:04:38.491Z" },
|
|
1093
1093
|
]
|
|
1094
1094
|
|
|
1095
1095
|
[package.optional-dependencies]
|
|
@@ -1106,16 +1106,16 @@ tracing = [
|
|
|
1106
1106
|
|
|
1107
1107
|
[[package]]
|
|
1108
1108
|
name = "nooa-cli"
|
|
1109
|
-
version = "0.0.
|
|
1109
|
+
version = "0.0.10"
|
|
1110
1110
|
source = { registry = "https://pypi.org/simple" }
|
|
1111
1111
|
dependencies = [
|
|
1112
1112
|
{ name = "click" },
|
|
1113
1113
|
{ name = "nooa" },
|
|
1114
1114
|
{ name = "pyyaml" },
|
|
1115
1115
|
]
|
|
1116
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1116
|
+
sdist = { url = "https://files.pythonhosted.org/packages/36/c6/d1e555b461b508cf43b9c8ba55f3359dc94fbcbda231013c101ef5543297/nooa_cli-0.0.10.tar.gz", hash = "sha256:845d029afaed26166ca16ed15bfa69308703aac7aefc6afece5cddfa47ecbf6e", size = 89911, upload-time = "2026-09-04T06:04:33.448Z" }
|
|
1117
1117
|
wheels = [
|
|
1118
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1118
|
+
{ url = "https://files.pythonhosted.org/packages/4a/cd/c9a53e62e08394b30f0f2de466f6d61b2ab66b32de0be1e23be2ff9ffbfa/nooa_cli-0.0.10-py3-none-any.whl", hash = "sha256:0a9414898f12c51797b0ba2cf8ece97899dcf30e684690d72b54cceac6afdb4e", size = 86303, upload-time = "2026-09-04T06:04:34.245Z" },
|
|
1119
1119
|
]
|
|
1120
1120
|
|
|
1121
1121
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|