Commit Graph
26 Commits
Author SHA1 Message Date
can1357 e9888367d1 refactor: migrated packages to internal utility modules and removed external dependencies
- Implemented in-house, zero-dependency utility modules in `pi-utils` covering DOM manipulation, markdown parsing, templating, browser automation helpers, and terminal buffers.
- Migrated packages across the repository to consume the new internal utilities and `omptype` schema validators instead of external dependencies.
- Removed multiple external runtime and development dependencies including Zod, Marked, LRU cache, Turndown, and Puppeteer browser packages.
2026-08-05 13:39:09 +02:00
can1357 a872d77068 chore: cleanup dumb tests 2026-08-02 20:39:23 +02:00
can1357 fadcd1a650 fix(ai): made credential-pattern redaction opt-in
- Credential-shaped token redaction now requires explicit opt-in via `configureCredentialRedaction` and is disabled by default, so user-supplied credential-like strings reach providers unmodified unless the host enables redaction.
- Wired the `secrets.enabled` ("Hide Secrets") setting to `configureCredentialRedaction` in the coding-agent so the pattern redaction follows the existing secret-obfuscation toggle.
2026-07-23 16:23:08 +02:00
can1357 0f26558d03 chore: reformat 2026-07-17 07:40:21 +02:00
can1357 eac51b6a04 Merge: darkphilosophy/feat/advisor-per-agent-toggle
Brings the per-advisor toggle, status-line glyphs, quota display, and the
failing-advisor stall/abort fix (f4c8143) onto main's rewritten advisor
runtime. Conflict reconciliation kept main's architecture (fingerprint
prefix reconciliation, host-level onTurnError recovery + fallback chains,
terminal-failure classification) and ported the branch semantics onto it:

- #failing latch: waitForCatchup resolves immediately while an advisor is
  mid-failure; parked waiters wake the moment a turn fails, before any
  async hook or retry sleep.
- Turn-end render containment: a formatter bug restores the cursor/prefix/
  dedup snapshot and never propagates into the primary's turn-end callback
  (per-advisor try/catch boundary in AgentSession).
- Quota pause: when host recovery declines a usage-limit failure, the
  runtime latches quotaExhausted, requeues the batch, and notifies —
  cleared only by an explicit reset.
- Hard halt after a permanent rejection or three backlog-drop cycles.
- #recoverAdvisorTurn also marks usage limits for structural errors thrown
  before any assistant turn is recorded.
