Merge pull request #5 from wxianfeng/codex/devapp-codex-connect-skill

This commit is contained in:
Aemeath
2026-06-10 21:48:07 +08:00
committed by GitHub
5 changed files with 60 additions and 35 deletions
+21 -28
View File
@@ -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_<PRODUCT>_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
}
+6 -7
View File
@@ -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) {
@@ -108,12 +108,24 @@ dws devapp robot connect --channel auto --robot-client-id <clientId> --robot-cli
# 用统一应用 ID,复用 credentials get 自动取凭证
dws devapp robot connect --unified-app-id <unifiedAppId> --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 <unifiedAppId> --channel codex --format json
# 预览建联方案不实际起连接
dws devapp robot connect --robot-client-id <clientId> --robot-client-secret <clientSecret> --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 <clientId> --robot-client-secret <cli
|------|------|
| `robot info is not exist` | 应用未配置机器人,先用 `robot config` 创建 |
| 应用名重复 | `app-name` 企业内需唯一,换个名字 |
| 本地 Codex agent 返回 `Not inside a trusted directory and --skip-git-repo-check was not specified` | 用 `DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check"` 重启 `robot connect --channel codex` |
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
| 创建任务 `EXPIRED` | 任务过期,重新 `submit`(可带原 taskId 重试) |
@@ -212,6 +212,12 @@ dws devapp robot connect --channel auto --robot-client-id CLIENT_ID --robot-clie
# 用统一应用 ID,复用 credentials get 自动取凭证(省得手填)
dws devapp robot connect --unified-app-id 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 UNIFIED_APP_ID --channel codex --format json
# 预览建联方案不实际起连接
dws devapp robot connect --robot-client-id CLIENT_ID --robot-client-secret CLIENT_SECRET --dry-run --format json
```
@@ -223,6 +229,7 @@ dws devapp robot connect --robot-client-id CLIENT_ID --robot-client-secret CLIEN
- **会话记忆 `--agent-memory`**(默认开):同一群/单聊共享 agent 会话,追问保留上下文。仅 claudecode/codebuddy/workbuddy 支持(CLI 有 `--session-id/--resume`);其余渠道自动无状态。`--agent-memory=false` 关闭。
- **模型覆盖 `--agent-model`**:claudecode 默认锁 haiku 求快,可改 sonnet/opus 换聪明(env `DWS_AGENT_MODEL`)。
- **运行目录 `--agent-workdir`**:放知识文件给机器人企业上下文;默认空白临时目录求快(env `DWS_AGENT_WORKDIR`)。
- **Codex 渠道**:真实建联优先用 `DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check"`,避免 Codex CLI 在默认临时目录内拒绝执行。需要固定上下文时,把可信目录写进覆盖命令:`DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check -C /path/to/repo"`(路径不要包含空格)。设置 `DWS_AGENT_CMD` 后,DWS 不再自动拼接 `--agent-model/--agent-workdir/会话参数`。
- **stream-bridge 渠道**(qoder/qoderwork/claudecode/workbuddy/codex/gemini/opencode):Go 原生进程内 Stream 转发器,订阅 `TOPIC_ROBOT`,每条 @机器人消息起一个无头 CLI 实例(如 `claude -p`)→ stdout 回钉钉,可 7×24 无人值守。
- **官方渠道**(openclaw/hermes):dws 不代建机器人,输出官方 onboarding 指引(连接器自带建号 + AI 卡片回复)。
- 覆盖项:`DWS_AGENT_CMD`(指定 agent 可执行命令) / `DWS_CONNECT_CMD`(指定外部连接器) / `DWS_CONNECT_NO_INSTALL=1`(关闭缺包自动安装) / `DWS_AGENT_TIMEOUT_MS`。
@@ -108,12 +108,24 @@ dws devapp robot connect --channel auto --robot-client-id <clientId> --robot-cli
# 用统一应用 ID,复用 credentials get 自动取凭证
dws devapp robot connect --unified-app-id <unifiedAppId> --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 <unifiedAppId> --channel codex --format json
# 预览建联方案不实际起连接
dws devapp robot connect --robot-client-id <clientId> --robot-client-secret <clientSecret> --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 <clientId> --robot-client-secret <cli
|------|------|
| `robot info is not exist` | 应用未配置机器人,先用 `robot config` 创建 |
| 应用名重复 | `app-name` 企业内需唯一,换个名字 |
| 本地 Codex agent 返回 `Not inside a trusted directory and --skip-git-repo-check was not specified` | 用 `DWS_AGENT_CMD="$CODEX_BIN exec --skip-git-repo-check"` 重启 `robot connect --channel codex` |
| `ServiceResult.success=false` | 透传 `errorCode/errorMsg` |
| 创建任务 `EXPIRED` | 任务过期,重新 `submit`(可带原 taskId 重试) |