mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-30 11:24:36 +00:00
* feat(channels): add GitLab polling channel adapter
Poll GitLab todos via @gitbeaker/rest, dispatch notes through the
existing PollingChannelBase pipeline. Key design points:
- action_prompt_template config drives event filtering and metadata
rendering (unconfigured actions are skipped)
- Per-repo cursor (repo[chatId].last_read) as notes window lower bound,
global lastProcessedAt for todo-level dedup
- mark_done after successful processing; failure skips mark_done for
retry on next poll
- Mention gating delegated to base GroupGate (adapter only sets
isMentioned flag)
- First-contact body fallback for todos with no notes (e.g. mention in
issue description)
* fix(channels/gitlab): persist cursor after each successful todo
Call saveCursor() immediately after advancing lastProcessedAt so that
progress is durable even if the process crashes mid-poll. Also removes
the local watermark variable in favor of direct assignment.
* fix(channels/gitlab): persist cursor on every advancement including skips
* fix(channels/gitlab): address review critical issues
- Remove non-functional proxyAgent (gitbeaker doesn't support it)
- Construct repo_url from host + path (API doesn't return web_url)
- Handle directly_addressed action (falls back to mentioned template)
- First-contact fetches target description instead of using todo.body
- Move todo.project dereference inside try block
- Filter confidential notes
- Update channel-registry.test.ts for gitlab entry
* fix(channels/gitlab): address review suggestions
- Warn on connect if action_prompt_template is not configured
- Guard todo.target.iid before use
- Skip paths now mark_done (best-effort) to clean GitLab UI
- Remove postErrorComment (avoids duplicate comments on retry)
- Fetch only first page of notes (desc, maxPages:1, perPage:100)
instead of paginating entire note history
- Extract fetchRecentNotes for single-page windowed enumeration
* refactor(channels/gitlab): simplify to todo.body dispatch, add description mention support
- Remove notes API fetching; dispatch todo.body directly
- Detect description mentions via target_url anchor (#note_ absence)
- Always fetch target description for %description% metadata
- Remove per-repo cursor; dedup via cursor + mark_done only
- Cursor advances regardless of success/failure (no retry)
- Use zod for cursor validation
- Rename template vars to GitLab terminology:
%project% %project_url% %target_type% %iid% %title% %description% %todo_id%
- Support %% escape for literal percent
* docs(channels): add GitLab adapter documentation
- New user guide: docs/users/features/channels/gitlab.md
- Update _meta.ts navigation
- Update developer adapter matrix and SDK list
* fix(channels/gitlab): use correct Issues.show(issueIid, { projectId }) signature
* chore: regenerate NOTICES.txt for new gitlab channel dependencies
* fix(channels/gitlab): address review suggestions
- Add todo.project null guard (item 2)
- Single-pass regex for %% escape + %var% substitution (item 4)
- sendThreadMessage throws directly on undefined threadId (item 5)
- Dedup fetchDescription with per-poll cache (item 6)
- Remove per-todo saveCursor; base class saves after pollOnce (item 7)
- Add undefined threadId test (item 8)
- Expand confidential notes limitation in docs (item 3)
* test(channels/gitlab): add mention tests, directly_addressed coverage, skip assertions, temp cleanup
- New mention.test.ts: 14 cases for testBotMention/stripBotMention/escapeRegex
- Add directly_addressed fallback test
- Skip tests now assert TodoLists.done + cursor advancement
- afterEach cleans up mkdtempSync temp dirs
* fix(channels/gitlab): address review round 4
- Non-mention actions (assigned, etc.) set forceMentioned=true to bypass GroupGate
- Merge dead note-filter tests into single 'skips todo authored by bot'
- Log fetchDescription errors to stderr instead of silent swallow
- Post error comment on issue/MR when handleInbound fails (best-effort)
* fix(channels/gitlab): always force isMentioned=true, remove regex re-derivation
The action_prompt_template config is already the event filter, and
GitLab has already decided the mention when creating the todo.
Re-deriving isMentioned via regex on todo.body causes permanent
message loss when the regex misses (description mention + fetch
failure, group mentions). Always set forceMentioned=true so
GroupGate never drops a todo that passed the template filter.
* fix(channels/gitlab): propagate fetchDescription errors for description mentions
For note mentions, description is metadata-only — fetch failure is
logged and swallowed. For description mentions, description IS the
message — fetch failure now propagates to the outer catch, which
posts the ⚠️ error comment so the user knows to re-mention.
* perf(channels/gitlab): clean up stale todos, skip unnecessary fetchDescription
- Mark stale todos (updated_at <= cursor) as done on each poll to
prevent perpetual re-fetching of pre-existing pending todos
- Skip fetchDescription for note mentions when template does not
contain %description%, saving one API call per todo
- Update docs: stale todo cleanup, error comment on failure
* docs(channels/gitlab): clarify requireMention is bypassed, template is the real filter
* Apply suggestions from code review
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix(channels/gitlab): use todo ID cursor instead of timestamp to eliminate equal-timestamp loss
Timestamp-based cursors (second granularity) could silently destroy
todos sharing the same updated_at as the cursor boundary. Switch to
monotonically increasing todo IDs which are unique and collision-free.
Add initialized flag to preserve first-start drain semantics: pre-existing
pending todos are marked done without dispatch on the first poll cycle.
* fix(channels/gitlab): harden first-poll drain, add ordering tests, fix lockfile
- Replace Math.max(...spread) with reduce to avoid RangeError on large
backlogs (~100k+ todos). Move initialized=true after the drain work so
any throw retries the drain instead of falling through to dispatch.
- Add unit tests: identical-timestamp delivery and id-order-when-updated_at-disagrees
(kills M2 sort mutant).
- Align lockfile: file:../base → ^0.21.0 for channel-base dep.
* fix(channels/gitlab): include dot in mention lookahead for GitLab usernames
GitLab usernames may contain dots (e.g. bot.name). The lookahead
character class inherited from GitHub omitted '.', causing @bot.name
to match as @bot. Add '.' to the negated class.
* docs(channels/gitlab): align docs with ID cursor and drain semantics
- Add first-poll drain as step 2 in How It Works
- Clarify GroupGate always passes (isMentioned forced true)
- Document initialized flag in Known Limitations
* Apply suggestions from code review
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix(channels/gitlab): align package version and channel-base dependency to 0.21.1
Bump version from 0.21.0 to 0.21.1 to match other channel packages after
upstream merge. Pin @qwen-code/channel-base to exact 0.21.1 instead of
^0.21.0, matching the convention used by other published channels.
* fix(channels/gitlab): regenerate lockfile to match package.json versions
Manually add only gitlab-related lockfile entries (workspace, @gitbeaker
packages, transitive deps, channel-gitlab link) without unrelated npm
normalization churn.
* test(channels/gitlab): add regression tests for first-poll drain hardening
Two tests that kill the M1 (Math.max spread RangeError) and M2 (flag
ordering) mutants which survived the original 46-test suite:
- 150k todo drain verifies reduce() handles large backlogs without
RangeError and without dispatching
- Drain throw verifies initialized stays false so the next poll retries
the drain instead of falling through to dispatch
Test file duration: ~40ms → ~170ms.
* docs(channels/gitlab): clarify groupPolicy must be "open" and add runtime warning
The default groupPolicy "disabled" silently drops all mentions — todos are
marked done and cursor advances, but no dispatch occurs. Fix misleading docs
that said "GroupGate always passes" (only true at groupPolicy: "open") and
add a connect()-time warning when groupPolicy is not "open".
* fix(channels/gitlab): correct xcase integrity hash in lockfile
The manually added xcase entry had a typo in the sha512 hash (ys → ks),
causing npm ci EINTEGRITY failures in CI.
* fix(channels/gitlab): correct requester-utils integrity hash in lockfile
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(channels/gitlab): allow groupPolicy "allowlist" in warning and docs
The groupPolicy warning and docs incorrectly stated that groupPolicy
must be "open". In reality "allowlist" with the project listed also
works because isMentioned is forced true and GroupGate only requires
the group to be listed. Also fix the inaccurate "no error is logged"
claim — ChannelBase logs preflight rejected reason=group_disabled.
Fixes R5-🟡3 from PR #7862 review.
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
181 lines
8.9 KiB
JSON
181 lines
8.9 KiB
JSON
{
|
|
"name": "@qwen-code/qwen-code",
|
|
"version": "0.21.1",
|
|
"engines": {
|
|
"node": ">=22.0.0"
|
|
},
|
|
"type": "module",
|
|
"workspaces": [
|
|
"packages/*",
|
|
"packages/channels/base",
|
|
"packages/channels/telegram",
|
|
"packages/channels/weixin",
|
|
"packages/channels/dingtalk",
|
|
"packages/channels/wecom",
|
|
"packages/channels/feishu",
|
|
"packages/channels/qqbot",
|
|
"packages/channels/github",
|
|
"packages/channels/gitlab",
|
|
"packages/channels/plugin-example",
|
|
"integrations/external-context",
|
|
"!packages/desktop"
|
|
],
|
|
"repository": {
|
|
"type": "git",
|
|
"url": "git+https://github.com/QwenLM/qwen-code.git"
|
|
},
|
|
"config": {
|
|
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.21.1"
|
|
},
|
|
"scripts": {
|
|
"start": "node scripts/start.js",
|
|
"dev": "node scripts/dev.js",
|
|
"dev:daemon": "node scripts/daemon-dev.js",
|
|
"debug": "cross-env DEBUG=1 node --inspect-brk scripts/start.js",
|
|
"generate": "node scripts/generate-git-commit-info.js",
|
|
"generate:settings-schema": "node --import tsx/esm scripts/generate-settings-schema.ts",
|
|
"build": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" node scripts/build.js",
|
|
"build-and-start": "npm run build && npm run start",
|
|
"build:vscode": "node scripts/build_vscode_companion.js",
|
|
"build:all": "npm run build && npm run build:sandbox && npm run build:vscode",
|
|
"build:packages": "npm run build --workspaces",
|
|
"build:sandbox": "node scripts/build_sandbox.js",
|
|
"bundle": "npm run generate && node esbuild.config.js && node scripts/copy_bundle_assets.js",
|
|
"test": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" npm run test --workspaces --if-present --parallel",
|
|
"test:ci": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" npm run test:ci --workspaces --if-present --parallel && npm run test:scripts",
|
|
"test:release": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" npm run test:ci --workspaces --if-present --parallel -- --coverage.enabled=false && npm run test:scripts",
|
|
"test:scripts": "vitest run --config ./scripts/tests/vitest.config.ts",
|
|
"test:e2e": "cross-env VERBOSE=true KEEP_OUTPUT=true npm run test:integration:sandbox:none",
|
|
"test:integration:all": "npm run test:integration:sandbox:none && npm run test:integration:sandbox:docker && npm run test:integration:sandbox:podman",
|
|
"test:integration:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests",
|
|
"test:integration:sandbox:docker": "cross-env QWEN_SANDBOX=docker npm run build:sandbox && QWEN_SANDBOX=docker vitest run --root ./integration-tests",
|
|
"test:integration:sandbox:podman": "cross-env QWEN_SANDBOX=podman vitest run --root ./integration-tests",
|
|
"test:integration:no-ak:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests ./fake-openai-server.test.ts ./cli/qwen-serve-routes.test.ts ./cli/qwen-serve-streaming.test.ts ./cli/daemon-invocation-context.test.ts",
|
|
"test:integration:sdk:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests --poolOptions.threads.maxThreads 2 sdk-typescript",
|
|
"test:integration:sdk:sandbox:docker": "cross-env QWEN_SANDBOX=docker npm run build:sandbox && QWEN_SANDBOX=docker vitest run --root ./integration-tests --poolOptions.threads.maxThreads 2 sdk-typescript",
|
|
"test:sdk:python": "python3 -m pytest -c packages/sdk-python/pyproject.toml packages/sdk-python/tests -q",
|
|
"test:integration:cli:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests cli",
|
|
"test:integration:cli:sandbox:docker": "cross-env QWEN_SANDBOX=docker npm run build:sandbox && QWEN_SANDBOX=docker vitest run --root ./integration-tests cli",
|
|
"test:integration:interactive:sandbox:none": "cross-env QWEN_SANDBOX=false vitest run --root ./integration-tests interactive",
|
|
"test:integration:interactive:sandbox:docker": "cross-env QWEN_SANDBOX=docker npm run build:sandbox && QWEN_SANDBOX=docker vitest run --root ./integration-tests interactive",
|
|
"test:terminal-bench": "cross-env VERBOSE=true KEEP_OUTPUT=true vitest run --config ./vitest.terminal-bench.config.ts --root ./integration-tests",
|
|
"test:terminal-bench:oracle": "cross-env VERBOSE=true KEEP_OUTPUT=true vitest run --config ./vitest.terminal-bench.config.ts --root ./integration-tests -t 'oracle'",
|
|
"test:terminal-bench:qwen": "cross-env VERBOSE=true KEEP_OUTPUT=true vitest run --config ./vitest.terminal-bench.config.ts --root ./integration-tests -t 'qwen'",
|
|
"lint": "eslint . --ext .ts,.tsx && eslint integration-tests",
|
|
"lint:fix": "eslint . --fix && eslint integration-tests --fix",
|
|
"lint:ci": "eslint . --ext .ts,.tsx --max-warnings 0 && eslint integration-tests --max-warnings 0",
|
|
"lint:sdk:python": "python3 -m ruff check --config packages/sdk-python/pyproject.toml packages/sdk-python",
|
|
"lint:all": "node scripts/lint.js",
|
|
"audit:runtime:critical": "node scripts/audit-runtime-critical.js",
|
|
"format": "prettier --experimental-cli --write .",
|
|
"typecheck": "npm run typecheck --workspaces --if-present",
|
|
"typecheck:sdk:python": "python3 -m mypy --config-file packages/sdk-python/pyproject.toml packages/sdk-python/src",
|
|
"smoke:sdk:python": "python3 packages/sdk-python/scripts/smoke_real.py",
|
|
"build:sdk:python": "python3 -m build packages/sdk-python",
|
|
"check-i18n": "npm run check-i18n --workspace=packages/cli",
|
|
"preflight": "npm run clean && npm ci && npm run format && npm run lint:ci && npm run build && npm run typecheck && npm run test:ci && npm run check:serve-fast-path-bundle",
|
|
"postinstall": "patch-package",
|
|
"prepare": "node scripts/prepare.js",
|
|
"prepare:package": "node scripts/prepare-package.js",
|
|
"package:hosted-installation": "node scripts/build-hosted-installation-assets.js",
|
|
"package:standalone": "node scripts/create-standalone-package.js",
|
|
"package:standalone:release": "node scripts/build-standalone-release.js",
|
|
"verify:installation-release": "node scripts/verify-installation-release.js",
|
|
"release:version": "node scripts/version.js",
|
|
"changelog": "node scripts/generate-changelog.js",
|
|
"telemetry": "node scripts/telemetry.js",
|
|
"check:lockfile": "node scripts/check-lockfile.js",
|
|
"check:desktop-isolation": "node scripts/check-desktop-isolation.js",
|
|
"check:serve-fast-path-bundle": "node scripts/clean-package-build-artifacts.js && npm run build -- --cli-only && cross-env DEV=true npm run bundle && node scripts/check-serve-fast-path-bundle.js",
|
|
"desktop-openwork-sync": "bun run scripts/desktop-openwork-sync.ts",
|
|
"clean": "node scripts/clean.js",
|
|
"pre-commit": "node scripts/pre-commit.js"
|
|
},
|
|
"overrides": {
|
|
"ansi-regex": "6.2.2",
|
|
"cliui": {
|
|
"wrap-ansi": "7.0.0"
|
|
},
|
|
"baseline-browser-mapping": "^2.9.19",
|
|
"normalize-package-data": "^7.0.1",
|
|
"react": "^19.2.4",
|
|
"react-dom": "^19.2.4",
|
|
"@types/react": "^19.2.0",
|
|
"@types/react-dom": "^19.2.0"
|
|
},
|
|
"bin": {
|
|
"qwen": "scripts/cli-entry.js"
|
|
},
|
|
"files": [
|
|
"dist/",
|
|
"scripts/cli-entry.js",
|
|
"README.md",
|
|
"LICENSE"
|
|
],
|
|
"devDependencies": {
|
|
"@types/chrome": "^0.1.32",
|
|
"@types/marked": "^5.0.2",
|
|
"@types/mime-types": "^3.0.1",
|
|
"@types/minimatch": "^5.1.2",
|
|
"@types/mock-fs": "^4.13.4",
|
|
"@types/proper-lockfile": "^4.1.4",
|
|
"@types/shell-quote": "^1.7.5",
|
|
"@types/uuid": "^10.0.0",
|
|
"@vitest/coverage-v8": "^3.1.1",
|
|
"@vitest/eslint-plugin": "^1.3.4",
|
|
"@xterm/headless": "^5.5.0",
|
|
"@xterm/xterm": "^6.0.0",
|
|
"cross-env": "^7.0.3",
|
|
"esbuild": "^0.25.0",
|
|
"esbuild-plugin-wasm": "^1.1.0",
|
|
"eslint": "^9.24.0",
|
|
"eslint-config-prettier": "^10.1.2",
|
|
"eslint-plugin-check-file": "^3.3.1",
|
|
"eslint-plugin-import": "^2.31.0",
|
|
"eslint-plugin-license-header": "^0.8.0",
|
|
"eslint-plugin-react": "^7.37.5",
|
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
"glob": "^10.5.0",
|
|
"globals": "^16.0.0",
|
|
"husky": "^9.1.7",
|
|
"json": "^11.0.0",
|
|
"lint-staged": "^16.1.6",
|
|
"memfs": "^4.42.0",
|
|
"mnemonist": "^0.40.3",
|
|
"mock-fs": "^5.5.0",
|
|
"msw": "^2.10.4",
|
|
"npm-run-all": "^4.1.5",
|
|
"patch-package": "^8.0.1",
|
|
"prettier": "^3.5.3",
|
|
"react-devtools-core": "^6.1.5",
|
|
"semver": "^7.7.2",
|
|
"strip-ansi": "^7.1.2",
|
|
"tsx": "^4.20.3",
|
|
"typescript-eslint": "^8.30.1",
|
|
"vitest": "^3.2.4",
|
|
"yaml": "^2.8.1",
|
|
"yargs": "^17.7.2"
|
|
},
|
|
"dependencies": {
|
|
"@testing-library/dom": "^10.4.1",
|
|
"ink": "^7.0.3",
|
|
"simple-git": "^3.36.0"
|
|
},
|
|
"optionalDependencies": {
|
|
"@lydell/node-pty": "1.2.0-beta.10",
|
|
"@lydell/node-pty-darwin-arm64": "1.2.0-beta.10",
|
|
"@lydell/node-pty-darwin-x64": "1.2.0-beta.10",
|
|
"@lydell/node-pty-linux-x64": "1.2.0-beta.10",
|
|
"@lydell/node-pty-win32-arm64": "1.2.0-beta.10",
|
|
"@lydell/node-pty-win32-x64": "1.2.0-beta.10"
|
|
},
|
|
"lint-staged": {
|
|
"*.{js,jsx,ts,tsx}": [
|
|
"prettier --write",
|
|
"eslint --fix --max-warnings 0 --no-warn-ignored"
|
|
],
|
|
"*.{json,md}": [
|
|
"prettier --write"
|
|
]
|
|
}
|
|
}
|