Skip to content

fix(ui): isolate and restore agent sessions - #254

Closed
w0wl0lxd wants to merge 8 commits into
mainfrom
fix/agent-menu-visibility
Closed

fix(ui): isolate and restore agent sessions#254
w0wl0lxd wants to merge 8 commits into
mainfrom
fix/agent-menu-visibility

Conversation

@w0wl0lxd

@w0wl0lxd w0wl0lxd commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • propagate trusted invoking session identity and persist durable root/parent ownership plus explicit lifecycle
  • project Ctrl+X and control-plane entries from canonical runtime descriptors scoped to the active root
  • restore interrupted descendants and persisted reviewer transcripts without replaying incomplete tools
  • resume paused team checkpoints through direct dispatch and prevent nested task delegation leaks
  • harden malformed/legacy parent handling and multi-root history isolation

Tests

  • cargo test -p n00n-ui --lib
  • cargo test -p n00n-storage --lib
  • cargo test -p n00n-agent --lib
  • cargo test -p n00n-lua --lib
  • RUST_TEST_THREADS=1 cargo test -p n00n-lua --test plugin_host
  • cargo test -p n00n-lua --test real_plugins_restore
  • cargo check --all
  • cargo clippy --all --tests -- -D warnings
  • cargo fmt --all -- --check
  • cargo nextest run --workspace (4628 passed, 1 skipped; one flaky UDS startup test failed, then passed alone)

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@w0wl0lxd, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d202ea6b-aec5-4ff8-837f-e4c7bb6922df

📥 Commits

Reviewing files that changed from the base of the PR and between 7bb5afb and a02553f.

📒 Files selected for processing (29)
  • changelog.d/agent-session-runtime.added.md
  • n00n-agent/src/agent/compaction.rs
  • n00n-agent/src/agent/run.rs
  • n00n-agent/src/agent/tool_dispatch.rs
  • n00n-agent/src/tools/mod.rs
  • n00n-lua/src/api/agent.rs
  • n00n-lua/src/api/session.rs
  • n00n-lua/src/api/util/command.rs
  • n00n-lua/src/api/util/ctx.rs
  • n00n-lua/src/loader.rs
  • n00n-lua/src/runtime.rs
  • n00n-lua/tests/plugin_host.rs
  • n00n-storage/src/sessions.rs
  • n00n-ui/src/agent/agent_loop.rs
  • n00n-ui/src/agent/shared_queue.rs
  • n00n-ui/src/app/mod.rs
  • n00n-ui/src/app/queue.rs
  • n00n-ui/src/app/session.rs
  • n00n-ui/src/app/tests.rs
  • n00n-ui/src/app/view.rs
  • n00n-ui/src/components/mod.rs
  • n00n-ui/src/components/status_bar.rs
  • n00n-ui/src/event_loop.rs
  • plugins/lib/n00n/subagent.lua
  • plugins/sessions/init.lua
  • plugins/task/init.lua
  • plugins/team/init.lua
  • site/docs/content/lua-api/_index.md
  • src/cmd/tui_bridge.rs
📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added persistent agent-session tracking with lifecycle statuses, session hierarchy, and background-session visibility.
    • Added controls to focus, cancel, resume, and continue sessions, including recovery of interrupted transcripts and paused team runs.
    • Added categorized session listings and improved task-picker navigation.
    • Restricted delegated agents to exploration when configured, preventing further task or team delegation.
  • Bug Fixes
    • Improved cancellation, shutdown persistence, follow-up routing, and restoration of failed or interrupted sessions.
  • Documentation
    • Documented session delegation policy options.

Walkthrough

The change adds durable session ownership and lifecycle tracking, session-tree controls, interrupted-session recovery, paused team-run resumption, background-session visibility, and explore-only delegation enforcement.

Changes

Agent delegation and session context

Layer / File(s) Summary
Delegation policy propagation and enforcement
n00n-agent/src/agent/run.rs, n00n-agent/src/agent/tool_dispatch.rs, n00n-agent/src/tools/mod.rs, n00n-lua/src/api/agent.rs, n00n-lua/src/api/util/ctx.rs, plugins/task/init.lua, plugins/team/init.lua, plugins/lib/n00n/subagent.lua, site/docs/content/lua-api/_index.md
Agents and Lua sessions support configured and explore_only policies. Explore-only contexts reject nested delegation tools. Handler contexts expose session IDs.
Persisted lifecycle and ownership metadata
n00n-storage/src/sessions.rs
Session metadata and summaries now include root IDs and lifecycle states. Scan caches use v4, and legacy title patterns no longer infer parent ownership.

Session runtime and UI

Layer / File(s) Summary
Session hierarchy and runtime persistence
n00n-ui/src/event_loop.rs
The event loop restores sessions, tracks parent/root ownership, enforces live-session and nesting limits, projects related sessions, cancels trees, resumes paused runs, and persists synchronized shutdown snapshots.
Lifecycle-aware task picker and chat routing
n00n-ui/src/app/mod.rs, n00n-ui/src/app/queue.rs, n00n-ui/src/app/session.rs, n00n-ui/src/components/mod.rs
The task picker displays background sessions and lifecycle states. New actions focus, cancel, resume, and continue sessions. Submission routing follows the selected chat and lifecycle updates persist across outcomes.
Paused team resumption and session presentation
n00n-ui/src/agent/agent_loop.rs, n00n-ui/src/agent/shared_queue.rs, n00n-ui/src/app/view.rs, plugins/sessions/init.lua, n00n-ui/src/app/tests.rs
Paused team runs use dedicated queue entries and direct tool execution. Session boards use categorized groups. Non-main chats use the shared input layout. Tests cover lifecycle, recovery, routing, and persistence behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TaskPicker
  participant EventLoop
  participant SessionStorage
  participant AgentRuntime
  User->>TaskPicker: select session action
  TaskPicker->>EventLoop: focus, cancel, resume, or continue session
  EventLoop->>SessionStorage: load or update lifecycle metadata
  EventLoop->>AgentRuntime: restore, cancel, or resume session tree
  AgentRuntime-->>EventLoop: return runtime events and tool outputs
  EventLoop->>SessionStorage: persist updated session state
  EventLoop-->>TaskPicker: refresh session descriptors
