Skip to content

feat(provider): add GitLab Duo Agent provider (Agent Platform)#3060

Open
jiwangyihao wants to merge 20 commits into
can1357:mainfrom
jiwangyihao:feature/gitlab-duo-workflow
Open

feat(provider): add GitLab Duo Agent provider (Agent Platform)#3060
jiwangyihao wants to merge 20 commits into
can1357:mainfrom
jiwangyihao:feature/gitlab-duo-workflow

Conversation

@jiwangyihao

Copy link
Copy Markdown
Contributor

What

Adds a new GitLab Duo Agent provider (gitlab-duo-agent) that connects to GitLab's Duo Agent Platform via the Duo Workflow Service WebSocket, running an inline custom ambient flow. The existing AI Gateway proxy provider keeps its id (gitlab-duo) but is renamed for display to GitLab Duo Non-Agentic, matching GitLab's official "Agentic" / "Non-Agentic" classification.

Main changes:

  • New provider gitlab-duo-agent (packages/ai): full WebSocket protocol against the Duo Workflow Service, OAuth login via GitLab's official VS Code OAuth application (vscode://gitlab.gitlab-workflow/authentication paste-code callback), stream routing, and runMCPTool action bridging so OMP's local tools execute through the agent loop.
  • Inline custom ambient flow (flowConfig over the WebSocket, schema v1) instead of the built-in chat flow:
    • Supplies OMP's own system prompt — no server-side jinja wrapper, and no GitLab project/namespace/instance metadata reaches the agent.
    • MCP-only agent privileges, so GitLab native gitlab_* tools stay hidden and the agent only sees OMP's MCP tools.
    • Opts into on_agent_reasoning, mapping pre-tool-call commentary (message_sub_type: "reasoning") to thinking blocks — the chain-of-thought the official Duo CLI surfaces.
    • Uses the DUO_AGENT_PLATFORM unit primitive.
  • Project auto-discovery: the inline ambient flow requires a GitLab project server-side, but OMP has no project of its own. When none is configured, the provider discovers an accessible one (preferring a project under the resolved namespace group, then any membership project) and scopes direct_access, workflow creation, and WebSocket routing to it.
  • Catalog discovery (packages/catalog): gitlab-duo-agent model discovery via live aiChatAvailableModels, static contextWindow resolution (Claude/Gemini → 1,000,000, default 200,000) so the context panel / usage percentage / auto-compaction work, and per-checkpoint agent_context_usage mapped onto usage.input/totalTokens. Models are marked reasoning: false because the Duo Agent Platform path exposes no client-controllable thinking knob (the underlying Anthropic model params are server-fixed), so OMP no longer shows a thinking-effort selector for them.
  • Robustness: a 90s idle deadline aborts and reconnects once on the same workflowID when the WebSocket silently goes half-open (proxy/LB drops the TCP link without delivering close/error), instead of hanging until the user presses Esc.
  • cwd plumbing (packages/agent): threads the working directory into provider stream options so provider-side local tool execution targets the right workspace.

Why

GitLab is steadily moving Duo onto the Agent Platform (the agentic flow stack served by duo_workflow_service), and the older non-agentic AI Gateway proxy path is being deprecated. Two concrete reasons motivate a new provider rather than extending the old one:

  • Credit access: the old gitlab-duo provider goes through the AI Gateway proxy, which bills against the ai_gateway_proxy_use quota bucket. The Agent Platform path bills against duo_agent_platform_workflow_on_execute — a different entitlement. GitLab Plans grant GitLab Credits to the Agent Platform bucket, so the old proxy provider cannot consume the credits a user's plan actually provides. This was verified empirically: the same OAuth credential reaches the proxy endpoint but returns 402 USAGE_QUOTA_EXCEEDED on the proxy bucket while the Agent Platform flow runs.
  • Capabilities: the Agent Platform inline flow gives a fully custom system prompt, suppression of GitLab's injected tools and project/namespace metadata, multi-step agent loops, and chain-of-thought reasoning — none of which the proxy path can offer.

The two providers are deliberately kept side by side (the old one renamed to clarify its "Non-Agentic" classification) rather than removing the proxy path, since it still serves the classic Duo Chat surface.

Testing

  • packages/ai: bun run check (biome + tsgo, 465 files) clean; 49 GitLab provider tests pass.
  • packages/catalog: bun run check (92 files) clean; 20 GitLab discovery tests pass.
  • packages/agent and packages/coding-agent: typecheck clean.
  • Live-verified end to end against the real Duo Agent Platform (OAuth, project auto-discovery, MCP tool execution via runMCPTool, reasoning→thinking blocks, per-checkpoint usage mapping, and idle-timeout reconnect).

  • bun check passes
  • Tested locally
  • CHANGELOG updated (if user-facing)

