noah-code 0.4.1__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.4.1 → noah_code-0.5.1}/.github/workflows/ci.yml +0 -2
- {noah_code-0.4.1 → noah_code-0.5.1}/PKG-INFO +13 -5
- {noah_code-0.4.1 → noah_code-0.5.1}/README.md +8 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/interactive-reference.md +24 -3
- noah_code-0.5.1/docs/releases/v0.5.0.md +64 -0
- noah_code-0.5.1/docs/releases/v0.5.1.md +36 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/pyproject.toml +5 -5
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/__init__.py +1 -1
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/agent.py +70 -29
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/budget.py +40 -13
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/cli.py +15 -4
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/commands.py +2 -4
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/composer.py +27 -17
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/host.py +37 -8
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/llm_replies.py +3 -1
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/nooa_compat.py +1 -1
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/runtime_state.py +12 -8
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/sessions.py +4 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/steer.py +4 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tool_output.py +4 -4
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/process_tools.py +78 -39
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/task_tools.py +57 -17
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/workspace_tools.py +79 -51
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/ui/console.py +15 -12
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/ui/textual.css +16 -6
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/ui/textual_app.py +242 -52
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_agent_security.py +63 -2
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_budget.py +66 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_cli.py +123 -2
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_commands.py +11 -1
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_composer.py +70 -1
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_console_ui.py +15 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_host.py +122 -0
- noah_code-0.5.1/tests/test_lean_reasoning.py +201 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_llm_replies.py +23 -3
- noah_code-0.5.1/tests/test_nooa_migration.py +28 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_process_tools.py +172 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_runtime_state.py +26 -1
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_sessions.py +23 -1
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_task_tools.py +93 -1
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_textual_tui.py +379 -25
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_tool_output.py +21 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_workspace_tools.py +77 -7
- {noah_code-0.4.1 → noah_code-0.5.1}/uv.lock +30 -20
- {noah_code-0.4.1 → noah_code-0.5.1}/.github/workflows/release.yml +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/.gitignore +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/.pre-commit-config.yaml +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/assets/noah-in-action.svg +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/assets/noah-logo.svg +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/configuration.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/development.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/extensions.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.1.0.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.1.1.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.2.0.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.2.1.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.2.2.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.2.3.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.2.4.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.3.0.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.4.0.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/releases/v0.4.1.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/reliability.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/docs/security.md +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/install.sh +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/__main__.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/agents.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/approvals.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/checkpoints.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/config.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/credentials.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/custom_commands.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/event_bridge.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/events.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/github.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/hooks.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/llm.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/macos_sandbox.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/mcp_setup.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/permissions.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/predict.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/project_notes.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/providers.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/redaction.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/secure_files.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/skills_setup.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/snapshots.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/summarization.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/telemetry.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/themes.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/__init__.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/diff_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/git_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/github_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/lsp_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/media_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/memory_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/plan_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/question_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/tools/web_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/ui/__init__.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/ui/protocol.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/updates.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/usage.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/workspace.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/src/noah_code/worktree.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/conftest.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_agents.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_approvals.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_cache_context.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_checkpoints.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_compaction_eviction.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_config.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_credentials.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_custom_commands.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_diff_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_efficiency.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_event_bridge.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_event_bridge_and_shell.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_git_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_github.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_hooks.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_installer.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_llm.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_lsp_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_mcp_setup.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_permissions.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_plan_memory_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_project_notes.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_prompt_scorecard.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_providers.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_question_tools.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_redaction.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_run_exit.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_skills_setup.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_snapshots.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_steer.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_summarization.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_telemetry.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_updates.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_wave1_e2e.py +0 -0
- {noah_code-0.4.1 → noah_code-0.5.1}/tests/test_web_tools.py +0 -0
- {noah_code-0.4.1 → 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.
|
|
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
|
|
|
@@ -170,11 +170,17 @@ Type `/` to search the full command and configuration reference. The most common
|
|
|
170
170
|
| Drag, then `Cmd+C` / `Ctrl+Shift+C` | Select and copy TUI text |
|
|
171
171
|
| `Ctrl+Shift+C` with no selection | Copy the latest Noah reply |
|
|
172
172
|
| `Ctrl+G` | Open the searchable skills picker |
|
|
173
|
+
| `Ctrl+L` | Open the model picker |
|
|
174
|
+
| `Ctrl+R` | Search and recall a prior prompt |
|
|
175
|
+
| `Ctrl+T` | Expand or collapse live tool output |
|
|
176
|
+
| `Shift+Tab` | Open the reasoning-effort picker |
|
|
177
|
+
| `Alt+Up` | Recall the newest queued prompt for editing |
|
|
173
178
|
| `Ctrl+]` | Return to live transcript output |
|
|
174
179
|
| `Tab` | Switch between build and plan mode |
|
|
175
180
|
| `F2` | Open execution activity |
|
|
176
181
|
| `F3` | Open paginated conversation history |
|
|
177
182
|
| `F4` | Open the live agent, terminal, and job ledger |
|
|
183
|
+
| `Shift+F7` | Focus or leave the scrollable context rail |
|
|
178
184
|
| `/model` | Configure a provider or switch the session model |
|
|
179
185
|
| `/theme` | Choose Atom One Dark, Noah Ocean, Graphite, or High Contrast |
|
|
180
186
|
| `/diff` | Review staged and unstaged changes |
|
|
@@ -188,6 +194,8 @@ On wide terminals, the side rail prioritizes the active operation, delegated age
|
|
|
188
194
|
terminals, Git branch and change counts, session, model usage, update state, and plan. Git status is refreshed in the background at turn
|
|
189
195
|
boundaries, so the animated working state stays responsive. The main pane remains centered on the
|
|
190
196
|
large Noah wordmark until the first prompt, then becomes the conversation and execution timeline.
|
|
197
|
+
The composer footer keeps contextual keys and compact token, cache, and cost telemetry visible at
|
|
198
|
+
every terminal width where they fit.
|
|
191
199
|
|
|
192
200
|
## Sessions and crash recovery
|
|
193
201
|
|
|
@@ -125,11 +125,17 @@ Type `/` to search the full command and configuration reference. The most common
|
|
|
125
125
|
| Drag, then `Cmd+C` / `Ctrl+Shift+C` | Select and copy TUI text |
|
|
126
126
|
| `Ctrl+Shift+C` with no selection | Copy the latest Noah reply |
|
|
127
127
|
| `Ctrl+G` | Open the searchable skills picker |
|
|
128
|
+
| `Ctrl+L` | Open the model picker |
|
|
129
|
+
| `Ctrl+R` | Search and recall a prior prompt |
|
|
130
|
+
| `Ctrl+T` | Expand or collapse live tool output |
|
|
131
|
+
| `Shift+Tab` | Open the reasoning-effort picker |
|
|
132
|
+
| `Alt+Up` | Recall the newest queued prompt for editing |
|
|
128
133
|
| `Ctrl+]` | Return to live transcript output |
|
|
129
134
|
| `Tab` | Switch between build and plan mode |
|
|
130
135
|
| `F2` | Open execution activity |
|
|
131
136
|
| `F3` | Open paginated conversation history |
|
|
132
137
|
| `F4` | Open the live agent, terminal, and job ledger |
|
|
138
|
+
| `Shift+F7` | Focus or leave the scrollable context rail |
|
|
133
139
|
| `/model` | Configure a provider or switch the session model |
|
|
134
140
|
| `/theme` | Choose Atom One Dark, Noah Ocean, Graphite, or High Contrast |
|
|
135
141
|
| `/diff` | Review staged and unstaged changes |
|
|
@@ -143,6 +149,8 @@ On wide terminals, the side rail prioritizes the active operation, delegated age
|
|
|
143
149
|
terminals, Git branch and change counts, session, model usage, update state, and plan. Git status is refreshed in the background at turn
|
|
144
150
|
boundaries, so the animated working state stays responsive. The main pane remains centered on the
|
|
145
151
|
large Noah wordmark until the first prompt, then becomes the conversation and execution timeline.
|
|
152
|
+
The composer footer keeps contextual keys and compact token, cache, and cost telemetry visible at
|
|
153
|
+
every terminal width where they fit.
|
|
146
154
|
|
|
147
155
|
## Sessions and crash recovery
|
|
148
156
|
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
| `Tab` | Toggle `build`/`plan` mode; accept the highlighted slash option while suggestions are open |
|
|
10
10
|
| `Ctrl+P` | Open the command palette |
|
|
11
11
|
| `Ctrl+G` | Open the searchable skills picker |
|
|
12
|
+
| `Ctrl+L` | Open the model picker |
|
|
12
13
|
| `Ctrl+O` | Open the session picker |
|
|
14
|
+
| `Ctrl+R` | Search prior prompts and recall one into the composer |
|
|
15
|
+
| `Ctrl+T` | Expand or collapse live tool output |
|
|
16
|
+
| `Alt+Up` | Recall the newest queued prompt into the composer |
|
|
17
|
+
| `Shift+Tab` | Open the reasoning-effort picker |
|
|
13
18
|
| `Ctrl+N` | Start a new session |
|
|
14
19
|
| `Ctrl+C` | Cancel the active turn and clear queued follow-ups; press twice while idle to quit |
|
|
15
20
|
| `Ctrl+Q` | Quit |
|
|
@@ -17,6 +22,7 @@
|
|
|
17
22
|
| `F2` | Open recent activity and full captured output |
|
|
18
23
|
| `F3` | Open paginated persisted conversation history |
|
|
19
24
|
| `F4` | Open the live work ledger for agents, terminals, and background jobs |
|
|
25
|
+
| `Shift+F7` | Focus or leave the context rail on wide terminals |
|
|
20
26
|
| `Ctrl+]` | Return to live transcript output and clear the new-output counter |
|
|
21
27
|
|
|
22
28
|
At an approval prompt, press `1` to approve once, `2` to remember the approval for the current
|
|
@@ -29,16 +35,29 @@ branch and staged/modified/new counts, session, model usage, and up to six open
|
|
|
29
35
|
Narrow terminals retain the transcript, live activity, suggestions, and composer without the rail.
|
|
30
36
|
Terminals 25 rows high or shorter use compact spacing.
|
|
31
37
|
|
|
38
|
+
The line below the composer keeps the active keyboard guidance on the left and, when space permits,
|
|
39
|
+
session input/output tokens, cache hit rate, and estimated cost on the right. This telemetry remains
|
|
40
|
+
available even when the wide context rail is hidden.
|
|
41
|
+
|
|
42
|
+
Live tool output stays in a compact two-line drawer so long commands do not push the conversation
|
|
43
|
+
away. Press `Ctrl+T` to expand the drawer in place; `F2` retains the complete output after the tool
|
|
44
|
+
finishes. The drawer replaces the animated working banner while a visible tool is active, avoiding
|
|
45
|
+
duplicate status lines; the banner returns between tools.
|
|
46
|
+
|
|
32
47
|
Type `/` in the composer to open the inline command list; the list remains visible and filters
|
|
33
48
|
continuously. Use `Up`/`Down` to highlight a command, `Enter` or `Tab` to complete it, and `Esc` to
|
|
34
49
|
close the list. Press `Enter` again to run the completed command. Typing `/config` expands the list
|
|
35
50
|
to every resolved configuration path and its current redacted value.
|
|
36
51
|
|
|
52
|
+
Press `Ctrl+R` to search prompts already shown in the current session. Choosing one loads it into
|
|
53
|
+
the composer without sending it; pressing `Esc` keeps the current draft unchanged.
|
|
54
|
+
|
|
37
55
|
Until a session has its first user prompt, the main pane keeps the large Noah wordmark centered. Startup,
|
|
38
56
|
repository changes, model, mode, usage, and update state live in the context rail on wide
|
|
39
|
-
terminals.
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
terminals. The rail scrolls when its sections exceed the available rows; press `Shift+F7`, then use
|
|
58
|
+
arrows, Page Up/Down, Home, or End without leaving the keyboard. Git status is collected in a
|
|
59
|
+
background worker at startup and turn boundaries; the animated Noah path updates only the working
|
|
60
|
+
banner and live activity. Existing sessions with user history restore their transcript normally.
|
|
42
61
|
|
|
43
62
|
Drag across transcript, activity, diff, or history text to select it. `Cmd+C` on macOS or
|
|
44
63
|
`Ctrl+Shift+C` in other terminals copies the selection; when there is no selection, the same
|
|
@@ -59,6 +78,8 @@ While Noah is working, the composer stays open. `Enter` queues the current text
|
|
|
59
78
|
a second turn. Chrome shows `queued · n`. When the in-flight `handle()` returns (`DONE`,
|
|
60
79
|
`NEED_INPUT`, or `WAIT`), the host injects the next item in the same journaled turn — one persist
|
|
61
80
|
and one checkpoint for the whole steered run. `/undo` therefore reverts every follow-up together.
|
|
81
|
+
Press `Alt+Up` with an empty composer to pull the newest queued prompt and its attachments back for
|
|
82
|
+
editing.
|
|
62
83
|
|
|
63
84
|
The queue holds at most 100 items. A 101st `Enter` drops the oldest and status-prints
|
|
64
85
|
`steer dropped oldest`. `@path` mentions and `/attach` paths expand when the item is injected, not
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Noah Code v0.5.0
|
|
2
|
+
|
|
3
|
+
This release improves task completion, terminal interaction, and bounded resource use. It includes
|
|
4
|
+
the unreleased prompt recall, model shortcuts, scrollable context rail, and composer usage telemetry.
|
|
5
|
+
|
|
6
|
+
## More reliable task completion
|
|
7
|
+
|
|
8
|
+
- Token-limited model replies no longer report a partial answer as successfully completed. The
|
|
9
|
+
runtime retries within its existing limits and reports failure if the model cannot continue.
|
|
10
|
+
- Subagents wait for their background jobs and resume the task instead of closing their tools
|
|
11
|
+
early. Requests for user input remain visible even after result summarization.
|
|
12
|
+
- Session deadlines now bound asynchronous model requests, queued provider calls, and background
|
|
13
|
+
waits. Provider requests receive the remaining timeout as well.
|
|
14
|
+
- Resumed sessions honor newer mode, model, and reasoning choices over older checkpoints and agent
|
|
15
|
+
snapshots. Resuming another worktree loads its configuration while preserving explicit CLI
|
|
16
|
+
overrides. Failed one-shot startup releases its host resources; repeated cleanup preserves the
|
|
17
|
+
saved undo journal.
|
|
18
|
+
|
|
19
|
+
## Smoother terminal UX
|
|
20
|
+
|
|
21
|
+
- Search previous prompts, recall queued prompts for editing, and use direct model and reasoning
|
|
22
|
+
shortcuts. The context rail scrolls and the composer shows compact usage telemetry.
|
|
23
|
+
- Transcript refreshes preserve your reading position. Switching sessions clears stale activity,
|
|
24
|
+
buffered output, and unread indicators.
|
|
25
|
+
- Cancelling keeps the interface busy until cleanup finishes, preventing overlapping turns and
|
|
26
|
+
session changes during teardown.
|
|
27
|
+
- File mentions support quoted paths, prioritize matching directories, and preserve nested image
|
|
28
|
+
paths when pasted. Text and image attachments share the same file-count limit.
|
|
29
|
+
- Console output renders literal tool/error text safely and preserves shell output chunk boundaries.
|
|
30
|
+
- Console resume preserves the saved model; `-h` and slash commands pasted with whitespace work.
|
|
31
|
+
|
|
32
|
+
## Safer edits and execution
|
|
33
|
+
|
|
34
|
+
- Stale edit anchors cannot overwrite newly changed content, even after a file has been reread.
|
|
35
|
+
- Read-only shell calls enforce workspace permissions and retain their stdin and timeout options.
|
|
36
|
+
Generated code cannot invoke the internal trusted-command bypass.
|
|
37
|
+
- Attachment paths resolve symlinks before containment checks, preventing outside-workspace reads.
|
|
38
|
+
- Background launches serialize capacity and name checks. Split terminal completion markers and
|
|
39
|
+
oversized log events no longer break completion or stall pagination.
|
|
40
|
+
- Invalid process deadlines fail before execution. Log storage failures preserve bounded in-memory
|
|
41
|
+
output, keep pipes draining, and do not prevent process cleanup.
|
|
42
|
+
|
|
43
|
+
## Less context and file I/O
|
|
44
|
+
|
|
45
|
+
- Subagent results obey their character budget, preserve the end of the findings, and bound the
|
|
46
|
+
input sent for distillation.
|
|
47
|
+
- Text attachment reads stop at their inline limit. Managed output ranges stream only as far as
|
|
48
|
+
the requested lines, avoiding whole-file allocation.
|
|
49
|
+
- Repeated glob patterns reuse compiled translations, with bracket pattern support.
|
|
50
|
+
- Runtime database connections close after each transaction, preventing connection accumulation
|
|
51
|
+
during long-running sessions.
|
|
52
|
+
- The existing prompt-size, cache-prefix stability, and helper-context scorecards remain release
|
|
53
|
+
checks. No new runtime dependencies were added by these fixes.
|
|
54
|
+
|
|
55
|
+
## Upgrade
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
noah update
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Validation uses deterministic provider responses and headless terminal tests; live provider billing
|
|
62
|
+
and end-to-end model quality are not benchmarked by this release.
|
|
63
|
+
|
|
64
|
+
**Full changelog:** https://github.com/skundu42/noah-code/compare/v0.4.1...v0.5.0
|
|
@@ -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.
|
|
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
|
|
|
@@ -8,6 +8,7 @@ import platform
|
|
|
8
8
|
import site
|
|
9
9
|
import sys
|
|
10
10
|
import sysconfig
|
|
11
|
+
import threading
|
|
11
12
|
import types
|
|
12
13
|
from collections.abc import Iterator
|
|
13
14
|
from contextlib import contextmanager, suppress
|
|
@@ -16,10 +17,14 @@ from typing import Annotated, Any, Literal
|
|
|
16
17
|
|
|
17
18
|
from nooa import Agent, Context, hidden, strategy
|
|
18
19
|
from nooa.config import CodeActConfig, PredictConfig
|
|
20
|
+
from nooa.context_blocks import RenderedMessage, ResolvedBlock, Role, ToolCallEvent
|
|
19
21
|
from nooa.interactive import InteractiveAgent, RespondReason, RespondResult
|
|
22
|
+
from nooa.runtime.channels import Channel
|
|
20
23
|
from nooa.runtime.restrictions import RESTRICTED_MODULES, RestrictionsConfig
|
|
21
24
|
from nooa.runtime.sandbox.config import FileRule, SandboxConfig, resolve_spec
|
|
22
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
|
|
23
28
|
from nooa.strategies import CodeActStrategy
|
|
24
29
|
from nooa.strategies.codeact_lite import PlainCodeActBlockFormatter
|
|
25
30
|
from nooa.tools import TodoManager
|
|
@@ -126,7 +131,6 @@ class _PermissionSandboxedExecutor(SandboxedExecutor):
|
|
|
126
131
|
("ws", "read_output"),
|
|
127
132
|
("ws", "replace"),
|
|
128
133
|
("ws", "run"),
|
|
129
|
-
("ws", "run_trusted_readonly"),
|
|
130
134
|
("ws", "search"),
|
|
131
135
|
("ws", "write"),
|
|
132
136
|
("ws", "write_file"),
|
|
@@ -196,6 +200,8 @@ def _spawn_safe_local_agent(agent: Any) -> Any:
|
|
|
196
200
|
|
|
197
201
|
|
|
198
202
|
_OMIT = object()
|
|
203
|
+
# ``spawnv_passfds`` is process-global; patch and restore it as one critical section.
|
|
204
|
+
_SPAWN_PASSFDS_LOCK = threading.RLock()
|
|
199
205
|
|
|
200
206
|
|
|
201
207
|
def _pipe_picklable(value: Any) -> bool:
|
|
@@ -246,28 +252,29 @@ def _unique_spawn_passfds() -> Iterator[None]:
|
|
|
246
252
|
import multiprocessing.util as util
|
|
247
253
|
import os
|
|
248
254
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
255
|
+
with _SPAWN_PASSFDS_LOCK:
|
|
256
|
+
original = util.spawnv_passfds
|
|
257
|
+
|
|
258
|
+
def _inherit_fds(passfds: Any) -> tuple[int, ...]:
|
|
259
|
+
kept: list[int] = []
|
|
260
|
+
for fd in sorted({int(fd) for fd in passfds}):
|
|
261
|
+
if fd < 0:
|
|
262
|
+
continue
|
|
263
|
+
try:
|
|
264
|
+
os.fstat(fd)
|
|
265
|
+
except OSError:
|
|
266
|
+
continue
|
|
267
|
+
kept.append(fd)
|
|
268
|
+
return tuple(kept)
|
|
269
|
+
|
|
270
|
+
def _spawnv(path: Any, args: Any, passfds: Any) -> Any:
|
|
271
|
+
return original(path, args, _inherit_fds(passfds))
|
|
272
|
+
|
|
273
|
+
util.spawnv_passfds = _spawnv
|
|
274
|
+
try:
|
|
275
|
+
yield
|
|
276
|
+
finally:
|
|
277
|
+
util.spawnv_passfds = original
|
|
271
278
|
|
|
272
279
|
|
|
273
280
|
class _MacOSPermissionSandboxedExecutor(_PermissionSandboxedExecutor):
|
|
@@ -281,6 +288,8 @@ class _MacOSPermissionSandboxedExecutor(_PermissionSandboxedExecutor):
|
|
|
281
288
|
cell_timeout: float | None,
|
|
282
289
|
framework_builtins: dict[str, Any] | None = None,
|
|
283
290
|
restrictions: Any = None,
|
|
291
|
+
max_error: int | None = None,
|
|
292
|
+
error_tail: int | None = None,
|
|
284
293
|
) -> None:
|
|
285
294
|
# NOOA probes specifically for Linux Landlock/seccomp. Preserve
|
|
286
295
|
# its worker and broker implementation, but install equivalent native
|
|
@@ -298,6 +307,8 @@ class _MacOSPermissionSandboxedExecutor(_PermissionSandboxedExecutor):
|
|
|
298
307
|
self._cell_timeout = cell_timeout
|
|
299
308
|
self._framework_builtins = framework_builtins or {}
|
|
300
309
|
self._restrictions = restrictions
|
|
310
|
+
self._max_error = effective_error_limit(max_error)
|
|
311
|
+
self._error_tail = error_tail
|
|
301
312
|
self._spec = resolve_spec(worker_config)
|
|
302
313
|
self._degraded: list[str] = []
|
|
303
314
|
# Forking a multithreaded Textual process can deadlock in inherited
|
|
@@ -344,6 +355,8 @@ class _MacOSPermissionSandboxedExecutor(_PermissionSandboxedExecutor):
|
|
|
344
355
|
),
|
|
345
356
|
"restrictions": self._restrictions,
|
|
346
357
|
"spec": self._spec,
|
|
358
|
+
"max_error": self._max_error,
|
|
359
|
+
"error_tail": self._error_tail,
|
|
347
360
|
}
|
|
348
361
|
)
|
|
349
362
|
except Exception:
|
|
@@ -386,8 +399,9 @@ _NOAH_CODEACT_INSTRUCTIONS = """## Noah CodeAct
|
|
|
386
399
|
|
|
387
400
|
Work through tool calls; plain assistant prose cannot run code or end a turn.
|
|
388
401
|
Use `execute_python(code)` for work and `return_result(...)` to finish. Python
|
|
389
|
-
cell state persists. Parameters and `self` are preloaded
|
|
390
|
-
|
|
402
|
+
cell state persists. Parameters and `self` are preloaded. Every `self.ws.*`
|
|
403
|
+
call is async: always `await` it before iterating or accessing the result.
|
|
404
|
+
`self.message(text)` is synchronous—never await it.
|
|
391
405
|
|
|
392
406
|
### Tools
|
|
393
407
|
|
|
@@ -396,9 +410,10 @@ tools with `await`. `self.message(text)` is synchronous—never await it.
|
|
|
396
410
|
three arguments; two-argument calls are invalid. Prefer one atomic
|
|
397
411
|
`self.ws.apply_patch(changes)` for a
|
|
398
412
|
coherent batch; `self.ws.apply_unified_diff` accepts git-style hunks.
|
|
399
|
-
- Common shapes: `ws.search(pattern, path=".", paths=None, regex=True)`
|
|
400
|
-
iterable Matches plus `.stdout`; `ws.read(path, lines=None)`
|
|
401
|
-
with `.text`/`.content`; `
|
|
413
|
+
- Common shapes: `await ws.search(pattern, path=".", paths=None, regex=True)`
|
|
414
|
+
returns iterable Matches plus `.stdout`; `await ws.read(path, lines=None)`
|
|
415
|
+
returns text with `.text`/`.content`; `lines=60` reads the first 60 lines;
|
|
416
|
+
`await ws.list(pattern="**/*", path=".")` returns paths.
|
|
402
417
|
- Run commands with `await self.ws.run(command)` and inspect
|
|
403
418
|
returncode/stdout/stderr. `read_only=True` skips approval only for commands
|
|
404
419
|
the engine recognizes as read-only (Git inspection, search, listing, text
|
|
@@ -575,9 +590,31 @@ class _PermissionCodeActStrategy(CodeActStrategy):
|
|
|
575
590
|
cell_timeout=self.config.cell_timeout,
|
|
576
591
|
framework_builtins=framework_builtins,
|
|
577
592
|
restrictions=self.config.restrictions,
|
|
593
|
+
max_error=runtime.truncation_config.capture.max_error,
|
|
594
|
+
error_tail=runtime.truncation_config.capture.tail,
|
|
578
595
|
)
|
|
579
596
|
|
|
580
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
|
+
|
|
581
618
|
class _LeanPermissionCodeActStrategy(_PermissionCodeActStrategy):
|
|
582
619
|
"""Compact NOOA event rendering while retaining cross-turn history."""
|
|
583
620
|
|
|
@@ -589,7 +626,9 @@ class _LeanPermissionCodeActStrategy(_PermissionCodeActStrategy):
|
|
|
589
626
|
original = runtime.agent.render_config
|
|
590
627
|
event_format = nooa_compat.truncation_event_format(runtime.agent)
|
|
591
628
|
runtime.agent.render_config = original.model_copy(
|
|
592
|
-
update={
|
|
629
|
+
update={
|
|
630
|
+
"block_formatter": _ReasoningPlainCodeActBlockFormatter(event_format=event_format)
|
|
631
|
+
}
|
|
593
632
|
)
|
|
594
633
|
try:
|
|
595
634
|
return await super().execute(runtime, call)
|
|
@@ -625,6 +664,7 @@ class CodingAgent(InteractiveAgent):
|
|
|
625
664
|
|
|
626
665
|
workspace_root: str = "."
|
|
627
666
|
mode: Literal["build", "plan"] = "build"
|
|
667
|
+
_system_messages_in: Annotated[Channel, hidden, nosnapshot]
|
|
628
668
|
|
|
629
669
|
def __init__(
|
|
630
670
|
self,
|
|
@@ -651,6 +691,7 @@ class CodingAgent(InteractiveAgent):
|
|
|
651
691
|
|
|
652
692
|
llm = wrap_conversational_replies(llm)
|
|
653
693
|
super().__init__(llm=llm, storage=storage, **kwargs)
|
|
694
|
+
self._system_messages_in = self.queue_manager.queue("system_messages")
|
|
654
695
|
if cache_namespace:
|
|
655
696
|
# NOOA derives prompt_cache_key from this id. A durable Noah
|
|
656
697
|
# session therefore keeps provider cache affinity across restart.
|
|
@@ -83,6 +83,11 @@ class BudgetGuard:
|
|
|
83
83
|
wall_elapsed = max(time.time() - self._started_wall, 0.0)
|
|
84
84
|
return max(monotonic_elapsed, wall_elapsed)
|
|
85
85
|
|
|
86
|
+
def remaining_seconds(self) -> float | None:
|
|
87
|
+
if self._config.max_seconds is None:
|
|
88
|
+
return None
|
|
89
|
+
return max(self._config.max_seconds - self.elapsed_seconds(), 0.0)
|
|
90
|
+
|
|
86
91
|
def add_usage(
|
|
87
92
|
self,
|
|
88
93
|
*,
|
|
@@ -257,28 +262,50 @@ class BudgetedLLM:
|
|
|
257
262
|
if self._prefix_observer is not None:
|
|
258
263
|
self._prefix_observer.observe_prefix(messages, route=route)
|
|
259
264
|
|
|
265
|
+
def _limit_timeout(self, kwargs: dict[str, Any]) -> None:
|
|
266
|
+
remaining = self._guard.remaining_seconds()
|
|
267
|
+
if remaining is not None:
|
|
268
|
+
configured_timeout = kwargs.get("timeout")
|
|
269
|
+
kwargs["timeout"] = (
|
|
270
|
+
min(configured_timeout, remaining)
|
|
271
|
+
if isinstance(configured_timeout, int | float)
|
|
272
|
+
else remaining
|
|
273
|
+
)
|
|
274
|
+
|
|
260
275
|
async def acall(self, messages: list[dict], tools=None, output_model=None, **kwargs) -> Any:
|
|
261
276
|
# Active caps serialize reservations across parent and subagent routes;
|
|
262
277
|
# without this lane, concurrent calls can all pass the same preflight.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
278
|
+
self._guard.enforce()
|
|
279
|
+
try:
|
|
280
|
+
async with asyncio.timeout(self._guard.remaining_seconds()):
|
|
281
|
+
async with self._guard._async_provider_lock:
|
|
282
|
+
self._guard.enforce()
|
|
283
|
+
self._observe_prefix(messages, route=kwargs.get("prompt_cache_key"))
|
|
284
|
+
# NOOA shields LiteLLM's provider task from cancellation.
|
|
285
|
+
# Bound its own request timeout as well as our outer wait.
|
|
286
|
+
self._limit_timeout(kwargs)
|
|
287
|
+
response = await self._inner.acall(
|
|
288
|
+
messages, tools=tools, output_model=output_model, **kwargs
|
|
289
|
+
)
|
|
290
|
+
prompt, completion, cost = _usage_from_response(response)
|
|
291
|
+
self._guard.add_usage(
|
|
292
|
+
prompt_tokens=prompt,
|
|
293
|
+
completion_tokens=completion,
|
|
294
|
+
cost_usd=cost,
|
|
295
|
+
)
|
|
296
|
+
self._guard.enforce()
|
|
297
|
+
return response
|
|
298
|
+
except TimeoutError:
|
|
299
|
+
# Preserve a provider's own timeout unless the session deadline
|
|
300
|
+
# expired. A session timeout must surface as a sticky budget stop.
|
|
275
301
|
self._guard.enforce()
|
|
276
|
-
|
|
302
|
+
raise
|
|
277
303
|
|
|
278
304
|
def call(self, messages: list[dict], tools=None, output_model=None, **kwargs) -> Any:
|
|
279
305
|
with self._guard._provider_lock:
|
|
280
306
|
self._guard.enforce()
|
|
281
307
|
self._observe_prefix(messages, route=kwargs.get("prompt_cache_key"))
|
|
308
|
+
self._limit_timeout(kwargs)
|
|
282
309
|
response = self._inner.call(
|
|
283
310
|
messages, tools=tools, output_model=output_model, **kwargs
|
|
284
311
|
)
|
|
@@ -146,7 +146,7 @@ def _configure_first_run_model(model_override: str | None) -> str | None:
|
|
|
146
146
|
return selected
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
@click.command("noah-code")
|
|
149
|
+
@click.command("noah-code", context_settings={"help_option_names": ["-h", "--help"]})
|
|
150
150
|
@click.version_option(__version__, prog_name="noah-code")
|
|
151
151
|
@click.argument("path", required=False, type=click.Path())
|
|
152
152
|
@click.option("--continue", "continue_session", is_flag=True, help="Resume latest session")
|
|
@@ -856,6 +856,7 @@ async def _prepare(
|
|
|
856
856
|
store = SessionStore(config.session_dir)
|
|
857
857
|
|
|
858
858
|
meta = None
|
|
859
|
+
launch_root = workspace.root
|
|
859
860
|
try:
|
|
860
861
|
if session_id:
|
|
861
862
|
meta = store.load_meta(session_id)
|
|
@@ -866,11 +867,18 @@ async def _prepare(
|
|
|
866
867
|
click.echo("error: no prior session for this workspace", err=True)
|
|
867
868
|
return None, EXIT_CONFIG
|
|
868
869
|
workspace = store.workspace_for_resume(meta, workspace)
|
|
869
|
-
if
|
|
870
|
+
if workspace.root != launch_root:
|
|
871
|
+
config = load_config(
|
|
872
|
+
workspace.root,
|
|
873
|
+
cli_overrides={**overrides, "session_dir": store.session_dir},
|
|
874
|
+
)
|
|
875
|
+
if meta is not None and any(value is not None for value in (model, reasoning_effort, mode)):
|
|
870
876
|
if model is not None:
|
|
871
877
|
meta.model = config.model
|
|
872
878
|
if reasoning_effort is not None:
|
|
873
879
|
meta.reasoning_effort = config.reasoning_effort
|
|
880
|
+
if mode is not None:
|
|
881
|
+
meta.mode = config.mode
|
|
874
882
|
store.save_meta(meta)
|
|
875
883
|
except SessionError as exc:
|
|
876
884
|
click.echo(f"error: {exc}", err=True)
|
|
@@ -894,7 +902,7 @@ async def _interactive(
|
|
|
894
902
|
unsafe_inprocess_code_execution: bool,
|
|
895
903
|
) -> int:
|
|
896
904
|
first_run = user_default_model() is None
|
|
897
|
-
if first_run and (model is not None or use_console):
|
|
905
|
+
if first_run and (model is not None or (use_console and not (continue_session or session_id))):
|
|
898
906
|
try:
|
|
899
907
|
model = _configure_first_run_model(model)
|
|
900
908
|
except (OSError, ValueError) as exc:
|
|
@@ -922,7 +930,7 @@ async def _interactive(
|
|
|
922
930
|
return code
|
|
923
931
|
workspace, config, store, meta = prepared
|
|
924
932
|
use_tui = config.ui.frontend == "tui" and not use_console
|
|
925
|
-
if first_run and model is None and not use_tui:
|
|
933
|
+
if first_run and model is None and not use_tui and meta is None:
|
|
926
934
|
try:
|
|
927
935
|
model = _configure_first_run_model(model)
|
|
928
936
|
except (OSError, ValueError) as exc:
|
|
@@ -1038,6 +1046,9 @@ async def _run_session(
|
|
|
1038
1046
|
|
|
1039
1047
|
click.echo(f"error: {safe_error_message(exc)}", err=True)
|
|
1040
1048
|
return EXIT_AGENT
|
|
1049
|
+
finally:
|
|
1050
|
+
# Startup can fail before run_once reaches its own cleanup block.
|
|
1051
|
+
await host.close()
|
|
1041
1052
|
|
|
1042
1053
|
|
|
1043
1054
|
def main(argv: list[str] | None = None) -> None:
|
|
@@ -324,7 +324,5 @@ def parse_slash(text: str) -> tuple[str, str] | None:
|
|
|
324
324
|
body = stripped[1:]
|
|
325
325
|
if not body:
|
|
326
326
|
return None
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return name.lower(), rest.strip()
|
|
330
|
-
return body.lower(), ""
|
|
327
|
+
parts = body.split(maxsplit=1)
|
|
328
|
+
return parts[0].lower(), parts[1].strip() if len(parts) > 1 else ""
|