Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 27 additions & 16 deletions agents/langchain-deepagents-code/patch-managed-deepagents-code.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,29 @@ async def _nemoclaw_skip_launch_tavily(self) -> None:
return None


async def _nemoclaw_skip_launch_model(
self,
) -> "tuple[bool, tuple[str, str] | None]":
"""Skip model picker during first-run; NemoClaw owns model configuration."""
return (False, None)


def _nemoclaw_skip_launch_dependencies_prompt(self):
"""Return a pre-resolved dependency result that skips the model picker.

The mount path pre-builds the model screen and passes it as
continue_screen to the name prompt, bypassing
_prompt_launch_dependencies_then_model. This override returns None
as the screen (so no model picker is pushed after the name prompt)
and a pre-resolved future with (False, None).
"""
import asyncio
loop = asyncio.get_running_loop()
result_future = loop.create_future()
result_future.set_result((False, None))
return None, result_future


async def _nemoclaw_block_model_auth(self, model_spec: str) -> bool:
del model_spec
self.notify(_NEMOCLAW_MANAGED_UI_MESSAGE, severity="warning", markup=False)
Expand Down Expand Up @@ -316,6 +339,8 @@ def _nemoclaw_block_mcp_login(self, server_name: str) -> None:
DeepAgentsApp.action_toggle_auto_approve = _nemoclaw_block_auto_approve
DeepAgentsApp._set_rubric_model = _nemoclaw_block_rubric_model
DeepAgentsApp._prompt_launch_tavily = _nemoclaw_skip_launch_tavily
DeepAgentsApp._prompt_launch_dependencies_then_model = _nemoclaw_skip_launch_model
DeepAgentsApp._build_launch_dependencies_prompt = _nemoclaw_skip_launch_dependencies_prompt
DeepAgentsApp._prompt_model_auth_if_needed = _nemoclaw_block_model_auth
DeepAgentsApp._show_auth_manager = _nemoclaw_block_auth_manager
DeepAgentsApp._enter_service_api_key = _nemoclaw_block_service_key
Expand Down Expand Up @@ -862,15 +887,6 @@ def _nemoclaw_select_with_auth_check(self, model_spec: str, provider: str) -> No
ModelSelectorScreen._select_with_auth_check = _nemoclaw_select_with_auth_check
'''

ONBOARDING_PATCH = r'''