@github-actions github-actions Bot added the vouched Passed the vouch gate label Jun 19, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47c4dfa364

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from 47c4dfa to 8e5917b Compare June 19, 2026 17:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e5917b4b4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
Comment thread packages/catalog/src/discovery/gitlab-duo-workflow.ts Outdated
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from 8e5917b to 88ccafb Compare June 19, 2026 18:22
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Addressed all three Codex suggestions (force-pushed, folded into the relevant atomic commits):

  • P1 — stop workflows via a non-aborted cleanup path: the stop PATCH no longer receives the request's already-aborted signal (which cancelled it before sending), and now runs from a finally block on any abort with a fresh request, also dropping the resumable session so the next turn does not reuse the closed socket.
  • P1 — preserve paused sessions after action responses: the post-action resume now keeps providerSessionState.active on a pause result (matching the paused-session resume branch) instead of clearing it, so a server-side tool boundary crossed during resume no longer discards the buffered continuation. Added a regression test.
  • P2 — honor project path during model discovery: catalog namespace discovery now resolves the configured project from a projectPath config field and GITLAB_DUO_PROJECT_PATH (in addition to projectId/GITLAB_DUO_PROJECT_ID). Added a regression test.

Also reverted an unrelated cursorEventEmitter rename and dropped a dead cwd param to keep the diff focused. bun run check clean for ai (465 files) + catalog (92); GitLab suites 50 + 21 pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88ccafb093

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from 88ccafb to 33a0294 Compare June 19, 2026 18:34
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Follow-up on the latest Codex review (commit 88ccafb):

  • P1 — clean up workflows after socket errors: valid and now fixed. The previous finally only stopped the workflow when options.signal was aborted, so a runGitLabDuoWorkflowSocket rejection (e.g. ws.onerror) skipped cleanup and left providerSessionState.active pointing at the dead socket with the remote workflow still running. The cleanup now also runs when the socket loop throws (tracked via a settledNormally flag): it drops active and issues the stop PATCH with a fresh signal on abort or abnormal exit, while still preserving active for the intentional action/pause resume path. Added a regression test that drives a socket onerror and asserts the stop PATCH fires and the session is cleared. Folded into the feat(ai) commit.

  • P2 — handle GitLab API requests with HTTP, not read: acknowledged, leaving as-is for now. runHTTPRequest/gitlab_api_request are only ever emitted by the legacy chat/software_development flows; the default (and only shipped) flow is the inline ambient flow with MCP-only privileges (agent_privileges: [6]), under which the server emits only runMCPTool, so this mapping is unreachable in practice. Implementing a real GitLab HTTP request path would be net-new scope exercised by no current flow, and dropping the action could regress the legacy paths. Happy to address it if those flows become reachable.

All checks green: packages/ai bun run check clean (465 files), 49 GitLab provider tests pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 33a0294638

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch 2 times, most recently from 553c32c to 04111ca Compare June 19, 2026 19:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 04111ca8f7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/catalog/src/discovery/gitlab-duo-workflow.ts Outdated
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from 04111ca to c4e8f9f Compare June 19, 2026 20:21
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Addressed the latest Codex review (P2, packages/catalog/src/discovery/gitlab-duo-workflow.ts): a numeric GITLAB_DUO_PROJECT_ID whose REST project payload exposes no explicit root (the normal payload only carries the immediate namespace) was blocked from the GraphQL rootAncestor fallback by the missing-slash guard, so discovery fell through to remotes/top-level groups. fetchProjectRootNamespace now keys the GraphQL fallback off the project's path_with_namespace returned in the REST payload, so numeric ids resolve the correct root. Added a regression test exercising a realistic numeric-id payload (no rootAncestor) -> GraphQL fallback by full path. The earlier P1 (resume turns hanging on non-terminal socket results) was already fixed by routing both resume paths through the shared finalizer. bun run check clean for packages/ai and packages/catalog; 48 + 22 GitLab tests pass.

