mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
951091fb3d9dc5e858b2a60a476efc5579808f3c
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1761b696f7 |
feat: add cache-aware token usage dashboard (#1338)
* feat: add cache-aware token usage dashboard Track normalized Claude, Codex, and ACP usage with incremental SQLite backfill. Exclude imported transcript history, rebuild usage after history rewrites, and expose an owner-only dashboard with cache-aware totals and breakdowns. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> * fix: preserve usage model and local dates via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> * fix: normalize cached usage and timezone buckets via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> --------- Co-authored-by: HAPI <noreply@hapi.run> |
||
|
|
042ffced8d | fix(kimi): sync native local session titles | ||
|
|
40c1789357 |
fix(acp): sync native agent session titles (#1028)
* fix(acp): sync native session titles * fix(acp): keep title refresh off turn path * fix: reconcile native ACP titles with skill lookup * fix: preserve OpenCode image tool instruction |
||
|
|
af962fc61f |
fix(cli): stop prepending skill_lookup $name instruction onto user turns (#1096)
Cursor ACP (and other remotes) flagged the glued-on SKILL_LOOKUP_INSTRUCTION as prompt injection. Keep discovery on the skill_lookup MCP tool description and on system prompts (OpenCode/Grok); do not taint user messages. Fixes #1095 Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
22e9b38f70 |
fix(kimi): sync local sessions to web and adapt to new kimi-code architecture
hapi kimi local mode spawned the kimi TUI with no transcript sync, so terminal conversations never reached the hub and the web UI stayed empty. After the kimi-code rewrite (data moved from ~/.kimi to ~/.kimi-code), model resolution also broke: hapi read the gone ~/.kimi/config.toml and fell back to the invalid hardcoded default kimi-k2, and the KIMI_MODEL / KIMI_PROJECT_DIR env vars it set no longer exist upstream. Local sync (mirrors the codex transcript scanner): - kimiWireLocator: derive the kimi-code workspace id (wd_<slug>_<sha256(cwd).12>, ported verbatim from upstream workdir-slug), poll for the session dir created by the just-spawned process, and watch its agents/main/wire.jsonl. Pre-existing sessions are snapshotted and excluded (awaited before spawn) so a retry cannot bind to a stale session; multiple fresh candidates are refused as ambiguous. - kimiWireScanner: incrementally read wire.jsonl and convert events into hapi messages (user prompts/steers, assistant text/thinking, tool call/result incl. is_error, step.end usage with cached input summed into inputTokens). - kimiLocalLauncher: attach locator+scanner, report kimiSessionId on discovery (enables web resume and local<->remote handoff). Model handling: - config.ts: read <KIMI_CODE_HOME|~/.kimi-code>/config.toml (legacy ~/.kimi fallback); drop the hardcoded kimi-k2 default and the dead KIMI_MODEL env source - when nothing is configured, omit --model so kimi-code uses its own default_model. - kimiBackend/kimiLocal: stop setting KIMI_MODEL and KIMI_PROJECT_DIR (both unused by new kimi-code). - kimiRemoteLauncher: apply the resolved model over ACP after session creation (session/set_model, falling back to the advertised model config option), and display the agent-reported current model instead of the env guess. Verified against live kimi-code 0.26.0: ACP initialize/session-new/ prompt probes, locator discovery of a running session, and converter robustness over a real 800-line wire.jsonl. |
||
|
|
f457156bd1 |
feat(cli): add skill_lookup MCP for non-native agents (#1035)
* test: reproduce issue #752 * fix: expose skill lookup MCP tool (closes #752) * test: cover ACP skill lookup instructions * fix: inject ACP skill lookup instruction * test: narrow skill lookup auto-approval * fix: restrict skill lookup auto-approval * test: cover exact skill lookup tool names |
||
|
|
2ab3b39887 |
fix(hub,cli): four hub-restart-cascade cleanup bugs (#913 #914 #916 #919) (#923)
* fix(hub,cli): four hub-restart-cascade cleanup bugs (#913 #914 #916 #919) These four contained bugs were uncovered by a 2026-06-15 hub-restart incident where `hapi-restart-hub` SIGTERMed 23 cursor ACP sessions. Each fix lands independently of the architectural #915 (hub-restart cascade-archive) and the hypothesis-pending #917 (reopen creates dead session); audit-trail correctness and idempotency wins stand on their own. Fresh ACP sessions could be SIGTERMed during the async `update-metadata` ACK round-trip, stranding the on-disk ACP store with no DB handle. Add `ApiSessionClient.flushMetadata()` and await it after `onSessionFoundWithProtocol` on the fresh-session branch. Resume-path pre-registration (PR #834) is unchanged. Hub-restart-cascade SIGTERMs went through the same path as web-UI Archive clicks, both writing archiveReason='User terminated'. New default is 'Hub restart'; the KillSession RPC handler (the authoritative user-archive signal) now explicitly stamps 'User terminated' before cleanupAndExit. SIGINT (local-terminal Ctrl-C) keeps the 'User terminated' label too. `rpcGateway.killSession` threw a generic Error when no target socket was registered, and the archive route surfaced that as 500. Add typed `RpcTargetMissingError`, narrow on it in `syncEngine.archiveSession`, fall back to a hub-side `markSessionArchivedFromHub` write so lifecycleState still flips to 'archived'. Drop the requireActive guard on the route and 2xx-noop for already-archived rows. without refresh, producing forever-409 on rename/reopen until an unrelated event triggered a cache refresh. `renameSession`, `clearSessionArchiveMetadata`, `restoreSessionArchiveMetadata` now retry-with-refresh (5 attempts, then throw) mirroring the existing good pattern in `mergeSessions`. Refs tiann/hapi#913 Refs tiann/hapi#914 Refs tiann/hapi#916 Refs tiann/hapi#919 AI disclosure: implementation by Claude Sonnet 4.5 (Cursor agent peer) under operator supervision. Issue triage by a sibling discovery agent. Per CONTRIBUTING.md AI-assisted contributions policy. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): runner-spawned children use 'Stopped by runner' as default archive reason Addresses bot review of #923: with the #914 default-archiveReason flip to 'Hub restart', runner-driven SIGTERM paths (`hapi runner stop-session`, webhook-timeout cleanup at run.ts:587, orphan-cleanup at run.ts:267) all mislabel as 'Hub restart' which is also inaccurate audit-trail noise. Smallest defensible change: parameterise the lifecycle default via HAPI_DEFAULT_ARCHIVE_REASON env, and have the runner set 'Stopped by runner' on spawn. Terminal-launched sessions (no runner parent, no env var) still default to 'Hub restart' since hub-restart cascade documented at #915 is the most plausible SIGTERM source for those. Explicit overrides via setArchiveReason (KillSession RPC, SIGINT Ctrl-C, markCrash uncaught exception) still win. Two new unit tests cover the env-var default and the override precedence. Refs tiann/hapi#914. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hub): markSessionArchivedFromHub surfaces persistence failures as 5xx Addresses second-round bot review of #923 (Major): `markSessionArchivedFromHub` silently returned on DB write errors and on exhausted version-retry attempts, which would let `/archive` claim 200 OK while the row stayed unarchived. That regresses the #916 acceptance criterion that non-RPC errors during archive must still propagate as 5xx. Both fall-through paths now throw, matching the contract of the sibling writers in this file (renameSession, mergeSessions). The sessionModel test suite gains two cases that spy on `store.sessions.updateSessionMetadata` to force `error` and `version-mismatch` shapes and asserts the helper throws. The existing route test at `hub/src/web/routes/sessions.test.ts:1015` already covers the route-level 500 propagation for any error thrown out of `archiveSession`, so no new route test is needed. Imports `spyOn` from `bun:test` to match this test file's runtime (the rest of the hub package uses bun:test, not vitest). Refs tiann/hapi#916. Co-authored-by: Cursor <cursoragent@cursor.com> * revert(cli): drop HAPI_DEFAULT_ARCHIVE_REASON env override Reverts `1c8972a3`. Bot review round 3 surfaced that the env-on-spawn approach (the bot's own round-1 suggestion shape) mislabels hub-restart-cascade SIGTERMs against runner-spawned children: systemd killcgroup on `hapi-runner.service` stop sends SIGTERM to all runner-children directly, and those would archive as 'Stopped by runner' instead of 'Hub restart'. The two suggestions are mutually incompatible without adding an IPC channel (stdio: 'ipc' on spawn) so the runner can stamp setArchiveReason via childProcess.send() before SIGTERMing. That is a refactor, not a smallest-defensible change. Going back to the simple shape: SIGTERM default is 'Hub restart' for everyone, runner-internal stop paths share that label. The audit-trail-correctness criterion from the #914 issue is met (SIGTERM no longer falsely labels as 'User terminated'). Finer attribution between cascade vs runner-stop is deferred as a follow-up. Refs tiann/hapi#914. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): clean completions get 'Session completed', not 'Hub restart' Addresses bot review round 4 of #923 (Major): every agent runner (runClaude, runCodex, runCursor, runGemini, runKimi, runOpencode) calls setSessionEndReason('completed') on the natural exit path without touching archiveReason. With the SIGTERM default flipped to 'Hub restart', clean completions were now archived as restart cascades. Fix: setSessionEndReason flips archiveReason to 'Session completed' when it transitions to 'completed' AND no caller has already overridden the archive reason. This covers all six agent runners with a single setter change (no per-runner edits). Two new tests cover the natural-completion default and the override precedence (explicit setArchiveReason still wins). Refs tiann/hapi#914. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hub): restore inactive-session guard on /archive except split-brain Addresses post-rebase bot review Major on #923: dropping requireActive entirely let normal inactive non-archived rows (completed stubs, UI Delete/Reopen targets) fall through to archiveSession, which could stamp archivedBy=hub on sessions that were never active. Restore the 409 for inactive rows unless metadata.lifecycleState is still 'running' (hub-restart split-brain cleanup case from #916). Two route tests cover the guard and the exception. Refs tiann/hapi#916. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): merge runnerLifecycle tests after upstream rebase Post-rebase fix: Session completed tests referenced makeFakeSession which was renamed to createMockApiSessionWithMetadataCapture when merging upstream hasExplicitSessionEndReason tests with #914 archive reason coverage. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): pass lifecycle object to KillSession handler in Pi runner Upstream #862 (Pi agent) landed after this branch was cut. runPi.ts still registered the legacy bare cleanupAndExit callback, so web Archive for Pi sessions would persist archiveReason: Hub restart instead of User terminated. One-line fix matching the other six agent runners. Refs tiann/hapi#914. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
6f2bb7d32b |
feat(opencode): add plan mode, reasoning effort, and status telemetry (#688)
* feat(opencode): support plan mode * feat(opencode): support reasoning effort * feat(opencode): surface context usage in web Bridge OpenCode ACP usage updates into the existing token-count pipeline so the web status bar can show live context and cache information without a separate UI path. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(opencode): restrict plan mode to remote, rollback reasoning effort on failure - Block local OpenCode plan startup (tools not enforced in local path) - Allow remote OpenCode plan only (ACP permission handler denies tools) - Guard web /permission-mode endpoint for local OpenCode plan sessions - Rollback session reasoning effort when OpenCode rejects set_config_option - Wire rollback callback through opencodeLoop to runOpencode closure - Add tests: local plan rejected, remote plan allowed, web guard, effort rollback * fix(web): auto-retry OpenCode models query to populate model selector without refresh - Retry early failures (RPC may still be registering on new sessions) - Poll briefly until availableModels is non-empty - Stop polling once model options are discovered - Add tests for retry/poll/stop policy * fix(opencode): cap model discovery polling --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
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> |