From 3d398f19050944bc18d9a7387204c26f6845e364 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 1 Mar 2026 10:26:01 -0500 Subject: [PATCH] remove main context --- .gemini/settings.json | 5 + MainContext.md | 283 ------------------------------------------ 2 files changed, 5 insertions(+), 283 deletions(-) delete mode 100644 MainContext.md diff --git a/.gemini/settings.json b/.gemini/settings.json index e505627..aa239f4 100644 --- a/.gemini/settings.json +++ b/.gemini/settings.json @@ -1,4 +1,9 @@ { + "workspace_folders": [ + "C:/projects/manual_slop", + "C:/projects/gencpp", + "C:/projects/VEFontCache-Odin" + ], "experimental": { "enableAgents": true }, diff --git a/MainContext.md b/MainContext.md deleted file mode 100644 index 195a786..0000000 --- a/MainContext.md +++ /dev/null @@ -1,283 +0,0 @@ -# Manual Slop - -## Summary - -Is a local GUI tool for manually curating and sending context to AI APIs. It aggregates files, screenshots, and discussion history into a structured markdown file and sends it to a chosen AI provider with a user-written message. The AI can also execute PowerShell scripts within the project directory, with user confirmation required before each execution. - -**Stack:** -- `dearpygui` - GUI with docking/floating/resizable panels -- `google-genai` - Gemini API -- `anthropic` - Anthropic API -- `tomli-w` - TOML writing -- `uv` - package/env management - -**Files:** -- `gui_legacy.py` - main GUI, `App` class, all panels, all callbacks, confirmation dialog, layout persistence, rich comms rendering; `[+ Maximize]` buttons in `ConfirmDialog` and `win_script_output` now pass text directly as `user_data` / read from `self._last_script` / `self._last_output` instance vars instead of `dpg.get_value(tag)` — fixes glitch when word-wrap is ON or dialog is dismissed before viewer opens -- `ai_client.py` - unified provider wrapper, model listing, session management, send, tool/function-call loop, comms log, provider error classification, token estimation, and aggressive history truncation -- `aggregate.py` - reads config, collects files/screenshots/discussion, builds `file_items` with `mtime` for cache optimization, writes numbered `.md` files to `output_dir` using `build_markdown_from_items` to avoid double I/O; `run()` returns `(markdown_str, path, file_items)` tuple; `summary_only=False` by default (full file contents sent, not heuristic summaries) -- `shell_runner.py` - subprocess wrapper that runs PowerShell scripts sandboxed to `base_dir`, returns stdout/stderr/exit code as a string -- `session_logger.py` - opens timestamped log files at session start; writes comms entries as JSON-L and tool calls as markdown; saves each AI-generated script as a `.ps1` file -- `project_manager.py` - per-project .toml load/save, entry serialisation (entry_to_str/str_to_entry with @timestamp support), default_project/default_discussion factories, migrate_from_legacy_config, flat_config for aggregate.run(), git helpers (get_git_commit, get_git_log) -- `theme.py` - palette definitions, font loading, scale, load_from_config/save_to_config -- `gemini.py` - legacy standalone Gemini wrapper (not used by the main GUI; superseded by `ai_client.py`) -- `file_cache.py` - stub; Anthropic Files API path removed; kept so stale imports don't break -- `mcp_client.py` - MCP-style tools (read_file, list_directory, search_files, get_file_summary, web_search, fetch_url); allowlist enforced against project file_items + base_dirs for file tools; web tools are unrestricted; dispatched by ai_client tool-use loop for both Anthropic and Gemini -- `summarize.py` - local heuristic summariser (no AI); .py via AST, .toml via regex, .md headings, generic preview; used by mcp_client.get_file_summary and aggregate.build_summary_section -- `config.toml` - global-only settings: [ai] provider+model+system_prompt, [theme] palette+font+scale, [projects] paths array + active path -- `manual_slop.toml` - per-project file: [project] name+git_dir+system_prompt+main_context, [output] namespace+output_dir, [files] base_dir+paths, [screenshots] base_dir+paths, [discussion] roles+active+[discussion.discussions.] git_commit+last_updated+history -- `credentials.toml` - gemini api_key, anthropic api_key -- `dpg_layout.ini` - Dear PyGui window layout file (auto-saved on exit, auto-loaded on startup); gitignore this per-user - -**GUI Panels:** -- **Projects** - active project name display (green), git directory input + Browse button, scrollable list of loaded project paths (click name to switch, x to remove), Add Project / New Project / Save All buttons -- **Config** - namespace, output dir, save (these are project-level fields from the active .toml) -- **Files** - base_dir, scrollable path list with remove, add file(s), add wildcard -- **Screenshots** - base_dir, scrollable path list with remove, add screenshot(s) -- **Discussion History** - discussion selector (collapsible header): listbox of named discussions, git commit + last_updated display, Update Commit button, Create/Rename/Delete buttons with name input; structured entry editor: each entry has collapse toggle (-/+), role combo, timestamp display, multiline content field; per-entry Ins/Del buttons when collapsed; global toolbar: + Entry, -All, +All, Clear All, Save; collapsible **Roles** sub-section; -> History buttons on Message and Response panels append current message/response as new entry with timestamp -- **Provider** - provider combo (gemini/anthropic), model listbox populated from API, fetch models button -- **Message** - multiline input, Gen+Send button, MD Only button, Reset session button, -> History button -- **Response** - readonly multiline displaying last AI response, -> History button -- **Tool Calls** - scrollable log of every PowerShell tool call the AI made; Clear button -- **System Prompts** - global (all projects) and project-specific multiline text areas for injecting custom system instructions. Combined with the built-in tool prompt. -- **Comms History** - rich structured live log of every API interaction; status line at top; colour legend; Clear button - -**Layout persistence:** -- `dpg.configure_app(..., init_file="dpg_layout.ini")` loads the ini at startup if it exists; DPG silently ignores a missing file -- `dpg.save_init_file("dpg_layout.ini")` is called immediately before `dpg.destroy_context()` on clean exit -- The ini records window positions, sizes, and dock node assignments in DPG's native format -- First run (no ini) uses the hardcoded `pos=` defaults in `_build_ui()`; after that the ini takes over -- Delete `dpg_layout.ini` to reset to defaults - -**Project management:** -- `config.toml` is global-only: `[ai]`, `[theme]`, `[projects]` (paths list + active path). No project data lives here. -- Each project has its own `.toml` file (e.g. `manual_slop.toml`). Multiple project tomls can be registered by path. -- `App.__init__` loads global config, then loads the active project `.toml` via `project_manager.load_project()`. Falls back to `migrate_from_legacy_config()` if no valid project file exists, creating a new `.toml` automatically. -- `_flush_to_project()` pulls widget values into `self.project` (the per-project dict) and serialises disc_entries into the active discussion's history list -- `_flush_to_config()` writes global settings ([ai], [theme], [projects]) into `self.config` -- `_save_active_project()` writes `self.project` to the active `.toml` path via `project_manager.save_project()` -- `_do_generate()` calls both flush methods, saves both files, then uses `project_manager.flat_config()` to produce the dict that `aggregate.run()` expects — so `aggregate.py` needs zero changes -- Switching projects: saves current project, loads new one, refreshes all GUI state, resets AI session -- New project: file dialog for save path, creates default project structure, saves it, switches to it - -**Discussion management (per-project):** -- Each project `.toml` stores one or more named discussions under `[discussion.discussions.]` -- Each discussion has: `git_commit` (str), `last_updated` (ISO timestamp), `history` (list of serialised entry strings) -- `active` key in `[discussion]` tracks which discussion is currently selected -- Creating a discussion: adds a new empty discussion dict via `default_discussion()`, switches to it -- Renaming: moves the dict to a new key, updates `active` if it was the current one -- Deleting: removes the dict; cannot delete the last discussion; switches to first remaining if active was deleted -- Switching: flushes current entries to project, loads new discussion's history, rebuilds disc list -- Update Commit button: runs `git rev-parse HEAD` in the project's `git_dir` and stores result + timestamp in the active discussion -- Timestamps: each disc entry carries a `ts` field (ISO datetime); shown next to the role combo; new entries from `-> History` or `+ Entry` get `now_ts()` - -**Entry serialisation (project_manager):** -- `entry_to_str(entry)` → `"@\n:\n"` (or `":\n"` if no ts) -- `str_to_entry(raw, roles)` → parses optional `@` prefix, then role line, then content; returns `{role, content, collapsed, ts}` -- Round-trips correctly through TOML string arrays; handles legacy entries without timestamps - -**AI Tool Use (PowerShell):** -- Both Gemini and Anthropic are configured with a `run_powershell` tool/function declaration -- When the AI wants to edit or create files it emits a tool call with a `script` string -- `ai_client` runs a loop (max `MAX_TOOL_ROUNDS = 10`) feeding tool results back until the AI stops calling tools -- Before any script runs, `gui_legacy.py` shows a modal `ConfirmDialog` on the main thread; the background send thread blocks on a `threading.Event` until the user clicks Approve or Reject -- The dialog displays `base_dir`, shows the script in an editable text box (allowing last-second tweaks), and has Approve & Run / Reject buttons -- On approval the (possibly edited) script is passed to `shell_runner.run_powershell()` which prepends `Set-Location -LiteralPath ''` and runs it via `powershell -NoProfile -NonInteractive -Command` -- stdout, stderr, and exit code are returned to the AI as the tool result -- Rejections return `"USER REJECTED: command was not executed"` to the AI -- All tool calls (script + result/rejection) are appended to `_tool_log` and displayed in the Tool Calls panel - -**Dynamic file context refresh (ai_client.py):** -- After the last tool call in each round, project files from `file_items` are checked via `_reread_file_items()`. It uses `mtime` to only re-read modified files, returning only the `changed` files to build a minimal `[FILES UPDATED]` block. -- For Anthropic: the refreshed file contents are injected as a `text` block appended to the `tool_results` user message, prefixed with `[FILES UPDATED]` and an instruction not to re-read them. -- For Gemini: refreshed file contents are appended to the last function response's `output` string as a `[SYSTEM: FILES UPDATED]` block. On the next tool round, stale `[FILES UPDATED]` blocks are stripped from history and old tool outputs are truncated to `_history_trunc_limit` characters to control token growth. -- `_build_file_context_text(file_items)` formats the refreshed files as markdown code blocks (same format as the original context) -- The `tool_result_send` comms log entry filters out the injected text block (only logs actual `tool_result` entries) to keep the comms panel clean -- `file_items` flows from `aggregate.build_file_items()` → `gui.py` `self.last_file_items` → `ai_client.send(file_items=...)` → `_send_anthropic(file_items=...)` / `_send_gemini(file_items=...)` -- System prompt updated to tell the AI: "the user's context files are automatically refreshed after every tool call, so you do NOT need to re-read files that are already provided in the block" - -**Anthropic bug fixes applied (session history):** -- Bug 1: SDK ContentBlock objects now converted to plain dicts via `_content_block_to_dict()` before storing in `_anthropic_history`; prevents re-serialisation failures on subsequent tool-use rounds -- Bug 2: `_repair_anthropic_history` simplified to dict-only path since history always contains dicts -- Bug 3: Gemini part.function_call access now guarded with `hasattr` check -- Bug 4: Anthropic `b.type == "tool_use"` changed to `getattr(b, "type", None) == "tool_use"` for safe access during response processing - -**Comms Log (ai_client.py):** -- `_comms_log: list[dict]` accumulates every API interaction during a session -- `_append_comms(direction, kind, payload)` called at each boundary: OUT/request before sending, IN/response after each model reply, OUT/tool_call before executing, IN/tool_result after executing, OUT/tool_result_send when returning results to the model -- Entry fields: `ts` (HH:MM:SS), `direction` (OUT/IN), `kind`, `provider`, `model`, `payload` (dict) -- Anthropic responses also include `usage` (input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens) and `stop_reason` in payload -- `get_comms_log()` returns a snapshot; `clear_comms_log()` empties it -- `comms_log_callback` (injected by gui_legacy.py) is called from the background thread with each new entry; gui queues entries in `_pending_comms` (lock-protected) and flushes them to the DPG panel each render frame -- `COMMS_CLAMP_CHARS = 300` in gui_legacy.py governs the display cutoff for heavy text fields - -**Comms History panel — rich structured rendering (gui_legacy.py):** - -Rather than showing raw JSON, each comms entry is rendered using a kind-specific renderer function. Unknown kinds fall back to a generic key/value layout. - -Colour maps: -- Direction: OUT = blue-ish `(100,200,255)`, IN = green-ish `(140,255,160)` -- Kind: request=gold, response=light-green, tool_call=orange, tool_result=light-blue, tool_result_send=lavender -- Labels: grey `(180,180,180)`; values: near-white `(220,220,220)`; dict keys/indices: `(140,200,255)`; numbers/token counts: `(180,255,180)`; sub-headers: `(220,200,120)` - -Helper functions: -- `_add_text_field(parent, label, value)` — labelled text; strings longer than `COMMS_CLAMP_CHARS` render as an 80px readonly scrollable `input_text`; shorter strings render as `add_text` -- `_add_kv_row(parent, key, val)` — single horizontal key: value row -- `_render_usage(parent, usage)` — renders Anthropic token usage dict in a fixed display order (input → cache_read → cache_creation → output) -- `_render_tool_calls_list(parent, tool_calls)` — iterates tool call list, showing name, id, and all args via `_add_text_field` - -Kind-specific renderers (in `_KIND_RENDERERS` dict, dispatched by `_render_comms_entry`): -- `_render_payload_request` — shows `message` field via `_add_text_field` -- `_render_payload_response` — shows round, stop_reason (orange), text, tool_calls list, usage block -- `_render_payload_tool_call` — shows name, optional id, script via `_add_text_field` -- `_render_payload_tool_result` — shows name, optional id, output via `_add_text_field` -- `_render_payload_tool_result_send` — iterates results list, shows tool_use_id and content per result -- `_render_payload_generic` — fallback for unknown kinds; renders all keys, using `_add_text_field` for keys in `_HEAVY_KEYS`, `_add_kv_row` for others; dicts/lists are JSON-serialised - -Entry layout: index + timestamp + direction + kind + provider/model header row, then payload rendered by the appropriate function, then a separator line. - -**Session Logger (session_logger.py):** -- `open_session()` called once at GUI startup; creates `logs/` and `scripts/generated/` directories; opens `logs/comms_.log` and `logs/toolcalls_.log` (line-buffered) -- `log_comms(entry)` appends each comms entry as a JSON-L line to the comms log; called from `App._on_comms_entry` (background thread); thread-safe via GIL + line buffering -- `log_tool_call(script, result, script_path)` writes the script to `scripts/generated/_.ps1` and appends a markdown record to the toolcalls log without the script body (just the file path + result); uses a `threading.Lock` for the sequence counter -- `close_session()` flushes and closes both file handles; called just before `dpg.destroy_context()` - -**Anthropic prompt caching & history management:** -- System prompt + context are combined into one string, chunked into <=120k char blocks, and sent as the `system=` parameter array. Only the LAST chunk gets `cache_control: ephemeral`, so the entire system prefix is cached as one unit. -- Last tool in `_ANTHROPIC_TOOLS` (`run_powershell`) has `cache_control: ephemeral`; this means the tools prefix is cached together with the system prefix after the first request. -- The user message is sent as a plain `[{"type": "text", "text": user_message}]` block with NO cache_control. The context lives in `system=`, not in the first user message. -- `_add_history_cache_breakpoint` places `cache_control:ephemeral` on the last content block of the second-to-last user message, using the 4th cache breakpoint to cache the conversation history prefix. -- `_trim_anthropic_history` uses token estimation (`_CHARS_PER_TOKEN = 3.5`) to keep the prompt under `_ANTHROPIC_MAX_PROMPT_TOKENS = 180_000`. It strips stale file refreshes from old turns, and drops oldest turn pairs if still over budget. -- The tools list is built once per session via `_get_anthropic_tools()` and reused across all API calls within the tool loop, avoiding redundant Python-side reconstruction. -- `_strip_cache_controls()` removes stale `cache_control` markers from all history entries before each API call, ensuring only the stable system/tools prefix consumes cache breakpoint slots. -- Cache stats (creation tokens, read tokens) are surfaced in the comms log usage dict and displayed in the Comms History panel - -**Data flow:** -1. GUI edits are held in `App` state (`self.files`, `self.screenshots`, `self.disc_entries`, `self.project`) and dpg widget values -2. `_flush_to_project()` pulls all widget values into `self.project` dict (per-project data) -3. `_flush_to_config()` pulls global settings into `self.config` dict -4. `_do_generate()` calls both flush methods, saves both files, calls `project_manager.flat_config(self.project, disc_name)` to produce a dict for `aggregate.run()`, which writes the md and returns `(markdown_str, path, file_items)` -5. `cb_generate_send()` calls `_do_generate()` then threads a call to `ai_client.send(md, message, base_dir)` -6. `ai_client.send()` prepends the md as a `` block to the user message and sends via the active provider chat session -7. If the AI responds with tool calls, the loop handles them (with GUI confirmation) before returning the final text response -8. Sessions are stateful within a run (chat history maintained), `Reset` clears them, the tool log, and the comms log - -**Config persistence:** -- `config.toml` — global only: `[ai]` provider+model, `[theme]` palette+font+scale, `[projects]` paths array + active path -- `.toml` — per-project: output, files, screenshots, discussion (roles, active discussion name, all named discussions with their history+metadata) -- On every send and save, both files are written -- On clean exit, `run()` calls `_flush_to_project()`, `_save_active_project()`, `_flush_to_config()`, `save_config()` before destroying context - -**Threading model:** -- DPG render loop runs on the main thread -- AI sends and model fetches run on daemon background threads -- `_pending_dialog` (guarded by a `threading.Lock`) is set by the background thread and consumed by the render loop each frame, calling `dialog.show()` on the main thread -- `dialog.wait()` blocks the background thread on a `threading.Event` until the user acts -- `_pending_comms` (guarded by a separate `threading.Lock`) is populated by `_on_comms_entry` (background thread) and drained by `_flush_pending_comms()` each render frame (main thread) - -**Provider error handling:** -- `ProviderError(kind, provider, original)` wraps upstream API exceptions with a classified `kind`: quota, rate_limit, auth, balance, network, unknown -- `_classify_anthropic_error` and `_classify_gemini_error` inspect exception types and status codes/message bodies to assign the kind -- `ui_message()` returns a human-readable label for display in the Response panel - -**MCP file tools (mcp_client.py + ai_client.py):** -- Four read-only tools exposed to the AI as native function/tool declarations: `read_file`, `list_directory`, `search_files`, `get_file_summary` -- Access control: `mcp_client.configure(file_items, extra_base_dirs)` is called before each send; builds an allowlist of resolved absolute paths from the project's `file_items` plus the `base_dir`; any path that is not explicitly in the list or not under one of the allowed directories returns `ACCESS DENIED` -- `mcp_client.dispatch(tool_name, tool_input)` is the single dispatch entry point used by both Anthropic and Gemini tool-use loops; `TOOL_NAMES` set now includes all six tool names -- Anthropic: MCP tools appear before `run_powershell` in the tools list (no `cache_control` on them; only `run_powershell` carries `cache_control: ephemeral`) -- Gemini: MCP tools are included in the `FunctionDeclaration` list alongside `run_powershell` -- `get_file_summary` uses `summarize.summarise_file()` — same heuristic used for the initial `` block, so the AI gets the same compact structural view it already knows -- `list_directory` sorts dirs before files; shows name, type, and size -- `search_files` uses `Path.glob()` with the caller-supplied pattern (supports `**/*.py` style) -- `read_file` returns raw UTF-8 text; errors (not found, access denied, decode error) are returned as error strings rather than exceptions, so the AI sees them as tool results -- `web_search(query)` queries DuckDuckGo HTML endpoint and returns the top 5 results (title, URL, snippet) as a formatted string; uses a custom `_DDGParser` (HTMLParser subclass) -- `fetch_url(url)` fetches a URL, strips HTML tags/scripts via `_TextExtractor` (HTMLParser subclass), collapses whitespace, and truncates to 40k chars to prevent context blowup; handles DuckDuckGo redirect links automatically -- `summarize.py` heuristics: `.py` → AST imports + ALL_CAPS constants + classes+methods + top-level functions; `.toml` → table headers + top-level keys; `.md` → h1–h3 headings with indentation; all others → line count + first 8 lines preview -- Comms log: MCP tool calls log `OUT/tool_call` with `{"name": ..., "args": {...}}` and `IN/tool_result` with `{"name": ..., "output": ...}`; rendered in the Comms History panel via `_render_payload_tool_call` (shows each arg key/value) and `_render_payload_tool_result` (shows output) - -**Known extension points:** -- Add more providers by adding a section to `credentials.toml`, a `_list_*` and `_send_*` function in `ai_client.py`, and the provider name to the `PROVIDERS` list in `gui_legacy.py` -- Discussion history excerpts could be individually toggleable for inclusion in the generated md -- `MAX_TOOL_ROUNDS` in `ai_client.py` caps agentic loops at 10 rounds; adjustable -- `COMMS_CLAMP_CHARS` in gui_legacy.py controls the character threshold for clamping heavy payload fields in the Comms History panel -- Additional project metadata (description, tags, created date) could be added to `[project]` in the per-project toml - -### Gemini Context Management -- Gemini uses explicit caching via `client.caches.create()` to store the `system_instruction` + tools as an immutable cached prefix with a 1-hour TTL. The cache is created once per chat session. -- Proactively rebuilds cache at 90% of `_GEMINI_CACHE_TTL = 3600` to avoid stale-reference errors. -- When context changes (detected via `md_content` hash), the old cache is deleted, a new cache is created, and chat history is migrated to a fresh chat session pointing at the new cache. -- Trims history by dropping oldest pairs if input tokens exceed `_GEMINI_MAX_INPUT_TOKENS = 900_000`. -- If cache creation fails (e.g., content is under the minimum token threshold — 1024 for Flash, 4096 for Pro), the system falls back to inline `system_instruction` in the chat config. Implicit caching may still provide cost savings in this case. -- The `` block lives inside `system_instruction`, NOT in user messages, preventing history bloat across turns. -- On cleanup/exit, active caches are deleted via `ai_client.cleanup()` to prevent orphaned billing. - -### Latest Changes -- Removed `Config` panel from the GUI to streamline per-project configuration. -- `output_dir` was moved into the Projects panel. -- `auto_add_history` was moved to the Discussion History panel. -- `namespace` is no longer a configurable field; `aggregate.py` automatically uses the active project's `name` property. - -### UI / Visual Updates -- The success blink notification on the response text box is now dimmer and more transparent to be less visually jarring. -- Added a new floating **Last Script Output** popup window. This window automatically displays and blinks blue whenever the AI executes a PowerShell tool, showing both the executed script and its result in real-time. - - -## Recent Changes (Text Viewer Maximization) -- **Global Text Viewer (gui_legacy.py)**: Added a dedicated, large popup window (win_text_viewer) to allow reading and scrolling through large, dense text blocks without feeling cramped. -- **Comms History**: Every multi-line text field in the comms log now has a [+] button next to its label that opens the text in the Global Text Viewer. -- **Tool Log History**: Added [+ Script] and [+ Output] buttons next to each logged tool call to easily maximize and read the full executed scripts and raw tool outputs. -- **Last Script Output Popup**: Expanded the default size of the popup (now 800x600) and gave the input script panel more vertical space to prevent it from feeling 'scrunched'. Added [+ Maximize] buttons for both the script and the output sections to inspect them in full detail. -- **Confirm Dialog**: The script confirmation modal now has a [+ Maximize] button so you can read large generated scripts in full-screen before approving them. - -## UI Enhancements (2026-02-21) - -### Global Word-Wrap - -A new **Word-Wrap** checkbox has been added to the **Projects** panel. This setting is saved per-project in its .toml file. - -- When **enabled** (default), long text in read-only panels (like the main Response window, Tool Call outputs, and Comms History) will wrap to fit the panel width. -- When **disabled**, text will not wrap, and a horizontal scrollbar will appear for oversized content. - -This allows you to choose the best viewing mode for either prose or wide code blocks. - -### Maximizable Discussion Entries - -Each entry in the **Discussion History** now features a [+ Max] button. Clicking this button opens the full text of that entry in the large **Text Viewer** popup, making it easy to read or copy large blocks of text from the conversation history without being constrained by the small input box. -\n\n## Multi-Viewport & Docking\nThe application now supports Dear PyGui Viewport Docking. Windows can be dragged outside the main application area or docked together. A global 'Windows' menu in the viewport menu bar allows you to reopen any closed panels. - -## Extensive Documentation (2026-02-22) - -Documentation has been completely rewritten matching the strict, structural format of `VEFontCache-Odin`. -- `docs/guide_architecture.md`: Details the Python implementation algorithms, queue management for UI rendering, the specific AST heuristics used for context aggregation, and the distinct algorithms for trimming Anthropic history vs Gemini state caching. -- `docs/Readme.md`: The core interface manual. -- `docs/guide_tools.md`: Security architecture for `_is_allowed` paths and definitions of the read-only vs destructive tool pipeline. - - - - -## Updates (2026-02-22 — ai_client.py & aggregate.py) - -### mcp_client.py — Web Tools Added -- `web_search(query)` and `fetch_url(url)` added as two new MCP tools alongside the existing four file tools. -- `TOOL_NAMES` set updated to include all six tool names for dispatch routing. -- `MCP_TOOL_SPECS` list extended with full JSON schema definitions for both web tools. -- Both tools are declared in `_build_anthropic_tools()` and `_gemini_tool_declaration()` so they are available to both providers. -- Web tools bypass the `_is_allowed` path check (no filesystem access); file tools retain the allowlist enforcement. - -### aggregate.py — run() double-I/O elimination -- `run()` now calls `build_file_items()` once, then passes the result to `build_markdown_from_items()` instead of calling `build_files_section()` separately. This avoids reading every file twice per send. -- `build_markdown_from_items()` accepts a `summary_only` flag (default `False`); when `False` it inlines full file content; when `True` it delegates to `summarize.build_summary_markdown()` for compact structural summaries. -- `run()` returns a 3-tuple `(markdown_str, output_path, file_items)` — the `file_items` list is passed through to `gui_legacy.py` as `self.last_file_items` for dynamic context refresh after tool calls. - - -## Updates (2026-02-22 — gui_legacy.py [+ Maximize] bug fix) - -### Problem -Three `[+ Maximize]` buttons were reading their text content via `dpg.get_value(tag)` at click time: -1. `ConfirmDialog.show()` — passed `f"{self._tag}_script"` as `user_data` and called `dpg.get_value(u)` in the lambda. If the dialog was dismissed before the viewer opened, the item no longer existed and the call would fail silently or crash. -2. `win_script_output` Script `[+ Maximize]` — used `user_data="last_script_text"` and `dpg.get_value(u)`. When word-wrap is ON, `last_script_text` is hidden (`show=False`); in some DPG versions `dpg.get_value` on a hidden `input_text` returns `""`. -3. `win_script_output` Output `[+ Maximize]` — same issue with `"last_script_output"`. - -### Fix -- `ConfirmDialog.show()`: changed `user_data` to `self._script` (the actual text string captured at button-creation time) and the callback to `lambda s, a, u: _show_text_viewer("Confirm Script", u)`. The text is now baked in at dialog construction, not read from a potentially-deleted widget. -- `App._append_tool_log()`: added `self._last_script = script` and `self._last_output = result` assignments so the latest values are always available as instance state. -- `win_script_output` buttons: both `[+ Maximize]` buttons now use `lambda s, a, u: _show_text_viewer("...", self._last_script/output)` directly, bypassing DPG widget state entirely.