diff --git a/.github/workflows/upload.yml b/.github/workflows/upload.yml index af4f4639..02c34ec1 100644 --- a/.github/workflows/upload.yml +++ b/.github/workflows/upload.yml @@ -1,11 +1,14 @@ # Upload documentation to Meilisearch for AI Q&A bot indexing. # +# Every run syncs the whole doc set: all docs are upserted and index documents +# whose source file is gone are deleted. +# # Uses GitHub Environments for dev/prod separation. Create two environments in # Settings > Environments: "development" and "production". # # Per-environment secrets (all required, keep as secrets to hide Meilisearch endpoint): # MEILI_ENDPOINT - Meilisearch instance URL -# MEILI_API_KEY - API key with documents write permission +# MEILI_API_KEY - API key with documents read/write permission # MEILI_INDEX - Target index name # # Branch mapping: main -> production, test -> development @@ -29,22 +32,19 @@ on: options: - production - development - full_upload: - description: 'Re-upload all docs (use after clearing index or changing settings)' - required: false - default: false - type: boolean jobs: upload: runs-on: ubuntu-latest environment: ${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'production' || 'development') }} + # One sync per index at a time: an older run listing the index while a + # newer one uploads would delete the newer run's added docs. + concurrency: + group: meilisearch-upload-${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'production' || 'development') }} + cancel-in-progress: false steps: - name: Checkout uses: actions/checkout@v4 - with: - # Need history for git diff in incremental mode - fetch-depth: 2 - name: Install dependencies run: | @@ -57,7 +57,6 @@ jobs: MEILI_ENDPOINT: ${{ secrets.MEILI_ENDPOINT }} MEILI_API_KEY: ${{ secrets.MEILI_API_KEY }} MEILI_INDEX: ${{ secrets.MEILI_INDEX }} - FULL_UPLOAD: ${{ github.event.inputs.full_upload || 'false' }} run: | chmod +x ./scripts/upload.sh bash ./scripts/upload.sh diff --git a/api-reference/monitors.openapi.en.json b/api-reference/monitors.openapi.en.json index 145d7dab..bb59ed9f 100644 --- a/api-reference/monitors.openapi.en.json +++ b/api-reference/monitors.openapi.en.json @@ -5923,6 +5923,7 @@ "items": { "$ref": "#/components/schemas/InvestigationTarget" }, + "maxItems": 20, "description": "Drill-down entries linked from the alert event detail page; at most 20 items, duplicates rejected. On update the field is presence-based: omit it to keep the current value, pass `[]` to clear.", "x-flashduty-preserve-absence": true }, @@ -6223,14 +6224,23 @@ }, "InvestigationTarget": { "type": "object", - "description": "Drill-down entry linked to alert events. A deliberately closed tagged union: new kinds require explicit server support.", + "description": "Alert-event drill-down entry. A deliberately closed tagged union: new kinds require explicit server support, and unknown fields inside a target are rejected.", "properties": { "kind": { "type": "string", "enum": [ - "dashboard" + "dashboard", + "query" ], - "description": "Entry type; currently only `dashboard` is supported." + "description": "Entry kind: `dashboard` opens a dashboard panel, `query` opens an Explore query. It decides whether `dashboard` or `query` must be supplied; supplying the other one is rejected." + }, + "time_range": { + "$ref": "#/components/schemas/InvestigationTimeRange", + "description": "Window around the event time, required on every saved entry. A zero-length window is rejected; defaults belong to the editor." + }, + "query": { + "$ref": "#/components/schemas/QueryInvestigationTarget", + "description": "Configuration for the `query` kind; required when `kind` is `query`, and rejected when `kind` is `dashboard`." }, "dashboard": { "$ref": "#/components/schemas/DashboardInvestigationTarget", @@ -6238,7 +6248,8 @@ } }, "required": [ - "kind" + "kind", + "time_range" ] }, "DashboardInvestigationTarget": { @@ -6253,37 +6264,103 @@ "type": "string", "description": "Panel ID inside the dashboard; must be a canonical UUIDv7. Optional." }, - "variable_bindings": { + "variables": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/InvestigationVariableBinding" + "type": "string" }, - "description": "Dashboard variable bindings, keyed by dashboard variable name." + "description": "Dashboard variable values, keyed by variable name. Values may reference event labels through `{{ }}` templates; defaults to an empty object." + } + }, + "required": [ + "dashboard_id", + "variables" + ] + }, + "InvestigationTimeRange": { + "type": "object", + "description": "Window around the alert event time, expressed as two offsets. Both directions must be non-negative and at least one must be greater than zero, so an entry never resolves to an empty window.", + "properties": { + "before_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 9007199254740, + "description": "Seconds to look back from the event time." + }, + "after_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 9007199254740, + "description": "Seconds to look forward from the event time, so behaviour after the event stays visible." } }, "required": [ - "dashboard_id" + "before_seconds", + "after_seconds" ] }, - "InvestigationVariableBinding": { + "QueryInvestigationTarget": { "type": "object", - "description": "Binding between a dashboard variable and alert event data.", + "description": "Explore query used by a `query` drill-down entry. The expression may reference event labels through `{{ }}` templates, while `args` values may not.", "properties": { - "source": { + "datasource_id": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Data source the query runs against." + }, + "query": { + "$ref": "#/components/schemas/DashboardQuery", + "description": "Query payload." + } + }, + "required": [ + "datasource_id", + "query" + ] + }, + "DashboardQuery": { + "type": "object", + "description": "Query payload shared by dashboards and drill-down entries.", + "properties": { + "mode": { "type": "string", "enum": [ - "event_label" + "instant", + "range", + "window" ], - "description": "Where the bound value comes from; currently only `event_label` (the alert event's label value) is supported." + "description": "Evaluation mode: `instant` evaluates at a single timestamp, `range` evaluates a stepped series, `window` returns raw rows inside a time window." }, - "key": { + "expr": { "type": "string", - "description": "Alert event label name; must follow Prometheus label naming rules and must not be a reserved label." + "description": "Query expression in the target data source's language. May reference event labels through `{{ }}` templates." + }, + "args": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Named query arguments; defaults to an empty object. Values are passed through verbatim and must not contain `{{ }}` templates." + }, + "min_step_seconds": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1, + "maximum": 9007199254740, + "description": "Minimum step, in seconds. Only accepted when `mode` is `range`, and must be greater than zero; omit or pass null to let the server decide." } }, "required": [ - "source", - "key" + "mode", + "expr", + "args" ] } } diff --git a/api-reference/monitors.openapi.zh.json b/api-reference/monitors.openapi.zh.json index 4cea844d..d5aa2d4d 100644 --- a/api-reference/monitors.openapi.zh.json +++ b/api-reference/monitors.openapi.zh.json @@ -5923,6 +5923,7 @@ "items": { "$ref": "#/components/schemas/InvestigationTarget" }, + "maxItems": 20, "description": "告警事件详情页关联的排障入口列表,最多 20 项,不允许重复。更新接口中该字段按 presence 处理:省略时保留原配置,传 `[]` 清空。", "x-flashduty-preserve-absence": true }, @@ -6223,14 +6224,23 @@ }, "InvestigationTarget": { "type": "object", - "description": "告警事件的关联排障入口,为封闭的 tagged union:新增类型需服务端显式支持。", + "description": "告警事件的关联排障入口,为封闭的 tagged union:新增类型需服务端显式支持,入口内出现未知字段会被拒绝。", "properties": { "kind": { "type": "string", "enum": [ - "dashboard" + "dashboard", + "query" ], - "description": "排障入口类型,目前仅支持 `dashboard`。" + "description": "入口类型:`dashboard` 打开仪表盘面板,`query` 打开 Explore 查询。它决定必须提供 `dashboard` 还是 `query`,提供另一个会被拒绝。" + }, + "time_range": { + "$ref": "#/components/schemas/InvestigationTimeRange", + "description": "事件时间前后的取数窗口,每个已保存的入口都必填。长度为 0 的窗口会被拒绝;默认值由前端编辑器提供。" + }, + "query": { + "$ref": "#/components/schemas/QueryInvestigationTarget", + "description": "`query` 类型的入口配置;`kind` 为 `query` 时必填,`kind` 为 `dashboard` 时不允许出现。" }, "dashboard": { "$ref": "#/components/schemas/DashboardInvestigationTarget", @@ -6238,7 +6248,8 @@ } }, "required": [ - "kind" + "kind", + "time_range" ] }, "DashboardInvestigationTarget": { @@ -6253,37 +6264,103 @@ "type": "string", "description": "仪表盘内目标面板 ID,须为规范的 UUIDv7;可选。" }, - "variable_bindings": { + "variables": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/InvestigationVariableBinding" + "type": "string" }, - "description": "仪表盘变量绑定,键为仪表盘变量名。" + "description": "仪表盘变量取值,键为变量名。值可通过 `{{ }}` 模板引用事件标签;默认空对象。" + } + }, + "required": [ + "dashboard_id", + "variables" + ] + }, + "InvestigationTimeRange": { + "type": "object", + "description": "以两个偏移量表示告警事件时间前后的取数窗口。两个方向都必须非负,且至少一个大于 0,因此入口不会解析出空窗口。", + "properties": { + "before_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 9007199254740, + "description": "相对事件时间向前回溯的秒数。" + }, + "after_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 9007199254740, + "description": "相对事件时间向后延伸的秒数,用于保留事件发生后的表现。" } }, "required": [ - "dashboard_id" + "before_seconds", + "after_seconds" ] }, - "InvestigationVariableBinding": { + "QueryInvestigationTarget": { "type": "object", - "description": "仪表盘变量与告警事件数据的绑定。", + "description": "`query` 类型入口使用的 Explore 查询。表达式可通过 `{{ }}` 模板引用事件标签,`args` 的值不允许包含模板。", "properties": { - "source": { + "datasource_id": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 9007199254740991, + "description": "查询所用的数据源。" + }, + "query": { + "$ref": "#/components/schemas/DashboardQuery", + "description": "查询内容。" + } + }, + "required": [ + "datasource_id", + "query" + ] + }, + "DashboardQuery": { + "type": "object", + "description": "仪表盘与排障入口共用的查询内容。", + "properties": { + "mode": { "type": "string", "enum": [ - "event_label" + "instant", + "range", + "window" ], - "description": "绑定值来源,目前仅支持 `event_label`(取告警事件的标签值)。" + "description": "求值模式:`instant` 在单个时间点求值,`range` 求值一条按步长采样的曲线,`window` 返回时间窗口内的原始数据。" }, - "key": { + "expr": { "type": "string", - "description": "告警事件标签名,须符合 Prometheus 标签命名规则,且不能使用保留标签。" + "description": "目标数据源语法下的查询表达式,可通过 `{{ }}` 模板引用事件标签。" + }, + "args": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "命名查询参数,默认空对象。值按原样透传,不允许包含 `{{ }}` 模板。" + }, + "min_step_seconds": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1, + "maximum": 9007199254740, + "description": "最小步长(秒)。仅在 `mode` 为 `range` 时可用,且必须大于 0;省略或传 null 由服务端决定。" } }, "required": [ - "source", - "key" + "mode", + "expr", + "args" ] } } diff --git a/api-reference/on-call.openapi.en.json b/api-reference/on-call.openapi.en.json index 252afe6a..a76fc6d4 100644 --- a/api-reference/on-call.openapi.en.json +++ b/api-reference/on-call.openapi.en.json @@ -22750,6 +22750,14 @@ "type": "integer", "format": "int64", "description": "Soft-delete time, Unix epoch milliseconds. Omitted when not deleted." + }, + "via": { + "type": "string", + "description": "Surface that wrote the entry on a user's behalf; currently only `ai_sre`. Omitted when a user created the entry directly." + }, + "agent_session_id": { + "type": "string", + "description": "AI SRE session that produced the entry. Omitted when no agent wrote it." } } }, @@ -28552,6 +28560,20 @@ "type": "string", "description": "Zoom bot message template source." }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "IM apps whose closed-incident cards keep the custom action buttons. Supported values: `feishu_app`, `dingtalk_app`, `wecom_app`, `slack_app`, `teams_app`. An empty list hides the buttons on every app." + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "Incident card fields hidden per IM app type." @@ -28626,6 +28648,7 @@ "updated_by", "created_at", "updated_at", + "incident_card_closed_action_apps", "incident_card_hidden_fields" ], "properties": { @@ -28725,6 +28748,20 @@ "type": "string", "description": "Zoom bot message template source." }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "IM apps whose closed-incident cards keep the custom action buttons. Supported values: `feishu_app`, `dingtalk_app`, `wecom_app`, `slack_app`, `teams_app`. An empty list hides the buttons on every app." + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "Incident card fields hidden per IM app type; an empty object when none are configured." @@ -29015,6 +29052,20 @@ ], "description": "Zoom bot message template source. Omit to keep the current content; send an empty string to clear it." }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "Replaces the retained-app list when sent. Supported values: `feishu_app`, `dingtalk_app`, `wecom_app`, `slack_app`, `teams_app`. Omit the field to leave it unchanged." + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "Incident card fields hidden per IM app type." @@ -29691,7 +29742,8 @@ "incident_id", "status", "created_at", - "plugin_type" + "plugin_type", + "chat_name" ], "properties": { "account_id": { @@ -29734,6 +29786,14 @@ "plugin_type": { "type": "string", "description": "IM plugin type (e.g. `feishu`, `dingtalk`, `wecom`, `slack`)." + }, + "chat_name": { + "type": "string", + "description": "Display name of the group chat on the IM side." + }, + "integration_unavailable": { + "type": "boolean", + "description": "True when the IM integration behind this war room is disabled or no longer exists." } } }, diff --git a/api-reference/on-call.openapi.zh.json b/api-reference/on-call.openapi.zh.json index c203decf..3d49ef29 100644 --- a/api-reference/on-call.openapi.zh.json +++ b/api-reference/on-call.openapi.zh.json @@ -22750,6 +22750,14 @@ "type": "integer", "format": "int64", "description": "软删除时间,Unix 时间戳(毫秒)。未删除时不返回该字段。" + }, + "via": { + "type": "string", + "description": "由 AI SRE 代为写入该记录时的来源标识,目前只有 `ai_sre`;用户直接操作创建的记录不返回该字段。" + }, + "agent_session_id": { + "type": "string", + "description": "写入该记录的 AI SRE 会话 ID;非智能体写入时不返回该字段。" } } }, @@ -28552,6 +28560,20 @@ "type": "string", "description": "Zoom 机器人消息模板源。" }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "已关闭故障的卡片仍保留自定义操作按钮的 IM 应用。可选值:`feishu_app`、`dingtalk_app`、`wecom_app`、`slack_app`、`teams_app`;空列表表示所有应用都隐藏这些按钮。" + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "按 IM 应用类型隐藏的故障卡片字段。" @@ -28626,6 +28648,7 @@ "updated_by", "created_at", "updated_at", + "incident_card_closed_action_apps", "incident_card_hidden_fields" ], "properties": { @@ -28725,6 +28748,20 @@ "type": "string", "description": "Zoom 机器人消息模板源。" }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "已关闭故障的卡片仍保留自定义操作按钮的 IM 应用。可选值:`feishu_app`、`dingtalk_app`、`wecom_app`、`slack_app`、`teams_app`;空列表表示所有应用都隐藏这些按钮。" + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "按 IM 应用类型配置的故障卡片隐藏字段,未配置时为空对象。" @@ -29015,6 +29052,20 @@ ], "description": "Zoom 机器人消息模板源。省略时保持当前内容;传空字符串表示清空。" }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "传入时整体替换保留自定义操作按钮的应用列表。可选值:`feishu_app`、`dingtalk_app`、`wecom_app`、`slack_app`、`teams_app`;不传该字段则保持不变。" + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "按 IM 应用类型隐藏的故障卡片字段。" @@ -29691,7 +29742,8 @@ "incident_id", "status", "created_at", - "plugin_type" + "plugin_type", + "chat_name" ], "properties": { "account_id": { @@ -29734,6 +29786,14 @@ "plugin_type": { "type": "string", "description": "IM 插件类型(如 `feishu`、`dingtalk`、`wecom`、`slack`)。" + }, + "chat_name": { + "type": "string", + "description": "IM 侧群聊的显示名称。" + }, + "integration_unavailable": { + "type": "boolean", + "description": "该作战室背后的 IM 集成已禁用或已不存在时为 true。" } } }, diff --git a/api-reference/openapi.en.json b/api-reference/openapi.en.json index 6f739801..50243ea7 100644 --- a/api-reference/openapi.en.json +++ b/api-reference/openapi.en.json @@ -3494,7 +3494,8 @@ }, "limit": { "default": 20, - "description": "Page size.", + "description": "Page size. Values below 1 fall back to 20; values above 200 are capped at 200.", + "maximum": 200, "type": "integer" }, "p": { @@ -7575,6 +7576,46 @@ } } }, + "PendingUserMessage": { + "type": "object", + "description": "A queued user message that the agent has not consumed yet, as carried by `pending_messages`.", + "required": [ + "invocation_id", + "person_id", + "query" + ], + "properties": { + "invocation_id": { + "type": "string", + "description": "Invocation ID of the queued message." + }, + "client_msg_id": { + "type": "string", + "description": "Client-supplied message ID, echoed back for de-duplication. Omitted when absent." + }, + "person_id": { + "type": "integer", + "format": "int64", + "description": "Person ID of the sender." + }, + "steering": { + "type": "boolean", + "description": "True when the message was sent as a mid-turn steering instruction. Omitted when false." + }, + "query": { + "type": "string", + "description": "Message text." + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Request parts passed through verbatim (text, file, ref, or skill entries). Omitted when the original message carried none." + } + } + }, "PrometheusQueryParams": { "type": "object", "additionalProperties": true, @@ -10511,6 +10552,14 @@ "description": "Last update timestamp in Unix epoch milliseconds.", "format": "int64", "type": "integer" + }, + "via": { + "type": "string", + "description": "Surface that wrote the entry on a user's behalf; currently only `ai_sre`. Omitted when a user created the entry directly." + }, + "agent_session_id": { + "type": "string", + "description": "AI SRE session that produced the entry. Omitted when no agent wrote it." } }, "required": [ @@ -19376,6 +19425,14 @@ "$ref": "#/components/schemas/RumApplicationLinks", "description": "Optional external-link integration configuration." }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "Source code repositories to link, in order; the first entry is the primary repository. At most 10 entries." + }, "no_geo": { "description": "Do not infer geographic location.", "type": "boolean" @@ -19522,6 +19579,14 @@ "$ref": "#/components/schemas/RumApplicationLinks", "description": "External-link integration configuration." }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "Linked source code repositories, in order; the first entry is the primary repository. Linking grants no access by itself: AI sessions can only read repositories granted to the account's GitHub App installations." + }, "no_geo": { "description": "If `true`, geographic location is not inferred from IP.", "type": "boolean" @@ -19778,6 +19843,15 @@ "description": "External-link integration configuration. Omit to leave unchanged.", "x-flashduty-preserve-absence": true }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "Replaces the linked source code repositories when present; an empty array removes all links, and omitting the field leaves them unchanged. At most 10 entries.", + "x-flashduty-preserve-absence": true + }, "no_geo": { "description": "When `true`, stop inferring geographic location from IP; when `false`, resume inferring it. Omit to leave unchanged.", "type": [ @@ -23651,13 +23725,21 @@ "suggest_init": { "description": "Account-wide onboarding flag: true when the account has zero knowledge packs in any scope; not specific to this session.", "type": "boolean" + }, + "pending_messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PendingUserMessage" + }, + "description": "Human messages queued but not yet picked up by the agent, in execution order. Always an array — empty when nothing is queued." } }, "required": [ "session", "events", "has_more_older", - "suggest_init" + "suggest_init", + "pending_messages" ], "type": "object" }, @@ -23768,6 +23850,10 @@ "description": "True when an agent turn is currently in flight for this session.", "type": "boolean" }, + "standing_tasks": { + "type": "integer", + "description": "Number of process-type tasks (background shell or monitor) still alive when the response was rendered." + }, "last_event_at": { "description": "Unix timestamp in milliseconds of the most recent assistant-side event.", "format": "int64", @@ -23781,6 +23867,10 @@ "description": "Creator person id.", "type": "string" }, + "creator_name": { + "type": "string", + "description": "Display name of the session creator, resolved when the response is rendered. Omitted when the member lookup fails." + }, "pinned_at": { "description": "Caller's per-user pin time as a Unix timestamp in milliseconds; 0 means not pinned.", "format": "int64", @@ -23872,6 +23962,7 @@ "archived_at", "pinned_at", "is_running", + "standing_tasks", "has_unread", "current_turn_started_at", "current_turn_active_ms", @@ -26107,6 +26198,20 @@ "type": "boolean", "description": "Show the Create War Room button on Feishu app cards." }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "IM apps whose closed-incident cards keep the custom action buttons. Supported values: `feishu_app`, `dingtalk_app`, `wecom_app`, `slack_app`, `teams_app`. An empty list hides the buttons on every app." + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "Incident card fields hidden per IM app type." @@ -26265,6 +26370,20 @@ "type": "boolean", "description": "Whether Feishu app cards show the Create War Room button. Hidden when the incident has no responders." }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "IM apps whose closed-incident cards keep the custom action buttons. Supported values: `feishu_app`, `dingtalk_app`, `wecom_app`, `slack_app`, `teams_app`. An empty list hides the buttons on every app." + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "Incident card fields hidden per IM app type; an empty object when none are configured." @@ -26373,6 +26492,7 @@ "updated_by", "created_at", "updated_at", + "incident_card_closed_action_apps", "incident_card_hidden_fields" ], "type": "object" @@ -26532,6 +26652,20 @@ ], "description": "When set, show or hide the Create War Room button on Feishu app cards. Omit to keep the existing setting." }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "Replaces the retained-app list when sent. Supported values: `feishu_app`, `dingtalk_app`, `wecom_app`, `slack_app`, `teams_app`. Omit the field to leave it unchanged." + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "Incident card fields hidden per IM app type." @@ -28021,6 +28155,14 @@ "deleted" ], "type": "string" + }, + "chat_name": { + "type": "string", + "description": "Display name of the group chat on the IM side." + }, + "integration_unavailable": { + "type": "boolean", + "description": "True when the IM integration behind this war room is disabled or no longer exists." } }, "required": [ @@ -28031,7 +28173,8 @@ "incident_id", "status", "created_at", - "plugin_type" + "plugin_type", + "chat_name" ], "type": "object" }, @@ -28641,6 +28784,7 @@ "items": { "$ref": "#/components/schemas/InvestigationTarget" }, + "maxItems": 20, "description": "Drill-down entries linked from the alert event detail page; at most 20 items, duplicates rejected. On update the field is presence-based: omit it to keep the current value, pass `[]` to clear.", "x-flashduty-preserve-absence": true }, @@ -28941,14 +29085,23 @@ }, "InvestigationTarget": { "type": "object", - "description": "Drill-down entry linked to alert events. A deliberately closed tagged union: new kinds require explicit server support.", + "description": "Alert-event drill-down entry. A deliberately closed tagged union: new kinds require explicit server support, and unknown fields inside a target are rejected.", "properties": { "kind": { "type": "string", "enum": [ - "dashboard" + "dashboard", + "query" ], - "description": "Entry type; currently only `dashboard` is supported." + "description": "Entry kind: `dashboard` opens a dashboard panel, `query` opens an Explore query. It decides whether `dashboard` or `query` must be supplied; supplying the other one is rejected." + }, + "time_range": { + "$ref": "#/components/schemas/InvestigationTimeRange", + "description": "Window around the event time, required on every saved entry. A zero-length window is rejected; defaults belong to the editor." + }, + "query": { + "$ref": "#/components/schemas/QueryInvestigationTarget", + "description": "Configuration for the `query` kind; required when `kind` is `query`, and rejected when `kind` is `dashboard`." }, "dashboard": { "$ref": "#/components/schemas/DashboardInvestigationTarget", @@ -28956,7 +29109,8 @@ } }, "required": [ - "kind" + "kind", + "time_range" ] }, "DashboardInvestigationTarget": { @@ -28971,37 +29125,103 @@ "type": "string", "description": "Panel ID inside the dashboard; must be a canonical UUIDv7. Optional." }, - "variable_bindings": { + "variables": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/InvestigationVariableBinding" + "type": "string" }, - "description": "Dashboard variable bindings, keyed by dashboard variable name." + "description": "Dashboard variable values, keyed by variable name. Values may reference event labels through `{{ }}` templates; defaults to an empty object." } }, "required": [ - "dashboard_id" + "dashboard_id", + "variables" ] }, - "InvestigationVariableBinding": { + "InvestigationTimeRange": { "type": "object", - "description": "Binding between a dashboard variable and alert event data.", + "description": "Window around the alert event time, expressed as two offsets. Both directions must be non-negative and at least one must be greater than zero, so an entry never resolves to an empty window.", "properties": { - "source": { + "before_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 9007199254740, + "description": "Seconds to look back from the event time." + }, + "after_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 9007199254740, + "description": "Seconds to look forward from the event time, so behaviour after the event stays visible." + } + }, + "required": [ + "before_seconds", + "after_seconds" + ] + }, + "QueryInvestigationTarget": { + "type": "object", + "description": "Explore query used by a `query` drill-down entry. The expression may reference event labels through `{{ }}` templates, while `args` values may not.", + "properties": { + "datasource_id": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 9007199254740991, + "description": "Data source the query runs against." + }, + "query": { + "$ref": "#/components/schemas/DashboardQuery", + "description": "Query payload." + } + }, + "required": [ + "datasource_id", + "query" + ] + }, + "DashboardQuery": { + "type": "object", + "description": "Query payload shared by dashboards and drill-down entries.", + "properties": { + "mode": { "type": "string", "enum": [ - "event_label" + "instant", + "range", + "window" ], - "description": "Where the bound value comes from; currently only `event_label` (the alert event's label value) is supported." + "description": "Evaluation mode: `instant` evaluates at a single timestamp, `range` evaluates a stepped series, `window` returns raw rows inside a time window." }, - "key": { + "expr": { "type": "string", - "description": "Alert event label name; must follow Prometheus label naming rules and must not be a reserved label." + "description": "Query expression in the target data source's language. May reference event labels through `{{ }}` templates." + }, + "args": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Named query arguments; defaults to an empty object. Values are passed through verbatim and must not contain `{{ }}` templates." + }, + "min_step_seconds": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1, + "maximum": 9007199254740, + "description": "Minimum step, in seconds. Only accepted when `mode` is `range`, and must be greater than zero; omit or pass null to let the server decide." } }, "required": [ - "source", - "key" + "mode", + "expr", + "args" ] }, "MemberNotifyRequest": { @@ -29054,6 +29274,10 @@ "html": { "type": "string", "description": "Only present when `dry_run` is `true`: the complete email HTML exactly as recipients would receive it, after sanitization." + }, + "agent_instructions": { + "type": "string", + "description": "Present when the submitted HTML body does not follow the default email layout (no `max-width:600px` wrapper table): guidance telling the calling AI SRE agent how to conform. Advisory only — a format the caller deliberately chose needs no change. Returned on dry runs and real sends alike." } } }, @@ -29091,6 +29315,29 @@ "description": "Why the recipient was skipped. Only present when `status` is `skipped`. `not_member` — not an active member of the caller's account; `no_email` — the member has no email address on file; `email_disabled` — the member's notification preferences for this kind of message exclude email; `duplicate` — this recipient already received a message from the same AI SRE session turn; `rate_limited` — this recipient has already been sent 20 emails through this endpoint within the last hour; `send_failed` — enqueueing the email failed." } } + }, + "RumApplicationRepository": { + "type": "object", + "description": "Source code repository that builds the application.", + "required": [ + "repo" + ], + "properties": { + "repo": { + "type": "string", + "description": "GitHub repository in `owner/name` form.", + "examples": [ + "acme/web-app" + ] + }, + "subdir": { + "type": "string", + "description": "Directory holding the application inside the repository, relative to the repository root. `.` is the repository root; an empty value is saved as `.`.", + "examples": [ + "apps/web" + ] + } + } } }, "securitySchemes": { diff --git a/api-reference/openapi.zh.json b/api-reference/openapi.zh.json index 401b058d..ddaf0e7a 100644 --- a/api-reference/openapi.zh.json +++ b/api-reference/openapi.zh.json @@ -3494,7 +3494,8 @@ }, "limit": { "default": 20, - "description": "每页数量。", + "description": "每页数量。小于 1 时回退为 20,大于 200 时按 200 处理。", + "maximum": 200, "type": "integer" }, "p": { @@ -7575,6 +7576,46 @@ } } }, + "PendingUserMessage": { + "type": "object", + "description": "尚未被智能体消费的排队用户消息,由 `pending_messages` 返回。", + "required": [ + "invocation_id", + "person_id", + "query" + ], + "properties": { + "invocation_id": { + "type": "string", + "description": "该排队消息的 invocation ID。" + }, + "client_msg_id": { + "type": "string", + "description": "客户端传入的消息 ID,原样返回用于去重;未传时不返回。" + }, + "person_id": { + "type": "integer", + "format": "int64", + "description": "发送者 person ID。" + }, + "steering": { + "type": "boolean", + "description": "该消息是轮次中的 steering 指令时为 true;false 时不返回。" + }, + "query": { + "type": "string", + "description": "消息正文。" + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "原样透传的请求 parts(text / file / ref / skill 等);原消息未携带时不返回。" + } + } + }, "PrometheusQueryParams": { "type": "object", "additionalProperties": true, @@ -10511,6 +10552,14 @@ "description": "最后更新时间,Unix 毫秒时间戳。", "format": "int64", "type": "integer" + }, + "via": { + "type": "string", + "description": "由 AI SRE 代为写入该记录时的来源标识,目前只有 `ai_sre`;用户直接操作创建的记录不返回该字段。" + }, + "agent_session_id": { + "type": "string", + "description": "写入该记录的 AI SRE 会话 ID;非智能体写入时不返回该字段。" } }, "required": [ @@ -19376,6 +19425,14 @@ "$ref": "#/components/schemas/RumApplicationLinks", "description": "外部链接集成配置,可选。" }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "要关联的代码仓库,有序;第一个为主仓库。最多 10 个。" + }, "no_geo": { "description": "不推断地理位置。", "type": "boolean" @@ -19522,6 +19579,14 @@ "$ref": "#/components/schemas/RumApplicationLinks", "description": "外部链接集成配置。" }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "关联的代码仓库,有序;第一个为主仓库。关联本身不授予任何访问权限:AI 会话只能读取本账户 GitHub App 安装已授权的仓库。" + }, "no_geo": { "description": "为 `true` 时不推断地理位置。", "type": "boolean" @@ -19778,6 +19843,15 @@ "description": "外部链接集成配置;不传则保持不变。", "x-flashduty-preserve-absence": true }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "传入时整体替换关联的代码仓库;传空数组表示清空,不传则保持不变。最多 10 个。", + "x-flashduty-preserve-absence": true + }, "no_geo": { "description": "为 `true` 时不再基于 IP 推断地理位置,为 `false` 时恢复推断;不传则保持不变。", "type": [ @@ -23651,13 +23725,21 @@ "suggest_init": { "description": "账户级引导标志:当账户在任何范围内都没有知识包时为 true;并非该会话独有的属性。", "type": "boolean" + }, + "pending_messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PendingUserMessage" + }, + "description": "已排队但尚未被智能体消费的用户消息,按执行顺序排列。始终返回数组;无排队消息时为空数组。" } }, "required": [ "session", "events", "has_more_older", - "suggest_init" + "suggest_init", + "pending_messages" ], "type": "object" }, @@ -23768,6 +23850,10 @@ "description": "当该会话当前有正在进行的智能体轮次时为 true。", "type": "boolean" }, + "standing_tasks": { + "type": "integer", + "description": "渲染响应时仍存活的进程型任务数(后台命令或 monitor)。" + }, "last_event_at": { "description": "最近一条助手侧事件的时间,Unix 毫秒时间戳。", "format": "int64", @@ -23781,6 +23867,10 @@ "description": "创建者人员 ID。", "type": "string" }, + "creator_name": { + "type": "string", + "description": "会话创建者的显示名,渲染响应时解析得到;成员查询失败时不返回该字段。" + }, "pinned_at": { "description": "调用者的个人置顶时间,Unix 毫秒时间戳;0 表示未置顶。", "format": "int64", @@ -23872,6 +23962,7 @@ "archived_at", "pinned_at", "is_running", + "standing_tasks", "has_unread", "current_turn_started_at", "current_turn_active_ms", @@ -26107,6 +26198,20 @@ "type": "boolean", "description": "飞书应用卡片展示「创建作战室」按钮。" }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "已关闭故障的卡片仍保留自定义操作按钮的 IM 应用。可选值:`feishu_app`、`dingtalk_app`、`wecom_app`、`slack_app`、`teams_app`;空列表表示所有应用都隐藏这些按钮。" + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "按 IM 应用类型隐藏的故障卡片字段。" @@ -26265,6 +26370,20 @@ "type": "boolean", "description": "飞书应用卡片是否展示「创建作战室」按钮;故障无响应人时不展示。" }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "已关闭故障的卡片仍保留自定义操作按钮的 IM 应用。可选值:`feishu_app`、`dingtalk_app`、`wecom_app`、`slack_app`、`teams_app`;空列表表示所有应用都隐藏这些按钮。" + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "按 IM 应用类型配置的故障卡片隐藏字段,未配置时为空对象。" @@ -26373,6 +26492,7 @@ "updated_by", "created_at", "updated_at", + "incident_card_closed_action_apps", "incident_card_hidden_fields" ], "type": "object" @@ -26532,6 +26652,20 @@ ], "description": "设置后决定飞书应用卡片是否展示「创建作战室」按钮;省略时保持当前设置。" }, + "incident_card_closed_action_apps": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "feishu_app", + "dingtalk_app", + "wecom_app", + "slack_app", + "teams_app" + ] + }, + "description": "传入时整体替换保留自定义操作按钮的应用列表。可选值:`feishu_app`、`dingtalk_app`、`wecom_app`、`slack_app`、`teams_app`;不传该字段则保持不变。" + }, "incident_card_hidden_fields": { "$ref": "#/components/schemas/IncidentCardHiddenFields", "description": "按 IM 应用类型隐藏的故障卡片字段。" @@ -28021,6 +28155,14 @@ "deleted" ], "type": "string" + }, + "chat_name": { + "type": "string", + "description": "IM 侧群聊的显示名称。" + }, + "integration_unavailable": { + "type": "boolean", + "description": "该作战室背后的 IM 集成已禁用或已不存在时为 true。" } }, "required": [ @@ -28031,7 +28173,8 @@ "incident_id", "status", "created_at", - "plugin_type" + "plugin_type", + "chat_name" ], "type": "object" }, @@ -28641,6 +28784,7 @@ "items": { "$ref": "#/components/schemas/InvestigationTarget" }, + "maxItems": 20, "description": "告警事件详情页关联的排障入口列表,最多 20 项,不允许重复。更新接口中该字段按 presence 处理:省略时保留原配置,传 `[]` 清空。", "x-flashduty-preserve-absence": true }, @@ -28941,14 +29085,23 @@ }, "InvestigationTarget": { "type": "object", - "description": "告警事件的关联排障入口,为封闭的 tagged union:新增类型需服务端显式支持。", + "description": "告警事件的关联排障入口,为封闭的 tagged union:新增类型需服务端显式支持,入口内出现未知字段会被拒绝。", "properties": { "kind": { "type": "string", "enum": [ - "dashboard" + "dashboard", + "query" ], - "description": "排障入口类型,目前仅支持 `dashboard`。" + "description": "入口类型:`dashboard` 打开仪表盘面板,`query` 打开 Explore 查询。它决定必须提供 `dashboard` 还是 `query`,提供另一个会被拒绝。" + }, + "time_range": { + "$ref": "#/components/schemas/InvestigationTimeRange", + "description": "事件时间前后的取数窗口,每个已保存的入口都必填。长度为 0 的窗口会被拒绝;默认值由前端编辑器提供。" + }, + "query": { + "$ref": "#/components/schemas/QueryInvestigationTarget", + "description": "`query` 类型的入口配置;`kind` 为 `query` 时必填,`kind` 为 `dashboard` 时不允许出现。" }, "dashboard": { "$ref": "#/components/schemas/DashboardInvestigationTarget", @@ -28956,7 +29109,8 @@ } }, "required": [ - "kind" + "kind", + "time_range" ] }, "DashboardInvestigationTarget": { @@ -28971,37 +29125,103 @@ "type": "string", "description": "仪表盘内目标面板 ID,须为规范的 UUIDv7;可选。" }, - "variable_bindings": { + "variables": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/InvestigationVariableBinding" + "type": "string" }, - "description": "仪表盘变量绑定,键为仪表盘变量名。" + "description": "仪表盘变量取值,键为变量名。值可通过 `{{ }}` 模板引用事件标签;默认空对象。" } }, "required": [ - "dashboard_id" + "dashboard_id", + "variables" ] }, - "InvestigationVariableBinding": { + "InvestigationTimeRange": { "type": "object", - "description": "仪表盘变量与告警事件数据的绑定。", + "description": "以两个偏移量表示告警事件时间前后的取数窗口。两个方向都必须非负,且至少一个大于 0,因此入口不会解析出空窗口。", "properties": { - "source": { + "before_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 9007199254740, + "description": "相对事件时间向前回溯的秒数。" + }, + "after_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0, + "maximum": 9007199254740, + "description": "相对事件时间向后延伸的秒数,用于保留事件发生后的表现。" + } + }, + "required": [ + "before_seconds", + "after_seconds" + ] + }, + "QueryInvestigationTarget": { + "type": "object", + "description": "`query` 类型入口使用的 Explore 查询。表达式可通过 `{{ }}` 模板引用事件标签,`args` 的值不允许包含模板。", + "properties": { + "datasource_id": { + "type": "integer", + "format": "int64", + "minimum": 1, + "maximum": 9007199254740991, + "description": "查询所用的数据源。" + }, + "query": { + "$ref": "#/components/schemas/DashboardQuery", + "description": "查询内容。" + } + }, + "required": [ + "datasource_id", + "query" + ] + }, + "DashboardQuery": { + "type": "object", + "description": "仪表盘与排障入口共用的查询内容。", + "properties": { + "mode": { "type": "string", "enum": [ - "event_label" + "instant", + "range", + "window" ], - "description": "绑定值来源,目前仅支持 `event_label`(取告警事件的标签值)。" + "description": "求值模式:`instant` 在单个时间点求值,`range` 求值一条按步长采样的曲线,`window` 返回时间窗口内的原始数据。" }, - "key": { + "expr": { "type": "string", - "description": "告警事件标签名,须符合 Prometheus 标签命名规则,且不能使用保留标签。" + "description": "目标数据源语法下的查询表达式,可通过 `{{ }}` 模板引用事件标签。" + }, + "args": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "命名查询参数,默认空对象。值按原样透传,不允许包含 `{{ }}` 模板。" + }, + "min_step_seconds": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 1, + "maximum": 9007199254740, + "description": "最小步长(秒)。仅在 `mode` 为 `range` 时可用,且必须大于 0;省略或传 null 由服务端决定。" } }, "required": [ - "source", - "key" + "mode", + "expr", + "args" ] }, "MemberNotifyRequest": { @@ -29054,6 +29274,10 @@ "html": { "type": "string", "description": "仅当 `dry_run` 为 `true` 时返回:清洗后、收件人将收到的完整邮件 HTML。" + }, + "agent_instructions": { + "type": "string", + "description": "当提交的 HTML 正文不符合默认邮件版式(缺少 `max-width:600px` 外层包裹表格)时返回:告知调用的 AI SRE agent 如何对齐的指引。仅为建议——调用方有意选择的格式无需改动。dry run 与真实发送均会返回。" } } }, @@ -29091,6 +29315,29 @@ "description": "跳过原因,仅当 `status` 为 `skipped` 时出现。`not_member` —— 不是调用方账户的活跃成员;`no_email` —— 该成员没有邮箱地址;`email_disabled` —— 该成员针对此类消息的通知偏好中未包含邮件;`duplicate` —— 该收件人在同一个 AI SRE 会话轮次中已经收到过一次消息;`rate_limited` —— 该收件人通过该接口在过去一小时内已被发送 20 封邮件;`send_failed` —— 邮件入队失败。" } } + }, + "RumApplicationRepository": { + "type": "object", + "description": "构建该应用的代码仓库。", + "required": [ + "repo" + ], + "properties": { + "repo": { + "type": "string", + "description": "GitHub 仓库,格式为 `owner/name`。", + "examples": [ + "acme/web-app" + ] + }, + "subdir": { + "type": "string", + "description": "应用在仓库内所在的目录,相对仓库根目录。`.` 表示仓库根目录;传空值时保存为 `.`。", + "examples": [ + "apps/web" + ] + } + } } }, "securitySchemes": { diff --git a/api-reference/platform.openapi.en.json b/api-reference/platform.openapi.en.json index 47f747d4..f5c5e26a 100644 --- a/api-reference/platform.openapi.en.json +++ b/api-reference/platform.openapi.en.json @@ -4581,6 +4581,10 @@ "html": { "type": "string", "description": "Only present when `dry_run` is `true`: the complete email HTML exactly as recipients would receive it, after sanitization." + }, + "agent_instructions": { + "type": "string", + "description": "Present when the submitted HTML body does not follow the default email layout (no `max-width:600px` wrapper table): guidance telling the calling AI SRE agent how to conform. Advisory only — a format the caller deliberately chose needs no change. Returned on dry runs and real sends alike." } } }, diff --git a/api-reference/platform.openapi.zh.json b/api-reference/platform.openapi.zh.json index 3cca6c26..22d69cb8 100644 --- a/api-reference/platform.openapi.zh.json +++ b/api-reference/platform.openapi.zh.json @@ -4581,6 +4581,10 @@ "html": { "type": "string", "description": "仅当 `dry_run` 为 `true` 时返回:清洗后、收件人将收到的完整邮件 HTML。" + }, + "agent_instructions": { + "type": "string", + "description": "当提交的 HTML 正文不符合默认邮件版式(缺少 `max-width:600px` 外层包裹表格)时返回:告知调用的 AI SRE agent 如何对齐的指引。仅为建议——调用方有意选择的格式无需改动。dry run 与真实发送均会返回。" } } }, diff --git a/api-reference/rum.openapi.en.json b/api-reference/rum.openapi.en.json index eca2d6ac..61dd9d3b 100644 --- a/api-reference/rum.openapi.en.json +++ b/api-reference/rum.openapi.en.json @@ -610,6 +610,14 @@ "$ref": "#/components/schemas/RumApplicationLinks", "description": "Optional external-link integration configuration." }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "Source code repositories to link, in order; the first entry is the primary repository. At most 10 entries." + }, "no_geo": { "description": "Do not infer geographic location.", "type": "boolean" @@ -756,6 +764,14 @@ "$ref": "#/components/schemas/RumApplicationLinks", "description": "External-link integration configuration." }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "Linked source code repositories, in order; the first entry is the primary repository. Linking grants no access by itself: AI sessions can only read repositories granted to the account's GitHub App installations." + }, "no_geo": { "description": "If `true`, geographic location is not inferred from IP.", "type": "boolean" @@ -1012,6 +1028,15 @@ "description": "External-link integration configuration. Omit to leave unchanged.", "x-flashduty-preserve-absence": true }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "Replaces the linked source code repositories when present; an empty array removes all links, and omitting the field leaves them unchanged. At most 10 entries.", + "x-flashduty-preserve-absence": true + }, "no_geo": { "description": "When `true`, stop inferring geographic location from IP; when `false`, resume inferring it. Omit to leave unchanged.", "type": [ @@ -3938,6 +3963,29 @@ } }, "type": "object" + }, + "RumApplicationRepository": { + "type": "object", + "description": "Source code repository that builds the application.", + "required": [ + "repo" + ], + "properties": { + "repo": { + "type": "string", + "description": "GitHub repository in `owner/name` form.", + "examples": [ + "acme/web-app" + ] + }, + "subdir": { + "type": "string", + "description": "Directory holding the application inside the repository, relative to the repository root. `.` is the repository root; an empty value is saved as `.`.", + "examples": [ + "apps/web" + ] + } + } } }, "securitySchemes": { diff --git a/api-reference/rum.openapi.zh.json b/api-reference/rum.openapi.zh.json index c0362e4d..213c0676 100644 --- a/api-reference/rum.openapi.zh.json +++ b/api-reference/rum.openapi.zh.json @@ -610,6 +610,14 @@ "$ref": "#/components/schemas/RumApplicationLinks", "description": "外部链接集成配置,可选。" }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "要关联的代码仓库,有序;第一个为主仓库。最多 10 个。" + }, "no_geo": { "description": "不推断地理位置。", "type": "boolean" @@ -756,6 +764,14 @@ "$ref": "#/components/schemas/RumApplicationLinks", "description": "外部链接集成配置。" }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "关联的代码仓库,有序;第一个为主仓库。关联本身不授予任何访问权限:AI 会话只能读取本账户 GitHub App 安装已授权的仓库。" + }, "no_geo": { "description": "为 `true` 时不推断地理位置。", "type": "boolean" @@ -1012,6 +1028,15 @@ "description": "外部链接集成配置;不传则保持不变。", "x-flashduty-preserve-absence": true }, + "repositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RumApplicationRepository" + }, + "maxItems": 10, + "description": "传入时整体替换关联的代码仓库;传空数组表示清空,不传则保持不变。最多 10 个。", + "x-flashduty-preserve-absence": true + }, "no_geo": { "description": "为 `true` 时不再基于 IP 推断地理位置,为 `false` 时恢复推断;不传则保持不变。", "type": [ @@ -3938,6 +3963,29 @@ } }, "type": "object" + }, + "RumApplicationRepository": { + "type": "object", + "description": "构建该应用的代码仓库。", + "required": [ + "repo" + ], + "properties": { + "repo": { + "type": "string", + "description": "GitHub 仓库,格式为 `owner/name`。", + "examples": [ + "acme/web-app" + ] + }, + "subdir": { + "type": "string", + "description": "应用在仓库内所在的目录,相对仓库根目录。`.` 表示仓库根目录;传空值时保存为 `.`。", + "examples": [ + "apps/web" + ] + } + } } }, "securitySchemes": { diff --git a/api-reference/safari.openapi.en.json b/api-reference/safari.openapi.en.json index 8c513b5e..3ede44fa 100644 --- a/api-reference/safari.openapi.en.json +++ b/api-reference/safari.openapi.en.json @@ -5816,7 +5816,8 @@ "limit": { "type": "integer", "default": 20, - "description": "Page size." + "description": "Page size. Values below 1 fall back to 20; values above 200 are capped at 200.", + "maximum": 200 }, "scope": { "type": "string", @@ -7049,6 +7050,46 @@ "preflight" ] }, + "PendingUserMessage": { + "type": "object", + "description": "A queued user message that the agent has not consumed yet, as carried by `pending_messages`.", + "required": [ + "invocation_id", + "person_id", + "query" + ], + "properties": { + "invocation_id": { + "type": "string", + "description": "Invocation ID of the queued message." + }, + "client_msg_id": { + "type": "string", + "description": "Client-supplied message ID, echoed back for de-duplication. Omitted when absent." + }, + "person_id": { + "type": "integer", + "format": "int64", + "description": "Person ID of the sender." + }, + "steering": { + "type": "boolean", + "description": "True when the message was sent as a mid-turn steering instruction. Omitted when false." + }, + "query": { + "type": "string", + "description": "Message text." + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Request parts passed through verbatim (text, file, ref, or skill entries). Omitted when the original message carried none." + } + } + }, "PreflightResult": { "type": "object", "description": "Readiness checks computed before a manual run is allowed to start.", @@ -7215,13 +7256,21 @@ "suggest_init": { "type": "boolean", "description": "Account-wide onboarding flag: true when the account has zero knowledge packs in any scope; not specific to this session." + }, + "pending_messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PendingUserMessage" + }, + "description": "Human messages queued but not yet picked up by the agent, in execution order. Always an array — empty when nothing is queued." } }, "required": [ "session", "events", "has_more_older", - "suggest_init" + "suggest_init", + "pending_messages" ] }, "SessionItem": { @@ -7259,6 +7308,10 @@ "type": "string", "description": "Creator person id." }, + "creator_name": { + "type": "string", + "description": "Display name of the session creator, resolved when the response is rendered. Omitted when the member lookup fails." + }, "team_id": { "type": "integer", "format": "int64", @@ -7387,6 +7440,10 @@ "type": "boolean", "description": "True when an agent turn is currently in flight for this session." }, + "standing_tasks": { + "type": "integer", + "description": "Number of process-type tasks (background shell or monitor) still alive when the response was rendered." + }, "has_unread": { "type": "boolean", "description": "True when there is assistant output the caller has not yet viewed." @@ -7436,6 +7493,7 @@ "archived_at", "pinned_at", "is_running", + "standing_tasks", "has_unread", "current_turn_started_at", "current_turn_active_ms", diff --git a/api-reference/safari.openapi.zh.json b/api-reference/safari.openapi.zh.json index be93ac17..3b78f429 100644 --- a/api-reference/safari.openapi.zh.json +++ b/api-reference/safari.openapi.zh.json @@ -5816,7 +5816,8 @@ "limit": { "type": "integer", "default": 20, - "description": "每页数量。" + "description": "每页数量。小于 1 时回退为 20,大于 200 时按 200 处理。", + "maximum": 200 }, "scope": { "type": "string", @@ -7049,6 +7050,46 @@ "preflight" ] }, + "PendingUserMessage": { + "type": "object", + "description": "尚未被智能体消费的排队用户消息,由 `pending_messages` 返回。", + "required": [ + "invocation_id", + "person_id", + "query" + ], + "properties": { + "invocation_id": { + "type": "string", + "description": "该排队消息的 invocation ID。" + }, + "client_msg_id": { + "type": "string", + "description": "客户端传入的消息 ID,原样返回用于去重;未传时不返回。" + }, + "person_id": { + "type": "integer", + "format": "int64", + "description": "发送者 person ID。" + }, + "steering": { + "type": "boolean", + "description": "该消息是轮次中的 steering 指令时为 true;false 时不返回。" + }, + "query": { + "type": "string", + "description": "消息正文。" + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "原样透传的请求 parts(text / file / ref / skill 等);原消息未携带时不返回。" + } + } + }, "PreflightResult": { "type": "object", "description": "在允许发起手动运行前计算出的就绪检查结果。", @@ -7215,13 +7256,21 @@ "suggest_init": { "type": "boolean", "description": "账户级引导标志:当账户在任何范围内都没有知识包时为 true;并非该会话独有的属性。" + }, + "pending_messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PendingUserMessage" + }, + "description": "已排队但尚未被智能体消费的用户消息,按执行顺序排列。始终返回数组;无排队消息时为空数组。" } }, "required": [ "session", "events", "has_more_older", - "suggest_init" + "suggest_init", + "pending_messages" ] }, "SessionItem": { @@ -7259,6 +7308,10 @@ "type": "string", "description": "创建者人员 ID。" }, + "creator_name": { + "type": "string", + "description": "会话创建者的显示名,渲染响应时解析得到;成员查询失败时不返回该字段。" + }, "team_id": { "type": "integer", "format": "int64", @@ -7387,6 +7440,10 @@ "type": "boolean", "description": "当该会话当前有正在进行的智能体轮次时为 true。" }, + "standing_tasks": { + "type": "integer", + "description": "渲染响应时仍存活的进程型任务数(后台命令或 monitor)。" + }, "has_unread": { "type": "boolean", "description": "当存在调用者尚未查看的助手输出时为 true。" @@ -7436,6 +7493,7 @@ "archived_at", "pinned_at", "is_running", + "standing_tasks", "has_unread", "current_turn_started_at", "current_turn_active_ms", diff --git a/docs.json b/docs.json index d8e68309..ba716c72 100644 --- a/docs.json +++ b/docs.json @@ -1680,6 +1680,7 @@ "zh/on-call/integration/alert-integration/alert-sources/prometheus", "zh/on-call/integration/alert-integration/alert-sources/grafana", "zh/on-call/integration/alert-integration/alert-sources/datadog", + "zh/on-call/integration/alert-integration/alert-sources/new-relic", "zh/on-call/integration/alert-integration/alert-sources/zabbix", "zh/on-call/integration/alert-integration/alert-sources/flashcat", "zh/on-call/integration/alert-integration/alert-sources/open-falcon", @@ -1694,6 +1695,7 @@ "zh/on-call/integration/alert-integration/alert-sources/aliyun-cm-metrics", "zh/on-call/integration/alert-integration/alert-sources/aliyun-sls", "zh/on-call/integration/alert-integration/alert-sources/aliyun-prometheus", + "zh/on-call/integration/alert-integration/alert-sources/aliyun-dataworks-op", "zh/on-call/integration/alert-integration/alert-sources/aws-cloudwatch", "zh/on-call/integration/alert-integration/alert-sources/aws-eventbridge", "zh/on-call/integration/alert-integration/alert-sources/azure-monitor", @@ -3048,6 +3050,7 @@ "en/on-call/integration/alert-integration/alert-sources/prometheus", "en/on-call/integration/alert-integration/alert-sources/grafana", "en/on-call/integration/alert-integration/alert-sources/datadog", + "en/on-call/integration/alert-integration/alert-sources/new-relic", "en/on-call/integration/alert-integration/alert-sources/zabbix", "en/on-call/integration/alert-integration/alert-sources/flashcat", "en/on-call/integration/alert-integration/alert-sources/open-falcon", @@ -3062,6 +3065,7 @@ "en/on-call/integration/alert-integration/alert-sources/aliyun-cm-metrics", "en/on-call/integration/alert-integration/alert-sources/aliyun-sls", "en/on-call/integration/alert-integration/alert-sources/aliyun-prometheus", + "en/on-call/integration/alert-integration/alert-sources/aliyun-dataworks-op", "en/on-call/integration/alert-integration/alert-sources/aws-cloudwatch", "en/on-call/integration/alert-integration/alert-sources/aws-eventbridge", "en/on-call/integration/alert-integration/alert-sources/azure-monitor", diff --git a/en/ai-sre/apps.mdx b/en/ai-sre/apps.mdx index 7961befe..6f1edfb4 100644 --- a/en/ai-sre/apps.mdx +++ b/en/ai-sre/apps.mdx @@ -31,7 +31,7 @@ AI SRE sessions run in a **Flashduty cloud sandbox** by default. The sandbox is --- -Go to **Plugins → Apps**. Apps is the **first and default** tab in the Plugins area — opening Plugins lands you here. +Go to **Plugins → Apps**. Opening the Plugins area lands on the **Overview** tab by default; Apps is one of its tabs (the tab order is Overview / Apps / Skill / MCP / Agents). Viewing the Apps tab requires the appropriate permission; without it, the tab is hidden. Authorizing, disconnecting / revoking, and enabling / disabling each require their own action permission — when you lack one, the corresponding button is shown disabled. diff --git a/en/ai-sre/artifacts.mdx b/en/ai-sre/artifacts.mdx index 852600d7..448be8fd 100644 --- a/en/ai-sre/artifacts.mdx +++ b/en/ai-sre/artifacts.mdx @@ -60,6 +60,8 @@ The top-right of the list page offers a **Card view / List view** toggle; your c The **Created at** and **Updated at** columns support header sorting (ascending / descending toggle); sorting is executed server-side, with **Updated at descending** as the default. +Scope, search term, sorting, and page number are all reflected in the page URL (for example `?scope=team&teams=1,2&q=rebuild&page=2`), so copying the address shares or bookmarks a **filtered** list; returning from an artifact's detail page also lands back on the same filters and position instead of resetting to the default view. + ### Artifact cards Each card shows: diff --git a/en/ai-sre/automations.mdx b/en/ai-sre/automations.mdx index aa3fd513..ca06040f 100644 --- a/en/ai-sre/automations.mdx +++ b/en/ai-sre/automations.mdx @@ -170,7 +170,7 @@ If you create or update a rule through the API, use these fields: When a matching event arrives, the system creates a run with `trigger_kind: "oncall_incident"` and passes event context such as `incident_id`, `channel_id`, and `severity` into the session. The same trigger and the same `incident_id` reuse the same run, avoiding duplicate hidden sessions for one incident. -When the run finishes, AI SRE writes one summary comment back to the incident that triggered it: conclusion first, body kept concise, with a link to the full session at the end. The comment travels through the incident's existing notification chain (for example, incident card refreshes and thread replies in IM), so whoever is watching the incident sees the analysis without opening the console. This applies to every rule with the On-call incident trigger enabled — including rules with custom prompts — with no extra configuration. +When the run finishes, AI SRE writes one summary comment back to the incident that triggered it: conclusion first, body kept concise, with a link to the full session at the end. That write-back record also carries a persistent **Continue conversation** entry, so you can open that session straight from the conclusion on the incident timeline — see [Incident Timeline · AI SRE Action Records](/en/on-call/incident/search-view-incident#ai-sre-action-records). The comment travels through the incident's existing notification chain (for example, incident card refreshes and thread replies in IM), so whoever is watching the incident sees the analysis without opening the console. This applies to every rule with the On-call incident trigger enabled — including rules with custom prompts — with no extra configuration. ## Run History diff --git a/en/ai-sre/im.mdx b/en/ai-sre/im.mdx index 32e86ba7..caeb7410 100644 --- a/en/ai-sre/im.mdx +++ b/en/ai-sre/im.mdx @@ -50,7 +50,7 @@ IM interaction requires that you have already connected the corresponding platfo Open the corresponding IM integration under **On-call → Integration Center → Integration List → Instant Messaging** and configure its AI SRE behavior under **Enhanced features**. The following switches are on by default: - **Automatically start AI incident analysis**: available after you enable War Room. When the integration creates a war room, AI SRE starts one preliminary diagnosis and posts the result back to the room. -- **Allow group chat @ AI SRE**: available without enabling War Room. It allows group @mentions to enter AI SRE. In Slack, the switch is labeled **Allow group chat @ AI SRE and /fd command**; turning it off makes both group @mentions and `/fd` commands silent. +- **Allow AI SRE conversations in IM**: available without enabling War Room. It controls both entry points — **group @mentions** and **direct chats with the bot**; in Slack it also controls the `/fd` command. Turning it off makes group @mentions and direct chats with the bot (and, in Slack, `/fd`) silent. In WeCom's Third-party App mode, only direct chats with the bot are available; group @mentions require Custom App Integration mode. - **Use thread replies in normal group chats**: available only for Feishu/Lark and Slack. When enabled, each normal-group thread has an independent session; War Rooms still receive replies directly in the group. @@ -61,7 +61,7 @@ If AI SRE is not enabled for the account, these switches do not take effect. Aft --- -In a connected IM group where group chat @ AI SRE is allowed, **@mention the bot** and type your question (for example, "@AI SRE check why the payment service's 5xx rate is spiking") — the message is forwarded to AI SRE for processing via the platform webhook: +In a connected IM group where **Allow AI SRE conversations in IM** is enabled, **@mention the bot** and type your question (for example, "@AI SRE check why the payment service's 5xx rate is spiking") — the message is forwarded to AI SRE for processing via the platform webhook: @@ -120,6 +120,21 @@ While any standing task is alive, the IM session's root message stays open (the Notification rounds follow a silent "no message = no news" semantics: if a notification round has nothing new to deliver, AI SRE closes that round silently — **no placeholder receipt** is posted to the chat, and the monitoring card is not re-posted either. A new message appears in the chat only when there is a real new finding. +## Connections and Authorization + +--- + +When a credential is missing, the console renders inline cards (**"Authorize [resource name] to continue"**, **"Connect [vendor] to continue"** — see [Console](/en/ai-sre/sessions#when-authorization-is-required)). **IM and API channels get no card** — the agent hands the matter to you according to this session's channel, as follows: + +| Situation | Behavior on IM / API channels | +| --- | --- | +| Your OAuth authorization is missing (`per_user_oauth`) | The agent pastes the authorize link into the chat as a plain message (not a card); open it in a **desktop / PC browser** to complete the authorization, then reply **"已授权"** (or `authorized` in an English session) to continue. Continuing is your explicit action — a successful OAuth callback does not resume the task by itself | +| Your secret-type credential is missing (`per_user_secret`) | There is no safe way to collect it over IM: the agent simply tells you to open **this same session in the web console** and complete it there. It never asks you to paste the secret into the chat, and never points you at a settings page instead | +| The whole connection is missing (in the marketplace catalog, not connected in the account) | The agent gives the connect URL (a deep link into the console's plugins page) as a plain link; connect, then reply **"继续"** (or `continue`) to carry on | +| A shared server's own credentials (environment variables / headers) | Not any of the above: fill them in under **Plugins → MCP** (that server), then tell the agent to retry | + +On the automation channel nobody is watching and no reply will ever arrive: the agent does not wait and does not phrase anything as a question — it **records the missing connection or authorization as a blocker** in its final report and delivers whatever the evidence already supports. + ## In-session Switch Commands --- diff --git a/en/ai-sre/knowledge.mdx b/en/ai-sre/knowledge.mdx index 36c7b0cd..c9008d08 100644 --- a/en/ai-sre/knowledge.mdx +++ b/en/ai-sre/knowledge.mdx @@ -71,11 +71,24 @@ After reading `DUTY.md`, the agent decides which `@references` to expand based o --- -Go to the **Knowledges** management page to create, edit, enable/disable, or delete Knowledge Packs for your account or teams. The list shows each pack's **Name / Scope / Files / Status / Actions**, and a scope filter at the top lets you switch between Shared and Team views. +Go to the **Knowledges** management page to create, edit, enable/disable, or delete Knowledge Packs for your account or teams. Once packs exist, the list shows each pack's **Name / Scope / Files / Status / Actions**, and a scope filter at the top lets you switch between Shared and Team views. + +**First visit (empty state)**: when the current scope has no Knowledge Pack and the account has none at all, the page is taken over by the empty state. It lists five sources that can be drafted automatically — **Code repositories** (services, dependencies, tech stack), **Deployment topology** (environments, clusters, workloads), **Infrastructure** (cloud accounts, databases, middleware), **Changes** (releases and config changes), and **Monitoring** (metrics, logs, alert rules) — each showing how much has already been read from your connected tools and from Flashduty (labels like "N repositories", "N clusters", "N connections", "N in the last 30 days"); a source with nothing yet shows **Connect →**, which jumps to the plugins page to connect it. Below the cards the page notes that unconnected parts are left blank and can be filled in later, and that schedules, escalation rules, and handling records are read straight from Flashduty with no connection needed. + +While the empty state owns the page, the only two ways to start a pack are its own buttons: + +| Entry | Behavior | +| --- | --- | +| **Initialize AI-SRE** | Opens a new AI SRE session and **sends** `/init` immediately: the agent drafts content from those five sources and saves it after confirming each item with you | +| **Upload existing documents** | Opens a new AI SRE session with `/init` **pre-filled** in the input box (not sent) and the attachment picker open, so you can hand existing documents to the agent for distillation | + +**While the empty state owns the page, the 创建 (Create) button in the page header, the scope switcher, and the search box all disappear**: there is nothing to filter over, and a second entry point would only compete with the two buttons in the empty state. Once a pack exists the page returns to its regular layout — **Create** in the top-right corner, the scope filter and search at the top. When you enter within a team scope, the two empty-state entries bind the new session to that selected team. + +If you lack account-level edit permission, account scope shows a one-liner — "no account knowledge base yet" — instead of the full empty state; the toolbar stays, so you can switch to a team scope and inspect that team's pack. - Click **Create** in the top-right corner of the page to open the "Create knowledge base" dialog. A Knowledge Pack has no editable name of its own — it's a singleton resource per target (account or team), so the dialog only asks you to choose a **Scope**: Shared or a specific team. To create a team-level pack, you must belong to the target team; Shared-scope creation is limited to the Account Owner or admins. Each target can own only one pack. Accounts and teams that already have a pack remain in the dropdown and are marked as having an existing Knowledge Pack; after you select one, the primary button becomes **Open knowledge base** and opens that pack instead of creating another. Choose a scope without a pack and click **Create** to make one. The console checks again immediately before creation, so if someone else has just created a pack for that scope, it opens the existing pack instead. The console uses the scope (Shared / team name) as the pack's display identifier. + Click **Create** in the top-right corner of the page (this button appears only once a pack already exists — see the empty-state note above) to open the "Create knowledge base" dialog. A Knowledge Pack has no editable name of its own — it's a singleton resource per target (account or team), so the dialog only asks you to choose a **Scope**: Shared or a specific team. To create a team-level pack, you must belong to the target team; Shared-scope creation is limited to the Account Owner or admins. Each target can own only one pack. Accounts and teams that already have a pack remain in the dropdown and are marked as having an existing Knowledge Pack; after you select one, the primary button becomes **Open knowledge base** and opens that pack instead of creating another. Choose a scope without a pack and click **Create** to make one. The console checks again immediately before creation, so if someone else has just created a pack for that scope, it opens the existing pack instead. The console uses the scope (Shared / team name) as the pack's display identifier. Click any row in the list to open the inspector. The left panel shows the file tree; the right panel is an inline editor. Click **New File** to enter a filename (e.g., `runbook.md`), or use **Upload** to import a local file. Markdown files support both **Preview** and **Source** views. Click **Save** after editing. diff --git a/en/ai-sre/mcp.mdx b/en/ai-sre/mcp.mdx index 8ae3b3d6..a68e5500 100644 --- a/en/ai-sre/mcp.mdx +++ b/en/ai-sre/mcp.mdx @@ -43,38 +43,43 @@ In AI SRE, MCP extends an agent's capability from "built-in tools" to "any exter --- -Go to **Plugins → MCP** and click **Browse Marketplace** to open the **MCP Directory**, where you can browse Flashduty's curated selection of third-party MCP server templates. +Go to **Plugins → Overview** to see Flashduty's curated catalog of third-party MCP templates. The catalog is grouped into category tabs (All / Observability / Cloud & Infrastructure / Database & Middleware / Code & Collaboration / Skill), and the search box in the top-right corner filters it by name or description. + +The **Recommended** block above the catalog ("Recommended from your alert sources and common usage. Once connected, AI SRE can use them during investigations.") picks the templates that match the **alert integrations already configured** in your account (ranked by alert count over the last 30 days), then fills in the picks promoted for your current locale, showing at most 3. Installed templates, templates that need a Runner, and templates you may not install never appear here. - - Click **Browse Marketplace** on the MCP list page to open a directory dialog that displays all available MCP server templates in a card grid. Each card shows the MCP server name, author, description, and tags. + + Each catalog row shows the template name and description; when MCP / App / Skill rows are mixed on the same screen, the row also carries a kind tag. An uninstalled row has a round **+** button on its right: clicking **+** or clicking the row itself opens the **Connect** dialog. When a template needs a self-hosted Runner and the account has no Runner available, the row's tag reads **Requires a self-hosted runner**; for a template you may not install, the row shows no **+** and is not clickable. - - Click any MCP server card to open the detail view, which shows the full description, transport, authorization mode, whether a BYOC Runner is required, the vendor, connection parameter documentation, and a link to the vendor's official docs. Cards for already-installed MCP servers display a gear icon; clicking it takes you directly to that server's edit form. + + The dialog first shows the template's details: the vendor ("Official Datadog · MCP" or "Community · MCP"), a link to its official docs, its description, and a set of "**Ask things like this once connected**" example questions. Then come the connection parameters the template declares — URL / endpoint placeholders and environment variables. **Shared** fields (shared account-wide under service authorization) are labelled "Shared by the whole account."; in **Member authorization (key)** mode there is also a "**Your API token**" field (a password input) labelled "Only for your own use — other members fill in their own the first time they use it.", with a "How to get one" docs link beside it. - - For MCP servers not yet installed, click the **Install** button on the card. The system opens a new AI SRE session and injects the server's template metadata. The agent then guides you through entering the endpoint URL, completing credential authorization, and calls `tool_search` (probing the server's tools by name) to verify connectivity — the entire install flow happens **inside the conversation**, not through a one-click write to the database. + + Choose the **execution environment** this connection should be made from — "Connects in the session's own environment by default."; for a template that needs a self-hosted Runner, the hint reads "If 〈name〉 is only reachable on your internal network, pick a self-hosted Runner on that network." The primary button is **Connect**; for a **Member authorization (OAuth)** template it reads "**Authorize 〈name〉**" and creates the connector first, then opens the browser authorization window (OAuth discovery, Dynamic Client Registration, and token exchange all run from the chosen environment). - - Once the agent verifies connectivity, it writes the MCP server into the account via the `/safari/mcp/server/create` endpoint. The server then appears in the MCP list and is marked as "Installed" on the catalog card. + + On submit, the system creates the connector and runs one connection test against it. On success the dialog shows "**〈name〉 connected**" plus the example questions: click **Done** to close it, or click an example question to open a new chat with that question **prefilled** (it is not sent automatically). On failure it shows "**The connection test failed**" with the raw error: click **Retry** to re-run the test; for a template that needs a Runner, a non-cloud environment also offers a "Let AI SRE install it on the Runner" entry, which opens a new chat that carries no token and asks the agent to install the missing command-line tools (`npx`'s Node.js, `uvx`'s uv) on the self-hosted Runner. -The marketplace directory is **browse-only** — there is no one-click install endpoint. This is intentional: installing an MCP server must involve credential entry and connectivity verification. Skipping those steps would leave dead configurations in the account that can never actually connect. Conversational install ensures every MCP server has been validated by a real agent invocation before it is considered ready. +The dialog creates the connector on the **first submit only**. After that its connection parameters are locked, and it shows "This connector already exists, created from the values you first submitted. To change them, edit it under Plugins → MCP." When the name collides with a connector that already exists, the dialog no longer echoes the backend error and instead shows "This account already has a connector named “〈name〉”. If that is the one you want, finish authorizing it under Plugins → MCP; otherwise an account owner or admin has to rename or delete it there first, then connect again." Installed MCP servers record a `source_template_name` field that points back to the originating template, making it easy to trace the server's provenance later. **Marketplace-installed MCP servers are fixed to Shared scope** (account-level, `team_id` is 0) and available to all members of the account; no team owner can be chosen at install time, and the same template can only produce one instance per account. -MCP servers marked **requires Runner** (shown by the `requires_runner` flag in the detail view; `requires_runner` is independent of transport, and the entire current curated catalog is HTTP Streaming and needs no Runner) can only be used in environments where you have a BYOC Runner deployed; cloud Sandboxes do not support them. Before installing, confirm your account has a working BYOC Runner configured. See [Environments (BYOC)](/en/ai-sre/environments). +A template that **requires a Runner** (the `requires_runner` flag in its details) can only be used in an environment where you have deployed a BYOC Runner; cloud Sandboxes do not support it. `requires_runner` is independent of transport, and today's 55-template catalog contains both kinds: 27 **HTTP Streaming** templates that need no Runner, and 28 **stdio** templates that do — including Alibaba Cloud OpenAPI, Alibaba Cloud Observability, Nightingale (n9e), Gitee, GreptimeDB, Prometheus, Grafana (Self-Hosted), Kafka (Confluent), AWS, ClickHouse, MySQL, PostgreSQL, Alibaba Cloud RDS, Redis, and MongoDB. Those stdio templates are launched locally on each environment via `uvx` / `npx`, and a cloud Sandbox cannot launch local subprocesses; confirm your account has a working BYOC Runner configured before installing. See [Environments (BYOC)](/en/ai-sre/environments). ## Adding an MCP Server --- -Go to **Plugins → MCP**, click **Add Server** in the top-right corner, and fill in the form to define an MCP server. +Go to **Plugins → MCP**. The list page has two entries in the top-right corner: + +- **Add MCP**: opens a form for defining an MCP server by hand (its fields are listed under "Basic Fields" below). +- **Add in chat**: opens a new AI SRE session with this prompt prefilled — "I want to add an MCP server. First ask whether I want to connect my own service or install an existing one. If it is an existing one, ask which system I want to connect. If the plugin catalog has it, install it; if not, search the web and show me a few to pick from. Install the one I choose." The agent first asks whether you want to connect your own service or install an existing one; for an existing one it checks the plugin catalog first and only searches the web for candidates when the catalog has none, then completes the connection once you pick (nothing is sent automatically — you can edit the prompt first). The button appears only when there is an unambiguous scope to write into: always when you can author at account scope, otherwise when the current scope filter selects exactly one team you belong to (or you belong to exactly one team). ### Basic Fields @@ -176,6 +181,8 @@ The MCP list displays each server's **name** (including its AI description), **s Toggle the switch in the list. Only **enabled** servers are available to the agent; disabled servers are invisible to agents and cannot be called. + + A disabled connector (any status other than `enabled`) is **not reported as connected** by the connect entry point: opening its **Connect** dialog shows "**Connector disabled**" and "This connector is disabled, so AI SRE can't use it. An account owner or admin has to re-enable it under Plugins → MCP.", with an **Open Plugins → MCP** button that jumps to that connector's row. An account owner or admin re-enables it there with the **Enable** toggle. Disabled connectors also drop out of the **Enabled** strip on Plugins → Overview. Click the edit button (or click the row) to open the form. You can modify the name, transport, description, endpoint/command, authorization mode, and scope. If you do not have edit permission, the form opens in **read-only** mode with an explanation. @@ -210,7 +217,7 @@ MCP shares the same **two-level scope** model as other resources (Skills, Knowle **Edit permissions**: team-level MCP servers can be acted on only by members of that team — organization admins must join the team first; Shared-scope MCP servers can be acted on only by the account owner or admins. There is no creator-retains-rights exception. When you lack edit permission, the toggle and action buttons for that row appear as **read-only**. -**Create and reassign**: to create a new team-level MCP server, you must belong to the target team; Shared-scope creation is limited to the account owner or admins. **Marketplace installs are the exception**: they are fixed to Shared scope, and any account member can install one (no owner/admin permission required). When editing an existing MCP server, the account owner or admins can move it to any team to recover resources left behind by empty teams or departed members; regular members can move it only to teams they belong to. However, **Marketplace-installed MCP servers cannot be reassigned to a team** — their scope is shown as a fixed Shared value in the edit form. A small number of legacy team-scoped Marketplace rows from earlier versions can still be changed back to Shared scope; the reverse is not allowed. **Promoting to Shared scope carries the same gate as Shared-scope creation: only the account owner or admins can do it** — a regular member is denied even for servers belonging to their own team, and the prompt now reads "ask an admin to make it shared" instead of naming the owning team. +**Create and reassign**: to create a new team-level MCP server, you must belong to the target team; Shared-scope creation is limited to the account owner or admins. **Marketplace installs are the exception**: they are fixed to Shared scope and started by the installer, and any account member can install an ordinary template (no owner/admin permission required). One class of template is the exception to that exception: when a template in a **member authorization** mode (key or OAuth) also asks for a free-form address / host parameter, the installer would effectively decide where other members' credentials are sent — so, to keep other members' credentials from flowing to a host the installer chose, installing and connecting that template is **limited to the account owner or admins**. A regular member sees no **+** on that row (and cannot click it), and opening its **Connect** dialog directly says "You don't have permission to install this. Ask your account owner or an admin." When editing an existing MCP server, the account owner or admins can move it to any team to recover resources left behind by empty teams or departed members; regular members can move it only to teams they belong to. However, **Marketplace-installed MCP servers cannot be reassigned to a team** — their scope is shown as a fixed Shared value in the edit form. A small number of legacy team-scoped Marketplace rows from earlier versions can still be changed back to Shared scope; the reverse is not allowed. **Promoting to Shared scope carries the same gate as Shared-scope creation: only the account owner or admins can do it** — a regular member is denied even for servers belonging to their own team, and the prompt now reads "ask an admin to make it shared" instead of naming the owning team. **Runtime visibility**: At session start, the agent is offered only **Shared-scope** MCP servers and servers belonging to the **team bound to the current session**. Once the agent reads a team's knowledge during an investigation, that team's MCP servers and Skills are mounted into the session on demand. **The account is the only security boundary at runtime; the team is an ownership and editing tag only.** diff --git a/en/ai-sre/overview.mdx b/en/ai-sre/overview.mdx index b7fe196d..d8d6a6fc 100644 --- a/en/ai-sre/overview.mdx +++ b/en/ai-sre/overview.mdx @@ -53,17 +53,30 @@ AI SRE is more than a chat box in the console — it covers multiple collaborati --- -AI SRE is available to accounts on an On-call Pro or higher subscription, with no application needed. +AI SRE is available to accounts holding a **valid (enabled)** On-call Pro or higher subscription, with no application needed. AI SRE is billed on actual usage starting September 16, 2026, 08:00 Beijing time, settled in credits (1 credit = ¥1); usage before that date isn't charged. Metered items are model usage, sandbox online time, and web search. Activation is free, eligible accounts get gift credits each billing period, and you can also buy credit packages (1,000 / 5,000 / 20,000 credits for ¥950 / ¥4,500 / ¥17,000, valid 365 days from payment). Pay-as-you-go is off by default; an admin can turn it on and optionally set a per-period cap. Full terms: [AI SRE Credit Package Purchase Agreement](/en/compliance/ai-sre-credit-package-purchase-agreement). + + When the gift credits, credit packages, and wallet balance are all used up, AI SRE refuses new conversation requests: the composer is disabled and a **non-dismissible** notice stays pinned above it (the text comes from the server and covers all three shapes — "not activated yet", "this period's allowance is spent", and "the wallet is empty"), with a **Go to AI SRE billing** button that opens the billing center (`/wallet/plan?product=ai-sre`). An admin turns on pay-as-you-go, buys a credit package, or tops up the wallet there; once the state recovers, you can continue the conversation. Production changes, restarts, rollbacks, and external notifications all require your confirmation before they execute. +### When the subscription lapses + +Every request re-checks the account's On-call subscription, so a subscription that lapses pauses AI SRE immediately. The interface shows the server's own sentence, and distinguishes two cases whose remedies differ — don't apply the wrong one: + +| Case | Notice | Remedy | +|------|--------|--------| +| The subscription's tier is Pro or higher, but the subscription isn't enabled (expired, or disabled for any other reason) | "This account's On-call subscription is no longer active (expired or disabled), so AI SRE is paused. It resumes automatically once the On-call subscription is renewed." | Renew the On-call subscription | +| The account has no subscription, or its subscription is below Pro (whether or not it is enabled) | "AI SRE requires the On-call Professional plan. Upgrade the subscription to continue." | Upgrade to Pro or higher | + +The notice also appears in the console chat page (pinned above the composer, with a **Go to AI SRE billing** entry on its right), in IM sessions, in automation run records, and in A2A call errors; the credits overview reports the same sentence as `blocked_reason=no_license`. Recovery needs no re-activation: once the renewal or upgrade lands, the next request is admitted automatically. + ## Core Capabilities --- diff --git a/en/ai-sre/sessions.mdx b/en/ai-sre/sessions.mdx index 9175641a..e07c6715 100644 --- a/en/ai-sre/sessions.mdx +++ b/en/ai-sre/sessions.mdx @@ -137,7 +137,11 @@ When the console publishes a new version, a **version update notice** appears ab **Inline truncation of large files**: apart from archives (which are not parsed — the agent gets only their sandbox path), attachments reach the agent as extracted text. When the extracted text exceeds **64 KB**, only the first **32 KB** is inlined (cut at a valid UTF-8 boundary), and the attachment envelope carries a pointer to the full file staged in the sandbox (like `~/.flashduty/attachments/...`) — if the full content matters, ask the agent to read the file from the sandbox with the read / bash tools; nothing is lost. In addition, PDFs larger than **3 MB** are no longer passed natively to the model; they fall back to text extraction under the same truncation rule. - When you enter AI SRE from an incident, alert, monitor rule, or monitor target page, the related object is embedded into the input box as a **reference capsule** — a small inline tag indicating the kind of object referenced — an incident, alert event, alert, monitor rule, host, monitor target, or on-call analytics — that travels with the message so the agent can start its analysis from that object directly. Click the capsule to open the referenced object in a new tab, or click its close button to remove the reference before sending. A single message can carry multiple references. Besides objects carried in automatically from a related page, you can also type `@` directly in any session's input box to trigger an incident search dropdown (supporting fuzzy keyword search and a list of recent incidents); selecting one inserts the same kind of reference capsule — a standalone entry point available at any time. Typing an email address does not false-trigger it: when the `@` directly follows an email-address character (a letter, a digit, or one of `._%+-`), the picker does not open; an `@` after a space or adjacent to Chinese text still triggers it. + When you enter AI SRE from an incident, alert, monitor rule, monitor target, or datasource page, the related object is embedded into the input box as a **reference capsule** — a small inline tag indicating the kind of object referenced — an incident, alert event, alert, monitor rule, host, monitor target, datasource, or on-call analytics — that travels with the message so the agent can start its analysis from that object directly. Click the capsule to open the referenced object in a new tab, or click its close button to remove the reference before sending. + + **These entry points also carry a default question**, and the capsule sits at the spot the entry marks inside that sentence. Entering from a monitor target page, for instance, pre-fills "请分析这个监控对象 〈reference capsule〉:基于真实观测数据判断当前是否有问题或隐患,说明依据与影响面,并给出下一步排查或处置建议。请区分已确认事实、合理推断和待确认项;证据不足时明确说明不确定,不要过度归因"; entering from the datasource list pre-fills "调用 〈reference capsule〉 的 overview tool,校验 overview tool 是否可用". The prefill is only a draft — edit it, delete it, or send it as-is. + + **A multi-object batch handoff pre-fills nothing**: when you click **AI analysis** on several incidents from the incident list, the input box gets no default question (it may stay empty) and no reference capsule lands in it — those references travel with the message instead. A single message can carry multiple references. Besides objects carried in automatically from a related page, you can also type `@` directly in any session's input box to trigger an incident search dropdown (supporting fuzzy keyword search and a list of recent incidents); selecting one inserts the same kind of reference capsule — a standalone entry point available at any time. Typing an email address does not false-trigger it: when the `@` directly follows an email-address character (a letter, a digit, or one of `._%+-`), the picker does not open; an `@` after a space or adjacent to Chinese text still triggers it. When a session starts, the knowledge packs and skills for the bound team are loaded automatically. See Knowledges and Skills for details. @@ -221,6 +225,36 @@ When a tool or MCP call is blocked because it lacks credentials or has not compl OAuth authorization links expire. After expiry, the card shows "Authorization link expired, please retrigger the task" — you need to start a new task to get a fresh authorization link. + +Channel differences: only the console (`web`) renders this card. IM and API channels get no card — the agent pastes the OAuth authorize link into its reply as plain text, and you open it in a desktop browser, complete the authorization, then reply "authorized" or "已授权" to continue (continuing is your explicit action, not an automatic OAuth callback side effect); for a secret-type credential there is no safe way to collect it over IM, so the agent tells you to open **this same session in the web console** and **never** asks you to paste the secret into the chat. On the automation channel nobody is watching, so the agent does not wait — it records the missing authorization as a blocker and delivers what it can. See [IM Platforms](/en/ai-sre/im#connections-and-authorization). + + +### When a Connection Is Required + +When the agent searches for a capability to fulfil your request (`tool_search`), that search matches no tool at all, and the query happens to name a vendor that **exists in the marketplace catalog but your account has not connected**, a **"Connect [vendor] to continue"** card appears inline in the chat stream: the vendor's icon and name on the left, a **Connect** button on the right, and nothing else. + +This means you **don't have to go hunting in the plugins marketplace first**: name the vendor right in the conversation (for example, "connect Datadog for me") and you reach the connection step from there. If the catalog has no connector for that vendor at all, the agent just says so instead of showing a card. + +Clicking **Connect** opens that vendor's connect dialog (the same one as in the plugins marketplace: enter credentials, complete authorization and the connection test). Once the connection succeeds, the card flips in place: + +| Phase | Card state | +|---|---| +| Not connected | Title **"Connect [vendor] to continue"**, the vendor's description underneath, button **Connect** | +| Connected | Title **"[vendor] connected"**, subtitle "Click Continue task and AI SRE will pick up where it left off", button **Continue task** | + +Clicking **Continue task** makes the console send "连接已完成,请继续之前的任务。" to the agent on your behalf so it resumes the original task — **closing the connect dialog does not resume the turn by itself**; resuming is always an explicit click from you. After a page reload the card still shows as connected: the console re-derives that from the template's own install state in the account, not from temporary browser state. + +In the following cases **no card** appears and the agent just says so in one sentence: + +- The account already has a connector for that vendor (in any team, in any environment): the agent says it is connected (or that it must be enabled or its credentials re-entered). +- Only an account Owner or admin may connect that vendor: the agent says an admin has to do it. +- The vendor's template requires a self-hosted Runner and this session has no Runner available. +- The search matched a connected tool — a connect card only appears for "zero matches + the query names an unconnected marketplace template". + + +Channel differences: only the console (`web`) renders this card. IM and API channels get no card — the agent gives the connect URL as a plain link and asks you to reply "继续" or "continue" once connected, then carries on; on the automation channel nobody is watching, so the agent does not wait — it records the missing connection as a blocker and delivers what it can. The full per-channel behavior is in [IM Platforms](/en/ai-sre/im#connections-and-authorization). + + ### Subagents When the agent delegates a subtask, a clickable **dispatch reference line** appears in the conversation: a status ring, a type badge (Agent / A2A), the subtask's name and current intent, and right-aligned status and elapsed time; once finished it adds the tool-call count / token usage / total duration, and on failure a red reason line appears underneath. The reference line is a compact single row and carries **no stop button** — stopping lives in the task panel's detail header (see "Task Panel and Background Tasks" below). Clicking the line opens a subagent session panel on the right, side by side with the main conversation — the main chat area shrinks accordingly rather than being covered by a modal. The panel can be expanded to fill the main area, or collapsed back to the side-by-side layout. diff --git a/en/ai-sre/skills.mdx b/en/ai-sre/skills.mdx index 164cc4c5..b70d55a9 100644 --- a/en/ai-sre/skills.mdx +++ b/en/ai-sre/skills.mdx @@ -1,6 +1,6 @@ --- title: Skills -description: A Skill is a reusable capability bundle — a SKILL.md instruction file plus a declared set of allowed tools — that the AI SRE agent can invoke on demand during a conversation. Install from the Marketplace, upload your own, or create one in conversation with skill-creator. +description: A Skill is a reusable capability bundle — a SKILL.md instruction file plus a declared set of allowed tools — that the AI SRE agent can invoke on demand during a conversation. Install from the Marketplace, upload your own, or have skill-creator add one in a conversation — either authored from scratch or found online. keywords: ["AI SRE", "Skill", "SKILL.md", "Marketplace", "skill-creator", "agent", "resources"] sidebarTitle: Skills --- @@ -48,6 +48,9 @@ tags: - tag2 author: author-name license: MIT +examples: + - zh: 帮我看看这个服务最近的延迟为什么升高 + en: Why has this service's latency gone up recently? allowed-tools: bash, read, task --- @@ -68,6 +71,7 @@ Frontmatter fields: | `license` | string | No | License identifier. | | `allowed-tools` | string[] | No | List of tools this skill is allowed to use. Leave empty for no additional restrictions. | | `venues` | string[] | No | Restricts this skill to specific kinds of execution environment. Accepts `cloud` (Cloud Sandbox) and `byoc` (self-hosted Runner); multiple values allowed. Leave empty to make it available everywhere. Values are validated at upload time and an invalid one is rejected. | +| `examples` | object[] (`{zh, en}`) | No | "Try asking" sample questions. At most **10** entries, and both the Chinese and the English text are required. Each is validated under the same rule as `description` (no angle brackets `<` / `>`, at most 1024 characters), and an oversized or invalid entry is **rejected at upload time**. | Use `venues` for skills that structurally depend on one kind of environment — for example, a skill that needs a local binary, local files, or a private-network service on the environment's host simply cannot work inside a Cloud Sandbox. With `venues: [byoc]` declared, the skill neither appears in the agent's available-skills list nor can be triggered by `/` in a Cloud Sandbox session; only sessions bound to a self-hosted Runner can see and call it. @@ -86,25 +90,22 @@ The AI SRE runtime bundles a few skills that are available without installation. --- -Go to **Plugins → Skill** and click **Browse Marketplace** to open the skill **catalog**, where you can browse and install skill templates provided by Flashduty and Anthropic. +The skill catalog is part of the plugin catalog on **Plugins → Overview**: skill templates are listed alongside MCP templates and the built-in Apps, grouped by category, with skills in their own **Skill** category (they also appear under the "All" tab, tagged `Skill`). The search box above the catalog filters by name or description, the tabs narrow the list to one category, and "See all N" next to a category heading expands that category; the search term and the selected tab live in the page URL, so leaving and coming back lands you where you were. - - On the skill list page, click **Browse Marketplace** to open the catalog dialog, which displays all available skill templates in a card grid. - - - Use the search box at the top to search by name or description. The **Filter** in the top-right corner lets you view only "Installed" or "Not Installed" skills; **Sort** supports "Installed First" or "Name A–Z". + + On **Plugins → Overview**, find the template under the **Skill** category; each row shows the template name and description. - Click the **+** button on any uninstalled card to open the `Install skill ""` confirmation dialog, which notes "Installs to the account — available to all members": Marketplace installs are always **shared scope** (shared = account level), with no owner selection. Click **Install** to actually call the install endpoint — this copies the template content into your account as a regular skill entry and marks its source template (shown as a `v` badge on the card to indicate "from Marketplace"). + An uninstalled row has a round **+** button on its right; clicking **+** or the row itself opens the `Install skill ""` confirmation dialog: "Installs to the account — available to all members." Marketplace installs are always **shared scope** (shared = account level), with no owner selection. Click **Install** to actually call the install endpoint — this copies the template content into your account as a regular skill entry and marks its source template (shown on the list as a `From Marketplace —