mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 01:16:45 +08:00
* fix(opencode): retire future-dated resume sessions (MUL-8174) Co-authored-by: multica-agent <[email protected]> * fix(opencode): check resume clocks across CLI versions Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]>
163 lines
6.5 KiB
Go
163 lines
6.5 KiB
Go
//go:build !windows
|
|
|
|
package agent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCheckOpenCodeResumeClock(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name, body string
|
|
wantSkew, wantUnverified bool
|
|
}{
|
|
{"normal", `printf '%s' '{"info":{"id":"ses-test","time":{"updated":1}},"messages":[{"info":{"time":{"created":1}}}]}'`, false, false},
|
|
{"future", `printf '%s' '{"info":{"id":"ses-test","time":{"updated":9999999999999}},"messages":[]}'`, true, false},
|
|
{"failure with private stderr", `echo private-transcript >&2; exit 1`, false, true},
|
|
{"malformed", `echo private-transcript`, false, true},
|
|
{"oversized", `printf '{"ignored":"'; head -c 33554433 /dev/zero | LC_ALL=C tr '\000' x; printf '"}'`, false, true},
|
|
{"timeout", `sleep 30`, false, true},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cwd := t.TempDir()
|
|
bin := filepath.Join(t.TempDir(), "opencode")
|
|
// Assert storage/project selection without injecting task MCP servers
|
|
// or resolving an installed agent and its account.
|
|
body := `#!/bin/sh
|
|
[ "$1" = export ] && [ "$2" = ses-test ] && [ "$3" = --sanitize ] || exit 2
|
|
[ "$PWD" = "$CLOCK_EXPECTED_CWD" ] && [ "$(pwd)" = "$CLOCK_EXPECTED_CWD" ] || exit 3
|
|
[ "$XDG_DATA_HOME" = clock-test-storage ] || exit 4
|
|
[ -z "$OPENCODE_CONFIG_CONTENT" ] || exit 5
|
|
` + tc.body + "\n"
|
|
if err := os.WriteFile(bin, []byte(body), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg := Config{ExecutablePath: bin, BuiltinRuntime: true, CLIVersion: "1.18.30", Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), Env: map[string]string{"CLOCK_EXPECTED_CWD": cwd, "XDG_DATA_HOME": "clock-test-storage"}}
|
|
opts := ExecOptions{Cwd: cwd, ResumeSessionID: "ses-test", McpConfig: []byte(`{"mcpServers":{"clock-mcp":{"command":"unused"}}}`)}
|
|
ctx := context.Background()
|
|
if tc.name == "timeout" {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, 100*time.Millisecond)
|
|
defer cancel()
|
|
}
|
|
err := CheckOpenCodeResumeClock(ctx, cfg, opts)
|
|
if errors.Is(err, ErrOpenCodeClockSkew) != tc.wantSkew || (err != nil && !errors.Is(err, ErrOpenCodeClockSkew)) != tc.wantUnverified {
|
|
t.Fatalf("check = %v, want skew=%v unverified=%v", err, tc.wantSkew, tc.wantUnverified)
|
|
}
|
|
if err != nil && strings.Contains(err.Error(), "private-transcript") {
|
|
t.Fatal("export contents leaked")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckOpenCodeResumeClockUnsupportedContractsDoNotExecute(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
version string
|
|
builtin bool
|
|
args []string
|
|
}{
|
|
{"1.18.30", false, nil},
|
|
{"1.18.30", true, []string{"--server=https://unused.invalid"}},
|
|
{"1.18.30", true, []string{"--attach", "https://unused.invalid"}},
|
|
{"1.18.30", true, []string{"--session", "other-session"}},
|
|
{"1.18.30", true, []string{"-sother-session"}},
|
|
{"1.18.30", true, []string{"-csother-session"}},
|
|
} {
|
|
cfg := Config{ExecutablePath: filepath.Join(t.TempDir(), "does-not-exist"), BuiltinRuntime: tc.builtin, CLIVersion: tc.version}
|
|
err := CheckOpenCodeResumeClock(context.Background(), cfg, ExecOptions{ResumeSessionID: "ses-test", CustomArgs: tc.args})
|
|
if err == nil || errors.Is(err, ErrOpenCodeClockSkew) || strings.Contains(err.Error(), "executable not found") {
|
|
t.Fatalf("unsupported contract must be rejected before lookup: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCheckOpenCodeResumeClockAcrossVersions(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
version, command string
|
|
standalone bool
|
|
}{
|
|
{"1.1.54", "export", false}, {"1.18.30", "export", false}, {"1.18.31", "export", false}, {"1.99.0", "export", false},
|
|
{"opencode v2.0.10", "session", false}, {"opencode v2.1.0", "session", true}, {"", "export", false},
|
|
} {
|
|
t.Run(tc.version, func(t *testing.T) {
|
|
bin := filepath.Join(t.TempDir(), "opencode")
|
|
body := `#!/bin/sh
|
|
[ "$1" = "$CLOCK_EXPECTED_COMMAND" ] || exit 4
|
|
if [ "$1" = session ]; then
|
|
[ "$2" = export ] && [ "$3" = ses-test ] || exit 2
|
|
if [ "$CLOCK_STANDALONE" = true ]; then
|
|
[ "$4" = --standalone ] && [ "$5" = --sanitize ] || exit 5
|
|
else
|
|
[ "$4" = --sanitize ] || exit 6
|
|
fi
|
|
printf '%s' '{"info":{"id":"ses-test","time":{"updated":1}},"messages":[{"id":"msg-test","type":"user","time":{"created":9999999999999}}]}'
|
|
else
|
|
[ "$1" = export ] && [ "$2" = ses-test ] && [ "$3" = --sanitize ] || exit 3
|
|
printf '%s' '{"info":{"id":"ses-test","time":{"updated":1}},"messages":[{"info":{"time":{"created":9999999999999}}}]}'
|
|
fi
|
|
`
|
|
if err := os.WriteFile(bin, []byte(body), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg := Config{ExecutablePath: bin, BuiltinRuntime: true, CLIVersion: tc.version, Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), Env: map[string]string{"CLOCK_EXPECTED_COMMAND": tc.command, "CLOCK_STANDALONE": fmt.Sprint(tc.standalone)}}
|
|
opts := ExecOptions{ResumeSessionID: "ses-test"}
|
|
if tc.standalone {
|
|
opts.CustomArgs = []string{"--standalone"}
|
|
}
|
|
if err := CheckOpenCodeResumeClock(context.Background(), cfg, opts); !errors.Is(err, ErrOpenCodeClockSkew) {
|
|
t.Fatalf("version %q bypassed a future-dated session: %v", tc.version, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCheckOpenCodeResumeClockWithoutSanitizeFlag(t *testing.T) {
|
|
for _, unsupported := range []bool{true, false} {
|
|
t.Run(fmt.Sprint(unsupported), func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
bin, calls := filepath.Join(dir, "opencode"), filepath.Join(dir, "calls")
|
|
body := `#!/bin/sh
|
|
printf 'called\n' >> "$CLOCK_CALLS"
|
|
if [ -n "$3" ]; then
|
|
if [ "$CLOCK_UNSUPPORTED" = true ]; then
|
|
echo 'Unknown argument: sanitize' >&2
|
|
else
|
|
echo 'private export error' >&2
|
|
fi
|
|
exit 1
|
|
fi
|
|
printf '%s' '{"info":{"id":"ses-test","time":{"updated":1}},"messages":[{"info":{"time":{"created":9999999999999}},"parts":[{"text":"private transcript"}]}]}'
|
|
`
|
|
if err := os.WriteFile(bin, []byte(body), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg := Config{ExecutablePath: bin, BuiltinRuntime: true, CLIVersion: "1.1.54", Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), Env: map[string]string{"CLOCK_CALLS": calls, "CLOCK_UNSUPPORTED": fmt.Sprint(unsupported)}}
|
|
err := CheckOpenCodeResumeClock(context.Background(), cfg, ExecOptions{ResumeSessionID: "ses-test"})
|
|
if errors.Is(err, ErrOpenCodeClockSkew) != unsupported || err == nil || strings.Contains(err.Error(), "private") {
|
|
t.Fatalf("old CLI export check = %v", err)
|
|
}
|
|
raw, readErr := os.ReadFile(calls)
|
|
if readErr != nil {
|
|
t.Fatal(readErr)
|
|
}
|
|
want := 1
|
|
if unsupported {
|
|
want = 2
|
|
}
|
|
if strings.Count(string(raw), "called") != want {
|
|
t.Fatalf("export calls = %s, want %d", raw, want)
|
|
}
|
|
})
|
|
}
|
|
}
|