Repository navigation
Conversation
`omnigent devin` wraps the resident Devin CLI TUI in a runner-owned tmux pane and
mirrors it into an Omnigent conversation. It sits ALONGSIDE Devin's existing ACP
harness rather than replacing it:
* `devin-native` — this wrap. Omnigent policy enforcement, approval cards,
model + effort, resume, interrupt, cost tracking.
* `devin-acp` — the ACP path (`omnigent/inner/devin`), unchanged in behavior and
still the only one that surfaces Devin's sub-agents as child sessions. It moves
from the row id `devin` to `devin-acp` so the bare vendor spelling can
canonicalize to the native wrap, the way `opencode` resolves to
`opencode-native`.
Devin exposes a Claude-Code-shaped lifecycle-hook system carrying two ids the
other native harnesses have to reconstruct — `prompt_id` (used directly as the
Omnigent turn id) and `tool_use_id` (pairs a tool call with its output) — plus
`last_assistant_message` on `Stop`. So the mirror is hook-driven rather than
screen-scraped, and the forwarder is a fraction of the size of the
transcript-parsing ones.
Verified live against devin 3000.10.21:
* A `PreToolUse` hook returning `hookSpecificOutput.permissionDecision: "deny"`
blocks the tool even under `--permission-mode bypass`, and the reason reaches
the model — so the shared `native_policy_hook` seam drives Devin unmodified
(elicitation=HOOK, as claude-native). `PermissionRequest` accepts
`{"decision": "approve"}`.
* Interrupt needs Escape *twice* ("esc twice to interrupt"); one only clears the
composer draft.
* The composer stays writable mid-turn, so a queued message steers the running
turn (live_queue/steering).
* Effort is a model-variant suffix, not a flag, so a (model, effort) pair is
composed into one id at launch and validated against `devin models list`.
Hooks are registered through a session-scoped `--config` file merged from the
user's own config, so the user's settings survive and nothing is written into
their repository.
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: Dhruv Gupta <dhruv0811@gmail.com>
…ist pinned Three CI-surfaced fixes on top of the devin-native harness: * `pickerEffortOptions` returned `[]` for devin-native, so the New Chat composer rendered no Effort section at all. Devin has no `--effort` flag — effort is a model-variant suffix Omnigent composes at launch — so without the ladder there was no way to choose effort when starting a chat, which is the whole point of carrying it as a separate axis. Devin now shares the Anthropic rung set, which is exactly its ladder. * Dropped `fullySupported` from the Devin row. That flag pins the picker's primary list to Claude Code + Codex and `nativeCodingAgents.test.ts` asserts exactly those two, so promoting a brand-new harness there is a maintainer's product call rather than a side effect of adding it. Devin folds into "More" with the other nine natives; flipping it back is one line. * `configured_harness_map()` gained the three devin readiness spellings, so the hello-frame coverage test lists them. Adds `tests/e2e_ui/chat/test_devin_native_picker.py`, which the E2E UI judge asked for: it stubs the host catalog and asserts Devin is offered, that its model list comes from the devin-native probe (every other harness's stub is empty, so a wrong catalog shows nothing rather than passing by luck), and that the Effort ladder renders — the regression the first bullet fixes. Not run locally: the e2e_ui rig's runner will not come online here, and the existing sibling `test_harness_support_level_split` fails identically at the same fixture. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: Dhruv Gupta <dhruv0811@gmail.com>
Like hermes-native, devin-native is a terminal-first TUI launched via `omni devin` (tmux pane + bridge dir) rather than `omnigent run --harness devin-native`, AND it wraps the own-auth `devin` CLI — so its spawn env carries no gateway/profile probe vars for that matrix to drive. Devin's ACP row is already excluded as an ACP_CLI_HARNESSES member. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: Dhruv Gupta <dhruv0811@gmail.com>
The first version asserted Devin sits behind the Other submenu and that Pi is not in the primary list — i.e. it pinned the `fullySupported` product decision, which is not what the test is for. It also failed in CI: the row was not found where the assertion insisted it must be, and the e2e_ui rig will not boot locally (the existing sibling test_harness_support_level_split fails at the same fixture), so the placement could not be checked before pushing. `_reveal_devin` now looks in the primary list and expands Other only if it has to, so the test fails on a Devin regression rather than on a placement change. The substance is unchanged: Devin's model list comes from the devin-native probe (every other harness's stub is empty, so a wrong catalog shows nothing rather than passing by luck), and the Effort ladder renders. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: Dhruv Gupta <dhruv0811@gmail.com>
test_native_seed_ids_are_byte_stable pins every native built-in's name-derived agent id, because a drifting id orphans persisted conversation.agent_id rows on redeploy. devin-native-ui adds one entry. Verified this is an addition, not a drift: recomputing builtin_agent_id over NATIVE_CODING_AGENTS leaves all eleven existing ids byte-identical, with devin-native-ui as the only new key. So no migration is owed and no session is orphaned — which is exactly the distinction the table's comment asks for before touching it. Co-authored-by: Isaac <no-reply@databricks.com> Signed-off-by: Dhruv Gupta <dhruv0811@gmail.com>
…lity The devin-native row declares only `devinMode`, but two render gates keyed off `modelPicker`/`permissionMode`/codex only: `selectedAgentHasKnobs` (which wraps the whole config dropdown) and the models-section gate. So selecting Devin in the New Chat picker showed neither a model list nor an effort ladder. `pickerModelOptions` and `pickerEffortOptions` already resolve devin-native; only the two gates missed it. OR `supportsDevinMode` into both. Adds a NewChatDialog vitest that drives the Devin agent through the picker and asserts its model families + effort rungs render. It fails with the exact e2e_ui signature (models section absent) without the gate fix. Co-authored-by: Isaac <no-reply@databricks.com>
Resolves the composer refactor (#7243, #7260) against the devin-native picker: - pickerEffortOptions: keep Devin's Anthropic-ladder branch, adopt main's normalizeEffortLabel rename. - models section: main moved it into <ComposerConfigSections>; thread supportsDevinMode into the new models gate so Devin still renders its model list (selectedAgentHasKnobs auto-merged with the same flag). - ChatPage effectiveModel: keep main's sessionModelSeeded branch, keep devin in the vendor-owns-model group. Co-authored-by: Isaac <no-reply@databricks.com>
…elper The picker e2e never opened Devin's config submenu — it only revealed the row, so `new-chat-landing-agent-models` (which lives in that submenu) was never visible and the test failed regardless of the gate fix. The vitest passed because it opens the submenu; the e2e drove the UI wrong. Rewrite it on the shared `_open_entry_models` helper (same navigation the passing Pi/Codex picker tests use), which clicks the `agent-config-*` Edit entry. That entry exists only when `selectedAgentHasKnobs` honours `devinMode`, so the corrected test still fails without the gate fix and passes with it. Co-authored-by: Isaac <no-reply@databricks.com>
…tore Devin persists each run_subagent delegate's full transcript as its own chain in the parent session's message_nodes forest (sessions.db) — not in the hook stream or the ATIF export. This is the direct analogue of claude-native reading Claude Code's per-subagent transcript files; Devin keeps the same data in SQLite. Adds omnigent/harnesses/devin_native/subagents.py: - reconstruct_transcript_nodes: task-keyed longest leaf→root walk that recovers the executed chain and sidesteps the forest's compaction/streaming snapshots (a leaf has one ancestor path, so no snapshot guessing); - transcript_items: converts a chain to the forwarder's own conversation-item shapes (user/assistant messages, function_call + paired output, reasoning); - parsers for the spawned/completed agent_id (which ride only free text) and a read-only, fail-soft message_nodes reader. Pinned against a trimmed real capture (devin 3000.10.21, two parallel sub-agents) in tests/data/devin_subagent_nodes.json; 16 tests. Not yet wired into the forwarder — that lands next. Co-authored-by: Isaac <no-reply@databricks.com>
…l transcripts Wires the sub-agent transcript reconstruction into the live path so Devin's run_subagent delegates appear as child sessions in the Agents rail, each carrying its complete internal transcript (prompt -> tool calls + outputs -> final report) — parity with claude/codex/antigravity native sub-agents. Forwarder (harnesses/devin_native/forwarder.py): - record each run_subagent spawn (agent_id parsed from the tool result, task + title from the input), persisted in the forward state; - on each turn-end, read Devin's session store, and for every newly-completed sub-agent reconstruct its chain and post it into a minted child session, then close the child idle. Best-effort per sub-agent, marked done to avoid re-posts. Server (mirrors the codex/antigravity sub-agent-start family): - external_devin_subagent_start event type + _persist_external_devin_subagent_start, keyed idempotently on Devin's agent_id, stamping the devin-native-ui-subagent wrapper + title/tool_use_id labels; - _is_devin_native_subagent + _devin_subagent_display_tool drive the rail label (run_subagent title, agent_id as session_name); - devin joins _ALLOWED_EVENT_TYPES, the parse_item_data bypass set, and the terminal-status-forward exclusion (a mirror has no runner work entry). Capabilities + web: subagents=True + subagent_wrapper_label on the devin-native row; subagentWrapperLabel on the web row so the child resolves the Devin icon. Tests: server integration (mint / idempotency / missing-id 400) and forwarder flow (spawn recorded, completed -> mirrored child with full transcript, not mirrored until completion). Co-authored-by: Isaac <no-reply@databricks.com>
…t launch Two gaps kept the in-chat (in-conversation composer) model/effort controls from working for devin-native, even though the new-session composer had them: 1. UI gate: supportsEffortControl covered claude/codex/pi but not devin, so the in-chat Effort dial never rendered for a Devin session (the model picker did, via modelPickerKind). Add devin-native; effortLevelsForConv already returns the Anthropic ladder for it. 2. Switching didn't apply: the executor typed /model with config.model alone (the bare family), while reasoning_effort rode config.extra and was ignored — so an in-chat effort change (or effort-on-a-model change) never reached the pane. DevinNativeExecutor now recombines (family, effort) into one variant via resolve_devin_launch_model (cached per pair) before /model, matching the launch path. resolve_devin_launch_model degrades to the family if the catalog is unreachable, so a probe failure costs effort, not the turn. Tests: executor composition (compose, mid-chat effort switch re-applies /model, per-pair cache) and web capability gates (effort + model picker visible for devin-native in-chat). Co-authored-by: Isaac <no-reply@databricks.com>
… fixture Pre-commit (ruff format + end-of-file-newline) flagged two files the earlier commits added: the subagent reconstruction test's formatting and the captured node fixture, which json.dump wrote without a trailing newline. Co-authored-by: Isaac <no-reply@databricks.com>
The composer's slash-command menu unions a session's bundled skills with the extra skills its harness exposes in its own terminal, resolved per-vendor by resolve_harness_skills. devin-native had no provider, so it fell through to the generic host walk and Devin's own skills never appeared. Devin loads user-invocable skills (slash commands, including plugin-registered ones) from several of its own dirs (~/.config/devin/skills, ~/.agents/skills, .devin/skills, …) plus .claude/skills for compatibility, with its own precedence — more than a single dir walk here would faithfully reproduce. So devin_host_skills sources the menu authoritatively from `devin skills list --json --trigger user`, matching exactly the / commands the Devin terminal offers. Best-effort: a missing CLI / non-zero exit / timeout / bad output yields no host skills rather than raising, so the bundled skills still show. The web plumbing (session.skills -> composer menu) is generic, so no web change is needed; only the runner-side discovery was harness-specific. Co-authored-by: Isaac <no-reply@databricks.com>
resolve_devin_launch_model is typed str | None (it returns None only for a None family, which the guard above already excludes), so assigning it into the str-valued variant cache tripped pyrefly's unsupported-operation. Guard the None case explicitly — defensively correct and type-clean. Co-authored-by: Isaac <no-reply@databricks.com>
devin-native declared instruction_delivery=NOT_DELIVERED: the launch path never even received the agent spec, so a custom Devin agent's AgentSpec.instructions reached neither the CLI nor the model. Devin has no --append-system-prompt and ignores config-level instructions (verified against devin 3000.10.21). Its only channel that reaches every turn's system prompt is a Windsurf always-on rule (.windsurf/rules/*.md with a `trigger: always_on` frontmatter — without the frontmatter Devin treats the file as manual and never loads it; verified the model obeys it with the frontmatter). So thread ctx.agent_spec into _auto_create_devin_terminal (like pi/cursor) and write the raw instructions to <workspace>/.windsurf/rules/omnigent-agent-instructions.md at launch via the new write_devin_agent_rule. Stable name = one overwritten file, and a plain agent (no instructions) removes any rule a prior custom-agent launch left, so the workspace never carries stale instructions. Capability flips to AGENT_STARTUP_ADDITIVE. ponytail: the rule writes into the user's workspace — the only always-on channel Devin exposes (rules are CWD-relative; no out-of-tree rules dir). Co-authored-by: Isaac <no-reply@databricks.com>
# Conflicts: # web/src/shell/NewChatDialog.tsx
Devin's `--permission-mode` was reachable only via `omnigent devin` on the CLI; the composer had no picker. Add one, stacked on the devin-native PR. Devin uses the same `--permission-mode <mode>` launch flag as Claude Code, so the pick rides `terminal_launch_args` exactly like Claude's — but Devin has its own vocabulary (normal / accept-edits / smart / dangerous) and no running-session switch. To avoid entangling Claude's mid-chat permission machinery, this is an isolated `devinPermissionMode` capability mirroring Codex's separate `approvalMode` pattern rather than reusing `permissionMode`: - nativeHarnessModes.ts: DEVIN_NATIVE_PERMISSION_MODES (+ default "normal", which sends no flag so Devin keeps its own default) with args = ["--permission-mode", v]. - nativeCodingAgents.ts: `devinPermissionMode` capability on the devin row. - NewChatDialog: the permission hand-menu, chip summary, create-time terminal_launch_args emission, draft persistence, and harness-switch reconcile all gain a devin branch. Create-time only: Devin has no `/permissions` popup to drive a live switch. Server needs no change — terminal_launch_args validation is shape-only and the runner already appends passthrough to the Devin launch. Co-authored-by: Isaac <no-reply@databricks.com>
|
…ve-harness # Conflicts: # web/src/shell/NewChatDialog.tsx
Deprecate the `devin-acp` ACP harness in favor of native `devin-native`, the backwards-compatible way: keep it registered and resolvable (module, capability, `--harness devin-acp`, existing sessions), but stop offering it as a fresh pick so native Devin is the sole "Devin". - cli_config.py: skip `devin-acp` in the `omnigent config` setup list (the one fresh-pick surface that still showed a second "Devin (ACP)" row). Everything else in the ACP catalog is untouched. - acp_cli_harnesses.py / docs/devin-native.md: note the deprecation. - harness_plugins.py: document the one accepted backwards-compat break — a session persisted before this cutover with harness `devin` (which meant ACP then) resumes on the native wrap. Not fixable by a static alias (bare `devin` must mean native for a fresh launch yet ACP for an old session), and the ACP harness shipped only recently, so pre-cutover ACP sessions are thin. - tests: the setup overview no longer lists "Devin (ACP)"; row-dispatch indices shift down by one after native Devin. Co-authored-by: Isaac <no-reply@databricks.com>
|
Closed. If you want to pick this back up, comment |
Related issue
N/A — feature stacked on #7254 (devin-native harness). No separate issue.
Summary
Stacked on #7254. Adds a create-time permission-mode picker for Devin in the composer's permissions hand-menu — previously Devin's
--permission-modewas reachable only viaomnigent devinon the CLI.--permission-mode <mode>launch flag as Claude Code, so the pick ridesterminal_launch_argsexactly like Claude's. But Devin has its own vocabulary (normal/accept-edits/smart/dangerous, verified againstdevin 3000.10.21) and no running-session switch.devinPermissionModecapability mirroring Codex's separateapprovalModepattern rather than reusingpermissionMode.normal(default) sends no flag, so Devin keeps its own configured default.No server change:
terminal_launch_argsvalidation is shape-only and the runner already appends passthrough to the Devin launch, so the picked flag reaches the CLI as-is.ELI5: Devin already understood
--permission-mode; we just added the dropdown that sends it, with Devin's own list of modes.Test Plan
web:vitest run src/shell/NewChatDialog.test.tsx src/lib/nativeCodingAgents.test.ts src/pages/ChatPage.capabilities.test.ts— 443 pass, incl. two new tests asserting a picked mode emits["--permission-mode", <mode>]and the default emits nothing.tsc --noEmitclean;oxlintclean;prettierclean.Demo
UI change, but the composer menu could not be captured headlessly in this environment (no live browser). By-eye verification of the rendered permission chip/menu is pending a manual check: start
omnigent devinon this branch, open the composer permissions menu, and confirm the Normal/Accept edits/Smart/Dangerous options appear and a non-default pick starts Devin with that--permission-mode.Type of change
Test coverage
Coverage notes
Manual verification is limited to CLI + automated tests; the composer menu's visual rendering is unverified by eye in this environment (see Demo).
Changelog
Devin sessions can pick a permission mode (Normal / Accept edits / Smart / Dangerous) from the composer.
This pull request and its description were written by Isaac.