Commit Graph

81 Commits

Author SHA1 Message Date
Mason Daugherty c9c565970a Merge branch 'main' into nm/dcode-tools-allowlist 2026-07-21 15:04:48 -04:00
Mason Daugherty 46bb1a86c8 fix(code): abort project MCP approval on Esc (#4888)
Pressing Esc in the project MCP approval prompt now aborts startup and
returns to the terminal instead of treating Esc as **Deny** and
continuing into the TUI.

---

Esc now produces the same launch-cancellation outcome in both project
MCP selectors. The explicit **Deny** option remains available and
selected by default, so declining trust still requires choosing it and
pressing Enter.
2026-07-21 12:10:12 -04:00
Mason Daugherty ccaab2c6b2 feat(code): support a configurable tools allowlist 2026-07-21 11:59:23 -04:00
Mason Daugherty 69e33acb0e Merge branch 'main' into nm/dcode-tools-allowlist 2026-07-21 11:08:12 -04:00
Mason Daugherty eae7c28f11 feat(code): classifier-backed Auto approval mode (#4804)
Deep Agents Code now offers an opt-in **Auto** approval mode. Auto
immediately runs a narrow, deterministic set of routine actions. For
other approval-gated actions, the active model checks whether they match
the user's literal request. It runs approved actions and blocks the
rest. After repeated denials or classifier failures, Auto sends the next
batch to the normal approval UI, then continues in Auto mode.

Auto is experimental and currently limited to interactive, local TUI
sessions using `FilesystemBackend`. Manual remains the default.

> [!WARNING]
> Auto is an authorization heuristic for a local coding agent. It is
**not** sandbox containment, an operating-system boundary, or a
guarantee that model-generated actions are safe.

## Enable Auto

1. Set `DEEPAGENTS_CODE_EXPERIMENTAL=1`.
2. Select Auto in one of these ways:
- In-app: use <kbd>Shift</kbd>+<kbd>Tab</kbd> or
<kbd>Ctrl</kbd>+<kbd>T</kbd> to toggle between Manual and Auto.
   - At launch: pass `-y` or `--auto-approve`.
- By default: set `[startup].mode = "auto"` in
`~/.deepagents/config.toml`.

`-y` and `--auto-approve` now select Auto rather than unrestricted
execution. Users who want the previous unrestricted behavior must choose
`--yolo`. The removed `dangerously-auto` startup value is no longer
recognized and falls back to Manual.

---

## Why

The previous approval modes forced a binary choice:

- **Manual** preserved a human checkpoint, but repeatedly interrupted
routine coding work.
- **YOLO** removed that checkpoint entirely, including for actions that
exceeded the user's request (blanket approval).

Auto introduces a third policy: keep the low-friction path for clearly
routine work, but make authorization explicit for potentially
consequential actions.

| Mode | Policy | Entry points | Availability |
|---|---|---|---|
| **Manual** | Uses the normal human approval UI for gated actions |
Default; `[startup].mode = "manual"` | All interactive sessions |
| **Auto** | Uses narrow deterministic rules, then classifier review,
with human fallback after repeated denials or failures | `-y`,
`--auto-approve`, `[startup].mode = "auto"`, or the live keyboard toggle
| Experimental, local, unsandboxed TUI sessions |
| **YOLO** | Runs gated actions without review | `--yolo` or
`[startup].mode = "yolo"` | Interactive sessions after a one-time
acknowledgement |

## How Auto handles a proposed action

```mermaid
flowchart TD
    A[Model proposes tool calls] --> B{Covered by the approval policy?}
    B -->|No| X[Existing tool behavior is unchanged]
    B -->|Yes| S{Is this thread still in Auto, with readable denial/failure history?}
    S -->|No| J[Open the existing approval HITL widget]
    S -->|Yes| C{Narrow deterministic allow?}
    C -->|Yes| R[Execute without a classifier call]
    C -->|No| D[Build one structured decision batch]
    D --> H{Was this batch already reviewed, or do earlier denials/failures require human review?}
    H -->|Yes| J
    H -->|No| E[Active model reviews effects against user request/input]
    E -->|Allow| R
    E -->|Deny| Q{Total-denial threshold reached?}
    Q -->|No| F[Return a sanitized error result]
    Q -->|Yes| J
    E -->|Unavailable or invalid| G[Return a compact unavailable result]
    F --> I[Agent can revise its plan]
    G --> I
    J -->|Approve| R
    J -->|Reject| K[Return a rejection result]
    J -->|Switch to Manual| L[Persist Manual, then review the full gated batch]
    R --> M[Reconcile the result and continue]
    K --> M
    I --> M
    L --> M
```

In practice:

1. **Auto starts with Manual's existing human-in-the-loop (HITL)
rules.** It changes only how HITL-gated actions are reviewed.
- These built-in tools are not HITL-gated: `ls`, `read_file`, `glob`,
`grep`, `ask_user`, `get_goal`, `get_rubric`, and `update_goal`.
- MCP tools with valid read-only annotations, the optional `js_eval`
tool, and caller-supplied tools without HITL configuration continue to
run normally.
2. **Only narrow cases resolve locally.** The deterministic policy can
allow, never deny:
- A routine write such as `src/parser.py` can proceed based on its
resolved path and suffix; a sensitive target such as
`.github/workflows/ci.yml` goes to classifier review.
- A read-only Git command such as `git status` can proceed; a mutating
command such as `git commit -m change` goes to classifier review.
Explicit shell allow-list entries still apply after Auto removes broad
and wildcard rules.
- An MCP tool with a valid read-only annotation can proceed; tools
without read-only annotations, or with contradictory or malformed
annotations, go to classifier review.

     <details>
     <summary>How MCP read-only annotations are evaluated</summary>

MCP servers can attach standard `ToolAnnotations` when advertising a
tool. Deep Agents Code copies those annotations into the wrapped tool's
metadata and lets the tool bypass classifier review only when:

     - `readOnlyHint` is the literal Boolean `true`;
     - `destructiveHint` is absent, `null`, or `false`; and
- every supplied standard hint (`readOnlyHint`, `destructiveHint`,
`idempotentHint`, or `openWorldHint`) is a Boolean or `null`, rather
than a string or another type.

     For example:

- `readOnlyHint: true, destructiveHint: false` proceeds without
classifier review.
- No `readOnlyHint` goes to review because the tool never explicitly
claimed to be read-only.
- `readOnlyHint: true, destructiveHint: true` goes to review because the
hints contradict each other.
- `readOnlyHint: true, destructiveHint: "false"` goes to review because
`"false"` is a string, not a Boolean.

In Auto, tools that do not pass this check enter the classifier batch.
In Manual, they use the normal approval UI; headless runtimes reject
them because they have no approval UI. The annotation is a
server-provided assertion that Deep Agents Code does not independently
verify.

     </details>
3. **The classifier reviews all unresolved calls together.** Only
pre-expansion literal user text can authorize an action. Referenced
paths, summarized arguments, prior tool calls, and MCP metadata provide
effect context but cannot grant permission. Expanded files, assistant
prose, and tool results are excluded.
- **Example:** For the literal request `Research issue #123, update
@src/parser.py, and open a PR`, proposed `fetch_url` and `git push`
calls can be reviewed in one batch and receive separate decisions. The
referenced path and summarized call arguments help identify what each
action would affect, but the expanded file contents are not sent.
Instructions found in that file, assistant prose, or a prior tool result
therefore cannot widen the user's request or authorize another action.
4. **Auto revalidates before side effects.** The decision plan is bound
to the thread, mode, batch, and exact gated calls. Missing or invalid
state, a mode race, or a replay falls back to human review. Denied calls
are skipped and receive sanitized error results so the agent can revise
its plan.
- **Examples:** If the user switches to Manual while classifier review
is in progress, an earlier Auto decision cannot execute silently; the
normal approval UI opens instead. A plan associated with another thread,
a different set of tool-call IDs, missing decisions, or a previously
processed batch also requires human review. If Auto denies a proposed
`delete`, it returns an error result with that call's ID instead of
invoking the tool, allowing the agent to propose a safer next step.

## Limits and user-visible behavior

- The Manual approval menu can enable Auto for the current thread.
Threshold fallback can switch permanently to Manual or perform a one-off
review while leaving Auto enabled.
- Denials, classifier failures, and fallbacks appear as short sanitized
transcript events rather than raw classifier output.
- Auto requested without the experimental opt-in, or in a sandboxed
session, falls back to Manual with a warning. Explicit autonomous-mode
flags are rejected in non-interactive and ACP entry points.
- The active model is not an independent security authority. MCP
read-only annotations are trusted as a deliberate beta tradeoff.
- Parent-level Auto review does not cover actions performed inside
delegated subagents or broader explicitly configured PTC/`js_eval`
fan-out. Model providers and tracing backends may still observe
classifier inputs and outputs even though the TUI hides them.

<details>
<summary><strong>Implementation and reviewer details</strong></summary>

### Approval boundary and decision inputs

The following tools are HITL-gated:

- `execute`, `write_file`, `edit_file`, and `delete`;
- `web_search` and `fetch_url`;
- `task`;
- `start_async_task`, `update_async_task`, and `cancel_async_task`;
- `compact_conversation` when optional compaction approval is enabled;
and
- MCP tools without valid read-only annotations.

For deterministic writes and edits, the fast path uses only the resolved
target path and suffix; it does not inspect proposed content. Dependency
and lock files, sensitive names and locations, shell scripts,
unsupported file types, and paths outside the trusted root do not
qualify. Selected Git inspection commands must also pass shell-control
and outside-worktree path checks. Configured shell entries remain an
explicit user trust exception, but broad commands and wildcard entries
are ignored by Auto.

The classifier receives one typed batch containing:

- up to 20 client-attached, pre-expansion prompt rows, including literal
prompts, referenced path names, and turn IDs;
- trusted project-root and redacted-origin facts; and
- depth- and length-limited effect context from current arguments, prior
AI tool calls after the latest trusted prompt, and selected MCP
annotations.

Only `literal_user_text` is authorizing. Content-bearing write/edit
arguments are reduced to character counts. The classifier must return
exactly one unique decision for every unresolved tool-call ID; missing,
duplicated, unknown, or malformed decisions make the classifier
unavailable rather than partially authorizing the batch.

Before routing calls, Auto re-reads the live mode and validates its
private decision plan. Manual mode, invalid control state, mismatched
calls, or replayed batches require human review. Classifier review uses
a second call to the active session model with a 20-second timeout. Raw
classifier text and tool-call chunks are hidden from the transcript,
while usage accounting and tracing are retained.

Denial reasons are sanitized before persistence or display by removing
control characters, known credential values, credential-like
assignments, and URL credentials or query values. MCP tools without
valid read-only annotations are HITL-gated; headless runtimes reject
them because no approval UI is available.

### Failure and fallback behavior

| Condition | Result |
|---|---|
| Selected mode cannot be persisted | Persist Manual instead; if that
also fails, block graph execution |
| Classifier timeout, provider error, or invalid response | Block the
affected calls and return a compact unavailable result |
| Three consecutive policy denials in one user turn | Send the next
classifier-review-eligible calls to one-off human review |
| Two consecutive classifier-unavailable results | Send the next
classifier-review-eligible calls to one-off human review |
| A denial raises the thread's total denial count to 20 | Require human
review for that call immediately |
| Counter state cannot be read or reset before classification | Require
human review for every gated call in the batch |
| Counter state cannot be written after classification | Require human
review for classifier-path decisions; deterministic fast-path allows
remain eligible |
| Decision plan is missing, malformed, replayed, or bound to another
thread | Require Manual review |
| User chooses **Switch to Manual** during fallback | Persist Manual
before presenting the full gated batch for review |

### Python API and state compatibility

- Existing Boolean compatibility input retains its previous meaning:
`auto_approve=True` maps to YOLO, not Auto.
- Per-thread Store records persist the typed mode instead of a Boolean.
Missing, legacy, or malformed records fail closed.
- Startup and live mode changes take effect only after the Store write
succeeds.
- YOLO requires a versioned local acknowledgement and cannot be entered
through the live keyboard toggle.

### Reviewer guide

Suggested review order:

1. **Mode semantics and persistence** — `ApprovalMode`, Store payloads,
acknowledgement, startup resolution, and live mode writes.
2. **Authorization policy** — `AutoModeHITLMiddleware`, literal prompt
metadata, deterministic routing, structured classification,
sanitization, counters, and checkpoint validation.
3. **Graph boundary** — middleware replacement, the gated-tool
inventory, MCP annotation handling, headless rejection, PTC, and
delegated-subagent scope.
4. **Client behavior** — stream metadata, classifier-output filtering,
fallback interrupts, keyboard toggles, status presentation, and
transcript events.

Highest-risk invariants:

- every consequential tool in the supported boundary is gated or
explicitly outside scope;
- path-based writes and selected Git fast paths stay within their
documented checks;
- only literal user prompts can authorize classifier-reviewed effects;
- malformed output, Store failures, mode races, and checkpoint replay
fail closed; and
- hidden classifier chunks never create transcript content while usage
accounting remains intact.

</details>
2026-07-17 17:09:26 -04:00
Mason Daugherty 02f6eae1d3 Merge remote-tracking branch 'origin/main' into nm/dcode-tools-allowlist
# Conflicts:
#	libs/code/deepagents_code/agent.py
#	libs/code/deepagents_code/main.py
#	libs/code/tests/unit_tests/test_agent.py
#	libs/code/tests/unit_tests/test_main_acp_mode.py
2026-07-17 16:43:27 -04:00
Mason Daugherty 3f82114c37 refactor(code): drop config list/ls aliases for config show (#4783)
`dcode config list`/`dcode config ls` are removed; use `dcode config
show` (add `--verbose` to see all option keys).

---

`config list`/`config ls` duplicated `config show`, so this removes both
parser aliases and makes `config show` the sole command for displaying
effective config values and their sources. Drops `list_mode`, the
`config list` JSON label/catalog special-casing, backward-compat
comments, and now-dead paths; `config show --json` keeps effective-only
fields (catalog folded in only with `--verbose`) and always labels the
payload `"config show"`. User-facing suggestions now point at `config
show`/`config show --verbose`. Backward compatibility is intentionally
not preserved.

### Breaking change

`config list --json` was the machine-readable catalog endpoint (its JSON
envelope `command` field was `"config list"` and it always folded in
catalog fields). It is gone: the command no longer parses, and the
catalog fields are now available via `config show --verbose --json`
(payload labeled `"config show"`). Any machine consumer of `config list
--json` must migrate.

Made by [Open
SWE](https://openswe.vercel.app/agents/7351f382-9468-b4dc-2df4-03eaf75ef12f)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-17 15:48:59 -04:00
Mason Daugherty 7408378896 fix(code): disentangle version diagnostics for editable installs (#4816)
Editable installs conflated three distinct version facts, so `/version`,
`--version`, and `doctor` could silently report a single number that hid
stale metadata or a broken SDK dependency pin. Version diagnostics now
separate the live source version, the installed distribution metadata
version, and the `deepagents` requirement that `deepagents-code`
declares — reporting the source version as primary for editable
installs, showing the installed metadata alongside it when they drift,
and flagging an actionable mismatch when the installed SDK metadata does
not satisfy the declared requirement.

---

A new structured `DistributionVersion`/`VersionReport` representation in
`extras_info` is the single source of truth these surfaces render.
`resolve_sdk_version()` is preserved as a thin compatibility wrapper so
existing callers are untouched.

- `/version`, `--version`, and `doctor` render the same facts: an
editable SDK is now clearly identified (not just editable
`deepagents-code`), source/metadata drift shows both versions, and an
unsatisfied requirement (compared via `packaging.Requirement`) is
surfaced as `required by deepagents-code: <version> — mismatch`.
- `doctor` marks a dependency-requirement mismatch unhealthy, while
treating source/metadata drift as informational since it is normal while
editing SDK source.
- Normal non-editable output is unchanged when all versions agree, and
version collection stays fully offline and best-effort (no
`uv`/`pip`/PyPI, no subprocess).
- No package versions or dependency pins were changed.

Two existing `doctor` integration tests now isolate the SDK requirement
check so they stay deterministic regardless of the workspace's
intentional prerelease pin; the mismatch and drift paths have dedicated
new coverage.

Made by [Open
SWE](https://openswe.vercel.app/agents/1145d1a5-ba8a-abbb-e5b7-039e9c2a5b04)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-17 15:09:44 -04:00
Johannes du Plessis 3da6e471c2 feat(code): make plugins generally available (#4797)
Plugins are now available by default in deepagents-code without setting
`DEEPAGENTS_CODE_EXPERIMENTAL`.

This removes the flag gates from CLI and slash-command discovery, plugin
skills and MCP loading, and reload behavior while preserving the flag
for unrelated experimental features.

Made by [Open
SWE](https://openswe.vercel.app/agents/019f6c79-6211-734f-b05c-a5a3401c8a6b)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
2026-07-16 23:06:34 -04:00
Mason Daugherty 9fdd498937 feat(code): move /goal criteria generation server-side (#4754)
`/goal` acceptance-criteria drafting now runs on the agent server and
can use conversation, repository, web, and explicitly read-only
configured MCP context while preserving resumable review state.

---

Previously, `/goal` proposals were drafted in the client process from
the explicit objective, so remote or sandbox repository context and
references to the current conversation were unavailable. This moves
proposal and amendment drafting into a nested agent in the main server
graph, where it can use recent conversation context, bounded read-only
repository search, `fetch_url`, optional web search, and configured MCP
tools explicitly annotated as read-only.

Repository context remains read-only and repository-rooted across
backends: local reads use a virtual project root, while sandbox reads
are constrained to the provider's declared working directory, including
canonical-path checks that reject symlink escapes.

Proposals are stored as pending checkpoint state rather than chat
messages, so review survives remote interrupts and resumes without
polluting the coding conversation. Each proposal is correlated with its
originating request, and criteria requests are cleared after success,
failure, or cancellation without clearing newer requests. Cancelling
criteria generation stops the server run without adding normal
chat-interruption messages. The client refreshes checkpoint state after
generation and reuses the existing accept, edit, and reject flow; model
profile overrides now cross the client/server boundary consistently.
2026-07-15 17:17:41 -04:00
Mason Daugherty ffab82a945 feat(code): add "always allow" to project MCP approval prompt (#4562)
The project MCP server approval prompt now offers an "always allow"
option that saves approved servers to `config.toml`, with an
all/custom/none picker so you choose exactly which servers to persist.

---

The project-level MCP server approval prompt (`dcode` startup) only
offered per-session, fingerprint-based trust. This adds an "always
allow" choice (`a`/`always`) that persists approved servers to
`[mcp].enabled_project_servers` in the user-level `config.toml`, so they
load by name on future runs without re-prompting — even if the config
content changes.

Rather than presuming the whole list, "always allow" now lets you choose
*which* servers to save via an `[a]ll / [c]ustom / [n]one` menu.
`custom` reprints the servers numbered and accepts a `1,3`-style
selection; a single prompted server skips the menu. The writer only ever
touches the user's home `config.toml` (never a repo file), preserving
the boundary that a committed `.mcp.json` can't self-approve.

---

## ⚠️ Breaking change (migration)

This branch evolved past the original design above. As shipped, "always
allow" persists **fingerprint-scoped** approvals under
`[mcp].enabled_project_server_approvals` (bound to project root +
server-definition fingerprint, so a changed command/URL re-prompts) via
an arrow-key checkbox picker — not the flat, name-based
`[mcp].enabled_project_servers` list or the `all/custom/none` text menu
described above.

Consequences for anyone who adopted the previous per-server trust
feature (`[mcp].enabled_project_servers`, shipped in `0.1.33`/`0.1.34`):

- The legacy `[mcp].enabled_project_servers` TOML key is now **ignored**
(a debug-only log warns; the key is preserved on write, not deleted).
- The env override `DEEPAGENTS_CODE_ENABLED_PROJECT_MCP_SERVERS` is
renamed to `DEEPAGENTS_CODE_DANGEROUSLY_ENABLE_PROJECT_MCP_SERVERS`
(still name-based and project-agnostic — the explicit escape hatch).
- **Interactive `dcode`:** affected servers move back to the approval
prompt; re-approve once with `a`. Safe, mildly annoying.
- **Non-interactive / `dcode mcp login`:** affected servers silently
stop loading (no prompt in these surfaces). Re-run `dcode` in the
project to persist a scoped approval, or set
`DEEPAGENTS_CODE_DANGEROUSLY_ENABLE_PROJECT_MCP_SERVERS` to keep the old
name-based behavior.

This is fail-closed (no server loads with *less* scrutiny than before),
so it is a safety-preserving break.

Made by [Open
SWE](https://openswe.vercel.app/agents/6cb9d698-5a31-5568-306a-9ff7a3f4356b)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-15 10:06:10 -04:00
Johannes du Plessis 0187d14b83 feat(code): add plugin marketplace support (#4554)
`deepagents-code` now supports experimental plugin marketplaces with
namespaced skills and MCP servers. Enable with
`DEEPAGENTS_CODE_EXPERIMENTAL=1`.

Docs: https://github.com/langchain-ai/docs/pull/4905

---

`deepagents-code` can now manage experimental plugin marketplaces and
load installed plugins that contribute namespaced skills and MCP
servers. The new plugin manager establishes the `tui/modals`,
`tui/screens`, and `tui/widgets` UI convention, with colocated styling
and focused state/content modules.

- Supports local, GitHub, Git, and marketplace JSON sources.
- Adds install, enable, disable, uninstall, and safe marketplace removal
through `/plugins` and `dcode plugin`.
- Reloads plugin skills and plugin MCP configuration with `/reload` when
experimental mode is enabled.
- Namespaces plugin skills as `{plugin_id}:{skill_name}` through a
code-local `PluginSkillsMiddleware` adapter, so no SDK changes are
required.

Made by [Open
SWE](https://openswe.vercel.app/agents/019f5dea-0f1f-73fe-9803-32d1b13fbd4c)

---------

Signed-off-by: Johannes du Plessis <johannes@langchain.dev>
Co-authored-by: Alexander Olsen <aolsenjazz@gmail.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
2026-07-15 09:11:28 -04:00
Mason Daugherty 55b60ca08d feat(code): add memory.auto_save config flag (#4700)
`deepagents-code` adds a `memory.auto_save` setting (env
`DEEPAGENTS_CODE_MEMORY_AUTO_SAVE` / `[memory].auto_save`, default on).
Disable it to keep loading memory into context while stopping the agent
from automatically writing learnings back to your `AGENTS.md` files.

---

Automatic memory saving in `deepagents-code` is prompt-driven:
`MemoryMiddleware` injects guidance telling the agent to proactively
persist learnings to the `AGENTS.md` sources. Until now the only switch
was `enable_memory`, which is all-or-nothing — turning it off also stops
memory from being loaded into context. This adds a way to keep loading
memory while turning the automatic saving off.

A new `memory.auto_save` option (env `DEEPAGENTS_CODE_MEMORY_AUTO_SAVE`,
`[memory].auto_save` in `config.toml`; defaults on) flips
`MemoryMiddleware` to a read-only prompt when disabled. The read-only
prompt keeps the trust/verification framing but drops the "proactively
persist learnings" guidance, so memory still informs the agent and
explicit saves (e.g. the `remember` skill) keep working — only
unprompted auto-saving stops.

To support this, the SDK gains a canonical
`MEMORY_READONLY_SYSTEM_PROMPT`, exported from `deepagents.middleware`
alongside `MEMORY_SYSTEM_PROMPT`.

Made by [Open
SWE](https://openswe.vercel.app/agents/825df1d1-b8e0-91c3-f7f6-dac1fcdd5c5e)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-13 21:45:51 -04:00
Mason Daugherty 67f897264b cr 2026-07-13 14:10:25 -04:00
Mason Daugherty 5bf26fe26d cr 2026-07-13 12:59:20 -04:00
Mason Daugherty 20dd6cc821 Merge branch 'main' into nm/dcode-tools-allowlist 2026-07-13 11:24:26 -04:00
Mason Daugherty 724e24a315 fix(code): route explicit --stdin + --skill to headless path (#4611)
`dcode --skill ... --stdin` with piped input now runs headless instead
of launching the interactive TUI.

---

When a user explicitly passes `--stdin` together with `--skill`, `dcode`
previously routed the piped text into the interactive TUI seed
(`initial_prompt`), which hangs/fails on non-TTY environments.
`apply_stdin_pipe` now skips the skill→interactive convenience when
`--stdin` is explicit, falling through to the headless
`non_interactive_message` path (which already supports `--skill`).
Auto-detected pipes (no `--stdin`) keep the interactive seeding
behavior.

Made by [Open
SWE](https://openswe.vercel.app/agents/057272c4-7946-e220-f7b8-ea929d2fe9ce)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-13 09:52:59 -04:00
Ankush Gola 12a9c9d681 fix(code): avoid repeated startup auto-update stalls (#4648)
Startup auto-update retried the same cached target on every launch after
a failure, which could strand users before the TUI started. This adds a
same-version cooldown, skips opportunistic auto-update for `--resume`,
and terminates updater process groups on timeout so stale children do
not hold locks.

Focused unit coverage was added for the cooldown, resume gating, and
timeout cleanup paths.

Made by [Open
SWE](https://openswe.vercel.app/agents/3e0e7320-f512-6d6a-5f53-e5c08d2f899c)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
2026-07-13 02:36:27 -04:00
Mason Daugherty d1f3afecdc fix(code): detach owned langgraph dev server from terminal (#4642)
Fixed dcode hanging when its terminal was suspended or closed while it
was running the built-in `langgraph dev` server.

When dcode starts its built-in `langgraph dev` server, closing or
suspending the terminal could stop the server unexpectedly and leave
dcode hanging during cleanup. This PR separates the server from the
terminal on macOS and Linux while keeping dcode responsible for shutting
it down.

---

## Before

dcode and its built-in server shared the same terminal session. Actions
such as suspending dcode with Ctrl+Z or closing its terminal could
therefore affect both processes at once. The server could stop before
dcode had a chance to clean it up, which could leave dcode stuck.

## After

The built-in server now runs in a separate session on macOS and Linux,
so terminal signals intended for dcode no longer stop the server at the
same time. When dcode exits, restarts the server, or encounters a
startup failure, it still shuts down the server and any child processes
it started.

This separation matters in both directions: the server should not be
accidentally stopped by the terminal, but it also must not be left
running after dcode is gone. Shutdown first gives the full server
process tree a chance to exit cleanly, then forces it to stop if
necessary. Safety checks ensure dcode never signals its own process
group. Windows behavior is unchanged.

---

Made by [Open
SWE](https://openswe.vercel.app/agents/0c752fd0-cfc3-0b00-87ba-fb1e67b483ce)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-13 01:46:55 -04:00
Mason Daugherty ac15732815 fix(code): remove misleading agent names from help (#4671)
The CLI help used `coder` and `researcher` as examples even though dcode
does not ship those agents. This could make them look built in.

This replaces the named examples with `NAME` and `SOURCE` placeholders
in the top-level and reset help.
2026-07-12 22:21:07 -04:00
Mason Daugherty c9b7ac2075 feat(code): add -s alias for --skill (#4620)
`dcode` now accepts `-s` as a short alias for `--skill`, e.g. `dcode -s
code-review -m 'review this patch'`.

---

`-s` was previously unused across the entire `dcode` CLI surface (no
top-level flag or subparser claimed it), so it's a safe, ergonomic alias
for the existing `--skill` startup flag. This gives skill invocation the
same short-flag convenience as other common options and makes `dcode -s
<name> [-m task]` a quick way to auto-invoke a skill at startup.

Made by [Open
SWE](https://openswe.vercel.app/agents/577c1ebd-4ca4-47db-8244-99b4d2f15112)

Co-authored-by: Harrison Chase <11986836+hwchase17@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-11 18:56:44 -04:00
Nishitha Madhu 3d70ef02e9 lint fixes 2026-07-10 14:00:31 -04:00
Nishitha M cd766e4164 Merge branch 'main' into nm/dcode-tools-allowlist 2026-07-10 12:57:59 -04:00
Nishitha Madhu 0e9c30a23a add --allow-fs-tools to dcode 2026-07-10 11:59:56 -04:00
Mason Daugherty 997be1643a fix(code): reject --auto-approve in headless mode (#4617)
`dcode` now rejects `--auto-approve` in headless mode and points users
to `--shell-allow-list` for shell access. Interactive behavior is
unchanged.

---

Running `dcode` headlessly with `--auto-approve` currently accepts a
flag that does not control shell access. This can leave users believing
shell commands are approved when headless execution actually uses
`--shell-allow-list`.

Reject the unsupported combination with exit code 2 and direct users to
`--shell-allow-list`. Clarify the command help while preserving
`--auto-approve` for interactive launches, including `-m` startup
messages.
2026-07-09 18:31:26 -04:00
Mason Daugherty 58db2b78f4 refactor(code): rename in-app nomenclature to dcode (#4556)
Rebrand in-app references from "Deep Agents Code" to `dcode`.

---

Switches the in-app product name in `libs/code` from "Deep Agents Code"
to `dcode` so the running app matches the `dcode` CLI/branding. Covers
user-visible runtime strings only: Textual TUI widget copy (auth,
install-confirm, launch-init, update-progress, agent-selector),
CLI/console output in `main.py`, user notices in `app.py`/`config.py`,
install/update status messages in `update_check.py`, and the
system-prompt title (with its snapshot). Related UI test assertions
updated to match.

Intentionally left unchanged (not in-app nomenclature): external
contract/metadata identifiers `CODING_AGENT_RUNTIME` and
`_OPENROUTER_APP_TITLE` (coding-agent-v1 validator + attribution),
docstrings/comments, and markdown docs. Standalone "Deep Agents"
(SDK/framework) references were also left as-is since the request
targeted "Deep Agents Code".

Made by [Open
SWE](https://openswe.vercel.app/agents/60a3d442-7e8d-bd18-4575-a533ca9f9614)

---------

Signed-off-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-08 21:51:34 -04:00
Mason Daugherty 7c5bf542c2 feat(code): add [startup].mode default approval mode (#4573)
Add a `[startup].mode` option to `~/.deepagents/config.toml` for
`deepagents-code`, accepting `manual` (default) or `dangerously-auto`,
to set the interactive TUI's default approval mode without passing
`-y`/`--auto-approve` each launch.

---

Lets users pick the interactive TUI's default approval mode in
`~/.deepagents/config.toml` instead of passing `-y`/`--auto-approve` on
every launch. The `[startup].mode` option accepts `manual` (default,
human-in-the-loop approvals) or `dangerously-auto` (auto-approve gated
tool calls at launch).

Precedence: an explicit `-y`/`--auto-approve` flag still wins; when
omitted, `[startup].mode` decides; the built-in fallback is `manual`, so
existing behavior is unchanged. Implemented by switching the flag's
argparse default to `None` (so "omitted" is distinguishable from "set")
and resolving the effective value at interactive dispatch. A new
validated `load_startup_mode` loader mirrors the existing
`[threads].sort_order` pattern and falls back to `manual` on
unset/unreadable/invalid values. Follows the `config.toml`-only approach
(a `ConfigOption` in the manifest for `dcode config` discovery, no new
env var). Non-interactive (`-n`) mode computes its own approval behavior
and is untouched.

Made by [Open
SWE](https://openswe.vercel.app/agents/82b4ad9e-f42e-7f73-ae2f-5a0830f3f8cc)

## References
- Plan:
https://openswe.vercel.app/agents/82b4ad9e-f42e-7f73-ae2f-5a0830f3f8cc/plan

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-08 20:36:51 -04:00
Mason Daugherty 1398feeca5 fix(code): dedupe update/install log path output (#4553)
`dcode update` and install commands no longer print the same log path
twice.

---

Previously, update and install flows printed the log path once by itself
and then repeated it in the tail command:

```text
Update log: /tmp/deepagents-update.log
Tail progress: tail -f /tmp/deepagents-update.log
```

Now they print a single copy-pasteable command next to the log label:

```text
Update log: tail -f /tmp/deepagents-update.log
```

The same before/after applies to `Install log:` output for both extras
installs and arbitrary package installs. This keeps the path visible
while avoiding duplicate path output in the terminal.

Made by [Open
SWE](https://openswe.vercel.app/agents/91e79ce5-84aa-d27e-8cd5-dae63b3546c1)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-08 20:26:14 -04:00
Mason Daugherty 434c84ae14 fix(code): clarify managed rg install failures (#4578)
`dcode` now gives clearer messages when managed ripgrep is unavailable
for the current system or missing from the pinned upstream artifacts,
and POSIX managed ripgrep installs now use a relative symlink to a
versioned binary for better portability.

---

Managed ripgrep setup used to collapse permanent gaps and transient
download failures into the same generic failure path. This makes those
cases distinct and makes POSIX managed installs more portable.

## Before

- As a user on an unsupported system, I saw a generic managed ripgrep
install failure and could not tell whether retrying would help.
- As a user whose pinned managed ripgrep artifact was missing upstream,
I saw the same kind of failure as a network outage.
- As a user who moved, restored, or bind-mounted `~/.deepagents`, a
managed helper link could bake in the old absolute path or become stale.
- As a user with a broken managed `rg` symlink, dcode could fall back to
another `rg` on `PATH` instead of repairing the managed install.

## After

- As a user on an unsupported system, I get a direct message that
managed ripgrep is not available for my platform and can install ripgrep
manually or opt into the system installer path.
- As a user whose pinned managed ripgrep artifact returns HTTP 404, I
get a message that the artifact is unavailable for the pinned version,
instead of a generic network failure.
- As a user with transient download trouble, I still get the normal
download-failure path and can retry.
- As a user who moves or bind-mounts `~/.deepagents`, the `rg`
entrypoint is a relative symlink to a versioned managed binary, so it is
less fragile.
- As a user with a dangling managed `rg` symlink, dcode treats it as
managed state and repairs it rather than silently substituting a system
binary.
2026-07-08 20:06:53 -04:00
Mason Daugherty 8ee2d6affe refactor(code): move Textual adapter into tui package (#4532)
Continues the dcode package restructure by giving the Textual execution
adapter a canonical home under `deepagents_code.tui`. Shared session
stats formatting now lives outside the TUI layer so headless and client
paths do not need to import Textual-facing code.

## Changes
- Moves `TextualUIAdapter` and `execute_task_textual` under the TUI
package and updates app, integration, benchmark, and unit-test imports
to use the new canonical path.
- Keeps `client/non_interactive` and shared command logic independent of
`deepagents_code.tui` by moving `print_usage_table` alongside
`SessionStats` and `format_token_count`.
- Relocates adapter-specific tests under the matching
`tests/unit_tests/tui` layout and moves shared session-stat coverage to
the existing `_session_stats` test module.
- Refreshes threat-model and inline references that described the old
flat adapter location.
2026-07-06 17:05:58 -04:00
Mason Daugherty 8fc4dee4f9 refactor(code,talon): move CLI commands into client package (#4525)
Client-facing CLI command groups now have a canonical home under
`deepagents_code.client.commands`, matching the client/runtime package
layout started by the previous restructure slice. The top-level CLI
dispatch and Talon MCP bridge now import those command handlers from the
new package instead of the old flat module paths.

## Changes
- Moved the `auth`, `config`, `mcp`, and `tools` command implementations
under `deepagents_code.client.commands`.
- Updated `parse_args()` and `cli_main()` to register and dispatch
command groups from the new client command package.
- Updated Talon’s dynamic `mcp login` import to resolve `run_mcp_login`
from the new canonical MCP command module.
- Moved command-specific unit tests under
`tests/unit_tests/client/commands` and updated patch/import targets,
including the Ruff ignore path for MCP command test helpers.
2026-07-06 15:35:19 -04:00
Mason Daugherty 49bffe2145 refactor(code): move client runtime modules into package (#4523)
Starts the dcode package restructure by giving client-facing runtime
code a canonical home under `deepagents_code.client`.

- Moves the local LangGraph server helpers under `client.launch` and
moves `RemoteAgent` plus headless `run_non_interactive` under `client`.
- Updates app, CLI, and tests to import the new canonical paths directly
without compatibility alias modules.
- Changes runtime workspace scaffolding to load
`deepagents_code.server_graph:make_graph` from the package instead of
copying `server_graph.py` into the temp workspace.
- Resolves the generated runtime package dependency from the top-level
package location so editable checkouts keep pointing at the working tree
after the module move.
- Refreshes the threat model references for the moved client/server
components.
2026-07-06 14:34:55 -04:00
Mason Daugherty 1402d0e735 feat(code): add dcode tools list command (#4461)
Add `dcode tools list` (with `--json`) to show the tools available to
the agent, grouped by source.

---

Adds a `dcode tools list` verb under the existing `dcode tools` group
that prints the tools available to the agent, grouped by source
(built-in tools first, then per-server MCP tools), in an Amp-style
layout with a total-count header and a `--json` mode. Tools are
enumerated from the *real* tool objects the agent binds — the agent is
compiled with an offline placeholder model (no credentials or network)
and its bound tool node is read — so names/descriptions never drift from
what the model sees. MCP discovery is best-effort and skipped on
failure.

Made by [Open
SWE](https://openswe.vercel.app/agents/96e689b3-3bc7-3a04-7135-c2001b8ed058)

## References
- Plan:
https://openswe.vercel.app/agents/96e689b3-3bc7-3a04-7135-c2001b8ed058/plan

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-06 12:25:00 -04:00
Mason Daugherty aaa22a9340 feat(code): selective per-server project MCP trust (#4507)
Add `[mcp].enabled_project_servers` / `disabled_project_servers` (and
env equivalents) to selectively pre-approve or reject individual project
MCP servers by name from user-level config.

---

Mirrors Claude Code's `enabledMcpjsonServers` /
`disabledMcpjsonServers`: a user can pre-approve or reject specific
project MCP servers (from a repo's `.mcp.json`) by name via a new
`[mcp]` `config.toml` table (`enabled_project_servers` /
`disabled_project_servers`) or the
`DEEPAGENTS_CODE_ENABLED_PROJECT_MCP_SERVERS` / `..._DISABLED_...` env
vars — without the interactive fingerprint prompt and without trusting
the whole config. Reject wins over approval and over full trust.

The allow/deny lists are read **only** from the user-level
`~/.deepagents/config.toml` and process env (via the new
`model_config.load_mcp_server_trust_lists`), never from `.mcp.json` or
any repo-committed file, so a committed config cannot self-approve its
own servers. The untrusted path changes from "drop all" to "drop all
except allowlisted", and only allowlisted (or fully trusted) names
survive into the merged config — so the remote-SSRF /
header-exfiltration gate stays intact.

Made by [Open
SWE](https://openswe.vercel.app/agents/0b9a59e3-2c90-47ce-65f9-efac2532321a)

## References
- Plan:
https://openswe.vercel.app/agents/0b9a59e3-2c90-47ce-65f9-efac2532321a/plan

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-06 10:40:20 -04:00
Mason Daugherty 20107eda2d style(code): simplify web search disabled toast notification (#4448)
Simplified the "Web search disabled" toast to a single concise line.

Made by [Open
SWE](https://openswe.vercel.app/agents/497028b9-6bbc-add3-e0fc-b69936c9177d)

---------

Signed-off-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-07-02 18:23:12 -04:00
Mason Daugherty d6692a7c71 feat(code,sdk): add rubric iteration controls (#4405)
Closes #4404

Deep Agents Code now uses clearer acceptance-criteria grading messages,
lets the rubric grader inspect offloaded tool-result evidence referenced
in the transcript, defers the rubric iteration default to the SDK, and
adds interactive grader controls for goals and rubrics.

---

This improves the goal and rubric grading UX so the status messages
describe the acceptance criteria check rather than implying the rubric
text itself is wrong. When grading fails with `needs_revision`, the UI
now says the changes need revision. Iteration numbers are hidden unless
the user explicitly configured an iteration cap, keeping the default
`/goal` and `/rubric` flow less noisy.

Deep Agents Code no longer sets its own default rubric iteration cap. If
the user does not pass `--rubric-max-iterations`, dcode omits
`max_iterations` and lets `RubricMiddleware` use the SDK default.
Explicit `--rubric-max-iterations` values are still forwarded, and the
SDK now accepts any positive cap instead of enforcing the previous hard
upper bound.

The rubric grader also gets a narrow `read_file` tool that can only
inspect `/large_tool_results/` paths explicitly referenced in the
transcript. This gives the grader a way to verify offloaded evidence
without exposing broader filesystem access or waiting for an SDK-level
transcript reconstruction change.

The TUI now exposes `/rubric max-iterations <N|clear>` and `/goal
max-iterations <N|clear>` for interactive sessions. Changing the value
restarts the app-owned LangGraph server so the construction-time
`RubricMiddleware` setting takes effect. `/goal model
[provider:model|clear]` and `/goal max-iterations` are goal-first entry
points for the same shared grader settings surfaced by `/rubric model`
and `/rubric max-iterations`.
2026-07-01 03:18:04 -04:00
Mason Daugherty 8fca61dc03 feat(code): add rubric-backed goal workflow (#4365)
Adds rubric-driven acceptance criteria to Deep Agents Code and layers a
goal workflow on top of it.

A rubric is the explicit definition of done. Use it when you already
know the criteria the agent should satisfy before it considers the work
complete. Criteria can be sticky for the thread, one-shot for the next
turn, or loaded from a file. While the agent works, the TUI shows
grading lifecycle messages so the user can see when the work is being
checked, revised, satisfied, or stopped.

Usage examples:

```text
/rubric set tests pass; no unrelated files changed; help text is updated
/rubric next only change the auth callback; do not refactor unrelated code
/rubric file acceptance.md
/rubric show
/rubric clear
```

A goal is the objective-oriented workflow. Use it when you know the
outcome, but want `dcode` to propose acceptance criteria before
execution. `/goal <objective>` asks the model to draft criteria and
displays them in an inline review prompt. The user can accept the
criteria, edit them directly, reject them with feedback to regenerate,
or cancel. Accepted goal criteria become the sticky rubric for the
thread.

Usage examples:

```text
/goal add OAuth refresh handling
/goal show
/goal clear
```

The `--goal` CLI flag starts the same goal-review workflow when
launching the TUI. It is intentionally interactive: the generated
criteria must be reviewed before execution. After the user accepts the
proposal, the accepted goal is sent as the first task.

```bash
dcode --goal "add OAuth refresh handling"
```

`--goal` cannot be combined with `-n`, `-m`, `--skill`, or `--rubric`.
For non-interactive/headless runs, users should provide criteria
explicitly with `--rubric` instead:

```bash
dcode -n "implement OAuth refresh handling" --rubric "tests pass; no unrelated files changed"
dcode -n "implement OAuth refresh handling" --rubric @acceptance.md
```

Accepted goal/rubric state is persisted on the thread and restored on
resume. For example, a user can set `/goal add OAuth refresh handling`,
accept the proposed criteria, quit the TUI, and later resume the same
thread with the goal and criteria still active. That matters because the
acceptance criteria continue to guide future turns and remain visible in
`/goal show` instead of becoming hidden context that disappears between
sessions.

When the agent believes the goal is done, it does not get to declare
victory on its own. For example, after implementing OAuth refresh
handling, the agent can ask to mark the goal complete with evidence such
as "tests pass." `dcode` keeps that request pending until the rubric
check finishes. If the rubric still needs revision, the goal stays
active and the user sees why it was not completed. If the rubric is
satisfied, auto-approve mode records the completion automatically;
manual mode asks the user before changing the goal status. This keeps
the user's accepted criteria as the source of truth for whether the goal
is actually finished.

The active criteria and goal are also visible to the agent through
constrained tools. `get_rubric` lets the agent inspect the current
criteria and whether they came from a goal, a sticky rubric, or the
current invocation. `get_goal` lets it inspect the active objective,
status, criteria, and any prior note. `update_goal` lets it report when
it believes the goal is `complete` or `blocked` with evidence. The
constraints matter: the agent can read criteria and update progress, but
it cannot create, pause, resume, clear, or replace goals. Those
lifecycle actions stay user/system controlled so the model cannot
silently redefine or remove the user's objective.
2026-06-29 16:20:18 -04:00
Mason Daugherty 1bcb112ee7 feat(code): non-interactive rubric grading flags (#4305)
Wires `deepagents-code`'s headless (`-n`) path to the SDK's
`RubricMiddleware` so a run can self-evaluate against caller-supplied
acceptance criteria and loop until satisfied. This is the
non-interactive vertical slice of the rubric/"acceptance criteria"
feature; the interactive TUI `/rubric` command + status indicator are
intentionally left as a follow-up.

New flags (all `-n`/piped-stdin only):
* `--rubric TEXT|@PATH` accepts literal criteria, or reads criteria from
`@path`. `@path` may be absolute, relative to the `dcode` process
working directory, or use `~` for the home directory.
* `--rubric-model MODEL` defaults to the main agent model.
* `--rubric-max-iterations N` controls grader iterations per attempt.

The rubric text is passed as graph `rubric` state; grader model +
max-iterations flow to the server subprocess via `ServerConfig`. Grader
lifecycle events are surfaced by subscribing to the `custom` stream and
rendering `rubric_evaluation_start/end` ( grading / ✓ satisfied / ↻
needs revision / ⚠ max-iterations / failed), and the headless header
shows `Rubric: active`.

## Test Plan
- [ ] `dcode -n "implement X" --rubric "tests pass; minimal diff"`
against an SDK that ships `RubricMiddleware` loops the agent and renders
grading events
- [ ] `--rubric @rubric.md` resolves file contents from a relative path;
absolute paths and `~`-expanded paths are also supported
- [ ] `--rubric` without `-n` exits 2

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-29 00:50:16 -04:00
John Kennedy db441ed306 perf(code): speed up shutdown after Ctrl+C/Ctrl+D (#4351)
Interactive shutdown could spend several seconds in serial teardown
waits after Ctrl+C/Ctrl+D. This tightens the `dcode` shutdown path by
reducing the server SIGTERM wait (`_SHUTDOWN_TIMEOUT` 5s → 3s), sharing
a single checkpoint-existence query across both teardown hints (the
LangSmith link and the `dcode -r` resume hint) instead of running it
twice, and giving the active agent worker a bounded cancellation window
before the Textual app exits so an in-flight run can finish persisting
its trace instead of being torn down mid-request.

The teardown query runs whenever the session owns a thread. An earlier
iteration skipped it when no model turn had completed (`request_count ==
0`), but that proved unsafe: an interrupted first turn can persist a
checkpoint before any usage metadata is recorded, which would silently
drop the resume hint for a thread the user could still resume. Avoiding
the duplicate query keeps the teardown cost to one event-loop spin-up
regardless.

The agent-worker handoff uses Textual's supported `Worker.wait()` API,
wrapped in `asyncio.shield` so the bounded timeout cancels only the wait
and never the worker's own cancellation handler.

Made by [Open
SWE](https://openswe.vercel.app/agents/a3ed8343-0530-8ee7-dec2-11da90162628)

---------

Co-authored-by: Deep Agent <agent@deepagents.dev>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
2026-06-28 19:47:07 -04:00
Mason Daugherty b870b18750 fix(code): clear transient update launch status (#4355)
After a successful auto-update re-exec, the startup confirmation now
replaces the transient `Launching...` status with the stable completed
update message instead of saying `Launched.`. That avoids implying a
separate launch milestone while preserving the terminal rewrite behavior
after restart.
2026-06-28 19:00:28 -04:00
Mason Daugherty bbed726860 hotfix(code): allow prereleases for pinned alpha updates (#4247)
Stable `deepagents-code` releases can intentionally depend on prerelease
package versions, such as an alpha SDK pin during coordinated release
work. Previously, the updater discovered the stable app release but
invoked `uv` in stable-only mode, so `uv` resolved back to the old app
version while `dcode update` still reported success.

This teaches the update flow to inspect the target release metadata for
prerelease dependency specifiers and opt `uv` into prerelease resolution
only when the target needs it. The behavior is wired through CLI
updates, startup auto-update, in-app `/update`, and notification-driven
installs, so manual and automatic paths use the same resolver mode.

Users on older versions can recover manually with:

```bash
uv tool install --reinstall -U 'deepagents-code[anthropic,baseten,google-genai,openai]' --prerelease allow
```
2026-06-25 05:25:48 -04:00
Mason Daugherty 2e04ff397e feat(code): enable js_eval by default (#4245)
Local dcode sessions now start with the JS interpreter and safe PTC
bridge available by default, while remote sandbox launches keep it
disabled unless explicitly requested. `langchain-quickjs` is now a core
dependency, so users no longer need to install the `quickjs` extra just
to use interpreter support.
2026-06-25 04:26:41 -04:00
Mason Daugherty cf536f3399 fix(code): eager managed ripgrep install via dcode tools install (#4199)
`dcode tools install` provisions the managed ripgrep binary; the install
script now sets it up by default (opt out with
`DEEPAGENTS_CODE_RIPGREP_INSTALLER=system` or
`DEEPAGENTS_CODE_OFFLINE=1`).

---

Today the pinned, SHA-256-verified ripgrep binary only lands on first
run (via `managed_tools.ensure_ripgrep`), so a fresh user pays a
one-time download the first time they launch. This makes the install
script provision it eagerly so `rg` is ready immediately, without
reaching for `sudo` package managers by default.

Rather than re-encoding the pinned version + checksum table in bash
(drift risk), a new `dcode tools install` verb reuses the existing
managed-install code, and `scripts/install.sh` invokes the freshly
installed binary. The verb is also handy on its own to repair a missing
or stale `rg`.

Power users can keep their own toolchain with
`DEEPAGENTS_CODE_RIPGREP_INSTALLER=system`, which preserves the existing
brew/apt/cargo path in the install script and skips the managed download
at runtime. A system `rg` already on `PATH` is reused under either
setting, and `DEEPAGENTS_CODE_OFFLINE` / `DEEPAGENTS_CODE_SKIP_OPTIONAL`
continue to apply. The default eager install is announced and honors
those opt-outs, per the package's "default shell-outs must announce +
offer a documented opt-out" rule.

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-25 01:28:38 -04:00
Mason Daugherty 5e152ac025 fix(code): --reinstall on /install so upgrades rebuild a clean env (#4196)
`/install` now reinstalls the dcode tool cleanly, fixing server-startup
`ImportError`s caused by half-updated environments after adding an extra
or package.

---

When you run `/install <extra>` (e.g. `/install fireworks`) or `/install
<pkg> --package`, dcode runs `uv tool install -U
'deepagents-code[...]'`. The `-U` upgrades the tool **in place**, on top
of the copy that's currently running. An in-place upgrade can leave
stale files behind — for example an old `tools.py` (or its cached
`.pyc`) from the previous version. You then end up with a tool
environment that's a mix of old and new files.

The next time the server starts (after `/restart`), the new
`server_graph.py` tries to import something the stale `tools.py` doesn't
have, and the server dies on startup.

The fix is small: pass `--reinstall` so uv rebuilds the tool environment
from scratch instead of patching it in place. Every file then matches
the freshly resolved version, so adding a model/extra can't leave the
install in a broken state. This is the same thing the manual workaround
(`uv tool install --reinstall -U ...`) does, now baked into the
`/install` flows. The manual-command hints we show users are updated to
match.

Package installs now use the same receipt-preserving reinstall path as
extras, so adding a custom provider package keeps existing extras,
`--with` packages, the uv-managed Python interpreter, and prerelease
channel instead of rebuilding a narrower tool environment.

Made by [Open
SWE](https://openswe.vercel.app/agents/e196cb0c-bde5-c559-1675-3226784d7178)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-24 20:11:36 -04:00
Mason Daugherty fcc616cf9b fix(code): preserve uv tool context when installing extras (#4201)
Split extra installation into a display path and an execution path so
user-facing recovery instructions match the public install script, while
the in-app uv flow still preserves the existing tool environment. This
avoids dropping the recorded Python interpreter or `--with` packages
when `/install <extra>` rewrites the uv-managed `dcode` tool.

## Changes
- Add `INSTALL_SCRIPT_COMMAND` and make `install_extras_command` /
`install_extra_command` display the promoted `curl -LsSf
https://langch.in/dcode | DEEPAGENTS_CODE_EXTRAS=... bash` reinstall
flow instead of raw `uv tool install`
- Add `_install_extra_uv_tool_command` for the actual uv-managed
execution path, merging the requested extra with installed extras
through `_uv_tool_install_command`
- Extend `_uv_tool_install_command` with `extras_to_add`, preserving the
uv receipt’s `--python` interpreter and existing `--with` packages while
adding the new extra
- Update `perform_install_extra` to use `_install_extra_uv_tool_command`
for uv-managed installs and report `ToolRequirementIntrospectionError`
when the receipt cannot be safely preserved
- Keep unsupported install guidance in `perform_install_extra`
receipt-free, so Homebrew/unknown installs get the install-script
recovery command without failing on uv receipt parsing
- Update `DeepAgentsApp._install_extra` and the `--install` CLI fallback
path to surface the install-script command in user-facing manual
recovery messages
2026-06-24 02:27:39 -04:00
Mason Daugherty 8ca0a185a1 fix(code): unpin uv self-updates and warn when a stale dcode shadows PATH (#4185)
`uv tool upgrade` honors the requirement string baked into the uv tool
receipt, so a self-update could report success while silently
re-resolving the same pinned version — and even a genuine upgrade has no
effect if an older `dcode` sits earlier on the user's PATH. The update
path now forces an unpinned `uv tool install -U` and checks for a
shadowing binary after every upgrade, surfacing a copy-pasteable PATH
fix instead of a misleading "relaunch to use the new version."

## Changes

- **Unpinned uv upgrades.** `/update` and auto-update run `uv tool
install -U deepagents-code` instead of `uv tool upgrade`, clearing any
`==<version>` pin left in the receipt (from an original pinned install
or a prior dependency refresh) that would otherwise keep re-resolving
the same version. `upgrade_install_command` rebuilds the requirement
receipt-aware so installed extras, `--with` packages, and the recorded
`--python` survive; on receipt-introspection failure it falls back to
the bare command and emits a progress note that extras may not carry
over.
- **Shadowed-`dcode` detection.** `detect_shadowed_dcode` compares
`shutil.which("dcode")` against uv's documented executable directory
(`_uv_tool_bin_dir`, following the `UV_TOOL_BIN_DIR → XDG_BIN_HOME →
$XDG_DATA_HOME/../bin → ~/.local/bin` precedence). When a different
binary wins on PATH, `/update`, the "Install now" notification action,
and startup auto-update surface a warning naming both paths plus a
session-scoped `export PATH=…` fix from
`format_shadowed_dcode_fix_command`. The comparison is deliberately
against the un-followed PATH entry so healthy uv symlink shims aren't
flagged.
- **Startup auto-update skips the re-exec when shadowed.** Re-exec'ing
would relaunch the old binary and trip the no-op restart guard, so the
pre-launch path prints the warning and continues on the current version.
- **Detection can't sink a good upgrade.** All three call sites route
through `detect_shadowed_dcode_safe`, which logs and returns `None` on
any unexpected error, so a detector defect never turns a successful
upgrade into a user-facing failure.
- **Warning completion state for the progress modal.**
`UpdateProgressScreen.mark_warning` adds a third terminal state —
warning glyph, durable status, `c` to copy the fix command — distinct
from success/failure, and the "Install now" modal enters it instead of
the success state when the upgraded shim is shadowed.

## Testing

- Shadow detection is covered branch-by-branch: the paired symlink cases
(uv's own shim must not flag; a symlink in another PATH dir must), the
legacy `deepagents-code` name fallback, and `OSError` fault injection on
path resolution. `detect_shadowed_dcode_safe` is pinned to swallow an
unexpected raise.
- `upgrade_install_command` gets direct coverage of the `--python`
quoting and `--with` assembly that the `perform_upgrade` tests stub out,
and the modal's warning state asserts the durable status, glyph, and
copy-fix-command behavior.
2026-06-23 19:55:43 -04:00
Mason Daugherty 81797312c7 feat(code): dcode doctor diagnostics command (#4148)
Add a `dcode doctor` command that prints install diagnostics (versions,
platform, update status, config locations), with `--json` support.

---

Adds a `dcode doctor` subcommand to `deepagents-code`, inspired by
`claude doctor`. It prints a grouped, tree-style summary of the install
(versions, platform, install method, path), update status, and on-disk
config locations, with a `--json` mode for machine-readable output.

It deliberately runs offline — the update section reads only the local
cache via `get_cached_update_available` and never contacts PyPI — and
reuses existing helpers (`detect_install_method`,
`is_auto_update_enabled`, editable-install detection, config paths) so
the output stays consistent with `dcode --version` and `dcode update`.
All rendered values go through `rich.markup.escape` with
`highlight=False`.

The new command is a leaf subcommand (like `update`), so it is
intentionally not registered in `_HELP_SPECS`. `ui.show_doctor_help`
backs `dcode doctor -h`, and the command is listed in the top-level help
usage.

Made by [Open
SWE](https://openswe.vercel.app/agents/74c251d7-548f-ee57-6e48-c76e1bd66a1a)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-22 18:51:41 -04:00
Mason Daugherty df8db8af6a feat(code): confirm "Launched" after auto-update restart (#4098)
The startup auto-update now updates its `Launching...` line to
`Launched.` once the upgraded version is running.

---

During a startup auto-update, `Updated to v{latest}. Launching...` is
printed immediately before `os.execv` replaces the process, so the
printing generation can never resolve its own status line. The re-exec'd
process already detects the post-update restart by consuming the
`RESTARTED_AFTER_UPDATE` sentinel; once `is_installed_version_at_least`
confirms the upgrade actually took effect, it now rewrites that line in
place to `Updated to v{latest}. Launched.`.

The new `_confirm_launch_after_restart` helper performs the rewrite with
a Rich `Control` cursor-up + erase-line sequence, and is gated on
`console.is_terminal` so redirected (non-terminal) output is never
polluted with escape codes. A failed re-exec that did not change the
running version is correctly left as `Launching...`.

Made by [Open
SWE](https://openswe.vercel.app/agents/27a04727-aae3-d6e0-61ef-358ae09e8d92)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-20 19:56:29 -04:00
Mason Daugherty 90cc099b09 feat(code): manage Tavily web-search API key in /auth (#4062)
Tavily web-search API keys can now be entered and managed from the
`/auth` Manage API keys screen instead of only via the `TAVILY_API_KEY`
environment variable.

---

Setting up Tavily web search was a dead end: the only path was a
notification that pointed at `tavily.com`, with no way to enter a key
the way model-provider keys are entered. This surfaces non-model
services (starting with Tavily) in the `/auth` Manage API keys screen so
a key can be entered and stored on disk like any provider key.

- A new `SERVICE_API_KEY_ENV` registry (currently just `tavily` →
`TAVILY_API_KEY`) drives a service-aware `get_service_auth_status`, so
Tavily renders the same `[stored]` / `[env: …]` / `[missing]` badges and
reuses the existing `AuthPromptScreen`.
- `apply_stored_service_credentials` bridges a stored key onto its
canonical env var during bootstrap, so a key entered in the TUI
activates web search on the next launch without exporting anything.
- The "Web search disabled" notification now offers a primary `Enter API
key` action that opens the prompt directly.

Note: web search tools are wired at server start from
`settings.has_tavily`, so a freshly entered key takes effect on the next
launch/restart rather than mid-session — called out in the notification
copy.

Made by [Open
SWE](https://openswe.vercel.app/agents/cbecdb67-7ab2-8ada-6cf3-8e619c734f59)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-17 17:33:12 -04:00
Mason Daugherty 7ff6e2224d feat(code): make auto-update opt-out by default (#3994)
Automatic updates for `dcode` are now enabled by default; opt out with
`DEEPAGENTS_CODE_AUTO_UPDATE=0` or `[update].auto_update = false`.

---

Auto-update for `dcode` previously defaulted to disabled (opt-in). This
flips it to enabled by default so users stay current automatically,
while keeping an easy escape hatch.

`is_auto_update_enabled()` now defaults to `True` and recognizes falsy
env tokens, so users opt out via `DEEPAGENTS_CODE_AUTO_UPDATE=0` (or
`false`/`no`/`off`/empty) or `[update].auto_update = false` in
`config.toml`. Editable installs remain always disabled. The
`update.auto_update` manifest default is updated to match.

**Behavior change for existing installs:** anyone who never explicitly
opted in will begin auto-updating on their next launch. The upgrade
still announces itself (`Updating dcode from vX to vY...`) before
running and is fully overridable, so it is not a silent default. This is
the one default-enabled exception to the "shell commands must not run
silently by default" rule, now documented as such in
`libs/code/AGENTS.md`.

**Unrecognized env values fail safe-ish, not silent:** a value that is
neither truthy nor falsy (e.g. a typo like `ture`) is ignored and falls
through to config/default, but now emits a `logger.warning` mirroring
`config_manifest._coerce_env`. Because the default is `True`, an ignored
disable attempt would otherwise leave auto-update on with no feedback;
the warning surfaces it. Note that since the default flipped, a corrupt
`config.toml` (which `_read_update_config` logs and treats as empty) now
falls *open* to auto-update on rather than off — the env var still
overrides.

Made by [Open SWE](https://openswe.vercel.app)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-15 18:28:12 -04:00