# NemoClaw-managed Deep Agents Code hardening v2.
def should_run_onboarding(state_dir=None) -> bool:
"""Skip upstream first-run setup because NemoClaw owns model configuration."""
del state_dir
return False
'''


def _top_level_functions(tree: ast.Module) -> set[str]:
return {
Expand Down Expand Up @@ -963,7 +979,6 @@ def main() -> None:
"auth_ui": root / "widgets" / "auth.py",
"codex_ui": root / "widgets" / "codex_auth.py",
"model_selector": root / "widgets" / "model_selector.py",
"onboarding": root / "onboarding.py",
"approval": root / "widgets" / "approval.py",
"server": root / "server.py",
"server_config": root / "_server_config.py",
Expand Down Expand Up @@ -1029,6 +1044,8 @@ def main() -> None:
"_handle_update_action",
"_handle_update_command",
"_install_extra",
"_prompt_launch_dependencies_then_model",
"_build_launch_dependencies_prompt",
"_prompt_launch_tavily",
"_prompt_model_auth_if_needed",
"_show_auth_manager",
Expand Down Expand Up @@ -1101,9 +1118,6 @@ def main() -> None:
"ModelSelectorScreen",
{"_select_with_auth_check"},
)
_require_functions(
paths["onboarding"], texts["onboarding"], {"should_run_onboarding"}
)
_require_methods(
paths["approval"],
texts["approval"],
Expand Down Expand Up @@ -1178,9 +1192,6 @@ def main() -> None:
transformed["model_selector"] = _append_patch(
paths["model_selector"], texts["model_selector"], MODEL_SELECTOR_PATCH
)
transformed["onboarding"] = _append_patch(
paths["onboarding"], texts["onboarding"], ONBOARDING_PATCH
)
transformed["approval"] = _append_patch(
paths["approval"], texts["approval"], APPROVAL_PATCH
)
Expand Down
3 changes: 2 additions & 1 deletion docs/get-started/quickstart-langchain-deepagents-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ This agent-specific default does not change the shared Nemotron 3 Super default
NemoClaw/OpenShell keeps real provider credentials in credential handling and does not write them into the Deep Agents config file.
Deep Agents Code reaches `inference.local` through the managed OpenShell L7 proxy rather than direct sandbox DNS.
The image launcher normalizes the runtime proxy environment for interactive, login-shell, and direct-exec paths and removes inherited proxy credentials and bypass entries before `dcode` starts.
Managed interactive sessions skip Deep Agents Code's upstream first-run onboarding and model picker, then open the TUI with the model selected during NemoClaw onboarding.
Managed interactive sessions keep Deep Agents Code's optional first-run name prompt, skip its dependency and model selection screens, then open the TUI with the model selected during NemoClaw onboarding.
Press Enter at the name prompt to continue without setting a name.

## Choose the Default Sandbox

Expand Down
129 changes: 96 additions & 33 deletions test/deepagents-code-tui-startup-check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ function secretFixture(...parts: string[]): string {
return parts.join("");
}

type TuiExpectEvent = "eof" | "exit" | "firstRun" | "ready" | "timeout";
type TuiExpectEvent = "eof" | "exit" | "firstRun" | "namePrompt" | "ready" | "timeout";

const tclEventLiterals: Record<TuiExpectEvent, string> = {
eof: "{eof}",
exit: "{exit}",
firstRun: "{firstRun}",
namePrompt: "{namePrompt}",
ready: "{ready}",
timeout: "{timeout}",
};
Expand Down Expand Up @@ -97,40 +98,60 @@ proc send {args} {
set ::fake_closed 1
}
}
proc exp_continue {} {
return -code continue
}
proc expect {branches} {
if {[llength $::fake_events] == 0} {
error "fake Expect event queue exhausted"
}
set event [lindex $::fake_events 0]
set ::fake_events [lrange $::fake_events 1 end]
switch -- $event {
firstRun {
set branch_index [lsearch -exact $branches {$first_run_pattern}]
set ::expect_out(0,string) "Choose a Recommended Model"
}
ready {
set branch_index [lsearch -exact $branches {$ready_pattern}]
set ::expect_out(0,string) "What would you like to build?"
while {1} {
if {[llength $::fake_events] == 0} {
error "fake Expect event queue exhausted"
}
exit {
set branch_index [lsearch -glob $branches {NEMOCLAW_TUI_EXIT:*}]
set ::expect_out(0,string) "NEMOCLAW_TUI_EXIT:0"
set ::expect_out(1,string) "0"
set event [lindex $::fake_events 0]
set ::fake_events [lrange $::fake_events 1 end]
switch -- $event {
namePrompt {
set branch_index [lsearch -exact $branches {$name_prompt_pattern}]
set ::expect_out(0,string) "What should Deep Agents call you"
}
firstRun {
set branch_index [lsearch -exact $branches {$first_run_pattern}]
set ::expect_out(0,string) "Choose a Recommended Model"
}
ready {
set branch_index [lsearch -exact $branches {$ready_pattern}]
set ::expect_out(0,string) "What would you like to build?"
}
exit {
set branch_index [lsearch -glob $branches {NEMOCLAW_TUI_EXIT:*}]
set ::expect_out(0,string) "NEMOCLAW_TUI_EXIT:0"
set ::expect_out(1,string) "0"
}
timeout {
set branch_index [lsearch -exact $branches timeout]
}
eof {
set branch_index [lsearch -exact $branches eof]
}
default {
error "unsupported fake Expect event: $event"
}
}
timeout {
set branch_index [lsearch -exact $branches timeout]
if {$branch_index < 0} {
error "fake Expect event $event has no matching branch"
}
eof {
set branch_index [lsearch -exact $branches eof]
set branch_result ""
set branch_options {}
set branch_code [catch {
uplevel 1 [lindex $branches [expr {$branch_index + 1}]]
} branch_result branch_options]
if {$branch_code == 0} {
return $branch_result
}
default {
error "unsupported fake Expect event: $event"
if {$branch_code == 4} {
continue
}
return -options $branch_options $branch_result
}
if {$branch_index < 0} {
error "fake Expect event $event has no matching branch"
}
uplevel 1 [lindex $branches [expr {$branch_index + 1}]]
}
proc exit {{code 0}} {
set trace_file [open $::env(NEMOCLAW_TUI_TRACE) w]
Expand All @@ -146,8 +167,9 @@ proc exit {{code 0}} {
NEMOCLAW_TUI_CAPTURE: capture,
NEMOCLAW_TUI_CLOSE_AFTER_FIRST_CTRL_C: options.closeAfterFirstCtrlC ? "1" : "0",
NEMOCLAW_TUI_MARKERS: markers,
NEMOCLAW_TUI_FIRST_RUN_PATTERN:
"(your name \\(optional\\)|what should deep agents call you|choose a recommended model)",
NEMOCLAW_TUI_FIRST_RUN_PATTERN: "(choose a recommended model)",
NEMOCLAW_TUI_NAME_PROMPT_PATTERN:
"(your name \\(optional\\)|what should deep agents call you)",
NEMOCLAW_TUI_READY_PATTERN:
"(what would you like|enter (your )?(task|message|prompt)|how can i help)",
NEMOCLAW_TUI_SANDBOX_NAME: "fake-deepagents",
Expand Down Expand Up @@ -222,20 +244,36 @@ describe("Deep Agents Code TUI startup check helpers", () => {
expect(readiness("How can I help with the codebase today?")).toBe("ready");
});

it("matches the pinned first-run screens that managed DCode must suppress (#6410)", () => {
it("matches the pinned first-run model picker that managed DCode must suppress (#6410)", () => {
const isFirstRun = (capture: string) =>
runTuiStartupCheckHelper(
'if printf "%s" "$CAPTURE" | grep -Eiq "$TUI_FIRST_RUN_PATTERN"; then printf first-run; else printf other; fi',
{ CAPTURE: capture },
);

expect(isFirstRun("Your name (optional)")).toBe("first-run");
expect(isFirstRun("What should Deep Agents call you?")).toBe("first-run");
expect(isFirstRun("Choose a Recommended Model")).toBe("first-run");
expect(isFirstRun("Your name (optional)")).toBe("other");
expect(isFirstRun("What should Deep Agents call you?")).toBe("other");
expect(isFirstRun("Your project name")).toBe("other");
expect(isFirstRun("What would you like to build?")).toBe("other");
});

it("matches the name prompt pattern that managed DCode allows on first run", () => {
const isNamePrompt = (capture: string) =>
runTuiStartupCheckHelper(
'if printf "%s" "$CAPTURE" | grep -Eiq "$TUI_NAME_PROMPT_PATTERN"; then printf name-prompt; else printf other; fi',
{
CAPTURE: capture,
TUI_NAME_PROMPT_PATTERN: "(your name \\(optional\\)|what should deep agents call you)",
},
);

expect(isNamePrompt("Your name (optional)")).toBe("name-prompt");
expect(isNamePrompt("What should Deep Agents call you?")).toBe("name-prompt");
expect(isNamePrompt("Choose a Recommended Model")).toBe("other");
expect(isNamePrompt("What would you like to build?")).toBe("other");
});

itWithTclsh("fails before readiness when a first-run model picker appears (#6410)", () => {
const { markerText, result, traceText } = runTuiExpectStateMachine(["firstRun"]);

Expand All @@ -246,6 +284,31 @@ describe("Deep Agents Code TUI startup check helpers", () => {
expect(markerText).not.toContain("NEMOCLAW_TUI_READY");
});

itWithTclsh("allows the first-run name prompt and proceeds to ready state", () => {
const { markerText, result, traceText } = runTuiExpectStateMachine(
["namePrompt", "ready", "exit"],
{ closeAfterFirstCtrlC: true },
);

expect(result.status, result.stderr).toBe(0);
expect(traceText).toBe("0d,03");
expect(markerText).toContain("What should Deep Agents call you");
expect(markerText).toContain("NEMOCLAW_TUI_NAME_PROMPT");
expect(markerText).toContain("NEMOCLAW_TUI_READY");
expect(markerText).not.toContain("NEMOCLAW_TUI_UNEXPECTED_FIRST_RUN");
});

itWithTclsh("still rejects the model picker when it appears after the name prompt", () => {
const { markerText, result, traceText } = runTuiExpectStateMachine(["namePrompt", "firstRun"]);

expect(result.status, result.stderr).toBe(24);
expect(traceText).toBe("0d,03");
expect(markerText).toContain("NEMOCLAW_TUI_NAME_PROMPT");
expect(markerText).toContain("Choose a Recommended Model");
expect(markerText).toContain("NEMOCLAW_TUI_UNEXPECTED_FIRST_RUN");
expect(markerText).not.toContain("NEMOCLAW_TUI_READY");
});

itWithTclsh("captures a clean exit when dcode closes after the first Ctrl-C (tclsh)", () => {
const { markerText, result, traceText } = runTuiExpectStateMachine(["ready", "exit"], {
closeAfterFirstCtrlC: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ CONTEXT_SECRET_VALUE_PATTERN='[A-Za-z0-9_.+\/=-]{10,}'
# Upstream dcode does not expose a stable machine-readable TUI ready marker.
# Keep this localized heuristic prompt-shaped; do not match banner-only text.
TUI_READY_PATTERN='(what would you like|what do you want|enter (your )?(task|message|prompt)|describe (the )?(task|change)|how can i help)'
# NemoClaw configures DCode's model and managed provider before launch, so any
# upstream first-run screen is a regression that can expose unusable providers.
TUI_FIRST_RUN_PATTERN='(your name \(optional\)|what should deep agents call you|choose a recommended model)'
# NemoClaw configures DCode's model and managed provider before launch, so
# the model picker is a regression. The name prompt is allowed on first run.
TUI_FIRST_RUN_PATTERN='(choose a recommended model)'
TUI_NAME_PROMPT_PATTERN='(your name \(optional\)|what should deep agents call you)'
SENSITIVE_CAPTURE_FILES=()

ok() { printf '%s\n' "${PREFIX}: OK ($*)"; }
Expand Down Expand Up @@ -146,6 +147,7 @@ run_tui_expect() {
NEMOCLAW_TUI_CAPTURE="$raw_capture_file" \
NEMOCLAW_TUI_MARKERS="$marker_capture_file" \
NEMOCLAW_TUI_FIRST_RUN_PATTERN="$TUI_FIRST_RUN_PATTERN" \
NEMOCLAW_TUI_NAME_PROMPT_PATTERN="$TUI_NAME_PROMPT_PATTERN" \
NEMOCLAW_TUI_READY_PATTERN="$TUI_READY_PATTERN" \
NEMOCLAW_TUI_SANDBOX_NAME="$SANDBOX_NAME" \
NEMOCLAW_TUI_TIMEOUT="$TUI_TIMEOUT" \
Expand All @@ -155,6 +157,7 @@ set sandbox $env(NEMOCLAW_TUI_SANDBOX_NAME)
set capture $env(NEMOCLAW_TUI_CAPTURE)
set markers $env(NEMOCLAW_TUI_MARKERS)
set first_run_pattern $env(NEMOCLAW_TUI_FIRST_RUN_PATTERN)
set name_prompt_pattern $env(NEMOCLAW_TUI_NAME_PROMPT_PATTERN)
set ready_pattern $env(NEMOCLAW_TUI_READY_PATTERN)
log_file -a $capture

Expand All @@ -171,6 +174,14 @@ expect {
-nocase -re $ready_pattern {
set ready_match $expect_out(0,string)
}
-nocase -re $name_prompt_pattern {
append_marker $markers "$expect_out(0,string)"
append_marker $markers "NEMOCLAW_TUI_NAME_PROMPT"
puts "\nNEMOCLAW_TUI_NAME_PROMPT"
# Pinned Deep Agents Code 0.1.30 accepts an empty optional name and continues.
send -- "\r"
exp_continue
}
-nocase -re $first_run_pattern {
append_marker $markers "$expect_out(0,string)"
append_marker $markers "NEMOCLAW_TUI_UNEXPECTED_FIRST_RUN"
Expand Down
10 changes: 10 additions & 0 deletions test/fixtures/langchain-deepagents-code/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ async def _handle_auto_update_toggle(self):
async def _prompt_launch_tavily(self):
self.original_tavily = True

async def _prompt_launch_dependencies_then_model(self):
return (True, ("openai:gpt-4", "openai"))

def _build_launch_dependencies_prompt(self):
import asyncio
loop = asyncio.get_running_loop()
fut = loop.create_future()
fut.set_result((True, ("openai:gpt-4", "openai")))
return object(), fut

async def _prompt_model_auth_if_needed(self, model_spec):
del model_spec
return True
Expand Down
Loading
Loading