Loading

Possibly related PRs

  • w0wl0lxd/n00n#21: Related background agent-session lifecycle, cancellation, resumption, and UI controls.
  • w0wl0lxd/n00n#129: Related paused team-run recovery and resume queue handling.
  • w0wl0lxd/n00n#214: Related delegation policy propagation and tool-dispatch authorization.

Poem

A rabbit guards the session tree,
With roots that last through runtime snow.
“Explore, but do not delegate,”
The little tools now plainly know.
Paused teams hop back into flow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main UI change: isolating and restoring agent sessions.
Description check ✅ Passed The description directly covers session ownership, lifecycle, restoration, resume handling, delegation limits, and related tests.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/agent-menu-visibility

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Criterion

Details
Benchmark suite Current: 736ed63 Previous: b36e929 Ratio
fib/jit_mlua_hook 5066531 ns/iter (± 80371) 6455055 ns/iter (± 172769) 0.78
fib/jit_watchdog 1927566 ns/iter (± 8909) 2494875 ns/iter (± 4312) 0.77
fib/jit_none 1893455 ns/iter (± 61940) 2493442 ns/iter (± 15255) 0.76
fib/interp_mlua_hook 5841565 ns/iter (± 15779) 7718671 ns/iter (± 72364) 0.76
fib/interp_watchdog 3005037 ns/iter (± 35159) 4073914 ns/iter (± 16857) 0.74
fib/interp_none 3013945 ns/iter (± 6627) 3956463 ns/iter (± 10333) 0.76
buffer_rw/jit_mlua_hook 430496 ns/iter (± 1423) 554260 ns/iter (± 1328) 0.78
buffer_rw/jit_watchdog 130124 ns/iter (± 169) 167782 ns/iter (± 628) 0.78
buffer_rw/jit_none 130173 ns/iter (± 471) 167759 ns/iter (± 338) 0.78
buffer_rw/interp_mlua_hook 809197 ns/iter (± 18509) 1044457 ns/iter (± 12359) 0.77
buffer_rw/interp_watchdog 495780 ns/iter (± 20330) 629318 ns/iter (± 4016) 0.79
buffer_rw/interp_none 496679 ns/iter (± 11339) 639390 ns/iter (± 4827) 0.78
splash_render_120x40 56033 ns/iter (± 3749) 66065 ns/iter (± 4263) 0.85
splash_render_200x60 134447 ns/iter (± 11701) 149400 ns/iter (± 22243) 0.90

This comment was automatically generated by workflow using github-action-benchmark.

@w0wl0lxd

w0wl0lxd commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai review

