Kimi 1564 rebase - #1
Merged
whyihaveyou merged 28 commits intoAug 15, 2026
Merged
Conversation
The available models fallback list included opus[1m] but was missing sonnet[1m], making it impossible to use 1M context with Sonnet models when config.toml has no explicit models configured and the API fails to return model aliases. Co-authored-by: zhangyanbo2007 <zhangyanbo2007@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
chenhg5#965 added daemon.CheckLinger() in daemon/systemd.go (which has //go:build linux), and calls it unconditionally from cmd/cc-connect/daemon.go. The caller has no build tag, so building on macOS/Windows fails with: cmd/cc-connect/daemon.go:109:27: undefined: daemon.CheckLinger Add a !linux stub that returns (true, "") so callers skip the systemd linger warning on platforms where it doesn't apply. The runtime check in cmd/cc-connect/daemon.go (`strings.Contains(mgr.Platform(), "user")`) will still gate the warning on systems where it's meaningful, but the stub keeps the symbol resolvable cross-platform. Verified: `make build` now passes on darwin/arm64.
…ns (chenhg5#1368) (chenhg5#1373) The /model confirmation copy used wording ('New sessions will use this model') that misled users into thinking the current session still ran on the old model and that they needed /new to see the switch take effect. In reality cmdModel preserves the agent session id and re-invokes the runtime with --resume <id> --model <new>, so the new model is live immediately for the current turn and persists for all future sessions. Updated MsgModelChanged in all 5 languages (en, zh-CN, zh-TW, ja, es) to explicitly state 'this session and all future sessions will use it'. Added CHANGELOG Fixed entry under Unreleased. Co-authored-by: Claude <noreply@anthropic.com>
…order (chenhg5#1464) (chenhg5#1466) New users installing cc-connect were hitting a fail-fast path ("claudecode: claude CLI not found in PATH", Web UI on :9820 never comes up) because the README jumped straight to installing cc-connect itself with no guidance on installing the agent CLI first and authenticating it. Issue chenhg5#1464 (NirvanaCh7, fresh macOS Homebrew install) hit this; historical chenhg5#1122 and chenhg5#1139 were related but closed COMPLETED without addressing the README. Add a top-level "Prerequisites" section (zh-CN: "准备工作") to both README.md and README.zh-CN.md, placed immediately before "Quick Start". The section walks through the install order: 1. Install the agent CLI (Claude Code / Codex / Gemini / iFlow / Qoder / Cursor Agent / OpenCode) — pick one, verify it's on PATH 2. Authenticate the agent (e.g. `claude login`) — stores creds in home dir 3. Install cc-connect (npm / Homebrew / binary) 4. Start cc-connect and open Web UI on http://localhost:9820 5. Configure platform bot tokens in the Web UI The section explicitly warns about the failure mode so users recognize it before they hit it. cc-connect startup logic is unchanged — the fail-fast behavior is intentional and well-tested. Co-authored-by: dev-claudecode <dev-claudecode@cc-connect.local> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* fix(agent/antigravity): avoid print-mode stdin deadlock * feat(agent/antigravity): bridge tool permissions to chat * fix(agent/antigravity): harden permission bridge review items * test(agent/antigravity): update Send call signature
* feat(googlechat): scaffold platform skeleton
Add a Google Chat platform adapter registered as "googlechat", wired
into selective compilation via plugin_platform_googlechat.go (build tag
no_googlechat) and the Makefile ALL_PLATFORMS list.
This commit lands the skeleton only: option parsing/validation in New
(gws_path, project, target, subscription, allow_from, trigger,
session_scope, credentials_file) plus stub Start/Reply/Send/Stop.
Receive, send and gating follow in subsequent commits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(googlechat): receive messages via gws events subscribe
Start() now supervises a `gws events +subscribe` subprocess: stdout is
scanned as NDJSON (one Workspace Events CloudEvent per line, buffer
enlarged for the full message resource), stderr is logged, and the
process is restarted with a small backoff if it exits while the context
is alive. Stop() cancels the context to kill it.
parseEvent decodes chat.message.v1.created envelopes, ignores non-human
senders (so the app never replies to its own posts), and dispatches a
core.Message carrying the space/thread reply context. Trigger-word,
allow_from gating and session scoping follow in a later commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(googlechat): send replies via gws chat messages create
Reply and Send post content through `gws chat spaces messages create`,
using the generic raw method (--params/--json) rather than the +send
helper so replies can be threaded: when the reply context carries a
thread name, the request sets messageReplyOption and thread.name so the
answer lands in the originating thread.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(googlechat): add trigger-word, allow_from and session scoping
parseEvent now gates and routes incoming messages:
- trigger word: when configured, only messages starting with the prefix
are handled (prefix stripped) for user-OAuth mode without a Chat App;
otherwise argumentText (mention markup already removed by Google) is
used, falling back to text.
- allow_from via core.AllowList, and a core.IsOldMessage guard on
createTime so a restart does not replay backlog.
- session_scope space|thread|user via buildSessionKey, plus
ReconstructReplyCtx so proactive sends reach the right space/thread.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(googlechat): add config example
Document the googlechat platform options in config.example.toml,
including the gws prerequisites and the two operating modes (@mention
with a Chat App service account, vs trigger-word with user OAuth).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(googlechat): add event parsing and reply-arg unit tests
Cover parseEvent (mention vs trigger mode, argumentText fallback,
non-human and wrong-type skipping, allow_from, old-message guard),
buildSessionKey for each scope, ReconstructReplyCtx round-trips, and
buildCreateArgs threaded vs top-level argument shapes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(googlechat): switch to Chat app Pub/Sub receive + service-account send
Pivot the platform from the Workspace Events API to a registered Google
Chat app's native Cloud Pub/Sub connection. The subscription is fixed, so
there is no Workspace Events subscription expiry or per-restart resource
leak (the two operational problems of the previous approach).
- receive: parse the Chat-app event format — gws wraps each Pub/Sub
message as {"data":{"type":"MESSAGE","message":{...}}} — instead of
Workspace Events CloudEvents; pull a fixed subscription via
`gws events +subscribe --subscription`.
- send: post via the Chat REST API authenticated as the app's service
account (chat.bot scope) so replies appear as the bot. gws has no
service-account auth, so sending is native Go via
golang.org/x/oauth2/google; receiving still uses gws (its own OAuth).
- config: require `subscription`; `credentials_file` is the SA key used
for replies. Drop the Workspace Events target/project auto-create.
Verified end-to-end against a real Chat app: the receive format matches
gws output and a service-account reply posts as the bot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(googlechat): add GCP/Chat app setup guide
Add docs/googlechat.md documenting the end-to-end Google Chat setup:
enabling the Chat + Pub/Sub APIs, creating the Pub/Sub topic/subscription
and granting the Chat push service account publisher, creating a service
account key for bot replies, configuring the Chat app (LIVE, classic
model, Cloud Pub/Sub connection, visibility), cc-connect config, and an
FAQ covering the issues hit during bring-up (app must be LIVE to send,
Workspace-only, add-on vs classic). Link it from the README platform
table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(googlechat): receive via native Pub/Sub client instead of gws
Replace the `gws events +subscribe` subprocess with a native streaming pull
using cloud.google.com/go/pubsub. Sending was already native Go, so this
removes the gws binary dependency entirely — the platform is now fully native.
Both directions now authenticate with the same service-account key, so
`credentials_file` is required (it also needs roles/pubsub.subscriber on the
subscription for the receive path). The `gws_path` option is removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(googlechat): drop trigger, centralize session-key constants, harden session_scope (chenhg5#5)
- Remove the `trigger` field and its prefix-matching branch from extractContent;
Google Chat already scopes delivery so a prefix filter is redundant and subtly
broken for @mention spaces (m.Text still contains the markup).
- Extract sessionKeyPrefix and threadSep constants used by both buildSessionKey
and ReconstructReplyCtx so the encode/decode pair stays in sync automatically.
- Add a slog.Warn in the normalizeSessionScope default branch so typos like
"thraed" surface at startup instead of silently falling back to "space".
- Update tests, docs/googlechat.md, and config.example.toml accordingly.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(googlechat): implement FormattingInstructions for Google Chat text formatting (chenhg5#7)
* feat(googlechat): implement FormattingInstructions for Google Chat text formatting
Adds FormattingInstructions() to *Platform so the engine injects
Google Chat-specific formatting guidance into the agent system prompt,
steering agents away from unsupported ## headings and [text](url) links.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): add block quote and display-text link to FormattingInstructions
Add missing formatting rules per reviewer feedback:
- Block quote syntax (>text)
- Display-text hyperlink syntax (<url|text>) alongside raw URL auto-linking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): fix test call after base-branch refactor of newTestPlatform
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(googlechat): implement streaming preview (PreviewStarter + MessageUpdater) (chenhg5#6)
* feat(googlechat): implement streaming preview via PreviewStarter + MessageUpdater
Adds SendPreviewStart (POST a threaded message, return resource-name handle)
and UpdateMessage (PATCH spaces.messages with updateMask=text) so the engine's
real-time streaming-preview path is activated for Google Chat.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(googlechat): extract doRequest helper + drain response bodies
Extract a `doRequest` helper that centralizes HTTP execution and non-2xx
error handling. Both `post()` and `SendPreviewStart` become thin wrappers,
eliminating the duplicated logic that would otherwise diverge on future
changes (retries, auth header tweaks, etc.).
Drain response bodies on success paths in `post()`, `SendPreviewStart`,
and `UpdateMessage` so the transport can return TCP connections to the
pool. Addresses the performance issue where each streaming PATCH would
open a new TLS+TCP handshake.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(googlechat): send images and files via attachment upload (chenhg5#8)
* feat(googlechat): implement SendImage and SendFile via attachment upload
Adds media-upload support to the Google Chat platform so cc-connect can
send images and files (e.g. /diff HTML output) instead of returning
ErrNotSupported.
Two-step flow for each send:
1. Upload raw bytes to the Chat media endpoint (multipart/related POST
to https://chat.googleapis.com/upload/v1/{space}/attachments:upload)
and retrieve the attachmentDataRef resource name.
2. Create a Chat message that references the data ref, respecting thread
context the same way as the existing text send path.
Shared helper postAttachment handles both steps; SendImage and SendFile
delegate to it with appropriate filename/MIME defaults. Compile-time
interface assertions ensure *Platform satisfies core.ImageSender and
core.FileSender.
Adds TestBuildAttachmentRequest mirroring TestBuildSendRequest to cover
the message body shape (attachment data ref + thread, reply option URL).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): address code-review feedback on SendImage/SendFile
- Add rc.space == "" guard to postAttachment (bug fix: prevents malformed
upload URL when ReconstructReplyCtx("googlechat:") yields empty space)
- Pre-size bytes.Buffer in uploadAttachment to avoid a full double-copy of
attachment data on large files
- Extract httpErrorBody helper to deduplicate the 3 error-drain sites
- Extract messageURL/applyThread helpers to deduplicate threading URL logic
shared between buildSendRequest and buildAttachmentRequest
- Extract coalesce helper and simplify SendImage/SendFile
- Use doRequest in postAttachment for the create-message step
- Add HTTP-level unit tests: uploadAttachment (success, 403 error,
empty resourceName), postAttachment (missing space, two-step flow),
SendImage/SendFile (default filename/MIME fallbacks)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): use doRequest in uploadAttachment and drain response body
Two follow-up fixes from code review:
- Route upload request through doRequest so it benefits from any future
centralized logging/retry/tracing alongside post() and postAttachment().
- Drain remaining bytes from resp.Body after json.Decode so the HTTP
transport can return the connection to the pool (partial reads prevent
connection reuse, causing a new TLS handshake per SendImage/SendFile call).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): drain resp.Body on decode error in uploadAttachment
Deferred drain ensures the HTTP connection is returned to the pool
even when JSON decoding fails, not just on success.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): handle Stop error, remove redundant drain defer, add compile-time assertion (chenhg5#12)
- Return psClient.Close() error from Stop() instead of silently discarding it
- Remove redundant drain defer in uploadAttachment; the json.Decoder already
consumes the body, and defer resp.Body.Close() handles cleanup
- Add compile-time assertion var _ core.ReplyContextReconstructor = (*Platform)(nil)
alongside the existing ImageSender and FileSender guards
Closes chenhg5#10
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): use doRequest in uploadAttachment and drain response body
Two follow-up fixes from code review:
- Route upload request through doRequest so it benefits from any future
centralized logging/retry/tracing alongside post() and postAttachment().
- Drain remaining bytes from resp.Body after json.Decode so the HTTP
transport can return the connection to the pool (partial reads prevent
connection reuse, causing a new TLS handshake per SendImage/SendFile call).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): drain resp.Body on decode error in uploadAttachment
Deferred drain ensures the HTTP connection is returned to the pool
even when JSON decoding fails, not just on success.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(googlechat): clear lint issues in googlechat package (chenhg5#13)
* fix(googlechat): resolve package lint errors
Handle all body/multipart close errors, remove deprecated auth option usage, and tighten HTTP test writers so golangci-lint passes for platform/googlechat.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(googlechat): ack Pub/Sub message after handler, nack on panic
Previously m.Ack() was called before p.handler(), so a handler panic or
failure would silently drop the message without redelivery. Move ack to
after handler completion and nack on panic so Pub/Sub can redeliver.
Introduce the ackable interface and dispatchMessage to make the timing
testable without a real Pub/Sub client, and add three regression tests
covering the success, panic, and parse-failure paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(googlechat): add unit tests for New() error cases (chenhg5#14)
Closes chenhg5#11
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ot (chenhg5#1560) (chenhg5#1588) * feat(feishu): on-demand download of quoted files when user mentions bot When a Feishu group member uploads a file and then replies/quotes it while @-mentioning the bot, the bot could not see the file because cc-connect does not persist message bodies. Resolve by detecting the quote metadata on the reply, fetching file_key from the parent message via /open-apis/im/v1/messages/:id, and downloading the bytes only when both (1) the reply explicitly @-mentions the bot and (2) the original file's Feishu sender matches the user who triggered the mention (same-user privacy gate). Downloads are deferred: the dispatcher collects quotedFileMeta during chain traversal and only when the gates pass does it call the file-resource API. Quotes without @bot and ordinary un-quoted messages make zero file-resource calls — bounds Feishu's high-frequency read path and matches the reporter's 'only fetch when explicitly asked' preference. State is transient by construction: quotedFileMeta lives only inside the per-invocation chain slice and never touches session JSON, so going offline drops it cleanly per the PM triage option (c). Tests cover the acceptance matrix (mention+quote fetch, quote-without- mention no fetch, ordinary message no fetch) plus same-user foreign-file rejection at platform/feishu/quote_file_test.go. Fixes chenhg5#1560 * fix(feishu): remove unused expectFileName field to satisfy lint QA review (PR chenhg5#1588) flagged an unused field 'expectFileName' on the scenario struct in TestDispatchMessageQuotedFileAcceptance. The only matcher actually used is 'expectFileNameIs'; the dead field caused the 'unused' linter to fail CI. - Remove the dead field from the scenario struct. - All scenario values and assertions continue to use 'expectFileNameIs'. - Local lint (golangci-lint v2.11.4 with --new-from-rev origin/main): 0 issues. - Local tests (TestFilterQuotedFilesForUser, TestDispatchMessageQuotedFileAcceptance, TestDispatchMessageQuotedFileForeignUserDropped, full feishu suite): PASS. - go vet ./platform/feishu/...: clean. Refs: PR chenhg5#1588, issue chenhg5#1560.
…#1250) When Reply() is called, scan content for @userid patterns (4+ digit numeric DingTalk user IDs) and attach matched IDs to the JSON payload's at.atUserIds field. Deduplicates and preserves first-seen order. Also hoist the regexp to a package-level variable and add 3 test functions (14 sub-cases) covering the new extractAtUserIds function and its Reply integration. Co-authored-by: wen_guoxing <wen_guoxing@itrus.com.cn> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…ixes chenhg5#1233) (chenhg5#1257) When progress_style=card is enabled (e.g. Feishu with structured card payload), a long tool input was rendered at its full length in the card, bypassing the display.tool_max_len limit that already applies to the rich-card path and to tool results. The compact progress writer (cp.AppendEvent) now receives the truncated tool input as the typed event text, while event.ToolInput itself is left unmutated and the legacy fallback toolMsg keeps its full formatted input so text-mode behavior is unchanged. Adds TestProcessInteractiveEvents_CardProgressTruncatesToolInputByToolMaxLen to verify the card writer applies truncateIf(toolInput, ToolMaxLen) without mutating the source event. Co-authored-by: dev-claudecode <dev-claudecode@spaceship.local> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…#1670) (chenhg5#1673) The qoder agent adapter (agent/qoder/) already routed through core.ParseCmdOpts, but ParseCmdOpts only accepted a whitespace- separated string for the unified 'cmd' field. Users who tried the documented array form in their config.toml: [projects.agent.options] cmd = ['qodercli', '--permission-mode', 'bypass_permissions'] saw their extra args silently dropped because opts['cmd'].(string) type-asserted against []any and the parser fell through to the default binary. WebSearch / WebFetch / Bash curl were then rejected by qodercli on IM platforms because --permission-mode never reached the spawned process. Fix: - Extend core.ParseCmdOpts to also accept []any (TOML inline array) and []string shapes for the 'cmd' field. Behavior for the string form, deprecated 'cli_path' / 'command' fields, and the default fallback is unchanged. - Add toStringArray helper that returns ok=false for mixed-type arrays so invalid configs fall through instead of panicking. - Add filterNonEmpty so empty / whitespace-only array slots are dropped (matches the string form's whitespace-only skip). - Update config.example.toml qoder section to document both forms. - Add unit tests covering: array happy path, []string form, empty/whitespace-only arrays, mixed-type arrays, argv order preservation, plus the qoder IM permission-mode regression case. Backward compat: existing string-form configs keep their behavior. The fix automatically benefits all agents that route through core.ParseCmdOpts (claudecode, codex, cursor, qoder, opencode, iflow, copilot, kimi, antigravity, acp), not just qoder. Refs: issue chenhg5#1670.
…g5#1630) core.SplitMessageCodeFenceAware() closes an open fence at a chunk boundary and re-opens it in the next chunk, and the platform layers call it (e.g. platform/telegram/telegram.go). But engine.go already split the text one layer earlier with the naive splitMessage() at maxPlatformMessageLen, so every chunk was under the limit by the time the platform layer ran and the fence-aware splitter never had anything to do. The result was that a reply long enough to be split rendered as a code block in the first message and as plain text in the rest, with table columns misaligned. Switch the ten call sites in engine.go to SplitMessageCodeFenceAware(). Same signature, so it is a drop-in replacement. engine.go:4982 (tool messages) already used it, which suggests the reply paths were an oversight rather than a deliberate choice. Co-authored-by: leon.xu <leon.xu@opsmateai.com>
…henhg5#1288) * feat(dingtalk): derive chat-list title from content (chenhg5#1269) Both Reply (sessionWebhook) and sendProactiveMessage (oToMessages/batchSend) hardcoded markdown.title="reply", so every DingTalk chat list entry showed the same preview regardless of what the agent said. Replace with cardTitleFromContent(content): - strips markdown via core.StripMarkdown (already used by wecom/line) - takes first non-empty line - truncates to 20 runes (CJK counted as 1 rune so we never split mid-char) - falls back to 'reply' for empty / whitespace-only / pure-format / pure-emoji input Behaviour: "**Standup notes** for today" -> "Standup notes for to" (truncated) "你好世界..." (22 runes) -> "你好世界..." (first 20 runes) "****" -> "reply" (fallback) "🎉🎊" -> "reply" (fallback) Tests: TestCardTitleFromContent (22 subtests covering plain, markdown stripping, truncation, multi-byte CJK, fallback cases) TestCardTitleFromContent_UsedInReplyPayload (smoke test that the derived title actually flows into the outgoing sessionWebhook JSON payload) Other platform adapters (feishu/telegram/slack/...) untouched. Fixes chenhg5#1269 * ci: retrigger chenhg5#1288 CI (TestCUJ_H2_TwoPlatformsConcurrentNoBleed flaky) chenhg5#1288 (dingtalk chat-list title) is QA fresh review APPROVE on code (review PRR_kwDORa0x388AAAABDNua8A, 0 P0/P1/P2/P3 blockers). Local TestCUJ_H2 runs 1/3 flaky on pr-1288, 3/3 PASS on origin/main; chenhg5#1288 doesn't touch core/cuj_test.go, so the failure is from chenhg5#1348 CUJ framework's 30s tight timeout, not from this PR. Re-trigger CI via empty commit; if stable fail on 2-3 retries, the flaky test fix belongs in chenhg5#1348, not here. --------- Co-authored-by: dev-claudecode <dev-claudecode@spaceship.local> Co-authored-by: dev-claudecode <dev-claudecode@cc-connect.local>
…读不到话题内容的问题) (chenhg5#1627) * fix(feishu): bootstrap context for first thread mention * fix(feishu): simplify thread bootstrap condition --------- Co-authored-by: tanran <tanran@minimaxi.com>
listener.Addr() returns the IPv6 wildcard "[::]:port" on dual-stack systems. The wildcard address is not dialable — dialing "::" yields EOF on every request. Rewrite the test client URL to use the loopback form "[::1]" so the test client can actually reach the listener. Production config uses concrete IPs (0.0.0.0 / public IP), so this is test-only. The fail reproduced on origin/main (predates this branch). Co-authored-by: 张满良 <zhangml@tech.icbc.com.cn>
…henhg5#1636) * fix(pi): fall back to models-store.json when enabledModels is unset AvailableModels only read enabledModels from settings.json, which is empty by default. /model in Feishu then renders an empty select with only the current model, making model switching impossible. Fall back to pi's own model catalog (~/.pi/agent/models-store.json), which pi maintains as providers are added, when enabledModels is not configured. * test(pi): cover models-store.json fallback in readModelsStore Add TestReadModelsStore mirroring TestReadSettingsModels: missing file, valid file sorted provider-qualified, empty-id skip, and malformed JSON. Add TestAgent_AvailableModels_FallsBackToStore for the enabledModels-empty → catalog fallback path. Clarify in loadModelsContextWindows that models.json (context windows) is distinct from models-store.json (the /model catalog).
* feat(pi): inject permission mode into agent process env The pi permission-gate extension runs inside the pi CLI and has no way to learn cc-connect's permission mode. When a user switches to yolo (auto-approve) mode in Feishu, the engine restarts the session, but the new pi process is still spawned without any mode hint, so the extension keeps emitting permission cards. Inject CC_PERMISSION_MODE=<mode> into the spawned pi process environment in StartSession. The extension reads it and auto-approves all tool calls in yolo mode. Mode switches already recreate the session (pi does not implement LiveModeSwitcher), so the new process picks up the new value. * test(pi): check Close error in StartSession test to satisfy errcheck * refactor(pi): move permission-mode env injection into core helper Extract the hardcoded CC_PERMISSION_MODE append into core.InjectedAgentEnv so the env var becomes a documented, reusable part of cc-connect's CC_* extension contract instead of a one-off string in the pi agent. Other agents can opt into the same convention. Add TestAgent_StartSession_UserOverrideWins to lock in that the engine-injected value is appended after config/session env, so a user-supplied CC_PERMISSION_MODE keeps priority (getenv returns the first match).
chenhg5#1036) Both /commands and /cron currently have a single coarse-grained privilege check: the cmdID is or is not in privilegedCommands. Neither is listed there, so all subcommands run for any user — including `addexec`, which registers a custom command that shells out, or schedules one that shells out on a cron. A non-admin user who can send slash commands can effectively install arbitrary shell payloads for anyone else to trigger. (executeCustomCommand already gates Exec-bearing custom commands on admin_from after they have been registered, so executing such a payload also requires admin — but the registration itself shouldn't be open to non-admins either. This PR closes the registration side.) Introduce isPrivilegedCommandInvocation(cmdID, args) which: - falls through to the existing privilegedCommands map for the static cases (/shell, /show, /dir, /restart, /upgrade, /web, /diff) - additionally returns true for `/commands addexec ...` and `/cron addexec ...` (subcommand names matched via matchSubCommand so prefix forms like `/cron addex` and case variants are covered) - returns false for every other subcommand of /commands and /cron (list, add, del, etc. stay non-privileged) handleCommand swaps the privilegedCommands[cmdID] check for the new helper. No other privilege paths change. Co-authored-by: Shuchao Shao <shaoshch@yonyou.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…etail (chenhg5#233) (chenhg5#1251) Issue chenhg5#233 reports that selecting "yolo" (or any non-ClaudeCode mode) on a Codex project does not actually persist: the user picks yolo in the dropdown, saves, and the project reloads in "default" mode. Root cause: web/src/pages/Projects/ProjectDetail.tsx:517-527 hardcodes five ClaudeCode-specific mode options. The Codex backend (verified in agent/codex/codex.go:129-140 `normalizeMode`) actually supports `suggest`, `auto-edit`, `full-auto`, and `yolo`. None of the hardcoded options match those values, so: - Codex users never see the yolo option they expect - If a Codex project is saved with a non-ClaudeCode mode (e.g. via config.toml), the dropdown shows a stale value that maps to nothing on save This change makes the dropdown render agent-type-aware options, picking from a small lookup table keyed by agent type. Unknown agent types fall back to the ClaudeCode list (preserves prior behavior, so this is non-breaking for other agents like cursor/opencode). The reference fix in commit 847758f (PR chenhg5#816) also switches on agent type, but it bundles a breaking rename of the Codex modes ("auto-review" / "full-access" instead of the current "auto-edit" / "full-auto"). This change keeps the existing normalized Codex keys so saves round-trip with the backend as-is. No backend changes. No new dependencies. tsc -b and vite build pass locally. Verification (manual, see PR description): 1. Create a Codex project (or switch an existing project's agent type to "codex" in the Settings tab). 2. Open the Permission mode dropdown — it should now show: suggest (default), auto-edit, full-auto, yolo (bypass) 3. Pick "yolo (bypass)" and Save. 4. Reload the project — the dropdown should still show "yolo (bypass)" and the Codex subprocess should run with --dangerously-bypass-approvals-and-sandbox (verify in logs / project behavior). 5. Switch back to ClaudeCode — the dropdown should show the original five options (default, acceptEdits, plan, bypassPermissions, dontAsk). Co-authored-by: cc-connect dev-claudecode <dev-claudecode@spaceship.local> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…d fail fast (chenhg5#1643) * fix(weixin): treat sendmessage ret=-2 as a burst throttle, not a token issue Live testing showed ilink accepts any (or no) context_token on sendmessage; ret=-2 "prepare failed" is a bot-wide burst throttle that lasts on the order of an hour and is not cleared by restarting the daemon or re-binding getupdates. Rapid 500ms-interval retries during the throttle only add more refused sends (and may extend the penalty window). Replace the token-refresh retry logic with throttle-aware handling: - detect ret=-2 as a throttle, back off before a single retry, then fail with a clear error so the caller can retry the whole message later - skip the "incomplete delivery notice" send when the failure is a throttle - fix misleading "refresh the session token" log/error wording Applies to both the text (sendChunkWithRetry) and media (sendSingleItemWithRetry) send paths. Adds unit tests simulating throttled-then-success and persistent throttle. Closes chenhg5#1640 * docs(config): document weixin outgoing rate limit to avoid ilink burst throttle ilink sendmessage throttles the bot after a short burst of rapid sends (ret=-2 "prepare failed"). Add the weixin per-platform override example so deployments can pace sends and avoid triggering the throttle. * fix(weixin): fail fast on sendmessage throttle instead of backing off Live testing showed the ilink sendmessage penalty (ret=-2 "prepare failed") is escalated by every send attempt made while it is active: the original incident recovered in ~1h with no attempts, while probing attempts kept the bot throttled for 7h+. Retrying (even with a backoff) only prolongs the outage. Change the text and media send paths to fail fast on ret=-2 with a clear throttled error, issuing a single sendmessage call and skipping the extra "incomplete delivery notice" send. Remove the now-unused retry constants. * feat(weixin): add send-volume window quota to avoid ilink burst throttle Live testing showed ilink's sendmessage throttle (ret=-2 "prepare failed") is triggered by roughly 5-6 separate messages within a short window (not by chunk count: multi-chunk sends are fine), and that rate-based pacing alone does not prevent it. Add a per-platform sliding-window quota (burst_limit / burst_window_secs, default 4 per 120s) that paces separate messages so the bot stays below the trigger. fail-fast on ret=-2 already prevents the penalty from escalating. * docs(config): document weixin burst quota options (burst_limit/burst_window_secs) * fix(weixin): make send quota a 24h budget that fails fast instead of pacing Live testing showed ilink's sendmessage throttle is a per-account message budget (~5-6 sends per 24h window), not a rate/burst limit: sends spread over hours still trip it at ~5-6 total, and pacing (1/s) did not prevent it. Change the quota from a short-window pacing mechanism to a 24h budget that fails fast (no waiting — waiting up to a day is useless) once exhausted, so the bot never hammers a throttled API. Configurable via burst_limit / burst_window_secs (default 4 per 86400s).
…chenhg5#1483) Same pattern as the chenhg5#1456 / PR chenhg5#1461 --print fix. Newer Kimi Code CLI builds no longer accept --work-dir, exiting with `error: unknown option --work-dir` whenever the user's config sets a non-default workspace directory. - agent/kimi/probe.go: kimiFlagSupport gains a WorkDir bool; the probe fills it from `kimi --help` (parseKimiHelpFlags already scans the --work-dir token, no parser changes needed). - agent/kimi/session.go buildArgs: --work-dir is now emitted only when flagSupport.WorkDir is true. The agent still runs in the correct directory via exec.Command.Dir (set separately), so omitting the flag on modern CLIs is functionally equivalent for users on default dirs and graceful for users on non-default dirs whose CLI just ignores it. - agent/kimi/probe_test.go: extend the legacy test to assert --work-dir is detected, add modernKimiHelpWithoutWorkDir constant that mirrors the build the reporter hits, plus full TestKimiFlagSupport_LegacyHelpSetsWorkDir / TestKimiFlagSupport_ModernWithoutWorkDir coverage of the probe mapping. - agent/kimi/session_test.go: TestBuildArgs_WorkDirFlagGated (no flag on modern CLI) + TestBuildArgs_WorkDirFlagEmitted (legacy CLI keeps it). Also asserts the work-dir value doesn't leak into args when the gate is closed, catching a partial-gate future bug. go test ./agent/kimi/... — all pass. Co-authored-by: dev-claudecode <dev-claudecode@cc-connect.local>
Fixes chenhg5#1561 The kimi agent targeted the legacy Python kimi-cli dialect; the newer Node.js Kimi Code CLI (kimi-code) speaks a different one. Extend the chenhg5#1461/chenhg5#1483 probe-gating approach to cover the remaining differences: - probe: also detect --quiet; add isModernFlavor() using --print absence as the family discriminator established in chenhg5#1456 - buildArgs: resume with -r instead of --resume on the modern dialect (--resume is rejected; -r matches the CLI's own resume hint); gate --quiet and emulate quiet mode via local event suppression when the binary dropped it; never pass --yolo/--auto (bare --prompt already auto-approves, and Kimi Code rejects combining them) - stream-json: accept plain-string content for assistant/tool messages alongside the legacy block-array shape (format-tolerant, no gating) - session continuity: capture the session id from Kimi Code's stdout meta line {"role":"meta","type":"session.resume_hint"} instead of the legacy plain-text/stderr hint - session listing: scan both ~/.kimi/sessions and ~/.kimi-code/sessions, understand the Kimi Code state.json schema, honor its workDir field Tests: real v0.26.0 --help fixture, per-dialect arg tests, content-shape and meta-hint regression tests, dual-flavor session listing test, and an env-guarded live e2e (KIMI_LIVE_E2E=1) that verifies anchor recall across a resumed turn against the production binary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kimi Code CLI sessions have no context.jsonl; their transcript lives at agents/main/wire.jsonl. Without a fallback, /list showed 0 messages for every modern session. Count user turns from wire.jsonl (and use the first user turn as the summary), matching legacy kimi-cli behavior. Addresses review feedback on chenhg5#1564.
CI lint failed on the PR merge ref: main added a messageID string param to
AgentSession.Send (core/interfaces.go), but the PR still called the old 3-arg
form in live_e2e_test.go, so the merged code failed to compile
('not enough arguments in call to s.Send').
origin/main was merged into kimi-code-flavor (no conflicts); kimi's Send now
carries the 4-arg signature matching main. This updates the remaining 3-arg
call site to the new signature. agent/kimi and core compile, vet and tests green.
golangci-lint flagged the deferred file Close calls in the new parseKimiTranscript/count* helpers as unchecked errors. Use an explicit blank-assignment close to satisfy errcheck. agent/kimi still compiles, vets and tests green.
…enhg5#1561) Ported from chenhg5#1586: an assistant event carrying plain-string content AND tool_calls must surface the text as a thinking event (via flushPendingAsThinking) before the tool-use event, not drop it silently.
Closed
24 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@whyihaveyou hi, 我是 chenhg5#1586 的作者。按 @chenhg5 在 chenhg5#1586 的建议(方案 2),我把你的分支 rebase 到了最新 main 并补了两块,开了个 PR 到你的分支:OctopusWen:cc‑connect:kimi‑1564‑rebase
谢谢!