From 2f2d5497df53c7fab2a80be0791fa1cc9c874718 Mon Sep 17 00:00:00 2001 From: debidong <1953531014@qq.com> Date: Wed, 14 Oct 2026 18:23:27 +0800 Subject: [PATCH] feat: add IM automation execution destination contracts --- automations_models.go | 2 - automations_models_test.go | 42 ++++++++++++++++ models_gen.go | 31 ++++++++++-- openapi/openapi.en.json | 98 +++++++++++++++++++++++++++++++++----- openapi/openapi.zh.json | 98 +++++++++++++++++++++++++++++++++----- 5 files changed, 244 insertions(+), 27 deletions(-) diff --git a/automations_models.go b/automations_models.go index e7cda56..d47d082 100644 --- a/automations_models.go +++ b/automations_models.go @@ -8,8 +8,6 @@ type AutomationRuleUpdateRequest struct { RuleID string `json:"rule_id,omitempty" toon:"rule_id,omitempty"` // New rule name. Name *string `json:"name,omitempty" toon:"name,omitempty"` - // Only the current value is accepted; personal/team scope is immutable after creation. - TeamID *int64 `json:"team_id,omitempty" toon:"team_id,omitempty"` // Whether the rule is enabled. Enabled *bool `json:"enabled,omitempty" toon:"enabled,omitempty"` // Run cadence. Supports 4 fields (`hour day month weekday`, minute defaults to 0) and 5 fields (`minute hour day month weekday`). diff --git a/automations_models_test.go b/automations_models_test.go index 15f630d..e669928 100644 --- a/automations_models_test.go +++ b/automations_models_test.go @@ -2,6 +2,7 @@ package flashduty import ( "encoding/json" + "reflect" "strings" "testing" ) @@ -37,3 +38,44 @@ func TestAutomationRuleUpdateRequestTimezone(t *testing.T) { t.Fatalf("named timezone must be sent, got %s", named) } } + +func TestAutomationRuleCreateRequestExecutionDestination(t *testing.T) { + omitted, err := json.Marshal(&AutomationRuleCreateRequest{ + Name: "Daily SRE brief", + CronExpr: "0 9 * * *", + Prompt: "Summarize yesterday's incidents", + }) + if err != nil { + t.Fatal(err) + } + for _, field := range []string{"run_in", "team_id"} { + if strings.Contains(string(omitted), `"`+field+`"`) { + t.Fatalf("omitted %s must stay off the wire, got %s", field, omitted) + } + } + + explicit, err := json.Marshal(&AutomationRuleCreateRequest{ + Name: "Daily SRE brief", + CronExpr: "0 9 * * *", + Prompt: "Summarize yesterday's incidents", + RunIn: String("current-chat"), + TeamID: Int64(0), + }) + if err != nil { + t.Fatal(err) + } + for _, want := range []string{`"run_in":"current-chat"`, `"team_id":0`} { + if !strings.Contains(string(explicit), want) { + t.Fatalf("explicit destination must send %s, got %s", want, explicit) + } + } +} + +func TestAutomationRuleUpdateRequestHasNoExecutionTarget(t *testing.T) { + typ := reflect.TypeFor[AutomationRuleUpdateRequest]() + for _, field := range []string{"TeamID", "RunIn"} { + if _, ok := typ.FieldByName(field); ok { + t.Fatalf("update must not expose immutable execution target field %s", field) + } + } +} diff --git a/models_gen.go b/models_gen.go index 79653a7..928bc3e 100644 --- a/models_gen.go +++ b/models_gen.go @@ -1404,6 +1404,24 @@ type AuditSearchResponse struct { Total int64 `json:"total" toon:"total"` } +// AutomationExecutionLocation is generated from the Flashduty OpenAPI schema. +type AutomationExecutionLocation struct { + // Display name of the bound IM conversation when available. + ChatName string `json:"chat_name" toon:"chat_name"` + // Bound IM conversation mode when execution place is `im`. + ConversationMode string `json:"conversation_mode" toon:"conversation_mode"` + // Human-readable destination description. + Description string `json:"description" toon:"description"` + // Execution destination: `im` or `web_console`. + Place string `json:"place" toon:"place"` + // IM platform when execution place is `im`. + Platform string `json:"platform" toon:"platform"` + // Pinned execution scope: `person` or `team`. + RunScope string `json:"run_scope" toon:"run_scope"` + // Pinned scope team ID. 0 means personal scope. + TeamID int64 `json:"team_id" toon:"team_id"` +} + // AutomationRuleCreateRequest is generated from the Flashduty OpenAPI schema. type AutomationRuleCreateRequest struct { // Run cadence. Supports 4 fields (`hour day month weekday`, minute defaults to 0) and 5 fields (`minute hour day month weekday`). The minute must be one fixed integer; 6-field seconds are not supported. A cron that sets both day-of-month and day-of-week is rejected. The create API currently requires this field even for HTTP-POST-only rules; send a valid cron and set `schedule_trigger_enabled=false`. @@ -1426,10 +1444,12 @@ type AutomationRuleCreateRequest struct { OncallIncidentTriggerEnabled bool `json:"oncall_incident_trigger_enabled,omitempty" toon:"oncall_incident_trigger_enabled,omitempty"` // Task prompt sent to the AI SRE agent on each run. Prompt string `json:"prompt" toon:"prompt"` + // Execution destination. `current-chat` binds the rule to the authenticated live IM conversation; `web` runs in the web console. Omit it so the server selects `current-chat` for an authenticated IM invocation and `web` otherwise. The server rejects `current-chat` outside a live IM conversation. + RunIn *string `json:"run_in,omitempty" toon:"run_in,omitempty"` // Whether the schedule trigger is enabled. Defaults to true when omitted; HTTP-POST-only rules should send false. ScheduleTriggerEnabled *bool `json:"schedule_trigger_enabled,omitempty" toon:"schedule_trigger_enabled,omitempty"` - // Scope team ID. 0 or omitted means a personal rule; >0 means a team in the account. Can be reassigned later via update (converting a team rule to personal is owner-only; moving into a team requires the caller to belong to it). - TeamID int64 `json:"team_id,omitempty" toon:"team_id,omitempty"` + // Scope team ID. Explicit 0 means personal scope; >0 means a team in the account. Omit it to inherit the authenticated IM conversation scope when one is available, otherwise let the server select the normal default. A current-chat rule must match that conversation's scope. + TeamID *int64 `json:"team_id,omitempty" toon:"team_id,omitempty"` // IANA timezone `cron_expr` is evaluated in, e.g. `Asia/Shanghai`. Must be a timezone name loadable by the server; an invalid value is rejected. Defaults to the caller's member timezone, then the account timezone, then the server default (Asia/Shanghai) when omitted. Timezone string `json:"timezone,omitempty" toon:"timezone,omitempty"` } @@ -1455,7 +1475,10 @@ type AutomationRuleItem struct { // BYOC Runner ID. EnvironmentID string `json:"environment_id" toon:"environment_id"` // Runtime environment kind. Omit or send an empty value for automatic selection. One of: `cloud` (platform-hosted cloud sandbox), `byoc` (self-hosted BYOC runner in the account); an empty value means automatic selection (prefers an online BYOC runner, falls back to the cloud sandbox). - EnvironmentKind string `json:"environment_kind" toon:"environment_kind"` + EnvironmentKind string `json:"environment_kind" toon:"environment_kind"` + ExecutionLocation AutomationExecutionLocation `json:"execution_location" toon:"execution_location"` + // Configured execution destination for ordinary triggers. War-room triggers resolve their event war room at execution time. + ExecutionPlace string `json:"execution_place" toon:"execution_place"` // HTTP POST trigger token. Returned only on create or token rotation; save it immediately. HTTPPostToken string `json:"http_post_token" toon:"http_post_token"` // Whether the HTTP POST trigger is enabled. @@ -1535,6 +1558,8 @@ type AutomationRunItem struct { ErrorCode string `json:"error_code" toon:"error_code"` // Error message; empty when the run did not fail. ErrorMessage string `json:"error_message" toon:"error_message"` + // Actual execution destination: `im` or `web_console`. IM runs omit session details and deep links. + ExecutionPlace string `json:"execution_place" toon:"execution_place"` // Run kind; runs listed for a rule are always `automation_rule`. Kind string `json:"kind" toon:"kind"` // Idempotency key for this occurrence. diff --git a/openapi/openapi.en.json b/openapi/openapi.en.json index 1a797fb..b810d48 100644 --- a/openapi/openapi.en.json +++ b/openapi/openapi.en.json @@ -3280,6 +3280,18 @@ "minLength": 1, "type": "string" }, + "run_in": { + "description": "Execution destination. `current-chat` binds the rule to the authenticated live IM conversation; `web` runs in the web console. Omit it so the server selects `current-chat` for an authenticated IM invocation and `web` otherwise. The server rejects `current-chat` outside a live IM conversation.", + "enum": [ + "current-chat", + "web", + null + ], + "type": [ + "string", + "null" + ] + }, "schedule_trigger_enabled": { "description": "Whether the schedule trigger is enabled. Defaults to true when omitted; HTTP-POST-only rules should send false.", "type": [ @@ -3288,10 +3300,13 @@ ] }, "team_id": { - "description": "Scope team ID. 0 or omitted means a personal rule; >0 means a team in the account. Can be reassigned later via update (converting a team rule to personal is owner-only; moving into a team requires the caller to belong to it).", + "description": "Scope team ID. Explicit 0 means personal scope; >0 means a team in the account. Omit it to inherit the authenticated IM conversation scope when one is available, otherwise let the server select the normal default. A current-chat rule must match that conversation's scope.", "format": "int64", "minimum": 0, - "type": "integer" + "type": [ + "integer", + "null" + ] }, "timezone": { "description": "IANA timezone `cron_expr` is evaluated in, e.g. `Asia/Shanghai`. Must be a timezone name loadable by the server; an invalid value is rejected. Defaults to the caller's member timezone, then the account timezone, then the server default (Asia/Shanghai) when omitted.", @@ -3317,6 +3332,55 @@ ], "type": "object" }, + "AutomationExecutionLocation": { + "description": "Read-only Automation execution destination. It never includes a chat, integration, or session identifier.", + "properties": { + "chat_name": { + "description": "Display name of the bound IM conversation when available.", + "type": "string" + }, + "conversation_mode": { + "description": "Bound IM conversation mode when execution place is `im`.", + "type": "string" + }, + "description": { + "description": "Human-readable destination description.", + "type": "string" + }, + "place": { + "description": "Execution destination: `im` or `web_console`.", + "enum": [ + "im", + "web_console" + ], + "type": "string" + }, + "platform": { + "description": "IM platform when execution place is `im`.", + "type": "string" + }, + "run_scope": { + "description": "Pinned execution scope: `person` or `team`.", + "enum": [ + "person", + "team" + ], + "type": "string" + }, + "team_id": { + "description": "Pinned scope team ID. 0 means personal scope.", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "place", + "run_scope", + "team_id", + "description" + ], + "type": "object" + }, "AutomationRuleItem": { "description": "Automation rule.", "properties": { @@ -3355,6 +3419,17 @@ ], "type": "string" }, + "execution_location": { + "$ref": "#/components/schemas/AutomationExecutionLocation" + }, + "execution_place": { + "description": "Configured execution destination for ordinary triggers. War-room triggers resolve their event war room at execution time.", + "enum": [ + "im", + "web_console" + ], + "type": "string" + }, "http_post_token": { "description": "HTTP POST trigger token. Returned only on create or token rotation; save it immediately.", "type": "string" @@ -3466,6 +3541,7 @@ "prompt", "environment_kind", "environment_id", + "execution_place", "schedule_trigger_enabled", "http_post_trigger_enabled", "can_edit", @@ -3652,15 +3728,6 @@ "null" ] }, - "team_id": { - "description": "Reassign the rule's scope: 0 converts to a personal rule (only the rule owner may convert a team rule); >0 moves it into a team the caller belongs to. Omit to leave unchanged.", - "format": "int64", - "minimum": 0, - "type": [ - "integer", - "null" - ] - }, "timezone": { "description": "New IANA timezone for evaluating `cron_expr`. Omit or send null to leave the current timezone unchanged.", "type": [ @@ -3700,6 +3767,14 @@ "format": "int64", "type": "integer" }, + "execution_place": { + "description": "Actual execution destination: `im` or `web_console`. IM runs omit session details and deep links.", + "enum": [ + "im", + "web_console" + ], + "type": "string" + }, "error_code": { "description": "Error code; empty when the run did not fail.", "type": "string" @@ -3800,6 +3875,7 @@ "started_at", "completed_at", "duration_ms", + "execution_place", "created_at", "updated_at", "error_code", diff --git a/openapi/openapi.zh.json b/openapi/openapi.zh.json index 30821d0..809fdb7 100644 --- a/openapi/openapi.zh.json +++ b/openapi/openapi.zh.json @@ -3280,6 +3280,18 @@ "minLength": 1, "type": "string" }, + "run_in": { + "description": "执行位置。`current-chat` 会绑定到已认证的当前 IM 会话;`web` 在 Web 控制台执行。省略时,由服务端在已认证的 IM 调用中选择 `current-chat`,其他调用中选择 `web`。服务端会拒绝不在实时 IM 会话中的 `current-chat`。", + "enum": [ + "current-chat", + "web", + null + ], + "type": [ + "string", + "null" + ] + }, "schedule_trigger_enabled": { "description": "是否启用 schedule trigger。省略时为 true;HTTP-POST-only 规则应传 false。", "type": [ @@ -3288,10 +3300,13 @@ ] }, "team_id": { - "description": "作用域团队 ID。0 或省略表示个人规则;>0 表示账户下某团队。之后可通过 update 改派(团队规则转个人仅规则 owner 可操作;转入团队要求调用者属于该团队)。", + "description": "作用域团队 ID。明确传 0 表示个人作用域;>0 表示当前账户内的团队。省略时,若存在已认证的 IM 会话则继承其作用域,否则由服务端选择常规默认值。current-chat 规则必须与该会话作用域一致。", "format": "int64", "minimum": 0, - "type": "integer" + "type": [ + "integer", + "null" + ] }, "timezone": { "description": "`cron_expr` 计算所用的 IANA 时区,例如 `Asia/Shanghai`。必须是服务端可加载的合法时区名,非法值会被拒绝。省略时依次回退到调用者的成员时区、账户时区,最后是服务端默认时区(Asia/Shanghai)。", @@ -3317,6 +3332,55 @@ ], "type": "object" }, + "AutomationExecutionLocation": { + "description": "只读的自动化执行位置。不包含会话、集成或 session 标识符。", + "properties": { + "chat_name": { + "description": "可用时,已绑定 IM 会话的显示名称。", + "type": "string" + }, + "conversation_mode": { + "description": "执行位置为 `im` 时已绑定的 IM 会话模式。", + "type": "string" + }, + "description": { + "description": "供人阅读的目标位置描述。", + "type": "string" + }, + "place": { + "description": "执行位置:`im` 或 `web_console`。", + "enum": [ + "im", + "web_console" + ], + "type": "string" + }, + "platform": { + "description": "执行位置为 `im` 时的 IM 平台。", + "type": "string" + }, + "run_scope": { + "description": "固定执行作用域:`person` 或 `team`。", + "enum": [ + "person", + "team" + ], + "type": "string" + }, + "team_id": { + "description": "固定作用域团队 ID。0 表示个人作用域。", + "format": "int64", + "type": "integer" + } + }, + "required": [ + "place", + "run_scope", + "team_id", + "description" + ], + "type": "object" + }, "AutomationRuleItem": { "description": "自动化规则。", "properties": { @@ -3355,6 +3419,17 @@ ], "type": "string" }, + "execution_location": { + "$ref": "#/components/schemas/AutomationExecutionLocation" + }, + "execution_place": { + "description": "普通触发器配置的执行位置。战情室触发器会在执行时解析为对应事件的战情室。", + "enum": [ + "im", + "web_console" + ], + "type": "string" + }, "http_post_token": { "description": "HTTP POST trigger token。只在创建或轮换 token 的响应中返回;请立即保存。", "type": "string" @@ -3466,6 +3541,7 @@ "prompt", "environment_kind", "environment_id", + "execution_place", "schedule_trigger_enabled", "http_post_trigger_enabled", "can_edit", @@ -3652,15 +3728,6 @@ "null" ] }, - "team_id": { - "description": "改派规则作用域:0 表示转为个人规则(团队规则仅规则 owner 可转);>0 表示转入调用者所属的团队。省略则保持不变。", - "format": "int64", - "minimum": 0, - "type": [ - "integer", - "null" - ] - }, "timezone": { "description": "更新 `cron_expr` 所用的 IANA 时区。省略或传 null 表示保持当前时区不变。", "type": [ @@ -3700,6 +3767,14 @@ "format": "int64", "type": "integer" }, + "execution_place": { + "description": "实际执行位置:`im` 或 `web_console`。IM 运行不会返回 session 详情或深链。", + "enum": [ + "im", + "web_console" + ], + "type": "string" + }, "error_code": { "description": "错误码;运行未失败时为空字符串。", "type": "string" @@ -3800,6 +3875,7 @@ "started_at", "completed_at", "duration_ms", + "execution_place", "created_at", "updated_at", "error_code",