Commit Graph
6 Commits
Author SHA1 Message Date
763f45acdd feat: add support for Kimi Code CLI and fixed some bugs (#659)
* Add Kimi agent support via ACP protocol

Add full integration for the Kimi Code CLI agent using the standard
Agent Client Protocol (ACP). Includes:

- kimi command and CLI registry wiring
- Local launcher spawning kimi directly
- Remote launcher with ACP stdio transport via AcpSdkBackend
- Session management with resume support
- Permission handler supporting all Kimi permission modes
- Terminal UI display component
- Runtime config resolving model from env and ~/.kimi/config.toml

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>

* Fix Kimi ACP tool call input decoding on web

Kimi streams tool arguments as JSON text inside the content array
(e.g. {\"command\": \"df -h\"}) instead of rawInput/kind. The handler
now extracts input from three sources in priority order:

1. rawInput (Claude/Codex path)
2. kind + title fallback (Gemini path)
3. content JSON text (Kimi path)

Also handles:
- rawInput: null no longer blocks the kind+title fallback
- Title prefixes like \"Shell: free -h\" are stripped to extract args
- Stale placeholder inputs are re-derived when the title updates
- Normalized kind aliases (shell, run, read_file, write, etc.)

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>

* Add kimi support to web UI

* Fix some bugs

* fix(kimi): dedupe repeated tool_call display in terminal UI

* fix(web): keep tool block immutable so React detects input/state changes

* fix(web): recognise Kimi subagent titles like 'Agent: ...' as subagent tools

* fix(web): allow-for-session for ACP agents (kimi, cursor)

PermissionFooter treated all non-codex sessions as Claude, sending
Claude-specific acceptEdits/allowTools to ACP agents that don't
support them. Hub rejected acceptEdits for kimi, and the ACP
PermissionAdapter ignored allowTools.

- Only show 'allow all edits' for Claude sessions
- Send decision: approved_for_session for non-Claude ACP agents
- Update status display to check decision field

* fix(web): lookup subagent sidechains by tool-call id instead of msg id

* fix(web): don't trim newest messages when loading older history

fetchOlderMessages was using trimVisible(merged, 'prepend') which kept
the oldest 400 messages and dropped the newest ones. This caused:
1. Latest messages to disappear when user loaded older history
2. User to see no visible change when new old messages were drowned
   in the 400-message window.

Remove the incorrect trim so all fetched older messages are retained
alongside the current window. Subsequent ingestIncomingMessages
(append mode) will naturally keep the window bounded when new agent
messages arrive.

* fix(cli): route Kimi session resume to runKimi instead of runCursor

Kimi was present in AGENT_FLAVORS but dispatchLocalResume had no branch
for it, so resuming a Kimi session fell through to the Cursor launcher.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>

* fix(cli): pass selected model to Kimi ACP backend via KIMI_MODEL env

createKimiBackend was ignoring opts.model and only setting KIMI_PROJECT_DIR.
Use buildKimiEnv so the selected model reaches the subprocess as KIMI_MODEL.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>

* fix(web): bound message window on older loads with dedicated larger cap

fetchOlderMessages was keeping all messages unbounded, causing
sessionStorage bloat on repeated pagination. Reintroduce trimming
with OLDER_LOAD_WINDOW_SIZE (800) so growth is capped while the
newest messages are still preserved for far longer than before.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>

* fix(web): revert sidechain lookup to message id, matching tracer/grouping pipeline

tracer.ts sets sidechainId to the parent message id, and reducer.ts groups
by sidechainId. A prior commit changed reducerTimeline.ts to look up by
tool-call id (c.id), which broke sidechain attachment. Revert to msg.id
so the lookup matches the actual grouping key end-to-end.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>

* fix(cli): gate ACP title prefix stripping to known tool-kind labels

extractTitleArgument stripped at the first colon unconditionally,
corrupting commands/paths like curl http://localhost:3000 or
Windows paths. Now it only strips when the prefix normalizes to
the same tool kind as the event, verified via regex.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>

* fix(shared): include kimi in isCodexFamilyFlavor for ACP permission UI

Kimi is an ACP-style agent that supports the abort decision, but
isCodexFamilyFlavor excluded it, so PermissionFooter rendered the
non-Codex Allow/Deny UI without the Abort button.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>

---------

Co-authored-by: HAPI <noreply@hapi.run>
2026-05-22 10:08:14 +08:00
Haoqing WangandGitHub 7c6a7fa8ef fix(hub,web): deduplicate sessions by agent session ID (#448)
* fix(hub,web): deduplicate sessions by agent session ID

When multiple CLI wrappers independently resume the same Codex thread,
each generates a random tag, causing the hub to create duplicate session
records for a single underlying thread. This leads to duplicate
conversations in the web UI and messages routing to the wrong session.

Add two-layer deduplication:
- Hub: when a metadata update sets an agent session ID (codexSessionId,
  claudeSessionId, etc.) that already exists on another session in the
  same namespace, automatically merge the duplicate into the current
  session using the existing mergeSessions logic.
- Web: deduplicate the session list display by agentSessionId as a
  safety net, keeping the active/most-recent session visible.

Closes #446

* chore: add review-driven comments for dedup clarity

- Explain single-threaded assumption in before/after metadata comparison
- Document merge direction rationale (duplicate → active session)
- Document deduplicateInProgress guard as known limitation
- Add catch comment explaining web safety net fallback

* fix: address review feedback from bot, Opus, and Codex

- Skip active duplicates during hub-side dedup to avoid deleting
  sessions with live CLI sockets and pending agent state
- Pass selectedSessionId into web dedup sort to prevent hiding
  the session the user is currently viewing
- Add test for active-duplicate-not-merged case

* fix: retry dedup on session-end and preserve agentState in merge

- Trigger dedup when a session ends (handleSessionEnd), so active
  duplicates skipped during earlier dedup get merged once they disconnect
- Preserve agentState from old session during mergeSessions when the
  new session has no agentState (mirrors existing model/effort/todos
  preservation pattern)
- Extract triggerDedupIfNeeded helper for reuse across trigger points

* fix(web): prefer active session over selected in dedup sort

Active session always wins the dedup tie-break so the live connection
is never hidden in favor of a selected inactive duplicate. Among
inactive duplicates the selected one is still preferred.

* fix: dedup on inactivity timeout and deep-merge agentState

- expireInactive now returns expired session IDs so SyncEngine can
  trigger dedup for sessions that timed out (crash/network drop)
  instead of only on explicit session-end
- mergeSessions now deep-merges agentState requests/completedRequests
  from both sessions instead of only copying when new is null

* fix: exclude completed requests from merged pending set

Filter out request IDs that already appear in completedRequests when
merging agentState, preventing completed permission prompts from
resurrecting as pending after session dedup.

* fix: guard resume merge against prior auto-dedup

The automatic dedup (triggered when the spawned CLI sets its agent
session ID) can delete the old session before resumeSession reaches
its own explicit mergeSessions call. Skip the merge if the old session
no longer exists instead of failing the resume with a false error.

* test: add coverage for dedup retry paths and web dedup sort

Hub tests:
- session-end triggers dedup retry for previously-active duplicates
- inactivity timeout expiry triggers dedup retry
- agentState deep merge filters completed requests from pending set

Web tests:
- basic dedup by agentSessionId
- active session wins over inactive duplicate
- selected session preferred among inactive duplicates
- active always wins over selected inactive
- sessions without agentSessionId pass through
- independent dedup across different agentSessionIds

* fix: read latest agentState before merge write to avoid overwriting live updates

Re-read the target session's agentState right before writing the merged
result, with a version-mismatch retry loop, so concurrent update-state
events from the active CLI are not lost during dedup merge.

* fix: sort expired sessions by recency before dedup

When multiple duplicates for the same agent thread expire in a single
sweep, process the most recent one first so it becomes the merge target
and survives, rather than keeping the oldest by arbitrary iteration order.

* fix: select most recent session as merge target in dedup

deduplicateByAgentSessionId now collects all inactive candidates
(including the caller) and picks the one with the highest activeAt
(then updatedAt) as the merge target. This ensures the newest session
survives regardless of which trigger point or ordering calls the dedup.
2026-04-13 19:56:22 +08:00
a200fe9628 feat(claude): add effort setting parity with model across stack (#353)
Co-authored-by: Xiaoyi <xiaoyizhang@microsoft.com>
2026-03-24 21:15:48 +08:00
weishu 329d28a93c remove , using instead 2026-03-16 18:29:09 +08:00
weishu 02c8e12e80 unify model selection 2026-03-16 18:29:09 +08:00
weishu 21a64dfb49 refactor: extract session summary utilities to shared module 2026-01-05 13:00:48 +08:00