@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from c4e8f9f to 5bca4f6 Compare June 19, 2026 20:38

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5bca4f68fb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
Comment thread packages/catalog/src/discovery/gitlab-duo-workflow.ts
Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from 5bca4f6 to cd8fcec Compare June 19, 2026 21:01
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Addressed the latest Codex review:

  • P1 (pause per checkpoint): Fixed. GitLab checkpoints are full ui_chat_log snapshots, so a later frame replays the earlier request/tool boundary before the new agent delta; the prior logic paused on any boundary once a segment had been emitted earlier in the socket call, turning normal streaming into one pause_turn per checkpoint and eventually hitting the loop's pause-continuation cap. Pause now fires only on a boundary that follows a delta emitted in the current checkpoint. Added a regression test (does not pause on a stale boundary replayed at the head of a later checkpoint snapshot).
  • P2 (relative base path): Fixed. new URL("/api/...", baseUrl) dropped a self-managed relative install path (https://host/gitlab). direct_access, workflow creation, aiChatAvailableModels, project discovery/lookup, and the non-serviceEndpoint WebSocket URL now append onto the normalized base, preserving the install path. Added a WS URL regression test.
  • P2 (worktree commondir): Fixed. A linked worktree's .git points at .git/worktrees/<name> whose config holds no remotes; discovery now follows the gitdir's commondir to read the common config. Added a worktree-layout discovery test.

bun run check clean for both packages/ai (467 files) and packages/catalog (93); 50 + 23 GitLab tests pass. Rebased onto latest origin/main and folded into the 4 atomic commits.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd8fcec68b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts
Comment thread packages/catalog/src/discovery/gitlab-duo-workflow.ts Outdated
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from cd8fcec to 90e8965 Compare June 19, 2026 21:23
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Addressed the 21:06 review:

  • Strip relative GitLab install paths from remotes (P2): accepted. parseGitLabRemoteProjectPath now strips the install base path (e.g. /gitlab) from a remote like https://host/gitlab/group/project.git before deriving the project full path, so discovery queries /api/v4/projects/group%2Fproject rather than .../gitlab%2Fgroup%2Fproject. Added a regression test.

  • Use the discovered namespace when streaming (P2): not taking this one. resolveGitLabDuoWorkflowNamespaceSelection deliberately re-discovers from the current credentials/cwd each turn instead of trusting model.gitlabDuoWorkflowRootNamespaceId, which can be stale (a prior model-refresh may have listed a different group, and cwd/env can shift between refresh and the turn). An existing contract test pins this behavior. Explicit rootNamespaceId/namespaceId options and GITLAB_DUO_NAMESPACE_ID still take precedence over discovery; discovery is deterministic for the common single-group case.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 90e8965855

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/registry/oauth/gitlab-duo-workflow.ts
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from 90e8965 to 20e3ebd Compare June 20, 2026 07:24
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Addressed the broker-login P2: omp auth-broker login gitlab-duo-agent (and --via) previously hung until timeout because the provider uses GitLab's fixed vscode:// OAuth redirect, which never reaches the broker's local callback server, and runLocalLogin supplied no onManualCodeInput fallback. runLocalLogin now offers the same paste-the-redirect-URL prompt the interactive sign-in wizard uses (via the existing readline), so the code can be entered manually and credentials saved. Folded into the agent integration commit; coding-agent typecheck + biome clean.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20e3ebdcd7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/agent/src/agent.ts
Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from 20e3ebd to a299e65 Compare June 20, 2026 08:28

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a299e65909

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
@can1357

can1357 commented Jun 20, 2026

Copy link
Copy Markdown
Owner

wtf is gitlab doing? why two seperate providers?

@jiwangyihao

Copy link
Copy Markdown
Contributor Author

wtf is gitlab doing? why two seperate providers?

https://docs.gitlab.com/user/gitlab_duo/

Maybe you can see this. The old one already in omp is GitLab Duo Non-Agentic, and the one in this PR is GitLab Duo Agent.

These two use different credit system (in my test is), and the GitLab Ultimate Trial subscription can only access GitLab Duo Agent.

BTW, I may update a new change to this PR in hours, fix an issue I found a moment ago.

@jiwangyihao

Copy link
Copy Markdown
Contributor Author

CI follow-up: run 28008471116 (head 4d177cf69) is red on exactly one job — Test coding-agent native/unit (TS) — with a single failing test:

(fail) executeBash :async: background retention > keeps a per-job :async: shell's background process alive across turns
 1 fail

This is a pre-existing upstream flake, not from this PR:

  • It fails identically on main itself: run 28006645402 (2c3e0d79a) and 28003050043 (c6fcc155a) both fail on the same one test.
  • This branch is rebased onto that same main tip and touches no bash-executor / background-process code (changes are confined to packages/ai GitLab Duo provider + tests, packages/catalog generator comment + tests, and changelogs).
  • All GitLab-Duo-relevant suites are green: catalog discovery 28/28, catalog build 25/25, ai provider 65/65, ai oauth green; bun check:types clean for pi-ai/pi-catalog.

A re-run of that job (or the upstream flake fix landing on main, which this branch will pick up on the next rebase) should clear the check. Happy to re-run/rebase on your signal.

将 GitLab Duo Agent 的 goal 改为平权 ChatML 转录、system prompt 移入 inline flow system 槽;新增并行工具调用合并、用户打断 steering 重播种、通用错误重试一次、登录时启用 MCP/Beta 设置,并把 idle-timeout 恢复改为新建 workflow。
A resumed turn that held pending runMCPTool actions whose requestID could
not be paired with a persisted tool result (id mismatch) silently created a
fresh workflow while leaving the previous one running server-side. The
stranded workflow's LangGraph still treated the tool call as in-flight, so
the model never saw the result and re-issued the same call in a loop until
the user interrupted. The unresolvable-batch path now follows the same
cleanup as a mid-batch steer: stop the stranded workflow server-side and
drop the resumable session before seeding the fresh workflow, whose goal
transcript replays the full history including the unanswered tool result.

Also require the server-assigned requestID on each runMCPTool action frame
instead of synthesizing a random one: a fabricated non-empty id is silently
discarded by the DWS executor outbox (misses the awaiting-futures map),
which would strand the tool call. DWS always assigns a non-empty requestID
(verified against contract.proto and the proto->JSON relay shape), so a
genuinely missing id now fails the turn fast with diagnostics.
The 250ms action-flush window assumed DWS dispatches a model turn's parallel
tool calls as a back-to-back burst of runMCPTool frames. A bare-request probe
(hold the first action unanswered, watch for a second) proved the opposite:
the inline ambient flow's ToolNode runs a serial `for tool_call: await
tool.ainvoke(...)` loop and each MCP ainvoke blocks in
put_action_and_wait_for_response until the client returns the matching
actionResponse, so only one action is ever in flight per turn and the second
is never dispatched until the first is answered. The batching window therefore
only ever held one frame.

Remove the batching machinery (GITLAB_DUO_WORKFLOW_ACTION_FLUSH_MS, the flush
timer, pendingActionBatch, finishGitLabDuoWorkflowActionBatch) and restore the
serial model: each runMCPTool frame finalizes its own assistant message (one
done, one usage) and commits the single pending action; the action result
settles immediately without closing the socket so the resume path reuses it.
Update the test to assert the per-action contract and the CHANGELOG to describe
the serial wire behavior.
Namespace auto-discovery ran on every turn. The root namespace is a
function of the GitLab credential (account), not of the conversation or
cwd, and the inline ambient flow never exposes namespace to the model, so
re-discovering each request was pure overhead.

Cache the discovered namespace per account, keyed by a non-reversible
fingerprint of the credential plus base URL, in a module-level map that
outlives any single conversation session. On auto-discovery the cached
namespace is the first choice; only if its dependent direct_access /
workflow-create calls later fail (revoked access, deleted group, membership
change) is the cache invalidated and discovery rerun once. Explicit
rootNamespaceId/namespaceId/project configuration (option or env) bypasses
the cache and stays authoritative, preserving the current-credentials
re-discovery contract for stale model metadata.

Add tests: discovery runs once across two turns for the same account; a
cached namespace that fails triggers exactly one re-discovery.
Namespace auto-discovery was already being moved to a per-account cache, but
settings enablement (`ensureGitLabDuoWorkflowSettings`) still lived on the
per-session provider state. That meant independent side-requests like
compaction/handoff could still repeat the namespace settings PUT even though
namespace is account-scoped and never exposed to the model.

Promote the provider's account preparation state to a module-level per-account
map keyed by a non-reversible fingerprint of the credential plus base URL:
- keep the discovered namespace selection there,
- move `settingsEnsured` there too.

Auto-discovery now reuses the cached namespace across sessions/turns and
settings enablement is sent at most once per account. If a cached namespace
later fails in dependent direct_access/workflow-create calls, it is invalidated
and re-discovered once. Explicit namespace/project config still bypasses the
cache and stays authoritative.

Add tests: namespace discovery runs once across two turns for the same account,
re-discovers exactly once after a cached namespace fails, and Duo settings are
enabled once per account rather than once per provider session.
- 修复 special.ts 重复 return 片段导致 catalog 无法解析
- stop/settings-enablement 改用 gitLabApiUrl 保留自托管相对路径
- 重放命中 action 早返回前清除 paused,避免续帧被缓冲到超时
- setupForNamespace 返回值改用具名 GitLabDuoWorkflowNamespaceSetup(去除 ReturnType)
- toolChoice=none 的 side-request 不再向 Duo 暴露工具
- 套接字异常关闭(closed)也走 stop 清理,避免服务端工作流残留
- ProviderSessionState.close 关闭时发送 stop,避免会话销毁后残留工作流
- 命名空间/设置启用缓存按 (account, baseUrl, cwd) 分区
- 动态模型缓存按凭据+命名空间作用域分区
- resume 失败时丢弃 active 并 stop 工作流
driveOneTurn 原先固定轮询 30 个微任务等待 socket 创建;命名空间发现/
直连/创建工作流链路较深,在高负载 CI runner 上 socket 尚未创建就放弃
等待,导致 onopen 丢失、流空转至 5s 超时。改为按真实截止时间轮询直到
socket 出现,未出现则显式抛错。
- catalog CHANGELOG 删除 rebase 重放进已发布 [16.1.4] 段落的重复 Claude 4.6 条目,使该段落与上游 main 完全一致(已发布段不可变)
- Duo Agent finally 清理在最终 idle timeout(重试已耗尽)时也发送 stop PATCH,避免代理/LB 持续断连场景下服务端工作流残留
- auth-broker login 仅对 pasteCodeFlow provider 传入 onManualCodeInput,普通 loopback provider 不再让 readline 提示与 HTTP 回调竞争导致终端残留
- 动态模型缓存键改为镜像 discoverGitLabDuoWorkflowNamespace 真实解析输入:
  内置发现只传 apiKey/baseUrl/fetch,原键里的 namespaceId/projectId/cwd 恒为
  空,退化为 apiKey+baseUrl,导致同 token 下两个不同 group 的工作区互相复用
  权威模型缓存。改为按 (凭据, baseUrl, 命名空间/项目 config 或同名环境变量,
  有效 cwd) 指纹分区。
- 远程 host 比较改用 host(含端口)而非 hostname:同主机不同端口的自托管 GitLab
  不再被误判为同实例,避免从错误远程推导项目路径。SCP 远程无端口概念,仍按裸
  host 比较。新增跨端口远程不被当作工作区项目的回归测试。
- resumeGitLabDuoWorkflowSocket 在 closed/timeout 结果时也发送 stop PATCH:
  预留会话在工具结果或暂停重放后若 WebSocket 在任何终态前返回 closed/timeout,
  原先只 finalize 本地流并清 active,不发 fresh-workflow finally 同样会发的停止
  请求,导致代理/服务端断连或 idle 超时后服务端工作流残留且本地已无句柄可停。
- AI CHANGELOG 把 GitLab Duo 条目从已发布 [16.1.8] 段移至 [Unreleased]:
  已发布段不可变,新条目应入 Unreleased。移动后 [16.1.8] 与上游 main 一致。
- 自动项目发现优先复用命名空间解析所依据的项目(工作区 git remote 或显式
  项目),而不是从 group 列表泛选;多项目 group 下不再把工作流 scope 到无关项目。
  命名空间选择沿用 remote/project 来源的 projectPath,运行时据此 scope。
- Duo 设置启用仅在确定性尝试(任意 HTTP 响应,含 4xx)后才标记 ensured;
  瞬时网络错误/5xx 返回 false,使后续 turn 可重试,避免 fresh 命名空间因一次
  瞬时失败而永久跳过 PUT。
- agent CHANGELOG 把 cwd 转发条目从已发布 [16.1.8] 段移至 [Unreleased];
  移动后 [16.1.8] 与上游 main 一致。
机器人指出 gitlab-duo-agent 不在 models.json,fresh 安装(尚无带凭据的动态
发现/缓存)时内置 catalog 看不到默认模型。generator 现按 Sakana 同样方式播种
gitlab-duo-agent 的 fallback 模型(claude_sonnet_4_6_vertex):live aiChatAvailableModels
发现成功时其条目按 id 去重胜出,仅在无凭据/失败 regen 时落种子。

- generate-models.ts:导入 buildGitLabDuoWorkflowFallbackModel,在 authoritative
  发现未命中时 push 种子。
- 重新生成 models.json,仅保留 gitlab-duo-agent provider 块的新增,其余 provider
  数据维持基线不变(regen 在无凭据环境下未触碰其它 provider)。
- 新增针对 descriptor 的回归测试(非 bundled JSON):断言 manager options 暴露
  fallback 静态模型,符合 AGENTS.md 要求。
机器人指出两处问题,本提交处理:

1) direct_access 带 body message 的错误丢弃了 response.status,导致
   streaming auth-retry 路径(extractStatusFromAssistantError ->
   extractHttpStatusFromError)无法恢复状态、无法刷新/轮换过期 OAuth 或
   配额受限的 broker 凭据。现在即便有 body message 也内嵌 HTTP <status>,
   与 create 路径既有约定一致。新增 401 Unauthorized 回归测试断言状态可恢复,
   并强化既有 403 配额测试。

2) generate-models 种子注释过度暗示生成期会跑 namespace-scoped 发现。
   实际上 gitlab-duo-agent descriptor 故意不带 catalogDiscovery,被
   isCatalogDescriptor 过滤排除在生成发现循环之外,因此生成期绝不会拉取
   某账号的 aiChatAvailableModels,只播种通用 namespace-free fallback。
   修正注释表述,新增两条针对 descriptor 的回归测试:断言 descriptor 无
   catalogDiscovery(永不参与生成发现),以及 fallback 模型不带
   gitlabDuoWorkflowRootNamespaceId。
机器人指出 agent.ts 的 #cwd 在构造时固定,/move 更新 SessionManager 与
进程 cwd 后不会重建 Agent,导致 GitLab Duo Agent 的 namespace/project 发现
持续读取旧仓库的 git remote。

按既有 resolver 模式(getReasoning/getServiceTier)修复:

- Agent 新增可选 cwdResolver;构造时存入 #cwdResolver。
- AgentLoopConfig 新增 getCwd 每调用解析器,config 同时携带静态 cwd 与
  getCwd。
- agent-loop 在 streamFunction 调用点计算 effectiveCwd = getCwd?.() ?? cwd,
  每次 LLM 调用读取一次,因此运行中途的 /move 也能被工作区级 provider 发现
  感知。
- sdk.ts 主 Agent 传入 cwdResolver: () => sessionManager.getCwd(),该值在
  /move 时由 SessionManager.#cwd 更新。

新增针对可观测契约的回归测试(mock streamFn 记录 options.cwd):resolver
覆盖静态 cwd、resolver 返回 undefined 时回退静态 cwd、以及运行中途变更可被
逐次调用读取(模拟 /move)。
@jiwangyihao jiwangyihao force-pushed the feature/gitlab-duo-workflow branch from 4d177cf to a088037 Compare June 23, 2026 08:12
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Addressed the remaining current Codex thread.

Fixed — Forward the live session cwd after moves (agent.ts / sdk.ts) (a0880370c)

Correct catch. Agent.#cwd was captured at construction, and /move updates SessionManager + the process cwd without reconstructing the Agent, so GitLab Duo Agent namespace/project discovery kept reading the original repo's git remote (the provider keys workspace discovery and account-state caching off cwd).

Fix follows the existing per-call resolver pattern (getReasoning/getServiceTier):

  • Agent gains an optional cwdResolver; AgentLoopConfig gains a getCwd per-call resolver. The loop config now carries both the static cwd and getCwd.
  • agent-loop computes effectiveCwd = config.getCwd?.() ?? config.cwd at the streamFunction call site, read once per LLM call, so even a mid-run /move reaches workspace-scoped provider options (falls back to the static cwd when the resolver is unset/returns undefined).
  • sdk.ts passes cwdResolver: () => sessionManager.getCwd() to the main agent; SessionManager.#cwd is what /move updates, and getCwd() returns it live.

Regression tests assert the observable contract (mock streamFn records options.cwd): the resolver overrides the static cwd, falls back to the static cwd when it returns undefined, and is re-read per model call within one run (a /move mid-run is seen on the continuation request).

Verification: bun check:types clean for pi-agent-core and pi-coding-agent; agent suite 27/27 (incl. the 3 new cwd tests), agent-loop 47/47.

Still open by design

  • Avoid forcing a loopback tunnel for paste-code login (registry/gitlab-duo-workflow.ts) — left open intentionally: declining as previously explained (every paste-code provider sets callbackPort; special-casing only this one would create a second convention AGENTS.md prohibits). This is a maintainer judgment call, not something I should self-resolve.

Rebased onto latest main (now af2e53e07); 18 commits replay clean, gitlab-duo-agent remains the only provider added to models.json.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0880370c1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/coding-agent/src/cli/auth-broker-cli.ts
机器人指出之前的 CLI 侧 gating 是无效的:runLocalLogin 对非 paste-code provider
省略 onManualCodeInput,但 AuthStorage.login 仍以 ctrl.onManualCodeInput ??
manualCodeInput 注入默认值,因此 loopback OAuth provider 的 OAuthCallbackFlow
仍会让 readline 粘贴提示与 HTTP 回调竞争;回调先到时该提示悬挂,终端进入
脏/阻塞状态。

在唯一汇聚点 AuthStorage.login 做权威 gating:

- 仅当 provider 属于 PASTE_CODE_LOGIN_PROVIDERS 时才合成默认 manualCodeInput;
  loopback provider 不再获得手动码竞争。
- 调用方显式传入的 onManualCodeInput 对任意 provider 仍被透传(逃生舱)。
- 该修复覆盖所有调用方,不止 auth-broker CLI。
- CLI 侧的 usesManualInput gating 保留为纵深防御,并更新注释指明 storage 层
  才是权威闸门,纠正机器人指出的“只在此处省略”误导性表述。

新增针对 storage 契约的回归测试(auth-storage-manual-code-gate.test.ts):
loopback provider 不被注入默认提示;显式提示对 loopback 仍透传;paste-code
provider(gitlab-duo-agent)在调用方省略时被合成默认提示并经 onPrompt 路由。
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Addressed the newest Codex thread.

Fixed — Disable the default manual-code prompt for loopback logins (auth-storage.ts) (61755d49c)

Correct catch, and it exposes that the earlier CLI-only gating was a no-op: runLocalLogin omitted onManualCodeInput for non-paste-code providers, but AuthStorage.login re-injected a default via ctrl.onManualCodeInput ?? manualCodeInput, so OAuthCallbackFlow.#waitForCallback() still started the readline-paste race against the HTTP callback for every loopback provider (and callback-server.ts keys that race solely on a truthy onManualCodeInput). A browser-callback win left the prompt dangling.

Fixed at the authoritative seam — AuthStorage.login, which every login caller funnels through:

  • The default manualCodeInput is now synthesized only when PASTE_CODE_LOGIN_PROVIDERS.has(provider) (built from the registry's pasteCodeFlow flags). Loopback providers get no default → no race.
  • An explicit caller-supplied onManualCodeInput is still honored for any provider (escape hatch).
  • The CLI's usesManualInput gating stays as defense-in-depth; its comment now points at the storage-level gate as authoritative (correcting the "only omits here" framing).

Regression test (auth-storage-manual-code-gate.test.ts, against the storage contract): a loopback provider receives no synthesized onManualCodeInput; an explicit one is forwarded for a loopback provider; a paste-code provider (gitlab-duo-agent) gets the default synthesized (routed through onPrompt) when the caller omits it.

Verification: bun check:types clean for pi-ai and pi-coding-agent; new gate suite 3/3; callback-server-manual-input 2/2, gitlab-duo-workflow oauth + provider + provider-registry all green (79/79 across the related files). (The auth-storage-api-key-login file shows Windows-only EBUSY temp-dir teardown flakes locally — all its expect() assertions pass; CI runs on Linux.)

Still open by design

  • Avoid forcing a loopback tunnel for paste-code login (registry/gitlab-duo-workflow.ts) — left open intentionally, as previously explained (every paste-code provider sets callbackPort; special-casing only this one creates a second convention AGENTS.md prohibits). Maintainer judgment call.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61755d49c5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/ai/src/providers/gitlab-duo-workflow.ts Outdated
Comment thread packages/coding-agent/CHANGELOG.md Outdated
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

CI follow-up: run 28013607869 (head 61755d49c) is red on one job — Test coding-agent native/unit (TS) — with a single failing test:

(fail) eval Julia prelude helpers > supports output ranges, JSON queries, metadata, and ANSI stripping [31042.02ms]
 1 fail

This is an unrelated slow-test timeout, not from this PR:

  • It's a ~31s Julia prelude test that timed out on a loaded runner; this branch touches no eval/Julia/prelude files (changes are confined to packages/ai GitLab Duo provider + auth-storage, packages/agent, packages/coding-agent CLI/sdk, packages/catalog, and changelogs — git diff --name-only origin/main..HEAD lists nothing under eval/julia).
  • The earlier executeBash :async: flake that previously failed this job is gone (that one cleared after rebasing onto the upstream fix); this is a different intermittent.

I can't re-run the job (fork PR, no admin rights on the base repo). A maintainer re-run, or the next rebase/push, should clear it. All GitLab-Duo-relevant suites are green: ai gate/oauth/provider/provider-registry 79/79, catalog discovery 28/28, agent 27/27; bun check:types clean for pi-ai/pi-catalog/pi-agent-core/pi-coding-agent.

机器人指出两处问题,本提交处理:

1) checkpoint 内容签名去重用的是全局文本相等,导致后续一个合法的新 agent
   消息若文本与早先回合相同(例如两个回合都说 "Done"、或重复 reasoning),
   因 previousContent 对新 messageKey 为 undefined 而被判为 duplicateContent,
   provider 不发 delta,用户丢失第二条消息。现在把内容签名作用域绑定到回合
   位置(快照内 request/tool 边界计数):同一回合位置上重放的同文本(GitLab
   跨收缩快照重命名 message_id 的情形)仍被去重,而位于更晚回合的新消息照常
   发出。新增回合内重复文本回归测试;既有 rename 抑制与 pause/resume 测试不变。

2) coding-agent CHANGELOG 把 broker 登录修复条目误放在已发布的
   [16.1.16] 段;按 AGENTS.md 已发布段不可变,移动到 [Unreleased] 的
   ### Fixed。
@jiwangyihao

Copy link
Copy Markdown
Contributor Author

Addressed the two newest Codex threads in b7ab4b65a.

Fixed — Scope checkpoint dedupe to replayed message identity (gitlab-duo-workflow.ts)

Correct catch. The content-signature fallback (which suppresses replayed agent text when GitLab renames a message_id across a shrunk snapshot — see the agent-aagent-b "Working" repro) keyed on global text equality, so a genuinely new later message whose text equalled an earlier turn (two turns both "Done", repeated reasoning) hit previousContent === undefined + a global signature match and emitted no delta — the second message was lost.

Fix scopes the signature to turn position — the request/tool boundary count within the snapshot (<turnIndex>\0<kind>\0<content>):

  • A replayed message reappearing at the same turn position is still deduped (the rename case: both at turn 0 → suppressed).
  • A new message at a later turn (after an extra boundary) gets a distinct signature → emits.

Verified against all three existing tests (rename-shrink suppression, same-key non-prefix rewrite, pause/resume into a separate message) plus a new regression test: two agent turns separated by a tool boundary both saying "Done" now yield "DoneDone" (previously the second was swallowed). Provider suite 66/66.

Fixed — Move the broker login note to Unreleased (coding-agent/CHANGELOG.md)

Correct. The omp auth-broker login gitlab-duo-agent note had been added under the already-released ## [16.1.16] section; per AGENTS.md released sections are immutable. Moved it verbatim to the ## [Unreleased] ### Fixed section.

Verification: bun check:types clean for pi-ai; provider suite 66/66 (incl. the new dedupe regression test).

Still open by design

  • Avoid forcing a loopback tunnel for paste-code login (registry/gitlab-duo-workflow.ts) — left open intentionally, as previously explained (every paste-code provider sets callbackPort; special-casing only this one creates a second convention AGENTS.md prohibits). Maintainer judgment call.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7ab4b65a3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

});
}
const projectPath = configuredProjectPath ?? discoveredProject?.path;
const projectId = configuredProjectId ?? discoveredProject?.id;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve path-valued projectId before WebSocket routing

When callers set projectId/GITLAB_DUO_PROJECT_ID to a full path such as group/project—which namespace discovery explicitly accepts by carrying projectId.includes("/") as a project path—this assignment still treats that path as the numeric project id. The projectPath branch below resolves paths to a numeric id for WebSocket routing, while REST direct_access/create can keep using the path; skipping that lookup sends project_id=group/project on the WebSocket and can fail project-scoped Duo routing. Split path-valued configuredProjectId into the path flow before computing webSocketProjectId.

Useful? React with 👍 / 👎.

@jiwangyihao

Copy link
Copy Markdown
Contributor Author

CI follow-up: run 28016855306 (head b7ab4b65a) is red on one job — Test coding-agent native/unit (TS) — with the same single failing test as the prior run:

(fail) eval Julia prelude helpers > supports output ranges, JSON queries, metadata, and ANSI stripping [31033.21ms]
 1 fail

This is an unrelated ~31s Julia prelude slow-test timing out on a loaded runner, not from this PR:

  • This branch touches no eval/Julia/prelude files (git diff --name-only origin/main..HEAD lists nothing under eval/julia/prelude).
  • It is intermittent on the base branch too — recent main CI runs (af2e53e07, 2c3e0d79a, c6fcc155a) are red on the same Test coding-agent native/unit (TS) job family.

I can't re-run the job (fork PR, no admin rights on the base repo). A maintainer re-run, or the next rebase/push, should clear it. All GitLab-Duo-relevant suites are green: ai provider 66/66 (incl. the new dedupe regression test), and bun check:types is clean for pi-ai.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vouched Passed the vouch gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants