Skip to content

Kimi 1564 rebase - #1

Merged
whyihaveyou merged 28 commits into
whyihaveyou:kimi-code-flavorfrom
OctopusWen:kimi-1564-rebase
Aug 15, 2026
Merged

Kimi 1564 rebase#1
whyihaveyou merged 28 commits into
whyihaveyou:kimi-code-flavorfrom
OctopusWen:kimi-1564-rebase

Conversation

@OctopusWen

@OctopusWen OctopusWen commented Aug 15, 2026

Copy link
Copy Markdown

@whyihaveyou hi, 我是 chenhg5#1586 的作者。按 @chenhg5chenhg5#1586 的建议(方案 2),我把你的分支 rebase 到了最新 main 并补了两块,开了个 PR 到你的分支:OctopusWen:cc‑connect:kimi‑1564‑rebase

谢谢!

xxb and others added 27 commits August 13, 2026 16:42
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.
@whyihaveyou
whyihaveyou merged commit 80414d5 into whyihaveyou:kimi-code-flavor Aug 15, 2026
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.