REPOSITORY / ScuroNeko/Laniakea
Pull Requests
v1.0.0 #9
@@ -96,6 +96,9 @@ Prefer the repository’s documented commands. If multiple choices exist, use th
|
||||
- Changes made only in `AGENTS.md` must not be added to `CHANGELOG.md`.
|
||||
- Add changes only to the section for the next version after the latest published git tag.
|
||||
- The agent must check the latest published tag, `CHANGELOG.md`, and `utils/version.go` before editing the changelog.
|
||||
- Before editing `CHANGELOG.md`, the agent must inspect the full diff between the latest published tag and the current worktree, for example `git diff --name-status <latest-tag> -- .` and targeted `git diff <latest-tag> -- <files>`.
|
||||
- Changelog entries must be based on all user-visible changes present between the latest published tag and the current files, including earlier uncommitted or pre-existing worktree changes, not only changes made in the current turn.
|
||||
- The agent must not add changelog entries for changes that are not present in the diff from the latest published tag, and must remove or rewrite stale entries that no longer match that diff.
|
||||
- The agent must verify that the target changelog version matches the version declared in `utils/version.go`.
|
||||
- If the latest published tag is, for example, `v1.0.0`, and `CHANGELOG.md` does not yet contain the next version section, the agent must stop and ask the user which version the change belongs to:
|
||||
1. `v1.0.1`
|
||||
@@ -103,7 +106,7 @@ Prefer the repository’s documented commands. If multiple choices exist, use th
|
||||
3. `v2.0.0`
|
||||
- The agent must not guess the next version when that section is missing.
|
||||
- If the user-selected version does not match `utils/version.go`, the agent must warn about the mismatch and require the version file to be updated before proceeding.
|
||||
- Changelog entries must describe all user-visible behavior changes made in the turn, including API additions, fixes, behavior changes, and breaking changes.
|
||||
- Changelog entries must describe all user-visible behavior changes in the diff from the latest published tag, including API additions, fixes, behavior changes, and breaking changes.
|
||||
- When a framework backlog item recorded in `TODO.md` is completed, the agent must also update the backlog status using the existing format:
|
||||
1. move the completed item into the top of the `Done` section;
|
||||
2. replace the numbered backlog label with a version tag, for example `1. Scene Model` becomes `[v2.0.0] Scene Model`;
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
|
||||
## v1.0.0
|
||||
|
||||
### Added
|
||||
- Added `MsgContext.IsCallback()` and `MsgContext.HasPhoto()` helpers for callback-aware handler code.
|
||||
- Added `MsgContext.UpsertKeyboard(...)` and `MsgContext.UpsertKeyboardMarkdown(...)` helpers that edit callback messages, replace photo callback messages with a fresh chat message, and send a new chat message outside callback flow.
|
||||
|
||||
### Changed
|
||||
- Version metadata now reports the stable `v1.0.0` release instead of `v1.0.0-rc.16`.
|
||||
- Bot-level middleware blocks now emit a final `UpdateHandledEvent` with `Handled=false`, keeping observer update lifecycles balanced.
|
||||
- `BotOpts`, `tgapi.APIOpts`, and logger utility godoc now document `LOG_FORMAT`, `LogFormat`, and logger formatting options consistently.
|
||||
|
||||
### Tests
|
||||
- Added regression coverage proving bot-level middleware blocks still complete the observer update lifecycle.
|
||||
- Added webhook runtime regression coverage for request enqueue through worker execution of a command handler.
|
||||
- Added regression coverage for inline callback keyboard upserts and callback target detection.
|
||||
|
||||
## v1.0.0-rc.16
|
||||
|
||||
|
||||
+32
-4
@@ -756,10 +756,7 @@ func (ctx *MsgContext) EnterSceneStep(name, step string) error {
|
||||
return ErrCantFindSession
|
||||
}
|
||||
|
||||
session := SceneSession{
|
||||
Scene: scene.Name,
|
||||
Step: step,
|
||||
}
|
||||
session := SceneSession{Scene: scene.Name, Step: step}
|
||||
|
||||
return ctx.sceneRuntime.setSession(key, session)
|
||||
}
|
||||
@@ -790,3 +787,34 @@ func (ctx *MsgContext) ExitScene() error {
|
||||
|
||||
return ctx.sceneRuntime.deleteSession(key)
|
||||
}
|
||||
|
||||
// IsCallback reports whether the context belongs to a callback query.
|
||||
func (ctx *MsgContext) IsCallback() bool {
|
||||
return ctx.CallbackQueryID != "" || ctx.CallbackMsgID > 0 || ctx.InlineMsgID != ""
|
||||
}
|
||||
|
||||
// HasPhoto reports whether the current message contains a photo payload.
|
||||
func (ctx *MsgContext) HasPhoto() bool {
|
||||
return ctx.Msg != nil && ctx.Msg.Photo.Len() > 0
|
||||
}
|
||||
|
||||
func (ctx *MsgContext) upsertKeyboard(text string, keyboard *InlineKeyboard, parseMode tgapi.ParseMode) *AnswerMessage {
|
||||
if ctx.IsCallback() {
|
||||
if ctx.HasPhoto() {
|
||||
ctx.CallbackDelete()
|
||||
return ctx.answer(text, keyboard, parseMode)
|
||||
}
|
||||
return ctx.editCallback(text, keyboard, parseMode)
|
||||
}
|
||||
return ctx.answer(text, keyboard, parseMode)
|
||||
}
|
||||
|
||||
// UpsertKeyboard edits a callback message or sends a new plain-text message with a keyboard.
|
||||
func (ctx *MsgContext) UpsertKeyboard(text string, keyboard *InlineKeyboard) *AnswerMessage {
|
||||
return ctx.upsertKeyboard(text, keyboard, tgapi.ParseNone)
|
||||
}
|
||||
|
||||
// UpsertKeyboardMarkdown edits a callback message or sends a new MarkdownV2 message with a keyboard.
|
||||
func (ctx *MsgContext) UpsertKeyboardMarkdown(text string, keyboard *InlineKeyboard) *AnswerMessage {
|
||||
return ctx.upsertKeyboard(text, keyboard, tgapi.ParseMDV2)
|
||||
}
|
||||
|
||||
@@ -324,6 +324,90 @@ func TestErrorUserVisibleAnswersCallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCallbackIncludesInlineCallbackTargets(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx MsgContext
|
||||
want bool
|
||||
}{
|
||||
{name: "callback query id", ctx: MsgContext{CallbackQueryID: "cb-1"}, want: true},
|
||||
{name: "callback message id", ctx: MsgContext{CallbackMsgID: 12}, want: true},
|
||||
{name: "inline message id", ctx: MsgContext{InlineMsgID: "inline-1"}, want: true},
|
||||
{name: "not callback", ctx: MsgContext{}, want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.ctx.IsCallback(); got != tt.want {
|
||||
t.Fatalf("IsCallback() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsertKeyboardEditsInlineCallback(t *testing.T) {
|
||||
var requests int
|
||||
var gotPath string
|
||||
var gotBody map[string]any
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
requests++
|
||||
gotPath = req.URL.Path
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read request body: %v", err)
|
||||
}
|
||||
if err := json.Unmarshal(body, &gotBody); err != nil {
|
||||
t.Fatalf("failed to decode request body: %v", err)
|
||||
}
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
Body: io.NopCloser(strings.NewReader(`{"ok":true,"result":true}`)),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
api := tgapi.NewAPI(
|
||||
tgapi.NewAPIOpts("token").
|
||||
SetAPIURL("https://example.test").
|
||||
SetHTTPClient(client),
|
||||
)
|
||||
defer func() {
|
||||
if err := api.Close(); err != nil {
|
||||
t.Fatalf("Close returned error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx := &MsgContext{
|
||||
API: api,
|
||||
InlineMsgID: "inline-1",
|
||||
Logger: sneklog.NewLogger(),
|
||||
}
|
||||
kb := NewInlineKeyboardJSON(1).AddCallbackButton("A", "cmd")
|
||||
|
||||
answer := ctx.UpsertKeyboard("updated", kb)
|
||||
if answer == nil {
|
||||
t.Fatal("expected answer message")
|
||||
}
|
||||
if requests != 1 {
|
||||
t.Fatalf("expected one edit request, got %d", requests)
|
||||
}
|
||||
if gotPath != "/bottoken/editMessageText" {
|
||||
t.Fatalf("unexpected request path: %s", gotPath)
|
||||
}
|
||||
if got := gotBody["inline_message_id"]; got != "inline-1" {
|
||||
t.Fatalf("unexpected inline_message_id: %v", got)
|
||||
}
|
||||
if got := gotBody["text"]; got != "updated" {
|
||||
t.Fatalf("unexpected text: %v", got)
|
||||
}
|
||||
if _, ok := gotBody["reply_markup"]; !ok {
|
||||
t.Fatal("expected reply_markup in edit request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnswerRejectsEmptyMessage(t *testing.T) {
|
||||
ctx := &MsgContext{
|
||||
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate}},
|
||||
|
||||
Reference in New Issue
Block a user