main
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
78b37f20de |
fix(runner): resolve and re-materialize file attachments on remote-runner history reload (#2085)
* fix(runner): resolve and re-materialize file attachments on remote-runner history reload Signed-off-by: Sunny Yang <sunnyadn1130@gmail.com> * fix(runner): seed the native-session compaction anchor; tolerate malformed file metadata Native-harness sessions skip the history reload entirely, which also skipped seeding the last server item ID that harness compaction persistence anchors on — compactions then silently stopped persisting. Session create now fetches just the newest item ID (newest-first, single item, no attachment downloads) for native harnesses. A 200 metadata response with an unparseable body no longer aborts attachment resolution: both resolvers (the runner's message-content resolver and the claude-native transcript rebuild) fall back to the content response's Content-Type for the media-type hint. Signed-off-by: Sunny Yang <sunnyadn1130@gmail.com> * refactor(attachments): centralize file_id resolution and reference-line emission in native_attachments The transcript rebuild and the runner each carried a full copy of the file_id fetch-and-inline pipeline, and nine native executors repeated the same materialize-or-marker block. Both now live in native_attachments: resolve_file_id_block() serves the runner and the transcript rebuild, attachment_reference_line() serves the executors, and ATTACHMENT_MARKER_STRIP_PATTERN replaces four hand-copied forwarder regexes. Materialized filenames are sanitized the same way as marker names so a bracketed filename cannot break the marker consumers, and the resume dedupe short-circuits on file size before reading bytes. Signed-off-by: Sunny Yang <sunnyadn1130@gmail.com> * fix(attachments): replay resolved history attachments as structured content Cold-started claude-sdk sessions flattened prior turns into a text prefix, so a resolved historical image reached the model as the marker [image: name, media_type, N base64 chars]. The bytes never arrived, which leaves the #882 symptom in place for that harness: the model describes an attachment it cannot see. Prior-turn attachments now replay as real Anthropic image/document blocks via the existing converter, interleaved in transcript order. Text-only history still takes the plain-string path and renders byte-identically, unresolved attachments keep their existing marker, and base64 still never enters prompt text. Materialization also derives its collision suffix from a content hash rather than a random one, so a history carrying two distinct uploads of the same filename keeps one file per payload instead of gaining a copy on every transcript rebuild. The two tests that asserted the compact-placeholder shape are replaced by cold-reload tests: that shape is the behavior being corrected, but the invariant those tests protected (no base64 in prompt text) is asserted against the prompt's text blocks. Signed-off-by: Sunny Yang <sunnyadn1130@gmail.com> * refactor(attachments): collapse duplicated prompt-shape branches The structured and plain-text arms of _build_prompt returned the same value whenever the latest message was multimodal, and re-scanned the block list to decide which arm to take. Coalescing already leaves an all-text history as one block, so the block count answers that. Materialization's second identity check was a no-op guarding a write that produces the same bytes, so the collision path flattens to one branch. Signed-off-by: Sunny Yang <sunnyadn1130@gmail.com> * refactor(tests): keep runner conftest identical to upstream Move the file-server fake's items/failure/malformed-meta behaviors out of the shared _FakeFileServerClient into local subclasses in the one file that uses them, so conftest.py stays in sync with upstream and per-test modes stay next to their tests. Signed-off-by: Sunny Yang <sunnyadn1130@gmail.com> --------- Signed-off-by: Sunny Yang <sunnyadn1130@gmail.com> Co-authored-by: Pat Sukprasert <pattara.sk127@gmail.com> |
||
|
|
ae906b9733 |
fix(qwen-native): dedup window keeps most-recent uuids, not an arbitrary set slice (#1780)
The qwen-native forwarder stored posted-event uuids in a `set` and persisted `list(seen)[-512:]`. Because `set` iteration is hash-ordered, that kept an arbitrary 512 uuids, not the most recent 512 the docstring promises. After a qwen TUI relaunch (offset rewinds to 0, file re-read from the top) for a session with >512 events, recent uuids evicted from the window were re-posted as duplicate bubbles in the web session. Back `seen` with an insertion-ordered dict (an ordered set), mirroring the sibling opencode-native forwarder, so the `[-_DEDUP_WINDOW:]` cap keeps the real recent tail. `_read_new_events`' membership-only param is typed `Container[str]`. Closes #1779 Signed-off-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com> Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
08e85d30fa |
feat(qwen-native): support /compact via qwen /compress with spinner + divider (#1391)
Wire the web UI's compact control to qwen-native sessions, with a "Compacting…" -> "Conversation compacted" indicator that tracks qwen's real progress. Mirrors cursor-native (#1259). Previously the runner's /events compact dispatch had no qwen-native branch, so /compact returned a 204 no-op and the server fell through to its own AP-side compaction, which 400s on the LLM-less native pseudo-agent — explicit compaction must run inside the qwen TUI (it owns its own context window via /compress). Runner (omnigent/runner/app.py) — add _handle_qwen_native_compact: - Submits /compress into the TUI via the --input-file (submit_user_message). qwen's RemoteInputWatcher routes it through submitQuery (the keyboard's own path), which processes the slash command directly — no autocomplete-dropdown trap (cursor's send-keys bug) and no /compress user bubble on the stream (verified live, qwen v0.18.2). - Publishes response.compaction.in_progress to raise the spinner, and response.compaction.failed on injection error to dismiss it. - Returns 200 so the server skips its own compaction. Forwarder (omnigent/qwen_native_forwarder.py) — add supervise_qwen_compaction_mirror: - Compaction is invisible on the --json-file stream (session_start's supported_events omits it). But qwen writes a {system, chat_compression, info:{originalTokenCount,newTokenCount,compressionStatus}} record to its built-in chat recording (~/.qwen/projects/<slug>/chats/<id>.jsonl) the instant compression finishes. - The mirror tails that recording (seeded at EOF so a resumed session's prior records don't re-fire) and POSTs external_compaction_status — completed on compressionStatus==1, failed on the COMPRESSION_FAILED_* codes — which the server republishes as the SSE the web UI renders. - Fires for both explicit /compress and auto-compaction. Bridge (omnigent/qwen_native_bridge.py) — extract qwen_session_recording_path (reused by the mirror and the existing --resume guard). Co-authored-by: Isaac |
||
|
|
417b914a4d |
feat(qwen): add native-qwen TUI harness with resume, readiness gate, and clean-exit (#1134)
Add a terminal-native Qwen Code harness (`qwen-native`, alias `native-qwen`) that embeds the live `qwen` TUI in the web UI, alongside the existing ACP `qwen` harness. Unlike the goose/cursor tmux-send-keys natives, it drives qwen's built-in remote-control protocol: web turns are appended to qwen's `--input-file` and the transcript is mirrored back by tailing the structured `--json-file` event stream. Highlights (all verified against qwen v0.18.1-preview.1): - Bridge/executor/forwarder/CLI-wrapper + full registration (harness registry, aliases, native-coding-agent, wrapper labels, install spec, readiness, resume dispatch, resource role, server built-in seeding so Qwen Code shows in the new-session picker). - Readiness gate: the executor waits for qwen's first `system` event before the first submit, fixing the boot-order race where a message appended before qwen's input watcher started was silently dropped. - Session resume via the `external_session_id` convention (consistent with claude-/codex-/pi-native, fork-capable): deterministic per-conversation qwen session id, `--session-id` on first launch, `--resume` once a recording exists; qwen restores its own TUI history and emits only new events, so no double-mirroring. - Clean TUI quit: a qwen required-terminal exit is treated as a normal shutdown (publishes idle, no `required_terminal_exited` crash card). - Web UI: terminal pane recognized as an agent terminal; composer hides the model/effort chip for vendor-owned-model native sessions. Docs: docs/QWEN_NATIVE_DESIGN.md (design) and docs/QWEN_FOLLOWUPS.md (elicitation card, usage/cost/model surfacing tracked as follow-ups). Tests: executor, CLI wrapper, bridge/forwarder, server seeding, and web (nativeCodingAgents, chatStore flags, useTerminals, statusLine). Co-authored-by: Isaac |