@w0wl0lxd
w0wl0lxd marked this pull request as ready for review August 3, 2026 11:40
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@w0wl0lxd I will review the changes in #254.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7bb5afbdb1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread n00n-ui/src/app/mod.rs
match result {
ChatEventResult::Done => {
self.status_bar.clear_flash();
self.state.session.meta.lifecycle = SessionLifecycle::Succeeded;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep lifecycle running while draining queued turns

When a TurnEnd message is queued while the current turn is streaming, AgentLoop immediately starts that message after emitting Done for the first turn. This assignment persists Succeeded, while the subsequent QueueItemConsumed event only draws the message and never restores Running. The session therefore appears completed, disables cancellation in the task picker, and can be restored as successfully finished even while the queued provider call is active. Set the lifecycle back to Running when a deferred item is consumed, or only mark success after the queue is empty.

Useful? React with 👍 / 👎.

Comment thread n00n-ui/src/event_loop.rs
Comment on lines +1009 to +1012
runtime.app.set_agent_sessions(project_agent_sessions(
runtime.id(),
&self.stored_agent_sessions,
&descriptors,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain sessions when a runtime changes identity

The projection combines current runtime descriptors with a stored-session snapshot populated only at startup. If a background child is created during this process and the user later runs /new in that child, the runtime switches to a new session ID after saving the old one, but the old child is in neither descriptors nor stored_agent_sessions. It consequently disappears from its parent's Ctrl+X panel until restart despite remaining on disk. Archive the outgoing runtime descriptor into the stored set, or refresh the stored snapshot when runtimes change sessions.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces durable agent session identity (persisted root_id / parent_id / SessionLifecycle), tree-scoped Ctrl+X controls, background-session visibility in the task picker, paused-team checkpoint recovery, and a DelegationPolicy gate that restricts task agents to explore-only subagents. It also hardens lifecycle state across the entire session transition surface and unifies shutdown serialisation.

  • Storage layer: adds SessionLifecycle enum and root_id to SessionMeta / SessionSummary; bumps scan cache to v4; removes legacy implicit parent inference from the equal_creation_times heuristic.
  • Agent layer: propagates session_id into ToolContext; adds DelegationPolicy that blocks task, team, workflow, and fusion_delegate from task-spawned subagents.
  • UI layer: introduces do_direct_tool for direct team-tool dispatch on resume, ResumeTeam queue item, stored-session reconciliation at startup, and per-tick sync_agent_sessions projection.

Confidence Score: 4/5

Safe to merge; the new lifecycle tracking, delegation gate, and resume flow are well-guarded and extensively tested.

All findings are non-blocking quality concerns: one session-creation path skips the 64-session cap, a constant is duplicated across three files risking future drift, and the per-tick session projection runs unconditionally rather than on change. None affect correctness for realistic workloads. The core logic — lifecycle transitions, ancestry traversal, cycle detection, delegation enforcement, and shutdown serialisation — is well-covered by the new test suite (4628 passing).

Files Needing Attention: n00n-ui/src/event_loop.rs deserves a second look for the missing limit guard in Action::ContinueSubagent and the unconditional per-tick sync cost.

Important Files Changed

Filename Overview
n00n-ui/src/event_loop.rs Largest change: adds stored-session reconciliation at startup, sync_agent_sessions (called every tick), cancel_session_tree, resume_session, ensure_live_session, shutdown_lifecycle, and handlers for four new Action variants. One inconsistency: ContinueSubagent spawns a live session without checking MAX_LIVE_SESSIONS.
n00n-ui/src/agent/agent_loop.rs Adds do_direct_tool for injecting a tool call directly into agent history (used by ResumeTeam) and is_paused_team_output. do_direct_tool hardcodes AgentMode::Build regardless of session mode; for team-resume that is likely harmless but undocumented.
n00n-ui/src/app/mod.rs Adds AgentSessionEntry, TaskTarget, new TaskStatus variants, section grouping, and lifecycle writes across all terminal-event paths. Subagent prompt routing is now scoped to the active chat, fixing cross-agent input capture.
n00n-storage/src/sessions.rs Adds SessionLifecycle and root_id to SessionMeta/SessionSummary; bumps scan cache to v4; cleans up stale v2/v3 caches; removes legacy parent-inference heuristic. Backward compatibility is tested.
n00n-agent/src/agent/tool_dispatch.rs Adds delegation_policy_denied guard (checked before any tool runs) and unit tests covering all variants. Clean, well-tested addition.
n00n-lua/src/api/agent.rs Propagates delegation_policy into SessionState; moves parent_cancels/child_id onto LuaSession so cancel can be sync; AgentEvent::Error is no longer silently swallowed and is now forwarded to the parent context.
plugins/sessions/init.lua Rewrites build_tree to group descendants into synthetic category nodes (Research, Teams, Workflows, etc.) instead of direct parent-child nesting; adds a Recovered sessions group for malformed entries.
plugins/task/init.lua Background task sessions now pass parent_id from ctx:session_id(), inherit delegation_policy = explore_only, and return a title field. Foreground task sessions also exclude delegation tools.
n00n-ui/src/app/session.rs Relocates paused_team_run here (was in event_loop.rs); adds continued_subagent_session helper that copies parent/root ownership.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Agent session starts] -->|session_id propagated to ToolContext| B{Tool dispatched}
    B -->|DelegationPolicy::ExploreOnly| C{Is delegation tool?\ntask / team / workflow / fusion_delegate}
    C -->|Yes| D[Return DELEGATION_POLICY_DENIED error]
    C -->|No| E[Run tool normally]
    B -->|DelegationPolicy::Configured| E

    E -->|team tool paused| F[SessionLifecycle::Paused written to disk]
    F -->|ResumeTeam queued| G[QueueItem::ResumeTeam popped by agent_loop]
    G --> H[do_direct_tool injects\nassistant+user messages into history]
    H --> I[team plugin resumes run_id]

    J[Startup: stored sessions scanned] -->|Running/WaitingInput found| K[reconcile_restored_lifecycle\nInterrupted, saved to disk]
    K --> L[stored_agent_sessions populated]
    L -->|sync_agent_sessions every tick| M[project_agent_sessions\nscopes to active root tree]
    M --> N[Task picker shows background + live sessions]

    N -->|Delete key| O[cancel_session_tree\ncancels id + descendants]
    N -->|r key| P[resume_session → ensure_live_session\n→ push ResumeTeam]
    N -->|Enter| Q[FocusSession → focus_session\n→ ensure_live_session]
Loading

Comments Outside Diff (1)

  1. n00n-ui/src/agent/agent_loop.rs, line 793 (link)

    P2 do_direct_tool hardcodes AgentMode::Build for every ResumeTeam dispatch, even if the session was started in a different mode. Because team is only ever invoked in build mode this is harmless today, but if the mode is ever read inside the team plugin (or a future caller reuses do_direct_tool for a different tool) the context would be silently wrong. Consider deriving the mode from the session's own AgentMode instead.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: n00n-ui/src/agent/agent_loop.rs
    Line: 793
    
    Comment:
    `do_direct_tool` hardcodes `AgentMode::Build` for every `ResumeTeam` dispatch, even if the session was started in a different mode. Because `team` is only ever invoked in build mode this is harmless today, but if the mode is ever read inside the `team` plugin (or a future caller reuses `do_direct_tool` for a different tool) the context would be silently wrong. Consider deriving the mode from the session's own `AgentMode` instead.
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
### Issue 1
n00n-ui/src/event_loop.rs:1726-1737
`ContinueSubagent` bypasses the `MAX_LIVE_SESSIONS` cap. Every other session-creation path (`SessionRequest::New`, `ensure_live_session`) checks `self.sessions.len() >= MAX_LIVE_SESSIONS` before calling `push_runtime`. Omitting that guard here means a user can exceed the 64-session ceiling by pressing 'r' on finished subagent chats.

```suggestion
            Action::ContinueSubagent { name, messages } => {
                if self.sessions.len() >= MAX_LIVE_SESSIONS {
                    self.focused_app().flash(format!(
                        "live session limit reached ({MAX_LIVE_SESSIONS})"
                    ));
                    return;
                }
                let model = self.ctx.model_slot.load().model.spec();
                let session = continued_subagent_session(
                    &self.sessions[idx].app.state.session,
                    &model,
                    &name,
                    messages,
                );
                let position = self.push_runtime(self.ctx.spawn_runtime(session));
                self.sessions[position].app.save_session();
                self.set_focus(position);
            }
```

### Issue 2
n00n-ui/src/agent/agent_loop.rs:793
`do_direct_tool` hardcodes `AgentMode::Build` for every `ResumeTeam` dispatch, even if the session was started in a different mode. Because `team` is only ever invoked in build mode this is harmless today, but if the mode is ever read inside the `team` plugin (or a future caller reuses `do_direct_tool` for a different tool) the context would be silently wrong. Consider deriving the mode from the session's own `AgentMode` instead.

### Issue 3
n00n-ui/src/agent/agent_loop.rs:22
`TEAM_TOOL_NAME` is defined here, also defined as `pub(crate) const TEAM_TOOL_NAME` in `n00n-ui/src/app/session.rs`, and re-declared inside the `event_loop::tests` module. The three definitions are not shared so a future rename could drift. The `pub(crate)` copy in `session.rs` could be re-exported and reused here instead.

### Issue 4
n00n-ui/src/event_loop.rs:1003-1015
`sync_agent_sessions` is called on every tick and rebuilds the full descriptor list and runs `project_agent_sessions` (with ancestor traversal) for every live session. With `MAX_LIVE_SESSIONS = 64` and a large number of stored sessions that's O(n² + n·s) per tick. The `set_agent_sessions` equality guard prevents redundant UI redraws, but the computation itself runs unconditionally. Consider scheduling the sync only when a session's lifecycle or parent metadata actually changes (dirty flag / generation counter) rather than every tick.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "docs(lua): regenerate session API refere..." | Re-trigger Greptile

Comment thread n00n-ui/src/event_loop.rs
Comment on lines +1726 to +1737
Action::ContinueSubagent { name, messages } => {
let model = self.ctx.model_slot.load().model.spec();
let session = continued_subagent_session(
&self.sessions[idx].app.state.session,
&model,
&name,
messages,
);
let position = self.push_runtime(self.ctx.spawn_runtime(session));
self.sessions[position].app.save_session();
self.set_focus(position);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 ContinueSubagent bypasses the MAX_LIVE_SESSIONS cap. Every other session-creation path (SessionRequest::New, ensure_live_session) checks self.sessions.len() >= MAX_LIVE_SESSIONS before calling push_runtime. Omitting that guard here means a user can exceed the 64-session ceiling by pressing 'r' on finished subagent chats.

Suggested change
Action::ContinueSubagent { name, messages } => {
let model = self.ctx.model_slot.load().model.spec();
let session = continued_subagent_session(
&self.sessions[idx].app.state.session,
&model,
&name,
messages,
);
let position = self.push_runtime(self.ctx.spawn_runtime(session));
self.sessions[position].app.save_session();
self.set_focus(position);
}
Action::ContinueSubagent { name, messages } => {
if self.sessions.len() >= MAX_LIVE_SESSIONS {
self.focused_app().flash(format!(
"live session limit reached ({MAX_LIVE_SESSIONS})"
));
return;
}
let model = self.ctx.model_slot.load().model.spec();
let session = continued_subagent_session(
&self.sessions[idx].app.state.session,
&model,
&name,
messages,
);
let position = self.push_runtime(self.ctx.spawn_runtime(session));
self.sessions[position].app.save_session();
self.set_focus(position);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: n00n-ui/src/event_loop.rs
Line: 1726-1737

Comment:
`ContinueSubagent` bypasses the `MAX_LIVE_SESSIONS` cap. Every other session-creation path (`SessionRequest::New`, `ensure_live_session`) checks `self.sessions.len() >= MAX_LIVE_SESSIONS` before calling `push_runtime`. Omitting that guard here means a user can exceed the 64-session ceiling by pressing 'r' on finished subagent chats.

```suggestion
            Action::ContinueSubagent { name, messages } => {
                if self.sessions.len() >= MAX_LIVE_SESSIONS {
                    self.focused_app().flash(format!(
                        "live session limit reached ({MAX_LIVE_SESSIONS})"
                    ));
                    return;
                }
                let model = self.ctx.model_slot.load().model.spec();
                let session = continued_subagent_session(
                    &self.sessions[idx].app.state.session,
                    &model,
                    &name,
                    messages,
                );
                let position = self.push_runtime(self.ctx.spawn_runtime(session));
                self.sessions[position].app.save_session();
                self.set_focus(position);
            }
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

use n00n_lua::EventHandle;
use n00n_providers::{AgentError, Message, Model, OpenAiOptions, System, TokenUsage};
use n00n_providers::{
AgentError, ContentBlock, Message, Model, OpenAiOptions, RequestOptions, Role, System,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 TEAM_TOOL_NAME is defined here, also defined as pub(crate) const TEAM_TOOL_NAME in n00n-ui/src/app/session.rs, and re-declared inside the event_loop::tests module. The three definitions are not shared so a future rename could drift. The pub(crate) copy in session.rs could be re-exported and reused here instead.

Prompt To Fix With AI
This is a comment left during a code review.
Path: n00n-ui/src/agent/agent_loop.rs
Line: 22

Comment:
`TEAM_TOOL_NAME` is defined here, also defined as `pub(crate) const TEAM_TOOL_NAME` in `n00n-ui/src/app/session.rs`, and re-declared inside the `event_loop::tests` module. The three definitions are not shared so a future rename could drift. The `pub(crate)` copy in `session.rs` could be re-exported and reused here instead.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread n00n-ui/src/event_loop.rs
Comment on lines 1003 to +1015
self.tick_periodic_save();
}

fn sync_agent_sessions(&mut self) {
let descriptors = self.runtime_descriptors();
for runtime in &mut self.sessions {
runtime.app.set_agent_sessions(project_agent_sessions(
runtime.id(),
&self.stored_agent_sessions,
&descriptors,
));
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 sync_agent_sessions is called on every tick and rebuilds the full descriptor list and runs project_agent_sessions (with ancestor traversal) for every live session. With MAX_LIVE_SESSIONS = 64 and a large number of stored sessions that's O(n² + n·s) per tick. The set_agent_sessions equality guard prevents redundant UI redraws, but the computation itself runs unconditionally. Consider scheduling the sync only when a session's lifecycle or parent metadata actually changes (dirty flag / generation counter) rather than every tick.

Prompt To Fix With AI
This is a comment left during a code review.
Path: n00n-ui/src/event_loop.rs
Line: 1003-1015

Comment:
`sync_agent_sessions` is called on every tick and rebuilds the full descriptor list and runs `project_agent_sessions` (with ancestor traversal) for every live session. With `MAX_LIVE_SESSIONS = 64` and a large number of stored sessions that's O(n² + n·s) per tick. The `set_agent_sessions` equality guard prevents redundant UI redraws, but the computation itself runs unconditionally. Consider scheduling the sync only when a session's lifecycle or parent metadata actually changes (dirty flag / generation counter) rather than every tick.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@n00n-agent/src/tools/mod.rs`:
- Around line 303-308: Remove Copy from the derives on DelegationPolicy while
retaining Clone and the other traits. Update the ToolContext construction to
explicitly clone self.delegation_policy wherever the policy is passed by value.

In `@n00n-storage/src/sessions.rs`:
- Around line 1909-1918: Introduce a named ScannedMeta struct with title,
updated_at, first_message, root_id, and lifecycle fields, and change both
try_decode_last_meta_at and find_last_frame_meta to return Option<ScannedMeta>.
Update their construction, field access, and the call sites in
find_last_frame_meta and scan_zst_header to use named fields instead of
positional tuple destructuring.
- Around line 4050-4081: Add assertions to
session_lifecycle_is_backward_compatible_and_persisted covering
SessionLifecycle::Idle serialization: verify the idle lifecycle field is omitted
and deserializes back to Idle. Extend session_list_includes_parent_and_lifecycle
with a second list_in call and assert its parent_id, root_id, and lifecycle
match the first result, exercising the cached ScanCacheEntry path.
- Around line 1753-1754: Update both MetaScan decode sites that currently
construct MetaScan with title, updated_at, and meta so they deserialize only the
needed root_id and lifecycle fields directly into the scan structure. Remove the
flattened SessionMeta usage from MetaScan while preserving the existing title
and updated_at extraction and scan behavior.

In `@n00n-ui/src/agent/agent_loop.rs`:
- Around line 45-53: Centralize the paused team-result contract: in
n00n-ui/src/agent/agent_loop.rs lines 45-53, remove the private TEAM_TOOL_NAME
and local is_paused_team_output predicate, then import both from
crate::app::session; in n00n-ui/src/app/session.rs line 21, retain
TEAM_TOOL_NAME as the single pub(crate) definition and extract the
paused-payload validation from paused_team_run into a pub(crate) helper used by
both call sites.

In `@n00n-ui/src/app/mod.rs`:
- Around line 985-1031: Update the task-picker key handling around the
Delete/Backspace and Char('r') branches so unmatched picker input is delegated
to self.task_picker.handle_key(key). Preserve the existing cancel, resume, and
ContinueSubagent actions when their conditions match, but only return
immediately for those handled actions; otherwise fall through to the picker
handler.
- Around line 1943-1952: In the ChatEventResult::Done branch for chat_idx == 0,
synchronize the session state before checking paused_team_run, preserving the
refresh currently performed by save_session/session_snapshot. Determine whether
the run is paused, set lifecycle to Paused or Succeeded accordingly, then call
save_session only once after the final lifecycle is selected.

In `@n00n-ui/src/app/view.rs`:
- Line 328: Update the overlay-zone gate in view to require is_main_chat()
alongside the existing plan-form and layout conditions, matching the gates in
view and render_bottom_panel so subagent chat input areas remain selectable.

In `@n00n-ui/src/event_loop.rs`:
- Around line 1250-1294: Update the session-tree parent-map construction in
n00n-ui/src/event_loop.rs lines 1250-1294 to merge self.stored_agent_sessions
with live self.sessions before calling session_depth, preserving valid chains
through non-live ancestors. Apply the same merge in lines 1424-1449 before
calling is_descendant so live descendants are cancelled even when an
intermediate ancestor is stored rather than live.
- Line 986: Optimize tick’s sync_agent_sessions path by caching a fingerprint of
runtime descriptors and the stored session list, including titles, and returning
early when unchanged. Within each actual sync, build parents and explicit_roots
once from the full stored/live data and pass them into project_agent_sessions
for every runtime. Update project_agent_sessions and owned_session_root usage to
reuse these maps without allocating per-agent sets.
- Around line 2027-2048: Import SharedTranscript alongside the existing
n00n_agent type imports at the top of the file, then update sync_agent_mirrors
to use SharedTranscript directly in its transcript parameter type instead of
n00n_agent::SharedTranscript. Leave the rest of the function unchanged.
- Around line 226-249: Add a concise explanatory comment in owned_session_root
immediately before the `(!parents.contains_key(&current)).then_some(root)`
return, documenting that an absent parent entry means the parent was not
restored and allows trusting the persisted root, while a present `None`
indicates a different root and must reject it.
- Line 2089: Update the event_loop.rs tests to import and use the shared
app-session TEAM_TOOL_NAME constant defined in session.rs, removing the local
duplicate declaration and replacing test references to the hardcoded team tool
name.
- Around line 1424-1449: Update cancel_session_tree to collect matching session
IDs rather than indices, then re-resolve each ID to its current position
immediately before calling cancel_current_run and dispatch. Preserve the
existing lifecycle and descendant filtering, and ensure the loop skips IDs no
longer present after earlier dispatch mutations.
- Around line 328-341: In control_status_json, replace the paused_team map_or
call using std::convert::identity with unwrap_or(Value::Null), and apply the
same simplification at the other paused_team occurrences around the referenced
status-building code. Leave the output handling unchanged.
- Around line 1451-1477: Update resume_session after ensure_live_session so any
failure to find a resumable team run rolls back the newly loaded runtime,
matching the cleanup performed by SessionRequest::New. Remove the runtime and
cancel its handles before returning the “This session has no resumable team run”
error, while preserving the existing resume flow when a valid run_id is found.
- Around line 1726-1737: Add MAX_LIVE_SESSIONS and MAX_SESSION_DEPTH checks to
the Action::ContinueSubagent branch before creating the
continued_subagent_session. Reuse the existing guard logic and flash-message
behavior from SessionRequest::New or ensure_live_session, returning without
spawning or focusing a runtime when either limit is reached.

In `@plugins/sessions/init.lua`:
- Around line 255-260: Ensure nil session IDs are validated before tree
construction begins, rather than relying on the by_id assignment in build_tree.
Add the validation in the shared normalize_session path or perform a complete
upfront check in refresh(), covering every session before any s.id/n.id table
indexing; preserve build_tree’s existing behavior for valid IDs.

In `@site/docs/content/lua-api/_index.md`:
- Line 819: Update the delegation_policy entry in the n00n.agent.session() API
documentation to list the supported values, “configured” and “explore_only,” and
state that “explore_only” blocks fusion_delegate, task, team, and workflow while
preserving the default of “configured.”
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 899fa0f9-4780-497d-86ff-c7b0d17bbd9d

📥 Commits

Reviewing files that changed from the base of the PR and between f5be3f3 and 7bb5afb.

📒 Files selected for processing (21)
  • changelog.d/agent-session-runtime.added.md
  • n00n-agent/src/agent/run.rs
  • n00n-agent/src/agent/tool_dispatch.rs
  • n00n-agent/src/tools/mod.rs
  • n00n-lua/src/api/agent.rs
  • n00n-lua/src/api/util/ctx.rs
  • n00n-storage/src/sessions.rs
  • n00n-ui/src/agent/agent_loop.rs
  • n00n-ui/src/agent/shared_queue.rs
  • n00n-ui/src/app/mod.rs
  • n00n-ui/src/app/queue.rs
  • n00n-ui/src/app/session.rs
  • n00n-ui/src/app/tests.rs
  • n00n-ui/src/app/view.rs
  • n00n-ui/src/components/mod.rs
  • n00n-ui/src/event_loop.rs
  • plugins/lib/n00n/subagent.lua
  • plugins/sessions/init.lua
  • plugins/task/init.lua
  • plugins/team/init.lua
  • site/docs/content/lua-api/_index.md
📜 Review details
⏰ Context from checks skipped due to timeout. (15)
  • GitHub Check: Greptile Review
  • GitHub Check: Coverage
  • GitHub Check: Test
  • GitHub Check: MSRV (1.97)
  • GitHub Check: Lint
  • GitHub Check: Docs
  • GitHub Check: Build (Windows)
  • GitHub Check: Build
  • GitHub Check: Rustdoc
  • GitHub Check: Test (macOS)
  • GitHub Check: Lint (macOS)
  • GitHub Check: Test (Windows)
  • GitHub Check: Lint (Windows)
  • GitHub Check: Criterion
  • GitHub Check: Analyze (rust)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Do not add unsafe code, FFI, global mutable state, static mut, or unchecked transmute-like behavior without written review, an explicit lint exception, and a SAFETY comment where applicable.
Do not use unwrap, expect, panic!, todo!, unimplemented!, or dbg! in production Rust code; tests are exempt from the unwrap/expect/panic restriction.
Do not silently discard failures with unwrap_or, unwrap_or_default, .ok() on Result, or equivalent defaults; return typed errors, reject the operation, or use an explicitly named fallback with sanitized structured logging.
Use idiomatic Rust, descriptive names, minimal state, and avoid unnecessary comments, bloat, and magic numbers or strings.
Import types at the top of the file and use short imported names; keep constants immediately after imports.
Use Result<T, E> and explicit error handling instead of panics; use thiserror for library/domain errors and color-eyre at binary edges.
Use #[derive(Copy)] only for structs containing one primitive field.
Prefer structured logging with useful fields and provide helpful, sanitized error messages.
Place unit tests in the same file inside #[cfg(test)] modules; use #[test_case] and snake_case test names.
Propagate typed errors with ?, ok_or_else, and map_err; library crates use thiserror and binaries use color-eyre.
Treat LLM and provider output as untrusted input; validate schemas, domain constraints, and source evidence before persistence or action.
Do not log raw provider payloads, prompts, credentials, or user session data, and never commit credentials, API keys, tokens, cookies, or auth headers.
Validate and authorize HTTP, file, queue, configuration/environment, LLM, and provider-callback inputs before mutation or persistence.
Tool execution requires allowlisted tools, scoped credentials, explicit user context, audit events, and refusal or denial tests.

Files:

  • n00n-ui/src/app/queue.rs
  • n00n-lua/src/api/util/ctx.rs
  • n00n-ui/src/app/session.rs
  • n00n-ui/src/components/mod.rs
  • n00n-agent/src/tools/mod.rs
  • n00n-agent/src/agent/tool_dispatch.rs
  • n00n-agent/src/agent/run.rs
  • n00n-ui/src/app/view.rs
  • n00n-ui/src/app/tests.rs
  • n00n-lua/src/api/agent.rs
  • n00n-ui/src/agent/agent_loop.rs
  • n00n-ui/src/agent/shared_queue.rs
  • n00n-storage/src/sessions.rs
  • n00n-ui/src/app/mod.rs
  • n00n-ui/src/event_loop.rs
site/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

User documentation should be warm, simple, concise, easy for non-native English speakers, story-oriented, without em-dashes, emojis, or an AI tone.

Files:

  • site/docs/content/lua-api/_index.md
🧠 Learnings (1)
📚 Learning: 2026-07-31T19:15:04.814Z
Learnt from: w0wl0lxd
Repo: w0wl0lxd/n00n PR: 206
File: changelog.d/orchestration-hardening.fixed.md:1-1
Timestamp: 2026-07-31T19:15:04.814Z
Learning: Files in changelog.d are changelog fragments intended for user-facing release notes and may begin directly with summary prose. Do not flag a missing Markdown H1 or require an H1 solely because Markdownlint MD041 reports it in these fragment files.

Applied to files:

  • changelog.d/agent-session-runtime.added.md
🪛 markdownlint-cli2 (0.23.1)
changelog.d/agent-session-runtime.added.md

[warning] 1-1: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🔇 Additional comments (58)
n00n-agent/src/agent/run.rs (1)

22-23: LGTM!

Also applies to: 225-225, 283-283, 309-314, 887-887, 903-903

n00n-agent/src/tools/mod.rs (1)

310-334: LGTM!

Also applies to: 586-586, 603-603

n00n-agent/src/agent/tool_dispatch.rs (1)

36-36: LGTM!

Also applies to: 266-273, 381-383, 1046-1065

n00n-lua/src/api/agent.rs (1)

20-21: LGTM!

Also applies to: 119-126, 632-632, 677-681, 827-827, 847-848, 869-869, 888-888, 906-907, 1380-1380, 1469-1470, 1560-1560, 1689-1691, 2224-2231

n00n-lua/src/api/util/ctx.rs (1)

240-250: LGTM!

Also applies to: 403-403, 426-426, 451-461

plugins/lib/n00n/subagent.lua (1)

182-182: LGTM!

plugins/task/init.lua (1)

22-22: LGTM!

Also applies to: 91-103, 186-187, 226-226, 259-260

plugins/team/init.lua (1)

806-810: LGTM!

Also applies to: 829-833

changelog.d/agent-session-runtime.added.md (1)

1-1: LGTM!

n00n-storage/src/sessions.rs (8)

37-38: LGTM!


166-194: LGTM!


375-378: LGTM!


1771-1774: LGTM!

Also applies to: 1851-1852


1868-1874: LGTM!


1931-1932: LGTM!

Also applies to: 1948-1954, 1970-1970


2071-2090: LGTM!

Also applies to: 2105-2105, 2126-2127


2379-2380: LGTM!

Also applies to: 3690-3690, 3716-3717

n00n-ui/src/event_loop.rs (17)

10-12: LGTM!

Also applies to: 24-26, 36-49, 72-73


136-165: LGTM!


180-225: LGTM!


251-326: LGTM!


344-388: Accept the projection logic; the per-tick cost is flagged at the caller.

The ownership filtering is correct: stored and live parents merge, live descriptors override stored ones by id, and only descendants sharing active_root survive. The repeated map construction is a caller-side cost; see the comment on sync_agent_sessions at lines 1006-1016.


390-412: LGTM!


494-494: LGTM!

Also applies to: 713-757, 781-786, 809-809


828-834: LGTM!


1049-1050: LGTM!


1182-1184: LGTM!

Also applies to: 1197-1199, 1216-1217


1315-1320: LGTM!


1409-1422: LGTM!


1490-1496: LGTM!


1713-1725: LGTM!


1946-1977: LGTM!


2066-2088: LGTM!

Also applies to: 2091-2282, 2310-2345


166-178: 🩺 Stability & Availability

No change needed. A paused team checkpoint is preserved as Paused, so resume_session can still find it.

n00n-ui/src/app/mod.rs (12)

113-165: LGTM!


174-192: LGTM!


612-619: LGTM!

Also applies to: 633-633, 645-647


688-697: LGTM!


699-756: LGTM!


758-791: LGTM!


860-861: LGTM!


974-984: LGTM!

Also applies to: 1032-1047


1356-1387: LGTM!


1486-1486: LGTM!

Also applies to: 1515-1515, 1609-1610


1898-1899: LGTM!

Also applies to: 1915-1931


1933-1941: LGTM!

Also applies to: 1965-1965

n00n-ui/src/app/queue.rs (1)

8-8: LGTM!

Also applies to: 414-414

n00n-ui/src/app/view.rs (1)

33-34: LGTM!

Also applies to: 80-85, 99-99, 134-142, 170-170, 397-398

n00n-ui/src/components/mod.rs (1)

39-39: LGTM!

Also applies to: 209-221

n00n-ui/src/agent/agent_loop.rs (4)

12-24: LGTM!


251-343: LGTM!


655-662: LGTM!


213-221: 🗄️ Data Integrity & Integration

No change needed for the team resume schema.

goal is required and resume is declared in the team tool schema, so {"goal": "", "resume": resume_id} matches the accepted input shape.

n00n-ui/src/agent/shared_queue.rs (1)

27-27: LGTM!

Also applies to: 77-88, 109-120, 131-138, 184-184, 239-243, 301-301, 313-313, 346-346, 379-408

plugins/sessions/init.lua (3)

36-47: LGTM!


177-254: LGTM!

Also applies to: 261-309


623-627: LGTM!

n00n-ui/src/app/tests.rs (1)

19-19: LGTM!

Also applies to: 559-641, 1386-1407, 1549-1677, 1700-1728, 3438-3455, 3464-3498, 3675-3675, 3919-3936, 3953-3957

Comment thread n00n-agent/src/tools/mod.rs Outdated
Comment thread n00n-storage/src/sessions.rs Outdated
Comment thread n00n-storage/src/sessions.rs Outdated
Comment thread n00n-storage/src/sessions.rs
Comment thread n00n-ui/src/agent/agent_loop.rs Outdated
Comment thread n00n-ui/src/event_loop.rs
Comment thread n00n-ui/src/event_loop.rs
Comment thread n00n-ui/src/event_loop.rs Outdated
Comment thread plugins/sessions/init.lua
Comment on lines +255 to 260
local function build_tree(sessions, expanded_state)
local by_id = {}
for _, s in ipairs(sessions) do
by_id[s.id] = s
for _, session in ipairs(sessions) do
by_id[session.id] = session
session.children = {}
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether normalize_session guarantees an id, and where ids originate.
rg -n -C6 'normalize_session' plugins/sessions/init.lua
rg -n -C3 '\.id\b' plugins/sessions/init.lua | head -60

Repository: w0wl0lxd/n00n

Length of output: 2501


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file size =="
wc -l plugins/sessions/init.lua

echo "== normalize_session body =="
sed -n '155,175p' plugins/sessions/init.lua

echo "== refresh/build_tree section =="
sed -n '175,315p' plugins/sessions/init.lua

echo "== all .id key usages =="
python3 - <<'PY'
from pathlib import Path
p=Path('plugins/sessions/init.lua')
for i,line in enumerate(p.read_text().splitlines(),1):
    if '.id' in line:
        print(f"{i}: {line}")
PY

Repository: w0wl0lxd/n00n

Length of output: 6483


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repository references to sessions id fields =="
rg -n -C3 'session\.[^=\s]+|n00n\.session\.[^(]+' -g '*.lua' . | head -200

echo "== lua syntax nil-table-index probe =="
if command -v lua >/dev/null 2>&1; then
  lua - <<'LUA'
pcall(function() local t = {}; t[nil] = 1 end)
print(`#pcall`(function() local t = {}; t[nil] = 1 end))
LUA
elif command -v luajit >/dev/null 2>&1; then
  luajit - <<'LUA'
pcall(function() local t = {}; t[nil] = 1 end)
print(`#pcall`(function() local t = {}; t[nil] = 1 end))
LUA
else
  echo "lua/luajit not available"
fi

Repository: w0wl0lxd/n00n

Length of output: 12952


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import pathlib
p = pathlib.Path('plugins/sessions/init.lua')
needles = [
    'n00n.session.live()',
    'stored_map[st.id]',
    "seen[s.id]",
    'stored_map[s.id]',
    'seen[st.id]',
    'board.rank[s.id]',
    'board.items and board.items[idx] and board.items[idx].id or nil',
    'board.confirm ~= s.id',
    'board.confirm = s.id',
    'n00n.session.focus(s.id)',
    'n00n.session.delete(s.id)',
    'board.deleted[s.id]',
    'find_stored(s.id)',
    'board.rename = { id = s.id',
    'local id = board.rename.id',
    'n.id == id',
    'board.deleted[st.id]',
]
for i, line in enumerate(p.read_text().splitlines(), 1):
    for n in needles:
        if n in line:
            print(f"{i}: {line.strip()}")
            break
PY

printf '\n== lines 560-605 and 105-110 =='
sed -n '560,605p' plugins/sessions/init.lua
sed -n '100,112p' plugins/sessions/init.lua

Repository: w0wl0lxd/n00n

Length of output: 2292


Make nil session ids fail before building the tree.

refresh() indexes several tables by s.id / n.id before build_tree(), so a nil id is already rejected before line 258. build_tree() can fail separately, so either handle all nil ids up front or move this indexing check into normalize_session().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/sessions/init.lua` around lines 255 - 260, Ensure nil session IDs are
validated before tree construction begins, rather than relying on the by_id
assignment in build_tree. Add the validation in the shared normalize_session
path or perform a complete upfront check in refresh(), covering every session
before any s.id/n.id table indexing; preserve build_tree’s existing behavior for
valid IDs.

Comment thread site/docs/content/lua-api/_index.md Outdated
@w0wl0lxd
w0wl0lxd force-pushed the fix/agent-menu-visibility branch from 7bb5afb to f1e45b0 Compare August 3, 2026 23:54
@w0wl0lxd
w0wl0lxd enabled auto-merge August 3, 2026 23:58
@w0wl0lxd

w0wl0lxd commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 736ed639fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2011 to +2012
let decoder = Decoder::new(file).map_err(StorageError::from)?;
let mut reader = BufReader::new(decoder);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore bounded decoding for session logs

When a session .jsonl is corrupt or attacker-controlled, this unbounded decoder lets read_line grow the String to the full decompressed record and accepts arbitrary zstd window sizes; a small compressed file can therefore exhaust memory while sessions are listed or restored. This commit removes the prior decoded-byte, record-size, and window-log limits, so reinstate those limits before deserializing persisted file data.

AGENTS.md reference: AGENTS.md:L73-L78

Useful? React with 👍 / 👎.

@w0wl0lxd

w0wl0lxd commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- Add MAX_LIVE_SESSIONS and MAX_SESSION_DEPTH checks to ContinueSubagent
- Import shared TEAM_TOOL_NAME in tests instead of duplicating
- Replace map_or(..., std::convert::identity) with unwrap_or(...)
- Fix runtime_root and cancel_session_tree to include stored sessions in parent map
- Fix cancel_session_tree to collect IDs instead of indices to avoid invalidation
- Remove runtime when resume fails to prevent slot leak
- Import SharedTranscript instead of qualifying inline
- Document delegation_policy values in Lua API docs
- Do not truncate compaction history on transient server_overloaded errors.
- Check MCP tool exclusion before permission enforcement in tool dispatch.
- Replace disallowed unwrap_or calls with match expressions and a redundant closure with a method reference.
@w0wl0lxd

w0wl0lxd commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a02553fab5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread n00n-ui/src/event_loop.rs
Comment on lines +1395 to +1399
let parents: HashMap<_, _> = self
.sessions
.iter()
.map(|runtime| (runtime.id(), runtime.app.state.session.meta.parent_id))
.collect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include stored ancestors in depth validation

When a user focuses a stored descendant while its root or parent remains unloaded, this map contains only live runtimes. session_depth follows the descendant's persisted parent_id, cannot find that stored ancestor, and returns None, so any background task or team call using n00n.session.new is rejected as an "invalid session parent chain". Extend this map with stored_agent_sessions, as the ownership and cancellation paths already do.

Useful? React with 👍 / 👎.

Comment on lines +202 to +208
QueueItem::ResumeTeam { resume_id, .. } => {
self.do_direct_tool(
&event_tx,
run_id,
TEAM_TOOL_NAME,
&serde_json::json!({ "goal": "", "resume": resume_id }),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore paused team options before direct resume

When the paused run originally used waves=true, pressing r submits only an empty goal and the resume ID. plugins/team/init.lua later sees input.waves == nil and selects run_autonomous instead of run_waves, so the saved wave_index and step_index are ignored and work can restart through the wrong executor. Preserve the original team options in the checkpoint or paused payload and restore them in this direct invocation.

Useful? React with 👍 / 👎.

@w0wl0lxd

w0wl0lxd commented Aug 4, 2026

Copy link
Copy Markdown
Owner Author

This branch depends on the legacy session identity APIs (ToolContext.session_id, SessionMeta lifecycle/root_id, DelegationPolicy, TaskCell.session_id) that have been removed from main. Rebasing/merging would require a full rewrite against the new SessionIdentity model. Closing as stale/obsolete; a new PR should be opened if the feature is still needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant