Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- **Feishu: tighter `@` mention detection in `SendWithStatusFooter` / `buildReplyContent`** — a bare `@` inside an email address, URL, or escaped character no longer false-positives as a mention. Mention detection now checks for the resolved `<at user_id="...">` tag instead of a substring match, so card rendering (and the notation-style status footer) is preserved for content that merely contains `@`. Real `@mentions` still force `MsgTypeText` so Feishu fires the mention event (#1322).
- **feishu**: coalesce consecutive image messages from the same session into a single multi-image dispatch to fix first-image drop on batch sends (#1395). When the Feishu mobile client sends N images in quick succession, each image arrives as a separate `image` event with very close `create_time` values. Dispatching each immediately caused core/engine's `create_time` watermark (PR #1168) to drop the oldest image, so the agent only saw N-1 images. A per-session image buffer with a 150ms quiet window now merges the burst into one `core.Message` carrying all images, in send order. Single-image sends and quoted-image replies are unaffected.
- **claudecode**: fix per-spawn system-prompt temp file EACCES under `run_as_user` (#1429). The per-spawn temp file written by `writeTempAppendPromptFile` (the 1% edge-case path used when the prompt has session-specific platform formatting or user `append_system_prompt`) inherited `os.CreateTemp`'s 0600 mode and was owned by the cc-connect process user (often root under systemd). When the agent was spawned under a different `run_as_user`, it could not read the file and exited before any prompt was loaded. The file is now `chmod 0o644` immediately after write, matching the shared `ensureSharedSystemPromptFile` path. Prompt content is non-secret (a superset of the already-shared base prompt), so 0644 is consistent with the shared file. Does not affect the shared-file path (already 0644 since #1376) or the daemon-mode path resolution (#1419).
- **kimi**: gate `--work-dir` flag on `kimiFlagSupport.WorkDir` so the Kimi Code CLI build that dropped the flag no longer exits with `error: unknown option --work-dir` (#1476). Same probe-based pattern as the `--print` fix in #1456 / PR #1461: the agent probes `kimi --help` once at construction, then `buildArgs` emits the flag only when the installed CLI advertises it. The agent still runs in the correct directory because `exec.Command.Dir` is set separately, so legacy kimi-cli users (who still have `--work-dir`) keep non-default workspace support while modern-CLI users get clean startup.
- **core**: queue post-restart notification and dispatch on platform ready (#1383). Previously `/restart` sent the success notification immediately after engine startup, racing the platform's async connect window (Telegram: ~2.6s). On a not-yet-ready platform the send was silently dropped at debug log level. The notify is now queued on the engine and dispatched when the target platform reaches `OnPlatformReady`, with bounded retry (3 attempts, 0/500/1500 ms backoff) on transient send failure. Failed sends log at warn level. A 10s safety timeout drops the notify with a warning if the target platform never reaches ready, so startup is never blocked indefinitely. Also covers Discord / Weixin / Matrix (other AsyncRecoverablePlatform implementations) for free.

## v1.3.3 (2026-06-15)
Expand Down
14 changes: 10 additions & 4 deletions agent/kimi/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ import (
//
// The newer Kimi Code CLI removed the standalone `--print` flag — passing it
// now produces: `error: unknown option '--print' (Did you mean --prompt?)`.
// The older kimi-cli still requires `--print` for `--output-format` to take
// effect. We probe the help text once and adapt accordingly. See #1456.
// The same CLI release also dropped `--work-dir` (the working directory is
// now taken from the process cwd only) and rejects the flag with `error:
// unknown option --work-dir`. The older kimi-cli still requires `--print`
// for `--output-format` to take effect and exposes `--work-dir` for
// non-default workspace locations. We probe the help text once and adapt
// accordingly. See #1456, #1476.
type kimiFlagSupport struct {
Print bool
Print bool
WorkDir bool
}

// probeKimiFlags runs `<cmd> --help` with a short timeout and returns the
Expand Down Expand Up @@ -49,7 +54,8 @@ func probeKimiFlags(parent context.Context, cmd string, timeout time.Duration) k

flags := parseKimiHelpFlags(out.String())
support := kimiFlagSupport{
Print: flags["--print"],
Print: flags["--print"],
WorkDir: flags["--work-dir"],
}
slog.Debug("kimi: flag probe complete", "cmd", cmd, "support", support)
return support
Expand Down
71 changes: 70 additions & 1 deletion agent/kimi/probe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ const legacyKimiHelp = `
`

// modernKimiHelp emulates the newer Kimi Code CLI, where --print has been
// removed entirely. This is the surface that triggers #1456 today.
// removed entirely. This is the surface that triggers #1456 today. As of the
// build tested in #1476, the CLI also still advertises --work-dir here (we
// keep this constant realistic so #1456 tests stay anchored to a known CLI
// surface); see modernKimiHelpWithoutWorkDir below for the deeper-no-work-dir
// surface that triggers #1476.
const modernKimiHelp = `
Usage: kimi [OPTIONS] COMMAND [ARGS]...

Expand All @@ -51,9 +55,30 @@ const modernKimiHelp = `
--plan Start a new session in Plan mode.
`

// modernKimiHelpWithoutWorkDir models the Kimi Code CLI build the #1476
// reporter hits: --print is gone, --work-dir is gone too. The CLI now takes
// the working directory from the process cwd, so passing --work-dir must be
// avoided entirely (it triggers `error: unknown option --work-dir`).
const modernKimiHelpWithoutWorkDir = `
Usage: kimi [OPTIONS] COMMAND [ARGS]...

Kimi, your next CLI agent.

Options:
-V, --version Show version and exit.
-S, --session [ID] Resume a session.
-c, --continue Continue the most recent session.
-m, --model TEXT LLM model to use.
-p, --prompt TEXT Run a single prompt non-interactively.
--output-format FORMAT Non-interactive output format.
-y, --yolo Auto-approve regular tool calls.
--plan Start a new session in Plan mode.
`

func TestParseKimiHelpFlags_LegacyAdvertisesPrint(t *testing.T) {
flags := parseKimiHelpFlags(legacyKimiHelp)
assert.True(t, flags["--print"], "legacy help text advertises --print")
assert.True(t, flags["--work-dir"], "legacy help text advertises --work-dir (#1476)")
assert.True(t, flags["--prompt"], "legacy help text advertises --prompt")
assert.True(t, flags["--output-format"])
assert.True(t, flags["--plan"])
Expand All @@ -66,10 +91,26 @@ func TestParseKimiHelpFlags_ModernHidesPrint(t *testing.T) {
// Regression for #1456: the new Kimi Code CLI must not be detected
// as supporting --print.
assert.False(t, flags["--print"], "modern help text must not advertise --print")
assert.True(t, flags["--work-dir"], "this modern CLI build still advertises --work-dir")
assert.True(t, flags["--prompt"], "modern CLI still advertises --prompt")
assert.True(t, flags["--output-format"])
}

// TestParseKimiHelpFlags_ModernWithoutWorkDir is the regression test for
// #1476: when the installed Kimi Code CLI build no longer advertises
// --work-dir, parseKimiHelpFlags must NOT detect it, so the probe can drive
// buildArgs to omit --work-dir entirely (the CLI then takes the cwd from
// exec.Command.Dir, which the Go process already sets correctly).
func TestParseKimiHelpFlags_ModernWithoutWorkDir(t *testing.T) {
flags := parseKimiHelpFlags(modernKimiHelpWithoutWorkDir)
assert.False(t, flags["--work-dir"],
"modern CLI without --work-dir must NOT advertise it (#1476)")
assert.False(t, flags["--print"],
"modern CLI still without --print (#1456 compatibility)")
assert.True(t, flags["--prompt"], "still advertises --prompt")
assert.True(t, flags["--output-format"])
}

func TestParseKimiHelpFlags_IgnoresPositionalAndShortOnly(t *testing.T) {
help := `
Arguments:
Expand All @@ -94,3 +135,31 @@ func TestProbeKimiFlags_FallbackOnMissingBinary(t *testing.T) {
200*time.Millisecond)
assert.Equal(t, kimiFlagSupport{}, got)
}

// TestKimiFlagSupport_LegacyHelpSetsWorkDir verifies the full probe-mapping
// for the legacy help surface: both Print and WorkDir must come back true so
// existing kimi-cli users keep receiving --work-dir.
func TestKimiFlagSupport_LegacyHelpSetsWorkDir(t *testing.T) {
flags := parseKimiHelpFlags(legacyKimiHelp)
assert.True(t, flags["--work-dir"], "legacy help advertises --work-dir")
support := kimiFlagSupport{
Print: flags["--print"],
WorkDir: flags["--work-dir"],
}
assert.True(t, support.Print)
assert.True(t, support.WorkDir, "modern probe must surface work-dir support")
}

// TestKimiFlagSupport_ModernWithoutWorkDir verifies the full probe-mapping
// for the #1476 surface: WorkDir must come back false so buildArgs can drop
// the flag.
func TestKimiFlagSupport_ModernWithoutWorkDir(t *testing.T) {
flags := parseKimiHelpFlags(modernKimiHelpWithoutWorkDir)
support := kimiFlagSupport{
Print: flags["--print"],
WorkDir: flags["--work-dir"],
}
assert.False(t, support.Print)
assert.False(t, support.WorkDir,
"probe must NOT surface work-dir support on CLI builds that dropped it (#1476)")
}
2 changes: 1 addition & 1 deletion agent/kimi/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (ks *kimiSession) buildArgs(prompt string) []string {
if ks.model != "" {
args = append(args, "--model", ks.model)
}
if ks.workDir != "" {
if ks.workDir != "" && ks.flagSupport.WorkDir {
args = append(args, "--work-dir", ks.workDir)
}

Expand Down
42 changes: 42 additions & 0 deletions agent/kimi/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,48 @@ func TestBuildArgs_PrintSupportIncludesPrintFlag(t *testing.T) {
assert.Contains(t, args, "stream-json")
}

// TestBuildArgs_WorkDirFlagGated is the regression test for #1476. When the
// locally installed Kimi Code CLI does not advertise --work-dir in its help
// output, cc-connect must omit that flag — otherwise the CLI exits with
// `error: unknown option --work-dir`. The agent still runs in the correct
// directory because exec.Command.Dir is set separately (see session.go).
func TestBuildArgs_WorkDirFlagGated(t *testing.T) {
ctx := context.Background()
ks, err := newKimiSession(ctx, "kimi", nil, "/", "", "default", "", nil, 0, kimiFlagSupport{WorkDir: false})
require.NoError(t, err)
defer func() { _ = ks.Close() }()

args := ks.buildArgs("hello")

for _, a := range args {
if a == "--work-dir" {
t.Fatalf("buildArgs unexpectedly emitted --work-dir when flagSupport.WorkDir=false; args=%v", args)
}
}
// The work-dir value must also NOT leak into args even though we asked
// for one. (Confirms the gate is at the flag level, not just dropping
// the flag-name while keeping the value.)
for _, a := range args {
if a == "/" {
t.Fatalf("buildArgs leaked workDir value %q into args; args=%v", a, args)
}
}
}

// TestBuildArgs_WorkDirFlagEmitted covers the legacy kimi-cli branch — the
// binary advertises --work-dir, so we must keep emitting it for non-default
// workspace locations.
func TestBuildArgs_WorkDirFlagEmitted(t *testing.T) {
ctx := context.Background()
ks, err := newKimiSession(ctx, "kimi", nil, "/", "", "default", "", nil, 0, kimiFlagSupport{WorkDir: true})
require.NoError(t, err)
defer func() { _ = ks.Close() }()

args := ks.buildArgs("hello")
assert.Contains(t, args, "--work-dir")
assert.Contains(t, args, "/")
}

// TestBuildArgs_PlanMode confirms plan mode still passes --plan independent
// of --print support.
func TestBuildArgs_PlanMode(t *testing.T) {
Expand Down
Loading