odysseus/src
Kenny Van de Maele 8ce945d338
feat: Add plan mode to the chat agent (#638)
* feat: Add plan mode to the chat agent

Adds a plan mode: the agent investigates read-only, proposes a checklist, and
waits for approval before changing anything. On approval it runs with full
tools and checks items off as it goes. Enforcement reuses the existing
disabled_tools gate.

Includes a slash command: `/plan [on|off]` (and `/toggle plan`) to flip the
plan toggle from the chat input.

- src/tool_security.py, src/mcp_manager.py: read-only allowlist (tools + MCP).
- src/agent_loop.py, routes/chat_routes.py: union the disabled set, prepend the
  plan directive, force agent mode.
- static/: plan toggle pill, Approve & Run, dockable plan window, task-list
  checkboxes, and the /plan slash command.
- tests/test_plan_mode.py.

* Plan mode: persistent re-referenceable plan + agent write-back

Three improvements so a long plan survives a weak model and stays in reach:

1. Re-reference the plan (out-of-context fix). On the execution turn the frontend
   sends the approved checklist back (`approved_plan`); the backend pins it as a
   top-of-context `## ACTIVE PLAN` system note (kept by the context trimmer), so
   the agent can always re-read the plan instead of losing the thread on a long
   run. New `build_active_plan_note()` (unit-tested).

2. Re-open / dock the plan anytime. The plan checklist is stored per-session
   (localStorage). When a plan exists, the plan-mode button opens a small menu
   ("Show plan" / "Plan mode: On/Off") that re-opens the side-dockable plan
   window — so it can stay docked while the agent works. The window live-refreshes
   as the plan changes.

3. Agent write-back: new `update_plan` tool. The agent calls it to tick steps
   `- [x]` after finishing them, or to revise steps when the user asks. Marker
   tool (no I/O) → `plan_update` SSE event → the stored plan + docked window
   update live. The ACTIVE PLAN note instructs the agent to use it.

Backend: src/agent_loop.py (param + pin + note builder + emit + prompt blurb),
src/tool_execution.py (update_plan handler), routes/chat_routes.py (parse
`approved_plan`, relay `plan_update`), registration in tool_schemas / agent_tools
/ tool_index (always-available, not admin-gated).
Frontend: static/js/chat.js (plan store, send `approved_plan`, handle
`plan_update`, capture restated checklists), static/app.js (plan-button menu),
static/js/planWindow.js (`isPlanWindowOpen`), static/js/storage.js (PLAN key).
Tests: tests/test_plan_mode.py (plan-note), tests/test_update_plan_tool.py.

* Plan mode: drop bash/python, rely on read-only discovery tools

Shell can mutate (write files, hit the network) and can't be constrained to
read-only at the tool layer, so plan mode no longer relies on a prompt to keep
it well-behaved — bash/python are removed from the read-only allowlist and added
to the fail-closed block set. Discovery is covered by the dedicated read-only
tools (read_file, grep, glob, ls) instead.

Rewrites the plan-mode directive to state shell is disabled and lists the
available read-only tools positively. Addresses review feedback on #638.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Comment: note _MCP_READONLY_VERBS are prefixes not whole words

Clarifies that entries like "summar" are intentional stems matched via
startswith (covers summarise/summarize/summary), not typos. Addresses review
feedback on #638.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Plan mode: clarify why gating inverts the allowlist into a denylist

Rename _PLAN_MODE_FALLBACK_BLOCK -> _PLAN_MODE_KNOWN_MUTATORS and rewrite the
comments. The tool gate is a denylist (disabled_tools); plan mode's policy is an
allowlist, so it returns the inverse (all known tool names minus the allowlist).
The static mutator set is a backstop for the schema-derived name list, which
misses XML-only tools and can fail to import. Addresses review feedback on #638.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Plan mode: stop hardcoding the read-only tool list in the directive

The model is already shown its available (read-only) tools by _assemble_prompt,
which removes every disabled tool. Enumerating them again in the directive only
duplicated that list and would drift as tools change. Point at the tools listed
below instead. Addresses review feedback on #638.
2026-06-05 16:32:25 +02:00
..
search refactor(search): make src analytics a service shim (#2264) 2026-06-04 18:57:24 +02:00
action_intents.py fix(calendar): route read requests to agent (#2452) 2026-06-05 09:24:04 +01:00
agent_loop.py feat: Add plan mode to the chat agent (#638) 2026-06-05 16:32:25 +02:00
agent_runs.py Handle incomplete detached agent streams 2026-06-01 16:54:11 +09:00
agent_tools.py feat: Add plan mode to the chat agent (#638) 2026-06-05 16:32:25 +02:00
ai_interaction.py fix(ai): scope tool model resolution by owner 2026-06-04 00:37:28 +01:00
api_key_manager.py Stop API key save() from writing other providers' keys as plaintext (#1944) 2026-06-04 04:47:13 +01:00
app_helpers.py fix: inside_base_dir raises TypeError on a non-string path instead of failing closed (#1619) 2026-06-03 09:00:04 +09:00
app_initializer.py feat(memory): add provider interface (#72) 2026-06-04 16:26:11 +01:00
assistant_log.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
auth_helpers.py fix: require_privilege 500s on a non-dict privileges blob from auth.json (#1693) 2026-06-03 13:37:54 +09:00
bg_jobs.py Fix Windows Cookbook background tasks, exit statuses, and empty SSH logs wrapper (#1389) 2026-06-05 14:41:07 +02:00
bg_monitor.py Ignore non-string background stream deltas (#1549) 2026-06-03 14:11:45 +09:00
builtin_actions.py fix(actions): scope scheduled model resolution to owner (#2773) 2026-06-05 13:13:13 +02:00
builtin_mcp.py Add native Windows compatibility layer 2026-06-01 15:09:47 +09:00
caldav_sync.py fix(caldav): pull Google Calendar events from the events collection, not the /user principal (#2531) 2026-06-05 15:18:16 +02:00
caldav_writeback.py Harden DAV outbound URL validation (#2819) 2026-06-05 13:22:21 +02:00
chat_handler.py Use LM Studio-reported vision capability for image passthrough (#1130) 2026-06-02 23:01:04 +09:00
chat_helpers.py fix(vision): recognize Gemma 4 and Phi-4 as vision-capable models (#1704) 2026-06-03 13:36:50 +09:00
chat_processor.py Fix calendar routing and user-local time context (#408) 2026-06-04 13:20:04 +01:00
chroma_client.py fix: ChromaDB unreachable blocks app startup for 30-60s (#326) (#476) 2026-06-01 22:22:41 +09:00
cleanup_service.py Replace cleanup service datetime.utcnow calls (#1494) 2026-06-03 14:14:27 +09:00
config.py Add native Windows compatibility layer 2026-06-01 15:09:47 +09:00
constants.py Align SearXNG fallback URL 2026-06-01 10:50:07 +09:00
context_budget.py feat: adapt agent_input_token_budget to the model context window (#1170) (#1230) 2026-06-03 00:13:53 +09:00
context_compactor.py fix: context_compactor token helpers crash on non-string message text (#1634) 2026-06-03 14:12:14 +09:00
cookbook_serve_lifecycle.py Cookbook scheduler + serve: schedule via Tasks, Stop verifies kill, Ollama auto port-pick 2026-06-05 14:41:43 +09:00
copilot.py feat(provider): add GitHub Copilot provider with device-flow auth (#1480) 2026-06-04 21:13:14 +02:00
database.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
deep_research.py fix(research): support timeout defaults in direct tests (#2624) 2026-06-04 20:23:17 +02:00
document_actions.py fix: document tidy crashes on a duplicate with NULL timestamps (#1772) 2026-06-03 13:23:01 +09:00
document_processor.py fix: treat Nix files as readable uploads (#2249) 2026-06-04 12:06:24 +02:00
email_thread_parser.py Ignore non-string email thread bodies (#1654) 2026-06-03 14:06:31 +09:00
embeddings.py Add support for EMBEDDING_API_KEY (#2691) 2026-06-05 14:47:24 +02:00
endpoint_resolver.py feat(provider): add GitHub Copilot provider with device-flow auth (#1480) 2026-06-04 21:13:14 +02:00
event_bus.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
exceptions.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
generated_images.py Constrain generated-image paths to image root (#2837) 2026-06-05 10:33:47 +02:00
goal_based_extractor.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
integrations.py Ignore invalid integration rows (#1404) 2026-06-03 14:07:11 +09:00
llm_core.py fix(llm): route harmony thinking streams (#2449) 2026-06-05 15:22:08 +02:00
markitdown_runtime.py fix: is_markitdown_format crashes on a non-string path (#1618) 2026-06-03 09:00:10 +09:00
mcp_manager.py feat: Add plan mode to the chat agent (#638) 2026-06-05 16:32:25 +02:00
mcp_oauth.py feat(mcp): add Streamable HTTP transport with OAuth 2.0 (#1033) 2026-06-05 02:40:52 +02:00
memory.py refactor(memory): canonicalize memory imports (#50) 2026-06-04 05:31:15 +01:00
memory_provider.py feat(memory): add provider interface (#72) 2026-06-04 16:26:11 +01:00
memory_vector.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
model_context.py fix(model-context): count tool_calls in estimate_tokens so compaction sees real size (#2751) 2026-06-05 15:56:54 +02:00
model_discovery.py Reject invalid Tailscale discovery JSON (#1556) 2026-06-03 14:11:31 +09:00
pdf_form_doc.py fix(forms): keep PDF-form export from dropping values when the label has '*' (#1407) 2026-06-03 14:24:07 +09:00
pdf_forms.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
pdf_runtime.py Show a clear message when PyMuPDF is missing 2026-06-01 18:27:17 +09:00
personal_docs.py Skip malformed personal keyword index rows 2026-06-03 13:42:05 +09:00
preset_manager.py Fall back from invalid preset stores (#1402) 2026-06-03 14:12:31 +09:00
prompt_security.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
rag_manager.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
rag_singleton.py Re-enable VectorRAG init with lazy retry 2026-06-01 14:32:13 +09:00
rag_vector.py fix: RAG keyword fallback leaked owner-less documents across users (#1722) 2026-06-03 13:31:33 +09:00
rate_limiter.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
readiness.py feat: add /api/ready readiness probe (DB, data dir, local-first) (#1200) 2026-06-02 23:33:22 +09:00
request_models.py Odysseus v1.0 2026-05-31 23:58:26 +09:00
research_handler.py Constrain research handler JSON paths (#2846) 2026-06-05 13:20:02 +02:00
research_utils.py Treat non-string research summaries as low quality 2026-06-03 13:42:24 +09:00
secret_storage.py Add native Windows compatibility layer 2026-06-01 15:09:47 +09:00
session_actions.py Fix issue 135 chat context bleed (#281) 2026-06-04 13:27:46 +01:00
settings.py Revert calendar-based cookbook scheduler 2026-06-05 06:57:21 +09:00
settings_scrub.py Ignore non-object settings scrub inputs (#1645) 2026-06-03 14:11:05 +09:00
task_endpoint.py Harden session endpoint owner scope (#1308) 2026-06-03 02:40:22 +09:00
task_scheduler.py Cookbook scheduler + serve: schedule via Tasks, Stop verifies kill, Ollama auto port-pick 2026-06-05 14:41:43 +09:00
teacher_escalation.py fix: evaluate_turn_regex crashes on a non-string agent_reply (#1723) 2026-06-03 13:31:26 +09:00
text_helpers.py fix: normalize Gemma 4 thought-channel output (#2224) 2026-06-04 19:26:58 +02:00
tls_overrides.py Support extra CA bundle for private-CA LLM providers (#769) 2026-06-04 13:18:50 +01:00
tool_execution.py feat: Add plan mode to the chat agent (#638) 2026-06-05 16:32:25 +02:00
tool_implementations.py Calendar overnight-event rendering + clickable [View note] link from chat 2026-06-05 14:41:48 +09:00
tool_index.py feat: Add plan mode to the chat agent (#638) 2026-06-05 16:32:25 +02:00
tool_parsing.py fix: tool-block parsing crashes on a non-string input (#1628) 2026-06-03 08:59:42 +09:00
tool_schemas.py feat: Add plan mode to the chat agent (#638) 2026-06-05 16:32:25 +02:00
tool_security.py feat: Add plan mode to the chat agent (#638) 2026-06-05 16:32:25 +02:00
topic_analyzer.py Topics: hydrate session history before analysis 2026-06-02 20:44:27 +09:00
upload_handler.py fix: treat Nix files as readable uploads (#2249) 2026-06-04 12:06:24 +02:00
upload_limits.py fix(uploads): bound direct upload reads 2026-06-04 00:32:50 +01:00
url_safety.py fix: check_outbound_url crashes on a truthy non-string URL (#1623) 2026-06-03 08:59:49 +09:00
url_security.py Reapply "Merge branch 'main' of github.com:pewdiepie-archdaemon/odysseus" 2026-06-03 22:47:00 +09:00
user_time.py Fix calendar routing and user-local time context (#408) 2026-06-04 13:20:04 +01:00
visual_report.py security: sanitize rendered research-report HTML (#364) 2026-06-04 13:42:49 +01:00
webhook_manager.py Replace webhook manager datetime.utcnow calls (#1499) 2026-06-03 14:14:23 +09:00
youtube_handler.py fix: youtube transcript formatter crashes on a non-dict segment (#1745) 2026-06-03 13:29:08 +09:00