Skip to content

feat(agents): add qwen adapter#127

Merged
harshitsinghbhandari merged 1 commit into
agents/07-cursorfrom
agents/08-qwen
Jun 6, 2026
Merged

feat(agents): add qwen adapter#127
harshitsinghbhandari merged 1 commit into
agents/07-cursorfrom
agents/08-qwen

Conversation

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 6, 2026

Greptile Summary

This PR adds the Qwen Code agent adapter, following the same pattern as the existing cursor adapter: a Plugin struct with binary resolution and caching, GetLaunchCommand/GetRestoreCommand mapping to Qwen's -p/-r flags and --approval-mode, hook install/uninstall/check targeting .qwen/settings.json, and a DeriveActivityState deriver registered in the activity dispatch map.

  • qwen/hooks.go: Installs four hooks (SessionStart, UserPromptSubmit, PermissionRequest, Stop) into .qwen/settings.json with idempotent install and preserving user-defined hooks; introduces a private atomicWriteFile that duplicates hookutil.AtomicWriteFile but omits tmp.Sync().
  • qwen/qwen.go: Binary resolver searches PATH then Homebrew/npm-global well-known paths on both Unix and Windows; appendApprovalFlags maps AO permission modes to Qwen's --approval-mode values.
  • Registry + dispatch: Single-line additions in registry.go and activitydispatch/dispatch.go; wiring smoke-test extended in daemon/wiring_test.go.

Confidence Score: 4/5

Safe to merge after addressing the missing fsync in the local atomic-write helper.

The hook writer in qwen/hooks.go re-implements atomicWriteFile locally instead of using the shared hookutil.AtomicWriteFile, and the local copy skips tmp.Sync(). Without the fsync, a power loss or kernel panic after the rename but before OS writeback can leave the .qwen/settings.json file empty, silently disabling all Qwen hooks. All other changed files are correct and consistent with the existing adapter pattern.

backend/internal/adapters/agent/qwen/hooks.go — the atomicWriteFile implementation

Important Files Changed

Filename Overview
backend/internal/adapters/agent/qwen/hooks.go Implements Qwen hook install/uninstall/check; re-implements atomicWriteFile locally instead of using hookutil.AtomicWriteFile, and the local copy is missing tmp.Sync(), weakening crash-safety guarantees.
backend/internal/adapters/agent/qwen/qwen.go Core plugin implementation: binary resolution, launch/restore command building, session info; clean and consistent with the cursor adapter pattern.
backend/internal/adapters/agent/qwen/activity.go Activity state deriver mapping four hook events to domain states; straightforward and well-documented.
backend/internal/adapters/agent/qwen/qwen_test.go Comprehensive test suite covering launch command, approval mode mapping, hook install/uninstall idempotency, restore command, session info, context cancellation, and activity derivation.
backend/internal/adapters/agent/registry/registry.go Registers qwen.New() in Constructors(); one-line addition, no issues.
backend/internal/adapters/agent/activitydispatch/dispatch.go Adds "qwen" → qwen.DeriveActivityState to the Derivers map; correct and consistent with other adapters.
backend/internal/daemon/wiring_test.go Adds domain.HarnessQwen to the resolver smoke test; straightforward wiring validation.

Sequence Diagram

sequenceDiagram
    participant AO as AO Daemon
    participant QP as qwen.Plugin
    participant FS as .qwen/settings.json
    participant QB as qwen binary

    AO->>QP: GetAgentHooks(ctx, cfg)
    QP->>FS: readQwenSettings()
    FS-->>QP: topLevel + rawHooks
    QP->>QP: groupQwenHooksByEvent()
    loop each managed event
        QP->>QP: parseQwenHookType()
        QP->>QP: qwenHookCommandExists() → skip duplicates
        QP->>QP: addQwenHook()
        QP->>QP: marshalQwenHookType()
    end
    QP->>FS: writeQwenSettings() → atomicWriteFile()

    AO->>QP: GetLaunchCommand(ctx, cfg)
    QP->>QP: qwenBinary() → ResolveQwenBinary()
    QP-->>AO: [qwen, --approval-mode, mode, -p, prompt]

    QB-->>AO: hook fires (SessionStart / UserPromptSubmit / Stop / PermissionRequest)
    AO->>AO: DeriveActivityState(event) → ActivityState
Loading

Reviews (2): Last reviewed commit: "feat(agents): add qwen adapter" | Re-trigger Greptile

Comment thread backend/internal/adapters/agent/qwen/qwen_test.go
Registers the qwen harness, stacked on the agent platform. Includes its own activity deriver.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@yyovil yyovil force-pushed the agents/07-cursor branch from 7bdeb48 to 8eadf7d Compare June 6, 2026 03:31
@harshitsinghbhandari harshitsinghbhandari added this to the rewrite milestone Jun 6, 2026
@harshitsinghbhandari
Copy link
Copy Markdown
Collaborator

Re-verified both findings independently against live upstream Qwen Code sources. Both refuted — no code changes required.

Finding 1: Hook event-name casing — REFUTED

Upstream docs (docs/users/features/hooks.md) require PascalCase: SessionStart, UserPromptSubmit, PermissionRequest, Stop, etc.

The adapter already writes PascalCase JSON keys — see backend/internal/adapters/agent/qwen/hooks.go:69-74:

var qwenManagedHooks = []qwenHookSpec{
    {Event: "SessionStart", Matcher: &qwenStartupMatcher, Command: qwenHookCommandPrefix + "session-start"},
    {Event: "UserPromptSubmit", Command: qwenHookCommandPrefix + "user-prompt-submit"},
    {Event: "PermissionRequest", Command: qwenHookCommandPrefix + "permission-request"},
    {Event: "Stop", Command: qwenHookCommandPrefix + "stop"},
}

spec.Event is what becomes the JSON map key in .qwen/settings.json (via marshalQwenHookType and rawHooks[event] = data). The lowercase-dashed strings (session-start, etc.) are only the AO sub-command names — ao hooks qwen session-start — that AO invokes from inside the hook command, never serialized as Qwen event keys.

Tests confirm this:

  • qwen_test.go:192-197 iterates qwenManagedHooks and reads config.Hooks[spec.Event] to assert the JSON keys.
  • qwen_test.go:153,233 seed a user-owned "Stop" (PascalCase) hook that AO must preserve.

DeriveActivityState in qwen/activity.go is fed the AO sub-command name, not the Qwen event name (per its own docstring at lines 7-11), so it correctly switches on "session-start" etc.

Finding 2: -r resume short flag — REFUTED

Upstream CLI source (packages/cli/src/config/config.ts) defines:

.option('resume', {
  alias: 'r',
  type: 'string',
  description: 'Resume a specific session by its ID. Use without an ID to show session picker.',
})

-r <id> is the documented short alias for --resume <id>. The adapter at qwen/qwen.go:124 emits -r <agentSessionID>, which is valid. The package doc at qwen/qwen.go:7-9 already documents the -r/--resume <id> shape.

Validation

cd backend && go build ./... && go test -race ./... → 765 tests pass across 49 packages, qwen package 27/27 clean.

Action taken

None — no code changes. Leaving as-is. No push.

@harshitsinghbhandari harshitsinghbhandari merged commit 3cbd7c1 into agents/07-cursor Jun 6, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants