diff --git a/internal/app/direct_runtime.go b/internal/app/direct_runtime.go index 39bc5bdf..2acb0974 100644 --- a/internal/app/direct_runtime.go +++ b/internal/app/direct_runtime.go @@ -223,12 +223,9 @@ func directRuntimeToolEndpoint(toolName string) (string, bool) { return "", false } dynamicMu.RLock() - te := dynamicToolEndpoints - dynamicMu.RUnlock() - if te == nil { - return "", false - } - endpoint, ok := te[toolName] + defer dynamicMu.RUnlock() + + endpoint, ok := dynamicToolEndpoints[toolName] return endpoint, ok && strings.TrimSpace(endpoint) != "" } @@ -252,24 +249,19 @@ func directRuntimeEndpoint(productID, toolName string) (string, bool) { } } - dynamicMu.RLock() - de := dynamicEndpoints - te := dynamicToolEndpoints - dynamicMu.RUnlock() - // Priority 1: product-level endpoint. // When the caller already knows the productID (e.g. "drive"), the product // endpoint is authoritative. This prevents cross-product tool name // collisions (e.g. both "drive" and "doc" register "create_folder") from // routing the request to the wrong MCP server. See issue #219. + dynamicMu.RLock() for _, candidate := range []string{strings.TrimSpace(productID), normalized} { if candidate == "" { continue } - if de != nil { - if endpoint, ok := de[candidate]; ok { - return endpoint, true - } + if endpoint, ok := dynamicEndpoints[candidate]; ok { + dynamicMu.RUnlock() + return endpoint, true } } @@ -277,11 +269,13 @@ func directRuntimeEndpoint(productID, toolName string) (string, bool) { // This path is used when the caller does not know the productID but has a // tool name, e.g. in helper invocations or plugin routes where only the // tool name is available. - if tool := strings.TrimSpace(toolName); tool != "" && te != nil { - if endpoint, ok := te[tool]; ok { + if tool := strings.TrimSpace(toolName); tool != "" { + if endpoint, ok := dynamicToolEndpoints[tool]; ok { + dynamicMu.RUnlock() return endpoint, true } } + dynamicMu.RUnlock() // Priority 3: built-in PAT fallback for cold-start paths that run before // discovery/plugin registration has populated the dynamic registry. @@ -347,12 +341,12 @@ func endpointFromEditionServers(productID string, fn func() []edition.ServerInfo // through DINGTALK__MCP_URL instead of requiring discovery. func DirectRuntimeProductIDs() map[string]bool { dynamicMu.RLock() - dp := dynamicProducts - dynamicMu.RUnlock() - ids := make(map[string]bool, len(dp)+2) + defer dynamicMu.RUnlock() + + ids := make(map[string]bool, len(dynamicProducts)+2) ids[defaultPATProductID] = true ids[devappProductID] = true - for key := range dp { + for key := range dynamicProducts { ids[key] = true } return ids @@ -427,15 +421,14 @@ func AppendDynamicServer(server market.ServerDescriptor) { } func normalizeDirectRuntimeProductID(productID string) string { - dynamicMu.RLock() - da := dynamicAliases - dynamicMu.RUnlock() trimmed := strings.TrimSpace(productID) - if da != nil { - if normalizedID, ok := da[trimmed]; ok && normalizedID != "" { - return normalizedID - } + dynamicMu.RLock() + if normalizedID, ok := dynamicAliases[trimmed]; ok && normalizedID != "" { + dynamicMu.RUnlock() + return normalizedID } + dynamicMu.RUnlock() + if normalizedID, ok := legacyDirectRuntimeAliases[trimmed]; ok { return normalizedID } diff --git a/internal/app/direct_runtime_test.go b/internal/app/direct_runtime_test.go index 4be3a483..8363143a 100644 --- a/internal/app/direct_runtime_test.go +++ b/internal/app/direct_runtime_test.go @@ -62,7 +62,7 @@ func TestDirectRuntimeEndpoint_DevappEnvOverridePreservesQuery(t *testing.T) { assertEndpoint(t, "devapp", "list_open_dev_apps_by_condition", "https://example.test/server/devapp?key=secret") } -func TestDirectRuntimeEndpoint_DevappDynamicServerWithoutRegistry(t *testing.T) { +func TestDirectRuntimeEndpoint_DevappDynamicServerDoesNotOverrideHardcoded(t *testing.T) { withCleanDynamicRegistry(t) SetDynamicServers([]market.ServerDescriptor{ { @@ -74,10 +74,10 @@ func TestDirectRuntimeEndpoint_DevappDynamicServerWithoutRegistry(t *testing.T) }, }) - assertEndpoint(t, "devapp", "list_open_dev_apps_by_condition", "https://example.test/server/devapp-supplement") + assertEndpoint(t, "devapp", "list_open_dev_apps_by_condition", devappEndpoint) } -func TestDirectRuntimeEndpoint_DevappEditionSupplementWithoutRegistry(t *testing.T) { +func TestDirectRuntimeEndpoint_DevappEditionSupplementDoesNotOverrideHardcoded(t *testing.T) { withCleanDynamicRegistry(t) prev := edition.Get() edition.Override(&edition.Hooks{ @@ -95,11 +95,10 @@ func TestDirectRuntimeEndpoint_DevappEditionSupplementWithoutRegistry(t *testing }) t.Cleanup(func() { edition.Override(prev) }) - assertEndpoint(t, "devapp", "list_open_dev_apps_by_condition", "https://example.test/server/devapp-edition-supplement?key=secret") - assertEndpoint(t, "app", "list_open_dev_apps_by_condition", "https://example.test/server/devapp-edition-supplement?key=secret") + assertEndpoint(t, "devapp", "list_open_dev_apps_by_condition", devappEndpoint) } -func TestDirectRuntimeEndpoint_DevappEditionStaticWithoutRegistry(t *testing.T) { +func TestDirectRuntimeEndpoint_DevappEditionStaticDoesNotOverrideHardcoded(t *testing.T) { withCleanDynamicRegistry(t) prev := edition.Get() edition.Override(&edition.Hooks{ @@ -117,7 +116,7 @@ func TestDirectRuntimeEndpoint_DevappEditionStaticWithoutRegistry(t *testing.T) }) t.Cleanup(func() { edition.Override(prev) }) - assertEndpoint(t, "devapp", "list_open_dev_apps_by_condition", "https://example.test/server/devapp-edition-static") + assertEndpoint(t, "devapp", "list_open_dev_apps_by_condition", devappEndpoint) } func TestDirectRuntimeEndpoint_DevappEnvOverrideWinsOverEditionSupplement(t *testing.T) { diff --git a/skills/dingtalk-devapp/references/robot.md b/skills/dingtalk-devapp/references/robot.md index c2d996f6..16f65ce9 100644 --- a/skills/dingtalk-devapp/references/robot.md +++ b/skills/dingtalk-devapp/references/robot.md @@ -108,12 +108,24 @@ dws devapp robot connect --channel auto --robot-client-id --robot-cli # 用统一应用 ID,复用 credentials get 自动取凭证 dws devapp robot connect --unified-app-id --channel qoderwork +# Codex 渠道推荐显式覆盖 agent 命令;否则默认空白工作目录可能触发 +# "Not inside a trusted directory and --skip-git-repo-check was not specified" +CODEX_BIN="$(command -v codex || echo /Applications/Codex.app/Contents/Resources/codex)" +DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check" \ + dws devapp robot connect --unified-app-id --channel codex --format json + # 预览建联方案不实际起连接 dws devapp robot connect --robot-client-id --robot-client-secret --dry-run --format json ``` 只建联、不建号:缺凭证先用 `robot create` / `robot submit` 建号拿 clientId/clientSecret。 +Codex 渠道注意: + +- `--channel codex` 真实建联时,优先使用上面的 `DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check"` 形式,避免 Codex CLI 在默认临时目录内因非可信 Git 目录拒绝执行。 +- 如需给 Codex 固定知识/项目上下文,可把可信目录直接写进覆盖命令:`DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check -C /path/to/repo"`。路径不要包含空格。 +- 设置 `DWS_AGENT_CMD` 后,DWS 不再自动拼接 `--agent-model` / `--agent-workdir` / 会话参数;需要的参数要一起写进 `DWS_AGENT_CMD`。 + | flag | 说明 | |------|------| | `--channel` | `auto`(默认,运行时信号自动识别) / openclaw / qoder / qoderwork / hermes / workbuddy / claudecode / codebuddy / codex / gemini / opencode | @@ -134,5 +146,6 @@ dws devapp robot connect --robot-client-id --robot-client-secret --robot-cli # 用统一应用 ID,复用 credentials get 自动取凭证 dws devapp robot connect --unified-app-id --channel qoderwork +# Codex 渠道推荐显式覆盖 agent 命令;否则默认空白工作目录可能触发 +# "Not inside a trusted directory and --skip-git-repo-check was not specified" +CODEX_BIN="$(command -v codex || echo /Applications/Codex.app/Contents/Resources/codex)" +DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check" \ + dws devapp robot connect --unified-app-id --channel codex --format json + # 预览建联方案不实际起连接 dws devapp robot connect --robot-client-id --robot-client-secret --dry-run --format json ``` 只建联、不建号:缺凭证先用 `robot create` / `robot submit` 建号拿 clientId/clientSecret。 +Codex 渠道注意: + +- `--channel codex` 真实建联时,优先使用上面的 `DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check"` 形式,避免 Codex CLI 在默认临时目录内因非可信 Git 目录拒绝执行。 +- 如需给 Codex 固定知识/项目上下文,可把可信目录直接写进覆盖命令:`DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check -C /path/to/repo"`。路径不要包含空格。 +- 设置 `DWS_AGENT_CMD` 后,DWS 不再自动拼接 `--agent-model` / `--agent-workdir` / 会话参数;需要的参数要一起写进 `DWS_AGENT_CMD`。 + | flag | 说明 | |------|------| | `--channel` | `auto`(默认,运行时信号自动识别) / openclaw / qoder / qoderwork / hermes / workbuddy / claudecode / codebuddy / codex / gemini / opencode | @@ -134,5 +146,6 @@ dws devapp robot connect --robot-client-id --robot-client-secret