Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cf2761b
feat(deepagents-code): add dcode status and allow OpenShell TLS key i…
laitingsheng Jul 2, 2026
85588c9
fix(onboard): derive dcode sandbox name from create args
laitingsheng Jul 2, 2026
5bcdfd2
Merge remote-tracking branch 'origin/main' into fix/dcode-status-and-…
laitingsheng Jul 2, 2026
0bfdb01
merge: sync fix/dcode-status-and-infra-key-allowlist with main
prekshivyas Jul 2, 2026
96aa3c4
fix(deepagents-code): add Provider field to dcode status and tighten …
prekshivyas Jul 2, 2026
313ccfc
fix(deepagents-code): add tvly- prefix to secret-shape detection patt…
prekshivyas Jul 2, 2026
a60c2d8
fix(deepagents-code): narrow OpenShell TLS key allowance
apurvvkumaria Jul 2, 2026
467f150
fix(deepagents-code): address status review follow-ups
apurvvkumaria Jul 2, 2026
f25bac9
refactor(onboard): keep entrypoint growth neutral
apurvvkumaria Jul 2, 2026
e54e9ba
test(deepagents-code): extract start script fixture
apurvvkumaria Jul 2, 2026
b0494d9
fix(deepagents-code): complete managed identity status
apurvvkumaria Jul 2, 2026
ea57fdc
fix(deepagents-code): harden identity metadata
apurvvkumaria Jul 2, 2026
7b9891e
fix(dcode): harden identity display metadata
apurvvkumaria Jul 2, 2026
4aaaef6
fix(dcode): redact unsafe status endpoints
apurvvkumaria Jul 2, 2026
4f3f72b
fix(dcode): suppress secret-shaped status metadata
apurvvkumaria Jul 2, 2026
97246a0
fix(dcode): align context secret detection
apurvvkumaria Jul 2, 2026
4d52e7a
merge: sync main into PR 6202
apurvvkumaria Jul 2, 2026
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
187 changes: 183 additions & 4 deletions agents/langchain-deepagents-code/dcode-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export DEEPAGENTS_CODE_OPENAI_API_KEY="${DEEPAGENTS_CODE_OPENAI_API_KEY:-nemocla
export OPENAI_BASE_URL="${OPENAI_BASE_URL:-https://inference.local/v1}"

readonly DEEPAGENTS_ENV_FILE="/sandbox/.deepagents/.env"
readonly DEEPAGENTS_CONFIG_FILE="/sandbox/.deepagents/config.toml"
readonly OPENSHELL_TLS_KEY_PATH="/etc/openshell/tls/client/tls.key"