2026-07-17 07:37:29 +02:00
can1357 38b73a5cbc style(tests): removed unused fixture variable and formatted plan test
The redaction-contract rewrite left credentialTokens unused in the
GitLab Duo provider test, failing biome's lint gate.
2026-07-17 07:24:54 +02:00
can1357 5f46138c4d fix(ai): redacted the GitLab Duo goal transcript
The Duo goal bypasses transformMessages; apply the outbound credential
scrub (#5655) to the rendered ChatML transcript and latest-prompt goal,
and updated the provider test to the redaction contract.
2026-07-17 05:40:13 +02:00
roboomp 5c817cecf5 fix(gitlab-duo-workflow): bounded signal/timeout on post-start REST setup fetches
The workflow provider pushed `{ type: "start" }` and then ran six unsigned/untimed REST setup fetches (settings ensure, project discovery, project-id resolve, direct_access, workflow create, available models) plus the cleanup stop PATCH. `streamSimple` special-cases `gitlab-duo-agent` and skips the `register-builtins` `iterateWithIdleTimeout` wrapper, so a stalled setup fetch left the stream with no terminal event.

Added `GITLAB_DUO_WORKFLOW_REST_TIMEOUT_MS = 30_000` and a per-fetch `gitLabDuoWorkflowRestSignal(callerSignal)` helper (`AbortSignal.any([caller, AbortSignal.timeout])`), threaded through each setup helper. Best-effort helpers (settings, discovery, models, project-id resolve) still degrade to undefined/false on timeout; throwing helpers (direct_access, create) surface abort/timeout as a real `error` event through the existing outer catch. `stopGitLabDuoWorkflow` gets a fresh timeout-only signal (matching the existing decoupling from `options.signal` in the finally block) with a swallowing catch so cleanup remains best-effort.

Two regression tests: one drives a caller-signal cancel through a stalled fetch and asserts the stream finalizes with an error rather than hanging; the other structurally asserts every setup endpoint receives an `AbortSignal` on its RequestInit so an absolute deadline is always in place.

Fixes #4227
2026-07-02 08:40:20 +00:00
can1357 c3f7e849e5 refactor: centralized AI error handling into a dedicated module
- Migrated 288 lines of scattered error classification logic from `utils/error-id.ts` into a cohesive `packages/ai/src/error/` module with 13 specialized submodules covering flags, classes, OAuth, providers, rate-limiting, and finalization.
- Replaced 100+ generic `Error` throws across 60+ provider and registry files with semantic `AIError.*` classes (e.g., `AIError.MissingApiKeyError`, `AIError.OAuthError`, `AIError.ProviderResponseError`), improving error diagnostics and retry logic.
- Consolidated error utility imports from `pi-utils` and scattered classification functions into a single `AIError` namespace, reducing coupling and simplifying error handling across all packages.
2026-06-27 10:44:13 +02:00
can1357 577d2a8eb8 style: biome format/organize-imports across integrated PRs 2026-06-27 02:06:38 +02:00
can1357 8112c30893 test(ai): align GitLab Duo tool names 2026-06-27 01:39:31 +02:00
jiwangyihao 67dcc2f9ec fix(gitlab-duo): WS 路由解析 path 形式 projectId 并保留命名空间作用域
处理两条 Codex 审阅意见:(1) projectId/GITLAB_DUO_PROJECT_ID 配成 group/project 全路径时,原样作为 WS project_id 发送会导致 project-scoped 路由失败,现在含 / 的 projectId 走与 projectPath 相同的数字 id 解析;(2) 当无法解析出数字 project id(路径解析失败或自动发现无 project)时,原三元会把已解析的 namespace/root 一并丢弃,socket 无作用域打开,现在无论 project 是否存在都传 namespace/root。各加 1 个回归测试,并更新原 namespace-absent 测试为修正后的行为。
2026-06-26 16:13:27 +08:00
jiwangyihao 15b24be5c3 fix(gitlab-duo): 系统提示词加历史记录说明阻止模型模仿 ChatML 标记
上一轮把历史工具调用标记改成过去式 <ran> 后,Agent 仍会被 goal 中的 ChatML 转写格式误导,把 <|im_start|>/<ran …> 当成可发射语法直接输出。现在当 goal 是多回合 ChatML 转写时,在 inline flow 的系统提示词槽追加一条简短说明:这些标记是已执行回合/工具调用的历史记录,不是可发射语法,调用工具只能走结构化 tool-use 通道。说明只在多回合转写时追加(单回合是裸文本、无标记),存放于静态 .md 文件按 text 导入,不在代码里拼接提示词字符串。
2026-06-26 16:13:26 +08:00
jiwangyihao bce52b7568 refactor(gitlab-duo): 历史工具调用改用过去式 <ran> 记录标记避免被模仿
历史回合中的工具调用原渲染为 <tool_call>{"name":…,"arguments":…}</tool_call>,与模型可发起的文本调用语法字节相同,导致 Agent 误把它当成正确格式直接输出而非走结构化 tool-use 通道。改为过去式 <ran NAME>{args}</ran> 记录、结果为 <ran:result> / <ran:result status=error>:语义上是已执行的历史记录而非可发射的祈使语法,丢弃 {name,arguments} 包裹(工具名移到标签、裸参数入 body),结果头省略工具名(靠相邻位置配对)。每对调用+结果约省 48 字节。
2026-06-26 16:13:26 +08:00
jiwangyihao f93784d5e8 fix(gitlab-duo): 用连续字节相同的 checkpoint 判定 stall 以阻断工具调用死循环
原 stall 守卫比较 checkpoint 的 ui_chat_log 消息计数,但该计数是增量流式切片窗口,即使在健康的 FINISHED 运行中也被截断到约 2,无法区分真实循环。改为比较同一 workflow 在连续 tool-call 边界上 checkpoint 的字节长度:健康回合的 checkpoint 体积持续增长,stall 的 workflow 则重发字节相同的 checkpoint。连续两个边界字节相同时判定 stalled 并在新 workflow 上重启(受 GITLAB_DUO_WORKFLOW_MAX_STALL_RESTARTS 限制),不再执行注定循环的工具调用;单个边界绝不误判。
2026-06-26 16:13:26 +08:00
jiwangyihao 610eb34312 fix(gitlab-duo): register MCP tools under bare names
Register OMP's MCP tools under their bare names (read, bash, ...) instead of the mcp__omp__-prefixed form. The DWS server does not strip prefixes: it binds the model tool schema and matches incoming tool calls under the exact wire name (sanitize_llm_name only replaces illegal characters), verified live (registered name == name the model reports seeing). The prefix only forced the model to learn mcp__omp__read while OMP's own tool docs say read, with no namespacing benefit. Bare registration aligns the name the model sees, the toolset match key, and OMP's docs. The inbound parser still strips a leading mcp__omp__ defensively.
2026-06-26 16:13:26 +08:00
jiwangyihao 37d51a6571 feat(gitlab-duo): shrink goal transcript and bound oversized goals as overflow
Drop bytes the model never reads from the GitLab Duo Agent goal transcript: omit tool-call and tool-result ids (one call per turn, result rides the next turn, so pair by adjacency), strip the OMP-internal per-call intent (i) field on replay, and stop escaping </> in tool-call JSON (the goal is plain transcript text, not an HTML context). About 7% smaller rendered goal on a real session, no semantic change, live tool dispatch unaffected.

Bound the rendered goal by two byte thresholds and treat oversized goals as context overflow so the session auto-compacts. The DWS/Workhorse transport has no fixed token wall but failure probability rises with goal byte size (<=~1.25MB ok, ~1.4-1.7MB jitter, >=~2MB fails, 4MB gRPC hard cap). Soft threshold 1.25MB = last-guaranteed-success ceiling; hard threshold 2MB = necessary-fail floor. A goal in [soft,hard) is still attempted once and only relabeled as a 'prompt is too long' (OVERFLOW_PATTERNS) error if it actually fails; a goal >=hard is not sent at all, the stream ends proactively with the overflow error (stopping the created workflow) so no quota is spent. The goal body is never truncated; an in-budget error surfaces its raw server message verbatim.
2026-06-26 16:13:26 +08:00
jiwangyihao dfd55ea7d1 fix(gitlab-duo): checkpoint 去重按回合位置作用域,避免吞掉重复文本的新消息
机器人指出两处问题,本提交处理:

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。
2026-06-26 16:13:25 +08:00
jiwangyihao bf1b692df9 fix(gitlab-duo): direct_access 错误保留 HTTP 状态码以触发凭据轮换
机器人指出两处问题,本提交处理:

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。
2026-06-26 16:13:24 +08:00
jiwangyihao c509b839fe test(ai): 修复 Duo Agent 命名空间缓存测试的 socket 等待时序
driveOneTurn 原先固定轮询 30 个微任务等待 socket 创建;命名空间发现/
直连/创建工作流链路较深,在高负载 CI runner 上 socket 尚未创建就放弃
等待,导致 onopen 丢失、流空转至 5s 超时。改为按真实截止时间轮询直到
socket 出现,未出现则显式抛错。
2026-06-26 16:13:22 +08:00
jiwangyihao 1aaee9ba40 fix(ai): make Duo Agent account preparation account-scoped
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.
2026-06-26 16:13:22 +08:00
jiwangyihao 00305960ee feat(ai): cache Duo Agent namespace discovery per account
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.
2026-06-26 16:13:22 +08:00
jiwangyihao 0a62cd2102 refactor(ai): drop Duo Agent tool-call batching for serial dispatch
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.
2026-06-26 16:13:22 +08:00
jiwangyihao 2a717c53b1 fix(ai): stop stranded Duo Agent workflow on unresolvable tool batch
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.
2026-06-26 16:13:21 +08:00
jiwangyihao 3c144c6575 feat(ai): flatten Duo Agent goal transcript and harden tool-loop edges
将 GitLab Duo Agent 的 goal 改为平权 ChatML 转录、system prompt 移入 inline flow system 槽;新增并行工具调用合并、用户打断 steering 重播种、通用错误重试一次、登录时启用 MCP/Beta 设置,并把 idle-timeout 恢复改为新建 workflow。
2026-06-26 16:13:21 +08:00
jiwangyihao 01af422415 feat(ai): add GitLab Duo Agent provider 2026-06-26 16:13:20 +08:00