* feat(platform/max): add MAX messenger bridge with bidirectional file and voice support - Implement core.Platform plus InlineButtonSender, ImageSender, FileSender, AudioSender, MessageUpdater, FormattingInstructionProvider, ReplyContextReconstructor - Two-step attachment upload via POST /uploads + multipart to CDN - Route incoming audio (type=audio and audio/* file mime) to core.Message.Audio so the speech pipeline transcribes it for the agent - Outgoing voice replies via SendAudio for the TTS pipeline - Retry on attachment.not.ready with exponential backoff - Long poll /updates with reconnect backoff and allow_from filtering - Register max in Makefile ALL_PLATFORMS and document in config.example.toml - Tighten .gitignore: ignore config.*.toml (except example) and *.old - 19 unit/integration tests with httptest mock, passes -race Refs #741 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(platform/max): handle real MAX CDN response shapes and audio download Issues discovered when testing end-to-end with the live MAX API: 1. Image CDN returns nested {"photos": {<id>: {"token": ...}}}, not a flat {"token": ...}. Parse the right shape per kind. 2. Audio CDN returns XML "<retval>1</retval>" rather than JSON. The real attachment token is delivered in the /uploads response itself for video/audio, so we use urlInfo.Token as the source of truth and only scan the CDN body opportunistically. 3. /audios/{token} endpoint does not exist in MAX (returns 404). Incoming voice payloads already carry the download URL directly, mirroring images/files. Use payload.url; fall back to resolveMediaURL only if empty (for video where token resolution may still apply). Also raise the speech HTTP client timeout from 60s to 5m — enough for the first-request cold-start case when a local Whisper backend has to download or JIT-load the model before responding. Tests updated to emit the real CDN response shapes per attachment type. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(platform/max): address review — dedup + idiomatic session key parse - Add core.MessageDedup to Platform and check msg.Body.Mid / callback ID in handleMessage and handleCallback. Guards against reprocessing on poll-loop reconnect edge cases, matching the pattern used by feishu/ dingtalk/wecom platforms. - Replace fmt.Sscanf in ReconstructReplyCtx with strings.CutPrefix + strings.Cut — more idiomatic Go, simpler to read, and handles the "max:" prefix and optional ":userID" suffix without a manual loop. - Add TestHandleMessageDedupsByID regression test. Addresses review feedback on #742. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
175 lines
5.3 KiB
Makefile
175 lines
5.3 KiB
Makefile
APP := cc-connect
|
|
MODULE := github.com/chenhg5/cc-connect
|
|
CMD := ./cmd/cc-connect
|
|
DIST := dist
|
|
|
|
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
|
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "none")
|
|
BUILD_TIME := $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
|
|
|
|
LDFLAGS := -s -w \
|
|
-X main.version=$(VERSION) \
|
|
-X main.commit=$(COMMIT) \
|
|
-X main.buildTime=$(BUILD_TIME)
|
|
|
|
PLATFORMS := \
|
|
linux/amd64 \
|
|
linux/arm64 \
|
|
darwin/amd64 \
|
|
darwin/arm64 \
|
|
windows/amd64 \
|
|
windows/arm64
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Selective compilation via build tags.
|
|
#
|
|
# By default all agents and platforms are included. To build with only
|
|
# specific ones, set AGENTS and/or PLATFORMS_INCLUDE:
|
|
#
|
|
# make build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram
|
|
#
|
|
# You can also exclude specific ones:
|
|
#
|
|
# make build EXCLUDE=discord,dingtalk,qq,qqbot,line
|
|
# ---------------------------------------------------------------------------
|
|
|
|
ALL_AGENTS := acp claudecode codex cursor devin gemini iflow kimi opencode pi qoder
|
|
ALL_PLATFORMS := feishu telegram discord slack dingtalk wecom weixin qq qqbot line weibo max
|
|
ALL_EXTRAS := web
|
|
|
|
COMMA := ,
|
|
|
|
# Compute exclusion tags from AGENTS / PLATFORMS_INCLUDE / EXCLUDE variables
|
|
_EXCLUDE_TAGS :=
|
|
|
|
ifdef AGENTS
|
|
_WANTED_AGENTS := $(subst $(COMMA), ,$(AGENTS))
|
|
_EXCLUDE_AGENTS := $(filter-out $(_WANTED_AGENTS),$(ALL_AGENTS))
|
|
_EXCLUDE_TAGS += $(addprefix no_,$(_EXCLUDE_AGENTS))
|
|
endif
|
|
|
|
ifdef PLATFORMS_INCLUDE
|
|
_WANTED_PLATFORMS := $(subst $(COMMA), ,$(PLATFORMS_INCLUDE))
|
|
_EXCLUDE_PLATFORMS := $(filter-out $(_WANTED_PLATFORMS),$(ALL_PLATFORMS))
|
|
_EXCLUDE_TAGS += $(addprefix no_,$(_EXCLUDE_PLATFORMS))
|
|
endif
|
|
|
|
ifdef EXCLUDE
|
|
_EXCLUDE_TAGS += $(addprefix no_,$(subst $(COMMA), ,$(EXCLUDE)))
|
|
endif
|
|
|
|
ifdef NO_WEB
|
|
_EXCLUDE_TAGS += no_web
|
|
endif
|
|
|
|
_BUILD_TAGS := $(strip $(_EXCLUDE_TAGS))
|
|
_TAGS_FLAG := $(if $(_BUILD_TAGS),-tags '$(_BUILD_TAGS)',)
|
|
|
|
.PHONY: build run clean test test-fast test-full test-smoke test-e2e test-release test-performance pre-test lint release release-all web
|
|
|
|
web:
|
|
@if [ ! -d web/node_modules ]; then cd web && npm install; fi
|
|
cd web && npm run build
|
|
|
|
build: web
|
|
go build $(_TAGS_FLAG) -ldflags "$(LDFLAGS)" -o $(APP) $(CMD)
|
|
|
|
build-noweb:
|
|
go build $(_TAGS_FLAG) -tags 'no_web' -ldflags "$(LDFLAGS)" -o $(APP) $(CMD)
|
|
|
|
run: build
|
|
./$(APP)
|
|
|
|
clean:
|
|
rm -f $(APP)
|
|
rm -rf $(DIST)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Testing targets.
|
|
#
|
|
# test-fast: Unit tests + smoke tests (< 2 min). Runs on every push.
|
|
# test-full: Full test suite including regression (< 10 min). PR requirement.
|
|
# test-smoke: Smoke tests only (< 1 min). Quick sanity check.
|
|
# test-e2e: E2E and regression tests only.
|
|
# test-release: Full + performance benchmarks. Before release.
|
|
# pre-test: Prerequisites (build + vet) before running tests.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
pre-test:
|
|
go build ./...
|
|
go vet ./...
|
|
|
|
# Fast test: unit tests + smoke tests
|
|
test-fast: pre-test
|
|
go test -parallel=4 -race ./...
|
|
go test -parallel=4 -tags=smoke ./tests/e2e/...
|
|
|
|
# Full test: unit + smoke + regression (PR requirement)
|
|
test-full: pre-test
|
|
go test -parallel=4 -race ./...
|
|
go test -parallel=4 -tags=smoke ./tests/e2e/...
|
|
go test -parallel=2 -tags=regression ./tests/e2e/...
|
|
|
|
# Smoke tests only
|
|
test-smoke: pre-test
|
|
go test -v -tags=smoke ./tests/e2e/...
|
|
|
|
# E2E/regression tests only
|
|
test-e2e: pre-test
|
|
go test -v -tags=regression ./tests/e2e/...
|
|
|
|
# Performance benchmarks only
|
|
test-performance: pre-test
|
|
go test -bench=. -benchmem -tags=performance ./tests/performance/...
|
|
|
|
# Release test: full + performance benchmarks
|
|
test-release: pre-test
|
|
go test -parallel=4 -race ./...
|
|
go test -parallel=4 -tags=smoke ./tests/e2e/...
|
|
go test -parallel=2 -tags=regression ./tests/e2e/...
|
|
go test -bench=. -benchmem -tags=performance ./tests/performance/...
|
|
|
|
# Legacy: runs unit tests only
|
|
test:
|
|
go test -v ./...
|
|
|
|
lint:
|
|
golangci-lint run ./...
|
|
|
|
release-all: clean
|
|
@mkdir -p $(DIST)
|
|
@$(foreach platform,$(PLATFORMS), \
|
|
$(eval GOOS := $(word 1,$(subst /, ,$(platform)))) \
|
|
$(eval GOARCH := $(word 2,$(subst /, ,$(platform)))) \
|
|
$(eval EXT := $(if $(filter windows,$(GOOS)),.exe,)) \
|
|
$(eval OUT := $(DIST)/$(APP)-$(VERSION)-$(GOOS)-$(GOARCH)$(EXT)) \
|
|
echo "Building $(OUT)" && \
|
|
GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 \
|
|
go build $(_TAGS_FLAG) -ldflags "$(LDFLAGS)" -o $(OUT) $(CMD) && \
|
|
) true
|
|
@echo "Packaging archives..."
|
|
@cd $(DIST) && for f in $(APP)-*; do \
|
|
case "$$f" in \
|
|
*.tar.gz|*.zip) continue ;; \
|
|
*.exe) zip "$${f%.exe}.zip" "$$f" ;; \
|
|
*) tar czf "$$f.tar.gz" "$$f" ;; \
|
|
esac; \
|
|
done
|
|
@cd $(DIST) && sha256sum * > checksums.txt
|
|
@echo "Done. Binaries and archives in $(DIST)/"
|
|
|
|
release:
|
|
@if [ -z "$(TARGET)" ]; then \
|
|
echo "Usage: make release TARGET=linux/amd64"; \
|
|
echo "Available: $(PLATFORMS)"; \
|
|
exit 1; \
|
|
fi
|
|
@mkdir -p $(DIST)
|
|
$(eval GOOS := $(word 1,$(subst /, ,$(TARGET))))
|
|
$(eval GOARCH := $(word 2,$(subst /, ,$(TARGET))))
|
|
$(eval EXT := $(if $(filter windows,$(GOOS)),.exe,))
|
|
$(eval OUT := $(DIST)/$(APP)-$(VERSION)-$(GOOS)-$(GOARCH)$(EXT))
|
|
GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=0 \
|
|
go build $(_TAGS_FLAG) -ldflags "$(LDFLAGS)" -o $(OUT) $(CMD)
|
|
@echo "Built: $(OUT)"
|