Skip to content

docs: fix duplicate word in README configuration section#309

Open
meichuanyi wants to merge 455 commits into
agentforce314:mainfrom
meichuanyi:docs/fix-readme-config-duplicate-in-20260612
Open

docs: fix duplicate word in README configuration section#309
meichuanyi wants to merge 455 commits into
agentforce314:mainfrom
meichuanyi:docs/fix-readme-config-duplicate-in-20260612

Conversation

@meichuanyi

Copy link
Copy Markdown

Summary

  • Remove a duplicated in from the README configuration section.

Testing

  • Documentation-only change; no tests run.

ericleepi314 and others added 30 commits May 16, 2026 02:59
…out-distinguishable-from-esc

fix(bash): distinguish timeout from ESC-abort in tool_result
…-abort-isolation

test(esc): pin async-subagent abort-controller isolation invariants
Brings the Read tool's image handling to full parity with the TypeScript
reference at typescript/src/utils/imageResizer.ts + FileReadTool.ts image
flow. Pillow replaces sharp.

What changes for the user:
- Oversized images (>3.75 MB) are now downscaled to fit the API's 5 MB
  base64 limit instead of being rejected outright.
- File extension is no longer trusted blindly — magic-byte detection
  sniffs the actual format (PNG/JPEG/GIF/WebP), so a misnamed file.png
  containing JPEG bytes gets media_type=image/jpeg correctly.
- Read emits a supplemental isMeta UserMessage carrying image dimensions
  ("Multiply coordinates by X to map to original image") for
  coordinate-mapping prompts.
- The PDF Read with pages="N-M" parameter now extracts page images via
  poppler's pdftoppm and routes them through the same image pipeline.
- Bash commands that print data:image/...;base64,... (matplotlib,
  mermaid, etc.) surface as image content blocks in the tool_result
  instead of garbage text.
- Images >5 MB base64 are rejected pre-API with an actionable error
  rather than letting the API round-trip fail.

What changes architecturally:
- New module src/utils/image_processor.py: magic-byte detection,
  bounded readFileBytes, maybe_resize_image (3.75 MB / 1568px envelope,
  JPEG fallback at q={80,60,40,20}), compress_image_to_byte_budget
  (progressive scale × quality, 800×800 PNG palette, 400×400 q=20
  ultra-fallback), compress_image_to_token_budget,
  create_image_metadata_text.
- New module src/utils/image_validation.py: validate_images_for_api
  walks messages, rejects any base64 image >5 MB before the API call.
  Wired into claude.py:call_model.
- New module src/utils/pdf_extraction.py: extract_pdf_pages shells out
  to pdftoppm, 100 MB input cap, empty-file guard, install-hint error
  when poppler-utils is missing.
- New module src/tool_system/tools/bash/image_output.py:
  is_image_output / parse_data_uri / build_image_tool_result for shell
  image output, with 25 MB pre-decode cap to prevent OOM from hostile
  shells.
- src/tool_system/tools/read.py image branch rewritten: bounded read
  -> magic-byte sniff -> resize -> token-budget compress -> returns
  type='image' with dimensions field + supplemental metadata message.
  PDF pages= path wired with try/finally tempdir cleanup. Old
  MAX_IMAGE_SIZE_BYTES rejection removed.
- src/query/query.py _dispatch_single_tool now returns
  tuple[UserMessage, list[UserMessage]] (primary, extras) so
  result.new_messages reach the model. Callers concatenate all
  primaries first, then all extras, so multi-tool batches don't break
  ensure_tool_result_pairing (a regression the critic caught and the
  primaries-first ordering fixes).
- New EventType.IMAGE_PROCESSING analytics event with subtype in data.

Dependency: Pillow>=10.0 (pure-Python wheels on all platforms).
System dependency for PDF page extraction (optional, runtime-detected):
pdftoppm from poppler-utils.

Tests: 60+ new tests across tests/test_image_processor.py,
test_image_validation.py, test_bash_image_output.py,
test_pdf_extraction.py, plus 8 added to tests/parity/test_e2e_file_read.py.
The critical regression test test_multi_tool_batch_preserves_tool_result_pairing
drives _run_tools_partitioned -> normalize_messages_for_api end-to-end to
lock in the primaries-first ordering. Wider parity suite: 442 pass,
4 pre-existing unrelated failures.

Critic review loop completed with APPROVE after two rounds.

See my-docs/image-handling-gap-analysis.md and
my-docs/image-handling-refactoring-plan.md for the full analysis.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ndling-parity

feat(read): port TS image-handling pipeline to Python (Tier C parity)
…mage tool_result shape

User-reported correctness bug: the Python build hallucinated wildly when
asked about an @-mentioned image ("a Google search results page") while
TS correctly described it ("an aerial view of a research ship"). Root
cause was two independent bugs both contributing to the failure.

Bug A: ``expand_at_mentions`` opened every @-mentioned file with
``open(path, "r", encoding="utf-8", errors="replace")`` regardless of
extension. For a PNG that produced mojibake (utf-8 replacement chars
over binary bytes) which ``format_at_mention_attachments`` wrapped in a
``<system-reminder>Contents of foo.png:`` block and prepended to the
user message. The model latched onto ASCII fragments (XMP "Screenshot"
metadata, type tags) and hallucinated. Fix: detect image extensions
(png/jpg/jpeg/gif/webp, matching the Read tool's IMAGE_EXTENSIONS)
BEFORE the text-mode open. For image files, run the same image
pipeline the Read tool now uses (post PR agentforce314#154): bounded read ->
magic-byte format sniff -> resize-to-envelope ->
``compress_image_to_token_budget`` fallback when still over the 5 MB
base64 API ceiling. Return a ``kind="image"`` attachment carrying
``base64`` + ``media_type``. New helper ``build_image_content_blocks``
materialises ``ImageBlock`` instances and the REPL composes a mixed
``[TextBlock, ImageBlock, ...]`` user message so the API receives a
real multimodal payload matching the TS auto-Read-on-@image behaviour.

Bug B: ``_dispatch_single_tool`` ran ``json.dumps`` on any non-string
tool_result content, turning Read's properly-shaped image list
``[{"type": "image", "source": {...}}]`` into a text JSON blob. The
Anthropic API then received an image tool_result whose content was
text JSON; the model literally could not see the image and would
hallucinate. PR agentforce314#154's regression test only checked for the synthetic-
error placeholder so this slipped through. Fix: preserve list shape
end-to-end. ``ToolResultBlock.content`` already accepts ``str |
list[Any]``, ``maybe_persist_large_tool_result`` already short-
circuits image lists via ``_has_image_block``, and
``content_block_to_dict`` already serializes per-element -- the
dispatcher just needed to stop coercing.

Collateral fix: PR agentforce314#154's Read tool image content also failed silently
on OpenAI-compatible providers (GLM, Minimax, DeepSeek, OpenRouter)
because ``_convert_anthropic_messages_to_openai`` passed Anthropic
image blocks through unchanged and OpenAI rejects them. New helper
``_anthropic_image_block_to_openai`` translates the base64-source
shape to OpenAI's ``image_url`` data-URI shape. For tool_result image
content the converter now emits ``role=tool`` (text body, with a
placeholder when the original was image-only) followed by a synthetic
``role=user`` message carrying the image_url blocks, since OpenAI's
``role=tool`` doesn't accept multimodal content. Empty-data guard
returns ``None`` rather than producing ``data:image/png;base64,``.

Signature widening to support the multi-block flow:
- ``Conversation.add_user_message(content: str | list[ContentBlock])``
- ``QueryEngine.submit_message(prompt: str | list[ContentBlock])``
Both bodies already supported list content via ``_normalize_message_
content`` / ``MessageContent``; only the type annotations changed, so
existing string-callers (TUI, headless) are unaffected.

REPL UX: image @-mentions skip the direct-stream short-circuit (it
can only carry plain text) and print ``Read image <path>`` instead of
``Listed file <path>`` so the user sees the image was attached.

Test coverage (29 new tests):
- tests/test_at_mention_images.py (17): single + multi-image + mixed
  text+image @-mentions, magic-byte detection beats extension, empty/
  undecodable images dropped silently, oversize-image compression
  brings base64 under API limit (real 4000x4000 random-noise PNG),
  doubly-oversize image dropped (monkeypatched), end-to-end through
  ``normalize_messages_for_api``.
- tests/test_openai_compat_image_translation.py (13): user message
  text+image, multi-image, JPEG/PNG/missing-media-type defaults,
  empty-data guard, tool_result image-only + image+text + text-only-
  no-regression.
- tests/parity/test_e2e_file_read.py (+2): Bug B dispatcher list-
  preservation; aggregate-budget pressure path (image still survives
  when ``tool_result_chars_so_far = MAX - 1``, counter NOT bumped by
  image bytes).

Wider suite: 4984 pass, 0 new failures, 9 pre-existing failures (mcp/
zhipuai import issues + workspace-path tests) unchanged.

Critic review loop: APPROVE after three rounds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…mention-hallucination

fix: @image.png mentions hallucinate; image tool_result stringified
Addresses the four pre-existing gaps the PR agentforce314#155 audit surfaced:

1. validate_images_for_api ran only on the Anthropic-direct call_model
   path; all provider.chat_stream_response paths skipped it. Promoted
   into BaseProvider._prepare_messages so every provider validates
   client-side. query._call_model_sync catches ImageSizeError and
   surfaces a media_size assistant error (matches the existing
   server-side classification). agent_loop._call_provider_for_turn
   re-raises ImageSizeError instead of falling back to chat() (which
   would re-trigger the same validation). Validator also now walks
   images nested inside tool_result.content — post-PR agentforce314#154 the Read
   tool returns image blocks there and the old walker missed them.

2. OpenAI tool->user split symmetric correlation. Tool message body
   now always carries [multimodal content for tool_use_id=X ...] when
   the synthetic role=user message follows; the user message leads
   with a [content for tool_use_id=X] text block so the link is
   visible from both directions. Empty content sentinel ([empty tool
   result]) guards the "non-empty content required" invariant for
   content=[] cases.

3. @file.pdf and other binary @-mentions no longer hit the text-mode
   open() that produced mojibake-in-system-reminder. Known binary
   extensions (pdf/zip/docx/...) plus a NUL-byte content sniff route
   to a new "binary" attachment kind with a Read-tool hint (PDF
   specifically mentions the pages parameter). BOM-aware decoder
   (_read_text_with_encoding) handles Windows-emitted UTF-16/UTF-32
   files via the BOM-aware codecs (utf-16, utf-32, utf-8-sig) so the
   BOM is consumed rather than preserved as a literal U+FEFF char.
   Post-decode garbled-text detector (NUL zero-tolerance + 3% U+FFFD
   threshold) falls back to a binary attachment when an adversarial
   spoofed-BOM blob would otherwise leak.

4. _anthropic_image_block_to_openai now has a sibling
   _anthropic_document_block_to_openai that translates
   Anthropic DocumentBlock (PDF) to OpenAI's {type:"file"} shape.
   Defensive: no production path currently produces DocumentBlock
   for OpenAI-compat, but future ones won't silently pass through.

Test plan:
- 94 new tests across test_at_mention_binary_files.py,
  test_base_provider_image_validation.py,
  test_agent_loop_image_size_propagation.py,
  test_openai_compat_document_translation.py, plus extensions to
  test_image_validation.py, test_openai_compat_image_translation.py,
  test_query_loop.py.
- Wider suite: 5015 pass / 10 pre-existing failures unchanged.
- Critic review loop: APPROVE after three rounds (round-2 caught a
  UTF-16 BOM regression I introduced; round-3 fixed it and added the
  post-decode garble detector).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…dling-audit-followups

fix(image-handling): close 4 audit follow-ups from PR agentforce314#155
- Codebase stats: 894 files / 177,428 lines (up from 167,034 on
  2026-05-14; ~+10.4k lines in two days).
- New news entries:
  * 2026-05-16 Image-handling parity (agentforce314#149/agentforce314#154/agentforce314#155/agentforce314#156) — Read tool
    TS image pipeline; @image.png mention fix; image tool_result list
    shape preserved; OpenAI-compat image/document block translation;
    validate_images_for_api promoted into BaseProvider._prepare_messages;
    BOM-aware text decoding.
  * 2026-05-16 Subagent + Bash reliability — custom subagent discovery
    from .claude/agents/ (agentforce314#151); Bash timeout vs ESC-abort distinction
    (agentforce314#152); async-subagent AbortController isolation tests (agentforce314#153);
    REJECT_MESSAGE on cancelled production path (agentforce314#150).
  * 2026-05-15 to 2026-05-16 ESC cancellation hardening across providers
    (agentforce314#144agentforce314#148) — mid-stream cancel under ~50ms for every provider;
    shared StreamAbortGuard; LiteLLM worker-thread iteration fix.
- Core Systems table:
  * Multi-Provider description extends to call out Anthropic→OpenAI
    image/document block translation.
  * New "Cancellation / Abort" row (✅) capturing the recent sweep.
  * New "Image Handling" row (✅) capturing Tier C parity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pdate-2026-05-16

docs(readme): refresh stats + news + Core Systems for image/ESC work
Bring it in line with the length of the surrounding entries: four
facts joined by semicolons, no inline rationale or low-level
implementation details. The longer write-up lives in the PR
descriptions for agentforce314#149/agentforce314#154/agentforce314#155/agentforce314#156.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…horten-image-parity-news

docs(readme): shorten 2026-05-16 image-handling-parity news item
When OpenRouter routes a request to a text-only model (e.g.
deepseek/deepseek-v4-pro) and the user @-mentions an image, the
provider returns 404 "No endpoints found that support image input".
Until now the offending UserMessage stayed in engine and conversation
history, so every subsequent text-only prompt re-sent the image and
re-triggered the same 404 — the session became unusable.

Classify the error in _call_model_sync (mirrors TS's PDF-invalid /
image-too-large handlers in errors.ts), tag the AssistantMessage with
_api_error="image_unsupported", and surface a friendly message that
explains the image was dropped. The engine then strips image blocks
from _mutable_messages via the existing strip_images_from_typed_messages
helper; the REPL mirrors the strip into session.conversation so the
direct-stream path and persisted JSONL stay consistent. TS expects the
user to manually rewind via the Ink MessageSelector, which the Rich
REPL doesn't have — auto-strip fills that UI gap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…r-image-unsupported-context-stuck

fix(openrouter): break image_unsupported context-stuck loop
The parent agent's live stderr feed during a sync subagent run rendered
``⎿ [type] Name(...)`` for every nested tool use — the literal ``(...)``
was hard-coded, so the user couldn't tell which files the subagent was
reading. TS parity surface is ``FileReadTool.ts:369``'s
``getActivityDescription``; the closest Python equivalent (``summarize_tool_use``)
already existed but wasn't wired in.

Route nested tool_use blocks through ``summarize_tool_use`` via a new
``_format_subagent_tool_use`` helper that flattens newlines, caps at
200 chars, and falls back to bare ``Name`` (not ``Name()``) on empty
summaries. 12 tests pin the formatter contract, the call-site wiring,
and the legacy ``(...)`` regression.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…gent-read-display

fix(agent): show real tool input in subagent progress lines
Gemini's OpenAI-compatible endpoint rejects clawcodex's typical request
shape (content-block arrays without explicit ``type``, JSON Schema with
``oneOf`` / ``additionalProperties``). Native Gemini avoids the compat
layer entirely.

* New ``GeminiProvider`` (``src/providers/gemini_provider.py``):
  - Uses ``google-genai`` SDK against
    ``https://generativelanguage.googleapis.com/v1beta/openai/``
  - Converts Anthropic-style messages (text / tool_use / tool_result
    content blocks) into Gemini ``Content`` / ``Part`` structures with
    role normalization (``assistant`` -> ``model``).
  - Recursively sanitizes JSON-Schema tool definitions for Gemini's
    strict ``Schema``: drops ``oneOf`` / ``anyOf`` / ``allOf`` / ``not``
    (polymorphic params collapse to permissive ``string``),
    ``additionalProperties``, ``patternProperties``, ``$ref`` / ``$defs``,
    conditional / dependency keywords, array extensions, and a few
    documentation hints Gemini rejects. Per-tool conversion failures
    drop just that tool rather than failing the whole request.
  - Auth follows ``src/auth/gemini.py`` (``GEMINI_API_KEY`` /
    ``GOOGLE_API_KEY`` env or the ``gemini`` provider entry in
    ``~/.clawcodex/config.json``).
  - ``chat_stream`` / ``chat_stream_response`` fall back to non-streaming
    for the first cut; ``chat()`` covers the production path used by
    headless ``-p`` mode.

* ``PROVIDER_INFO`` and ``get_provider_class`` registered the new
  provider name; ``--provider gemini`` is now a first-class CLI value.

Smoke: ``clawcodex -p \"reply hi\" --provider gemini --model
gemini-2.5-pro`` round-trips in <2s. Full SWE-bench Verified run on
Gemini 2.5 Pro resolved 291/499 instances.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drives a side-by-side eval of clawcodex against the openclaude reference
on SWE-bench Verified, using the same model on both sides so the
comparison isolates agent-harness quality.

Workflow (all in ``eval/``):

* ``run_compare.py`` — single-command driver. Spawns each agent as a
  FastAPI wrapper (clawcodex_api_server / openclaude_api_server in the
  SWE-bench-dev fork), runs ``swebench.harness.run_evaluation`` against
  the resulting predictions, diffs the two summary JSONs, writes
  ``comparison.md`` plus per-bucket triage lists.
  - ``--cumulative`` — predictions append, harness scans whole preds
    file. Each invocation grows the result; ideal for "start with 50,
    add 50 more" iteration loops on the way to full Verified.
  - ``--capture-traces`` — wrappers emit stream-json per instance to
    ``traces/<agent>/<instance_id>.jsonl`` for turn-by-turn analysis.
  - ``--predict-workers N`` — concurrent in-flight predictions via
    ThreadPoolExecutor with a file write-lock. 3-5x speedup at N=4 if
    the model API tolerates it.

* ``pick_batch.py`` — picks the next N unseen instances from a target
  dataset given an existing predictions file. ``--random`` for uniform
  sampling (recommended for unbiased cumulative estimates) with
  optional ``--seed`` for reproducibility; default ``stratified``
  rotates one-per-repo.

* ``repair_preds.py`` — offline retrofit for an existing predictions
  JSONL: re-extracts the diff, recomputes ``@@ -a,b +c,d @@`` hunk
  line counts from the body, ensures trailing newline, rewrites
  ``model_patch`` in place. Same recomputer is wired into
  ``run_custom_api.py`` so new predictions are clean automatically.

* ``compare_results.py`` — standalone summary differ used by both the
  driver and ad-hoc analysis. Renders the headline table and the
  ``only_<agent>.txt`` / ``both_solved.txt`` / ``neither_solved.txt``
  bucket files.

* ``make_results_chart.py`` — renders the headline result as a
  publication-ready PNG (left panel: stacked outcomes per agent;
  right panel: per-instance disagreement). Also writes a copy to
  ``assets/`` for README embedding.

* Operational helpers (``_clear_infra_errors.py``,
  ``_describe_dataset.py``) for the common "kill stale Docker
  image-404 reports and re-run" and "summarize the 500-instance
  test set" recipes.

Headline on Gemini 2.5 Pro / full 499-instance Verified split:
  clawcodex: 291 resolved (58.2%)
  openclaude: 265 resolved (53.0%)

``.gitignore``: ignore ``eval/runs/`` (per-run output GBs of traces +
predictions + harness logs) and stray ``*.patch`` / ``*.diff``
artifacts; gitignore the sibling ``SWE-bench-dev/`` and ``openclaude/``
repos (they live next to clawcodex with independent git histories).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Embed the SWE-bench Verified result chart under the "Why ClawCodex?"
section as evidence backing the value props, and add two News entries:
the headline benchmark number (291/499 vs openclaude's 265/499 on
Gemini 2.5 Pro) and the native Gemini provider landing.

The chart (``assets/swebench-verified-gemini.png``) is regenerated by
``eval/make_results_chart.py`` so contributors can refresh it after a
new run; the script writes to both the per-run dir and ``assets/``.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /tools slash command crashed with AttributeError because
ToolRegistry exposes list_tools(), not list_specs(). The /context
handler had the same latent bug. Adds a regression test for /tools.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…s-slash

fix(repl): use list_tools() in /tools and /context handlers
Tool.description is a Callable[[dict], str] (see build_tool.py:49), but the
/context handler was stuffing the callable directly into tool_schemas,
giving the context analyzer a function reference instead of a string.
Also drop the dead `hasattr(spec, "to_dict")` branch — the Tool dataclass
has no such method.

Reuse the canonical tool_to_api_schema helper so /context and the real API
call site share one source of truth for the {name, description, input_schema}
shape.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ext-schema

fix(repl): resolve Tool.description to string in /context schema payload
Foundation layer for the /advisor port from typescript/src. Three
independent pieces, all read-only at runtime until the rest of the
stack lands:

- `src/utils/advisor.py` (NEW): constants (ADVISOR_BETA_HEADER,
  ADVISOR_TOOL_TYPE, ADVISOR_TOOL_INSTRUCTIONS), provider/env gates,
  block predicates, schema builder, strip_advisor_blocks, and
  extract_advisor_{result_text,error_code}. ADVISOR_TOOL_INSTRUCTIONS
  is a byte-for-byte copy of typescript/src/utils/advisor.ts:130-145;
  a SHA pin plus a live cross-check against the TS source catch drift.

- `src/settings/types.py`: adds `advisor_model: str = ""` to
  SettingsSchema. Empty string = unset; future layers read it via
  `get_settings().advisor_model` at request time.

- `src/types/messages.py`: extends `ensure_tool_result_pairing` to
  strip orphan `server_tool_use` and `mcp_tool_use` blocks whose
  matching `*_tool_result` never landed in the same assistant message
  (interrupted stream). Mirrors TS messages.ts:5217-5255. Prevents
  the API from 400-ing the next turn after a mid-advisor ESC.

## Test plan
- [x] `tests/test_advisor_helpers.py` (32 tests): constants, all gates,
      block detection (dict + attribute-style), strip pass shapes
      including the empty/thinking-only placeholder fallback, schema
      shape, SHA + live TS byte-equality.
- [x] `tests/test_advisor_orphan_pairing.py` (6 tests): advisor orphan
      stripped; pair retained; mcp orphan stripped; empty-content
      placeholder; regular client tool_use unaffected; multi-message
      independence.
- [x] No regressions in adjacent suites (settings, messages utility,
      command system, tool normalization, transcript, tui).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…undation

feat(advisor) 1/4: helpers + settings field + orphan tool_use guard
…rom TS

Closes the parity gap for typescript/src/assistant/ (88 LOC + 11-line
stub). Adds:

- src/assistant/session_history.py — paginated session-events client
  mirroring TS sessionHistory.ts (HistoryPage, HistoryAuthCtx,
  create_history_auth_ctx, fetch_latest_events, fetch_older_events).
  Preserves the TS four-case body-coercion contract and per-call-site
  debug labels; bool query params serialize lowercase to match axios.
- src/assistant/session_chooser.py — PascalCase stub mirror of
  AssistantSessionChooser.tsx so a future dialogLaunchers port can
  import the real name without an alias.
- src/assistant/__init__.py — re-exports new names; existing placeholder
  fields (ARCHIVE_NAME, MODULE_COUNT, SAMPLE_FILES, PORTING_NOTE)
  preserved.

Tests: 30 cases at 100% line coverage. Wire shape (URL path, all five
pinned headers, anchor_to_latest=true vs before_id, limit default,
15 s timeout), error policy (4xx/5xx/network/timeout/non-JSON/non-dict
all return None; malformed data preserves first_id + has_more), and
has_more JS-mirror default are pinned.

Signature deviation from TS: create_history_auth_ctx takes
access_token + org_uuid explicitly (no prepare_api_request global yet
in Python). Matches bridge/code_session_api.py + remote convention.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ing-folder-assistant

feat(assistant): port sessionHistory + AssistantSessionChooser stub from TS
Stacked on #PR1. The Anthropic provider's `_build_chat_response`
projects the SDK stream into `(content, tool_uses, ...)` for the
agent loop, but drops everything else — including server-side advisor
blocks. Once the next stack layer wires activation, those blocks need
to round-trip through history so the next turn's API call sees a
matched `server_tool_use(name=advisor)` + `advisor_tool_result` pair.

- `src/providers/base.py`: adds
  `ChatResponse.raw_content_blocks: Optional[list[dict]] = None`,
  the channel for passthrough blocks the projector chose not to
  flatten.
- `src/providers/anthropic_provider.py`: extends `_build_chat_response`
  to collect blocks where `type=='advisor_tool_result'` or
  `(type=='server_tool_use' and name=='advisor')`, serialize via
  `block.model_dump(exclude_none=True)`, and attach to
  `raw_content_blocks`. The SDK's lenient `construct_type` preserves
  the original fields on unknown discriminators (verified
  empirically against `anthropic==0.88.0`), so the round-trip is
  faithful.

Other server tools (web_search, code_execution, etc.) are
deliberately NOT scooped up — they have their own SDK projection.

## Test plan
- [x] `tests/test_advisor_chat_response_roundtrip.py` (6 tests):
      builds a real SDK ParsedMessage via `accumulate_event` for
      each of the three discriminated `advisor_tool_result` content
      shapes (`advisor_result`, `advisor_redacted_result`,
      `advisor_tool_result_error`), the orphan case, the non-advisor
      server-tool exclusion, and a mixed-block case (text + tool_use
      + advisor pair).
- [x] Pins the SDK round-trip contract — a future SDK upgrade (e.g.
      Pydantic v3, stricter discriminator handler) that silently
      drops extra fields on unknown discriminators would fail the
      `encrypted_content` and `error_code` assertions locally.
- [x] No regressions in adjacent provider tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stacked on #PR2. With helpers + provider preservation in place, this
is the actual server-side parity wiring — same beta header, same
schema, same instructions, same cache-preserving order as TS
typescript/src/services/api/claude.ts.

- `src/state/app_state.py`: adds `advisor_model: str | None = None`
  field + `_on_advisor_model_change` handler. The handler persists
  to `settings.advisor_model` via the shared default ConfigManager
  AND invalidates the settings cache, so mid-session toggles via the
  reactive store reach the next API call without a process restart.
  Registered in `_FIELD_HANDLERS` to satisfy the coverage contract.

- `src/query/query.py`: `_call_model_sync` now:
  1. Computes `advisor_active` via a single try/except wrapper:
     `is_advisor_enabled(provider)` (first-party Anthropic AND env
     not disabled) AND `get_settings().advisor_model` non-empty AND
     `model_supports_advisor(provider.model)` AND
     `is_valid_advisor_model(candidate)`. Any exception → inactive.
  2. When inactive, strips historical advisor blocks via
     `strip_advisor_blocks` (the API 400s on advisor blocks without
     the beta header).
  3. When active, appends the advisor schema AFTER the cached tool
     list (preserves the `cache_control` marker position), sets
     `betas=[ADVISOR_BETA_HEADER]`, and appends
     `ADVISOR_TOOL_INSTRUCTIONS` to the system prompt's tail. The
     append order mirrors TS claude.ts:1395, 1411-1421 — toggling
     /advisor doesn't bust the cache prefix.
  4. After the response, preserves `raw_content_blocks` (from PR2)
     into `assistant_blocks` so the pair survives history replay.

A stale `advisor_model` setting under a non-supporting base model is
silently ignored — never sent to the API. Mirrors the TS "skipping
advisor — base model X does not support advisor" branch.

## Test plan
- [x] `tests/test_advisor_request_wiring.py` (12 tests): full
      activation matrix on a first-party Anthropic provider, plus
      defensive activation (synthetic exception in the gate → turn
      proceeds without advisor). Verifies the beta header attaches,
      schema appends AFTER regular tools, instructions append to
      both string and block-list system prompts, advisor blocks
      survive in history when active. Negative cases: env disabled,
      custom base_url, unsupported base model, invalid advisor
      model, settings unset — none send the schema/header.
- [x] `tests/integration/test_advisor_smoke.py` (3 tests): mocked
      Anthropic stream produces text + server_tool_use(advisor) +
      advisor_tool_result + text — verify pair preserved across
      turns. Interrupt scenario: orphan use stripped on next-turn
      replay even when beta IS active (orphan strip is unconditional
      because the API rejects orphans regardless of header).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
agentforce314 and others added 29 commits June 11, 2026 03:02
…lows-two-pane-tui

Interactive two-pane /workflows TUI (Phases ⟷ Agents)
Replace the binary Allow/Deny permission surface with the TS
PermissionPrompt option set — allow once / allow always (persisted
rule) / deny / deny with feedback — and un-sever the engine:

- PermissionAskRequest/Reply protocol replaces the legacy 3-arg
  handler on all four producers (TUI bridge, REPL console menu,
  headless auto-deny, subagent share); real tool_input now reaches
  the modal so per-tool previews render
- registry applies chosen updates to the live context and persists
  via the new settings_paths resolver; setup_permissions is wired
  into all three entrypoints so persisted rules load at startup
- bash "don't ask again" suggestions ported from TS heuristics
  (2-word prefix, heredoc, safe env vars; no LLM extractor)
- engine fixes: multi-word prefix rules now match (TS
  bashPermissions.ts:879-882 semantics); new quote-aware chaining
  guard stops prefix/wildcard rules from auto-allowing compound
  commands (documented divergence — Python matches whole strings)

Suite at the verified main-0fa986f baseline (12 pre-existing
failures); +31 new tests incl. the persist→reload→auto-allow
restart round-trip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Typing y/n/a/d into the focused feedback Input must reach the Input,
not the screen bindings — pilot-verified, now regression-locked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- no-wildcard rules now match exact-or-word-boundary, closing the
  last-token elongation hole the new exact suggestions activated
- multiline/compound derivation can no longer mint dead rules
  (chained first line -> 2-word prefix or nothing) or bare-shell
  prefixes; heredoc at index 0 yields nothing
- chaining scanner: lone & detected as a separator (redirections
  >&, <&, &> skipped); ANSI-C quoting documented as a non-goal
- env-assign regex pinned to ASCII (JS \w parity); import hoisted
  out of the matcher hot path; __all__ completed; +13 tests locking
  the above incl. the accepted basename-normalization trade-off

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Read "don't ask again" suggestions removed: the engine consults
  content rules only for Bash, so the persisted Read(<dir>/**) rule
  could never match — re-asking right after promising not to is
  worse than no option (path-rule matcher = parked follow-up)
- project/local permission settings move to .clawcodex/ — the
  .claude/settings{,.local}.json names are owned by the real Claude
  Code harness; sharing them would read AND mutate the harness's
  live rules (this worktree's own .claude/settings.local.json
  proved the collision)
- registry apply/persist failures now logged (incl. per-update
  persist result); subagent rebind divergence documented at the
  rebind site; _with_default_suggestions returns a replace() copy
  instead of mutating tool-owned decisions
- protocol surfaces properly typed (TYPE_CHECKING imports); dead
  _preview_tool_input removed; stale doc-gate comment corrected;
  chosen_updates None-safe; defaultMode write-only asymmetry noted
- tests: REPL menu mapping (always/feedback rows, enable-row
  numbering shift, no-cache-on-always, rebind-target identity),
  headless PRODUCTION setup block loads persisted rules, autouse
  user-settings isolation fixture (hermeticity)

Suite re-verified at the main-0fa986f baseline (12, identical set).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-permission-options

components C1: multi-option permission flow wired to the rule engine
The dormant resume screen becomes real: /resume (alias /continue)
lists persisted sessions and selection swaps the live conversation.

- listing/filtering live in UI-neutral services/session_listing
  (headless /resume loads zero Textual modules); metadata-only
  sessions (message_count==0) are filtered with an honest hidden
  count per the standing critic condition; duplicate ids deduped
- AgentBridge.resume_session: busy-gated swap using the TS-parity
  reader (orphan repair, snip boundaries, path adjustment), then
  switch_session + restore_cost_state_for_session in TS lockstep
  (ResumeConversation.tsx:224-227), advisor scan cursor pinned to
  the list tail (index 0 would replay historical advisor events),
  persister re-targeted (title/counters preserved); unreadable
  transcripts refuse instead of crashing the callback chain
- degraded replay renders dict AND dataclass text blocks (the
  reader yields TextBlock dataclasses) and counts skipped
  tool/attachment blocks honestly; session-preview pane explicitly
  parked in the module docstring
- registry command: TS-verbatim metadata incl. aliases=[continue],
  substring search-term filter, output-style pattern (no ctx.ui)

19 resume/doctor tests (incl. a production-pipeline replay test and
the persister round-trip asserting title survival + no count
double-bump); full suite baseline-identical (12 known failures).
Critic-approved after one REQUEST CHANGES round (B1 dataclass-block
render bug, dependency direction, advisor replay, cost restore).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-resume-picker

components C2: resume picker wired end-to-end
Status/context half of plan §3 (C3b ships the transcript polish):

- context-% status segment + context-low transcript row, driven by
  the LAST response's prompt-side tokens (new last-wins last_* keys
  in the compat usage incl. cache read/creation — the cumulative
  sums double-count and drop cache, TS tokens.ts:407-420); zeroed
  on /clear and /resume
- threshold math delegates to the CANONICAL autoCompact port the
  engine already uses (review caught a forked module warning at the
  engine's compact point — the fork is gone); the canonical percent
  display modernized to current-TS raw-window half-up rounding;
  warning copy branches on auto-compact enabled ("N% until
  auto-compact") vs disabled ("Context low … /compact")
- settings statusLine.command runner (TS executeStatusLineCommand:
  JSON payload on stdin w/ TS field shapes, 5s timeout, failures →
  no text); event-driven refresh on run-finish + 30s hot-reload
  keepalive instead of hot polling; thread worker hardened so no
  codec/teardown error can exit the app

20 new tests (thresholds DERIVED from the canonical functions and
pinning warn < compact < window); suite baseline-identical.
Critic-approved after one REQUEST CHANGES round (usage measure,
threshold fork, worker crash vectors, payload shapes, hot polling).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-context-status

components C3a: context awareness + custom statusline command
Transcript half of plan §3, completing C3:

- compact boundary row: typed plumb (LocalCommandResult "compact" →
  CommandResult → dispatch compact flag → distinct boundary row);
  legacy REPL gained the compact elif so the executed command never
  falls through to re-handling
- ctrl+o expand-last-truncated (legacy _expandable_blocks parity):
  tool results exceeding the panel limits stash full text under the
  ROW's authoritative name — production result events carry
  tool_name="" (review B1; all tests now use that shape)
- read-group collapse: ≥3 consecutive completed Read/Grep/Glob rows
  fold into one summary row; breaks on user/assistant/thinking/
  advisor/system rows, non-read tools, and errors; ONE ctrl+o stash
  entry per group tracked by identity (per-read content stashes
  interleave — the positional guard flooded the deque, review M2);
  boundary/group/expanded rows participate in the post-exit
  scrollback dump via _SnapshotStatic (review M3)
- /thinking session toggle wired REAL: bridge → compat →
  QueryParams.extended_thinking; enable refuses honestly unless the
  provider+model actually support it (explicit True bypasses the
  query gate — review M4); first use disables (TS ThinkingToggle)

14 tests on production-shaped events; suite baseline-identical
(the one transient delta re-verified as the documented
prompt-assembly second-boundary flake, 3/3 isolated passes).
Critic-approved after two rounds. Known cosmetic: after the first
fold the group row stays pinned while later read rows flash below
it briefly — closest achievable to TS static-region grouping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…b-transcript-polish

components C3b: transcript polish + /thinking toggle
Port of TS processBashCommand: a user-typed !command runs DIRECTLY
through the Bash tool call (no registry permission flow — user-typed
commands aren't model-initiated; the tool's defense-in-depth
dangerous-pattern guard still applies and is pinned by a test that
also asserts the permission handler is NEVER consulted), produces no
agent turn, and feeds <bash-input>/<bash-stdout|stderr> user messages
into the conversation so the model sees them next turn.

- conversation integrity (review B1): texts route through the new
  AgentBridge.append_user_texts — deferred while a run is in flight
  (mid-run appends could interleave a tool_use/result pair, durably
  via the persister) and drained in _finish() before the durable
  flush, plus at the top of submit() so teardown-window stragglers
  land BEFORE the next prompt; this also replaces the app's
  _persister reach-in
- UI: echo row mounts synchronously ("running…"), the worker fills
  the output in place; BOTH streams render (git/npm write to stderr
  with exit 0); shared truncate_body() so panel/stash limits can
  never drift; ctrl+o-expandable; sequential-only with honest
  refusals (busy agent / bash already running); magenta input accent
  on the ! prefix
- divergences documented: no caveat message, both streams escaped,
  no PowerShell branch, no live progress/ESC cancel yet (follow-up)

15 tests incl. deferral through a real bridge, exit-143 timeout via
env shrink, !cd cwd-persistence pinned as deliberate TS parity, and
repl dispatch (bash called, agent NOT). Suite baseline-identical.
Critic-approved after one REQUEST CHANGES round.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-bash-mode

components C4: bash-mode (!) input
/search and /open dialogs (TS GlobalSearchDialog / QuickOpenDialog,
degraded: no preview pane, command-launched instead of ctrl+shift
chords), inserting TS-verbatim @file#Lline / @path mentions into the
composer — and the #L fragment now ATTACHES the file downstream
(expand_at_mentions strips it; range slicing is a noted follow-up).

- shared-engine bugfix: the ripgrep abort-poll runner never drained
  its pipes, so >64KB of output deadlocked rg until the 20s timeout
  (rg --files tripped it first; big content searches had the same
  latent stall); now drained via concurrent daemon threads, with
  errors="replace" so one non-UTF8 matched line can't kill the drain
  and silently truncate Grep/Glob results
- UI-neutral services: workspace_search (TS-cited caps 10/500, -i -F,
  the TS Windows-safe ^(.*?):(\d+):(.*)$ parse, POSIX-relative
  insertions, truncation flags) and fuzzy_match (fuzzy_score moved
  verbatim out of the Textual history module, which re-exports it)
- screens on the house DialogScreen+SelectList idiom: Input keeps
  focus, arrows route to the list, Enter selects; stale-result race
  closed in depth (immediate clear + query guard + per-search abort
  tripped on supersede/close + is_attached); honest counts incl.
  truncation labels; aborted workers never report

14 tests incl. the >64KB no-stall regression, the stale-enter pilot,
case-insensitivity, truncation flags, and the functional-mention pin.
Suite baseline-identical. Critic-approved after one round; known
pattern-level wart shared with HistorySearch (mouse-focused list
swallows enter/esc) noted for the shared-idiom follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-search-dialogs

components C5: workspace search dialogs + ripgrep drain fix
- /doctor finally reaches the dormant DoctorScreen (TUI inversion) and
  a new output-style registry command serves headless/REPL (python/
  platform, ripgrep availability, sessions dir, config + rule health)
- services/config_health: per-file JSON/encoding/top-level checks over
  the paths the loaders ACTUALLY read (git-root-anchored project
  config via get_project_config_path — review M1 — plus the .clawcodex
  settings trio and the managed file); startup warning rows in a new
  visually-distinct "-warning" SystemMessage variant, with "Run
  /doctor for details."
- loaders HARDENED so "file ignored" is true for every detected class
  (review M2): permissions/setup._load_settings_file catches
  ValueError/OSError + rejects non-dict top levels (a mis-encoded or
  array-shaped settings file crashed setup_permissions before the TUI
  mounted); config._read_json rejects non-object top levels (which
  previously blew up in _deep_merge)
- delivers the C1-deferred rule-warning surfacing (review M3):
  collect_rule_warnings formats dangerous + shadowed permission rules
  into the startup rows, the /doctor report, AND a new DoctorScreen
  section; the stale C1 comment now states it
- conftest: GLOBAL_CONFIG_DIR isolated per-test (full-app tests no
  longer read the developer's real global config)

18 tests incl. loader-hardening regressions through the REAL loaders,
a real-CommandContext pin, unreadable/dir-shaped paths, and a
subprocess textual-free import check. Suite baseline-identical.
Critic-approved after one round; KeybindingWarnings deferral and the
no-reset-gate divergence documented in the module.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-doctor-validation

components C6: /doctor wiring + config-validation surfacing
…alog)

Repo-declared MCP servers (.mcp.json, scopes project+local) are now
untrusted until the user approves them, porting TS
getProjectMcpServerStatus (utils.ts:351-390) + MCPServerApprovalDialog:

- services/mcp_approval.py: status precedence disabled->rejected,
  enabled->approved, enableAll->approved, else pending; merged
  user->local settings read (lists unioned, scalars last-win; project
  tier deliberately excluded so a repo cannot self-approve - documented
  safer-than-TS deviation); choices persist to
  .clawcodex/settings.local.json.
- Enforcement at both choke points: get_all_mcp_configs filters
  .mcp.json scopes PRE-merge (a name-colliding unapproved repo server
  can no longer shadow-then-drop a user-scope server - critic-reproduced
  bug), and get_mcp_config_by_name gates per-name resolves
  (reconnect/OAuth side door). Pending servers warn via the existing
  ValidationError channel; rejected servers drop silently.
- TUI: McpApprovalScreen (enable / enable all / disable; Esc = decide
  later) chained at boot per pending server; enable_all short-circuits
  the queue; write failures report honestly and keep servers pending.
- Elicitation declared GATED: the MCP client has no server->client
  request support, so ElicitationDialog parity stays parked with the
  client work (gap doc T12 corrected).

20 new tests incl. cross-scope shadowing regression and screen pilot
coverage; full suite baseline-identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-mcp-approvals

components C7: .mcp.json server approval gate
…pass)

Three TS-verified boot gates, chained strictly sequentially before the
C6 config warnings and C7 MCP approvals:

- Folder trust (TrustDialog.tsx, config.ts:771-817): per-project
  hasTrustDialogAccepted in the USER-owned global config's
  projects[path] map (git root else cwd) - never a committable file,
  so a repo cannot pre-trust itself; parent trust covers children;
  home-dir acceptance is session-only; decline exits 1. Warning
  enumeration degraded to the two subsystems this port models (bash
  allow rules + statusLine). config.py grows
  get/update_project_entry + get_project_path_for_config.
- External CLAUDE.md includes (ClaudeMdExternalIncludesDialog.tsx,
  claudemd.ts:805-815/1427-1437): asked once per project via the
  boolean pair hasClaudeMdExternalIncludesApproved/WarningShown;
  get_memory_files now derives include_external from the approval and
  keys its cache on the effective value; is_external_memory_file
  excludes the User tier exactly like TS; Esc maps to "no".
- Bypass acceptance (BypassPermissionsModeDialog.tsx): decline exits 1,
  Esc exits 0 (distinct TS codes, now propagated - run_tui returns
  app.return_code); accept persists skipDangerousModePermissionPrompt
  to user settings and syncs bootstrap session trust state.
- C7 deferred item closed: get_mcpjson_server_status ports the TS
  auto-approve branches (bypass-accepted flag; non-interactive
  session, utils.ts:377-403) - headless/SDK runs now match TS instead
  of the interim skip-with-warning; run_tui defensively pins
  set_is_interactive(True).

Critic loop: REQUEST CHANGES (1 MAJOR: forked session-trust state +
stale init.py TODO; 5 MINOR) -> all fixed -> APPROVE. 22 new tests;
full suite baseline-identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-startup-gates

components C8: startup security gates (trust / includes / bypass)
A prompt starting with '#' saves a note to a chosen memory file
instead of starting an agent turn: repl routing (precedence /, !, #;
bare '#' falls through) -> MemorySaveScreen over the /memory target
hierarchy (build_memory_options, promoted to a public shared API so
the two pickers can never drift) -> services/memory_append.py.

Honesty note (documented in the plan, gap doc, and module docstring):
the vendored TS snapshot ships only the RENDERER
(UserMemoryInputMessage.tsx - '#' row + random "Got it. / Good to
know. / Noted.") and the selector; no '#' detection or append exists
there. TS-verbatim pieces are ported verbatim (acknowledgement trio,
"Cancelled memory editing", target hierarchy); the connective glue is
explicitly port-derived: ensure-create + single-newline separation +
'- ' bullet unless the note supplies its own punctuation; every
failure/cancel path restores '#note' into the prompt so nothing typed
is lost; notes are recorded in prompt history like '!' commands.

Critic: APPROVE (3 MINOR / 4 NIT, all applied: docstring precision,
history recording, enumeration-failure test, ValueError guard,
UIOption typing, public build_memory_options). 17 tests; full suite
baseline-identical.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-memory-shortcut

components C9: '#' memory-append shortcut
… run finishes

Background workflows enqueue a <task-notification> envelope on completion, but
nothing in the REPL ever drained that queue — its own docstring admits the
consumer ("WI-3.3") was deferred. So a finished /deep-research run surfaced only
by polling /workflows, with no completion signal and no pointer to the results.

Wire the consumer:

* src/repl/task_notifications.py (new, pure/tested): deterministic completion
  banner from the terminal task state (✔/✗/⊘ · name · agents · tokens · duration
  · run journal), an envelope fallback, and build_notification_turn() which wraps
  drained envelopes in a guiding system-reminder for the agent.

* REPL glue (core.py): _deliver_pending_task_notifications() drains the queue at
  the turn boundary, prints each banner, then feeds the envelopes to the agent as
  one turn so it reads the result and summarizes conversationally — the Claude
  Code "the research is done…" behavior. A daemon watcher wakes the idle ❯ prompt
  the moment a run finishes (verified cross-thread against prompt_toolkit 3.0.x
  via app.exit), so it surfaces without a keystroke — and never clobbers a
  half-typed line or a permission dialog.

Tests (15): pure helpers, the REPL delivery method (banner + single agent turn,
no-op on empty queue), the wake guard (idle+empty exits; half-typed/not-running
don't), the watcher (wakes when idle+pending, silent otherwise), and an
end-to-end run through the production launcher → enqueue → REPL delivery with a
fake runner. Pre-existing test_workspace_search_c5 failures (ripgrep env) are
unrelated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…low-completion-notifications

Notify on background-workflow completion (banner + agent summary)
…oring keyword

Closes two gaps in the workflow-engine port (workflow-engine.md §4.1 + §4.7),
audited and planned in docs/workflow-commands-and-ultracode-plan.md.

1. Dynamic workflow slash commands were discovered but never dispatchable.
   load_workflow_commands fed only the aggregator's get_commands(), which has no
   real REPL consumers, so saved .claude/workflows/*.py never reached the global
   registry that dispatch + suggestions read (the same orphaning that hid
   /deep-research until agentforce314#267). Add load_and_register_workflows(registry=None) —
   mirroring load_and_register_skills, same shadowing guard (builtins/bundled
   win; project beats personal) — and call it at REPL (_init_command_system) and
   TUI (app.py) startup. Saved workflows now dispatch AND autocomplete (workflow
   tag). Confirmed end-to-end: a saved file that was `dispatch: False` is now
   registered, suggested, and tagged.

2. The `ultracode` authoring keyword (§4.1) was never built (0 hits in src/).
   New src/workflow/ultracode.py: the standalone `\bultracode\b` keyword in a
   prompt (one-shot) and `/effort ultracode` (session-long mode) append a
   <system-reminder> nudging the model to author a workflow via the Workflow tool
   instead of working turn by turn. Wired into REPL chat() (next to the
   companion-intro append) and effort_command.py. Gated by is_workflows_enabled()
   — §4.8: the keyword no-ops and the option leaves the /effort menu when off.
   (Python's effort pipeline is inert, so /effort ultracode contributes the
   orchestration mode only, not a reasoning level.)

Tests (48 new): saved-workflow registration + precedence + gating
(test_workflow_dynamic_commands.py); keyword detection, reminder precedence,
session toggle, and /effort ultracode (test_ultracode.py). Three base-effort
picker tests gated to workflows-off so they keep asserting the bare option set
(ultracode-in-menu is covered separately). Full sweep: 299 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…low-commands-and-ultracode

Dispatchable saved /<name> workflows + the ultracode authoring keyword
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.

5 participants