run_dcode() {
exec python3 -m deepagents_code "$@"
Expand Down Expand Up @@ -61,6 +63,11 @@ run_dcode() {
# rejects secret-shaped runtime/.env values, or (b) all dcode invocations
# route through a Node entrypoint that imports the canonical patterns directly.

has_context_secret_shape() {
local upper="${1^^}"
[[ "$upper" =~ (_KEY|API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)[=:[:space:]][\'\"]?[A-Z0-9_.+/=-]{10,} ]]
}

has_non_slack_secret_shape() {
local value="$1"
if [[ "$value" =~ (sk-proj-|sk-ant-)[A-Za-z0-9_-]{10,} ]]; then
Expand All @@ -69,7 +76,7 @@ has_non_slack_secret_shape() {
if [[ "$value" =~ sk-[A-Za-z0-9_-]{20,} ]]; then
return 0
fi
if [[ "$value" =~ (nvapi-|nvcf-|ghp_|hf_|glpat-|gsk_|pypi-)[A-Za-z0-9_-]{10,} ]]; then
if [[ "$value" =~ (nvapi-|nvcf-|ghp_|hf_|glpat-|gsk_|pypi-|tvly-)[A-Za-z0-9_-]{10,} ]]; then
return 0
fi
if [[ "$value" =~ github_pat_[A-Za-z0-9_]{30,} ]]; then
Expand All @@ -90,6 +97,9 @@ has_non_slack_secret_shape() {
if [[ "$value" =~ [Bb]earer[[:space:]]+[A-Za-z0-9_.+/=-]{10,} ]]; then
return 0
fi
if has_context_secret_shape "$value"; then
return 0
fi
if [[ "$value" =~ lsv2_(pt|sk)_[A-Za-z0-9]{10,}(_[A-Za-z0-9]+)* ]]; then
return 0
fi
Expand Down Expand Up @@ -154,7 +164,7 @@ is_secret_shaped_value() {
if [[ "$value" =~ sk-[A-Za-z0-9_-]{20,} ]]; then
return 0
fi
if [[ "$value" =~ (nvapi-|nvcf-|ghp_|hf_|glpat-|gsk_|pypi-)[A-Za-z0-9_-]{10,} ]]; then
if [[ "$value" =~ (nvapi-|nvcf-|ghp_|hf_|glpat-|gsk_|pypi-|tvly-)[A-Za-z0-9_-]{10,} ]]; then
return 0
fi
if [[ "$value" =~ github_pat_[A-Za-z0-9_]{30,} ]]; then
Expand All @@ -181,6 +191,9 @@ is_secret_shaped_value() {
if [[ "$value" =~ [Bb]earer[[:space:]]+[A-Za-z0-9_.+/=-]{10,} ]]; then
return 0
fi
if has_context_secret_shape "$value"; then
return 0
fi
if [[ "$value" =~ lsv2_(pt|sk)_[A-Za-z0-9]{10,}(_[A-Za-z0-9]+)* ]]; then
return 0
fi
Expand All @@ -200,6 +213,16 @@ has_credential_name_context() {
return 1
}

# SECURITY: OpenShell's supervisor injects this mounted TLS key path into the
# runtime environment. Allow only the exact name/value pair after the generic
# value scan. Never allow the name alone, and never apply this exception to the
# mutable Deep Agents Code .env file.
is_allowed_openshell_runtime_value() {
local name="$1"
local value="$2"
[ "$name" = "OPENSHELL_TLS_KEY" ] && [ "$value" = "$OPENSHELL_TLS_KEY_PATH" ]
}

is_dynamic_dotenv_value() {
local value="$1"
case "$value" in
Expand Down Expand Up @@ -238,7 +261,7 @@ assert_no_secret_runtime_env() {
if is_secret_shaped_value "$value"; then
refuse_secret_env "runtime environment variable" "$name"
fi
if has_credential_name_context "$name" && [ ${#value} -ge 10 ]; then
if has_credential_name_context "$name" && [ ${#value} -ge 10 ] && ! is_allowed_openshell_runtime_value "$name" "$value"; then
refuse_secret_env "runtime environment variable" "$name"
fi
done < <(env -0)
Expand Down Expand Up @@ -297,8 +320,164 @@ assert_no_secret_env_file() {
assert_no_secret_runtime_env
assert_no_secret_env_file

toml_section_scalar() {
local section="$1"
local key="$2"
local line current_section=""
[ -r "$DEEPAGENTS_CONFIG_FILE" ] || return 0
while IFS= read -r line || [ -n "$line" ]; do
line="$(trim_whitespace "$line")"
case "$line" in
\[*\])
current_section="${line#\[}"
current_section="${current_section%\]}"
continue
;;
esac
[ "$current_section" = "$section" ] || continue
case "$line" in
"$key = \""*)
line="${line#"$key = \""}"
printf '%s' "${line%\"}"
return 0
;;
esac
done <"$DEEPAGENTS_CONFIG_FILE"
return 0
}

toml_provider_metadata() {
local field="$1"
local line route provider _api
[ -r "$DEEPAGENTS_CONFIG_FILE" ] || return 0
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
"# NemoClaw provider route: "*)
line="${line#"# NemoClaw provider route: "}"
IFS=';' read -r route provider _api <<<"$line"
route="$(trim_whitespace "$route")"
provider="$(trim_whitespace "$provider")"
case "$provider" in
"upstream provider: "*) provider="${provider#"upstream provider: "}" ;;
*) provider="" ;;
esac
case "$field" in
route) printf '%s' "$route" ;;
provider) printf '%s' "$provider" ;;
esac
return 0
;;
esac
done <"$DEEPAGENTS_CONFIG_FILE"
return 0
}

is_safe_dcode_agent_name() {
local value="$1"
local pattern='^[A-Za-z0-9_ -]+$'
local LC_ALL=C
[ -n "$value" ] || return 1
[ -n "$(trim_whitespace "$value")" ] || return 1
[[ "$value" =~ $pattern ]]
}

resolve_dcode_agent() {
local config_dir candidate
config_dir="${DEEPAGENTS_CONFIG_FILE%/*}"
candidate="$(toml_section_scalar agents default)"
if is_safe_dcode_agent_name "$candidate" && [ -d "$config_dir/$candidate" ]; then
printf '%s' "$candidate"
return 0
fi
candidate="$(toml_section_scalar agents recent)"
if is_safe_dcode_agent_name "$candidate" && [ -d "$config_dir/$candidate" ]; then
printf '%s' "$candidate"
return 0
fi
printf '%s' 'agent (default)'
}

terminal_safe_identity_value() {
local value="$1"
local fallback="${2:-}"
local LC_ALL=C
if [ ${#value} -gt 256 ] || [[ "$value" =~ [[:cntrl:]] ]] || is_secret_shaped_value "$value"; then
printf '%s' "$fallback"
else
printf '%s' "$value"
fi
}

safe_endpoint_identity_value() {
local value scheme authority
value="$(terminal_safe_identity_value "$1")"
[ -n "$value" ] || return 0
case "$value" in
*\\* | *\?* | *\#*) return 0 ;;
esac
scheme="${value%%://*}"
[ "$scheme" != "$value" ] || return 0
case "${scheme,,}" in
http | https) ;;
*) return 0 ;;
esac
authority="${value#*://}"
authority="${authority%%/*}"
case "$authority" in
"" | *@*) return 0 ;;
esac
printf '%s' "$value"
}

print_identity() {
local sandbox_name agent model endpoint route provider
sandbox_name="$(terminal_safe_identity_value "${NEMOCLAW_SANDBOX_NAME:-unknown}" unknown)"
agent="$(terminal_safe_identity_value "$(resolve_dcode_agent)" 'agent (default)')"
model="$(terminal_safe_identity_value "$(toml_section_scalar models default)")"
[ -n "$model" ] || model="$(terminal_safe_identity_value "$(toml_section_scalar models recent)")"
endpoint="$(toml_section_scalar models.providers.openai base_url)"
route="$(terminal_safe_identity_value "$(toml_provider_metadata route)")"
provider="$(terminal_safe_identity_value "$(toml_provider_metadata provider)")"
[ -n "$endpoint" ] || endpoint="${OPENAI_BASE_URL:-}"
endpoint="$(safe_endpoint_identity_value "$endpoint")"
printf 'Sandbox: %s\n' "$sandbox_name"
printf 'Harness: %s\n' 'langchain-deepagents-code'
printf 'Agent: %s\n' "$agent"
if [ -n "$route" ]; then
printf 'Route: %s\n' "$route"
fi
if [ -n "$provider" ]; then
printf 'Provider: %s\n' "$provider"
fi
if [ -n "$model" ]; then
printf 'Model: %s\n' "$model"
fi
if [ -n "$endpoint" ]; then
printf 'Endpoint: %s\n' "$endpoint"
fi
printf 'Runtime: %s\n' 'Deep Agents Code (terminal)'
}

print_managed_help() {
cat <<'EOF'
NemoClaw-managed commands:
dcode status Show managed sandbox and dcode runtime identity
dcode whoami Alias for dcode status
dcode identity Alias for dcode status

EOF
}

case "${1:-}" in
--version | -v | -V | --help | -h)
status | whoami | identity)
print_identity
exit 0
;;
--help | -h | help)
print_managed_help
run_dcode "$@"
;;
--version | -v | -V)
run_dcode "$@"
;;
esac
Expand Down
1 change: 1 addition & 0 deletions agents/langchain-deepagents-code/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ prepare_runtime_env() {
write_export_if_set LANGSMITH_TRACING
write_export_if_set LANGSMITH_PROJECT
write_export_if_set DEEPAGENTS_CODE_LANGSMITH_PROJECT
write_export_if_set NEMOCLAW_SANDBOX_NAME
} >"$tmp"
# Dcode intentionally runs as the non-root sandbox user, unlike the
# root-supervised OpenClaw/Hermes startup path. This atomic, sandbox-user-owned
Expand Down
11 changes: 11 additions & 0 deletions docs/get-started/quickstart-langchain-deepagents-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ dcode -n "Summarize this repository"

The managed wrapper launches Deep Agents Code with `HOME=/sandbox`, update checks disabled, remote Deep Agents sandbox providers disabled, MCP auto-loading disabled, and shell allow-list overrides blocked.

To confirm which sandbox a session is in, run the identity command:

```bash
dcode status
```

The command prints the sandbox name, NemoClaw harness, active `dcode` agent, configured inference route, upstream provider, model, endpoint, and runtime, then exits without starting the interactive UI.
`dcode whoami` and `dcode identity` are aliases.
`dcode --help` lists the managed aliases before the upstream Deep Agents Code help.
The sandbox name resolves when you run the command from a `nemoclaw <sandbox-name> connect` shell, which loads the NemoClaw runtime environment.

## Python Environment

Deep Agents Code runs from a NemoClaw-managed Python virtual environment at `/opt/venv`.
Expand Down
4 changes: 2 additions & 2 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2998,10 +2998,9 @@ async function createSandbox(
const envMessagingState = MessagingHostStateApplier.readPlanStateFromEnv();
const plannedMessagingState =
envMessagingState?.plan.sandboxName === sandboxName ? envMessagingState : undefined;
const plannedMessagingPlan = plannedMessagingState?.plan;
sandboxBuildPatchConfig.prepareSandboxBuildPatchConfig({
configuredMessagingChannels:
getChannelsFromPlan(plannedMessagingPlan) ?? activeMessagingChannels,
getChannelsFromPlan(plannedMessagingState?.plan) ?? activeMessagingChannels,
});
const { buildId } = await sandboxDockerfilePatchFlow.prepareSandboxDockerfilePatch({
agent,
Expand All @@ -3025,6 +3024,7 @@ async function createSandbox(
agent,
chatUiUrl,
createArgs,
sandboxName,
env: process.env,
extraPlaceholderKeys,
getDashboardForwardPort,
Expand Down
35 changes: 35 additions & 0 deletions src/lib/onboard/sandbox-create-launch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,39 @@ describe("prepareSandboxCreateLaunch", () => {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});

it("forwards the validated sandbox name into the Deep Agents Code sandbox create env", () => {
const result = prepareSandboxCreateLaunch({
agent: { name: "langchain-deepagents-code" } as any,
chatUiUrl: "",
createArgs: ["--name", "rendered-name"],
sandboxName: "dcode-demo",
env: {},
extraPlaceholderKeys: [],
getDashboardForwardPort: vi.fn(() => "0"),
hermesDashboardState: disabledHermesDashboardState,
manageDashboard: false,
openshellShellCommand: (args) => args.join(" "),
buildEnv: () => ({}),
});

expect(result.envArgs).toContain("NEMOCLAW_SANDBOX_NAME=dcode-demo");
expect(result.envArgs).not.toContain("NEMOCLAW_SANDBOX_NAME=rendered-name");
});

it("does not forward the sandbox name for non-Deep-Agents-Code agents", () => {
const result = prepareSandboxCreateLaunch({
agent: { name: "openclaw", configPaths: { dir: "/sandbox/.custom-openclaw" } } as any,
chatUiUrl: "http://127.0.0.1:19000/",
createArgs: ["--name", "demo"],
env: {},
extraPlaceholderKeys: [],
getDashboardForwardPort: () => "19000",
hermesDashboardState: disabledHermesDashboardState,
openshellShellCommand: (args) => args.join(" "),
buildEnv: () => ({}),
});

expect(result.envArgs.some((arg) => arg.startsWith("NEMOCLAW_SANDBOX_NAME="))).toBe(false);
});
});
8 changes: 8 additions & 0 deletions src/lib/onboard/sandbox-create-launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface SandboxCreateLaunchInput {
agent: AgentDefinition | null | undefined;
chatUiUrl: string;
createArgs: readonly string[];
sandboxName?: string;
env?: NodeJS.ProcessEnv;
extraPlaceholderKeys: readonly string[];
getDashboardForwardPort(chatUiUrl: string): string;
Expand Down Expand Up @@ -74,6 +75,13 @@ export function prepareSandboxCreateLaunch(input: SandboxCreateLaunchInput): San
envArgs.push(formatEnvAssignment("NEMOCLAW_PROXY_PORT", sandboxProxyPort));
}

if (input.agent?.name === "langchain-deepagents-code") {
const sandboxName = input.sandboxName;
if (sandboxName) {
envArgs.push(formatEnvAssignment("NEMOCLAW_SANDBOX_NAME", sandboxName));
}
}

appendExtraPlaceholderKeysEnvArg(envArgs, input.extraPlaceholderKeys, formatEnvAssignment);

const sandboxEnv = (input.buildEnv ?? buildSubprocessEnv)();
Expand Down
Loading
Loading