Add app-server APIs for stored thread attachments (#44564)

## What changed

- Add `thread/attachment/add`, `thread/attachment/list`, and `thread/attachment/remove` to manage durable resource references without loading the owning thread or changing conversation history.
- Identify attachments by thread, `attachmentType`, and `identityKey`. Repeated adds return the existing attachment; repeated removals succeed without emitting another update. Listing supports cursor pagination.
- Broadcast `thread/attachment/updated` after creation or deletion, after responding to the requester. Serialize attachment mutations with thread lifecycle operations and reject stores that do not support attachments.
- Update protocol schemas, TypeScript and Python bindings, documentation, and TUI notification handling so attachment updates do not add untracked threads to the agent picker.

## Testing

Add coverage for unloaded threads, pagination, idempotent addition and removal, reattachment, invalid inputs, unsupported stores, response-before-notification ordering across clients, and TUI notification routing.

GitOrigin-RevId: 0d29cf903afd4e931a70a9cf96300475213b34aa
This commit is contained in:
joeytrasatti-openai authored and copyberry committed 2026-09-10 15:52:09 +00:00
1 parent 94697375cb
commit 3319d9b296
47 files changed
+2341 -6

No files matched your search

@@ -2022,6 +2022,78 @@
"title": "Thread/metadata/updateRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/attachment/add"
],
"title": "Thread/attachment/addRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadAttachmentAddParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/attachment/addRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/attachment/list"
],
"title": "Thread/attachment/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadAttachmentListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/attachment/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"thread/attachment/remove"
],
"title": "Thread/attachment/removeRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadAttachmentRemoveParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Thread/attachment/removeRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -15819,6 +15891,26 @@
"title": "Thread/name/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"thread/attachment/updated"
],
"title": "Thread/attachment/updatedNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/ThreadAttachmentUpdatedNotification"
}
},
"required": [
"method",
"params"
],
"title": "Thread/attachment/updatedNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -18267,6 +18359,200 @@
"title": "ThreadArchivedNotification",
"type": "object"
},
"ThreadAttachment": {
"description": "An independently persisted attachment associated with a thread.",
"properties": {
"attachmentType": {
"type": "string"
},
"createdAt": {
"format": "int64",
"type": "integer"
},
"id": {
"type": "string"
},
"identityKey": {
"type": "string"
},
"payload": true
},
"required": [
"attachmentType",
"createdAt",
"id",
"identityKey",
"payload"
],
"type": "object"
},
"ThreadAttachmentAddOutcome": {
"description": "Result of attempting to associate an attachment with a thread.",
"enum": [
"created",
"existing"
],
"type": "string"
},
"ThreadAttachmentAddParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Parameters for creating or locating an attachment on its owning thread.",
"properties": {
"attachmentType": {
"type": "string"
},
"identityKey": {
"type": "string"
},
"payload": true,
"threadId": {
"type": "string"
}
},
"required": [
"attachmentType",
"identityKey",
"payload",
"threadId"
],
"title": "ThreadAttachmentAddParams",
"type": "object"
},
"ThreadAttachmentAddResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "The created or existing attachment.",
"properties": {
"attachment": {
"$ref": "#/definitions/ThreadAttachment"
},
"outcome": {
"$ref": "#/definitions/ThreadAttachmentAddOutcome"
}
},
"required": [
"attachment",
"outcome"
],
"title": "ThreadAttachmentAddResponse",
"type": "object"
},
"ThreadAttachmentListParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Parameters for listing attachments from one thread.",
"properties": {
"cursor": {
"type": [
"string",
"null"
]
},
"limit": {
"format": "uint32",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"threadId": {
"type": "string"
}
},
"required": [
"threadId"
],
"title": "ThreadAttachmentListParams",
"type": "object"
},
"ThreadAttachmentListResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "One page of attachments associated with the requested thread.",
"properties": {
"data": {
"items": {
"$ref": "#/definitions/ThreadAttachment"
},
"type": "array"
},
"nextCursor": {
"type": [
"string",
"null"
]
}
},
"required": [
"data"
],
"title": "ThreadAttachmentListResponse",
"type": "object"
},
"ThreadAttachmentOperation": {
"description": "The persisted attachment change represented by a notification.",
"enum": [
"created",
"deleted"
],
"type": "string"
},
"ThreadAttachmentRemoveParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Parameters for deleting an attachment by its stable thread-local identity.",
"properties": {
"attachmentType": {
"type": "string"
},
"identityKey": {
"type": "string"
},
"threadId": {
"type": "string"
}
},
"required": [
"attachmentType",
"identityKey",
"threadId"
],
"title": "ThreadAttachmentRemoveParams",
"type": "object"
},
"ThreadAttachmentRemoveResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Successful deletion does not return additional attachment data.",
"title": "ThreadAttachmentRemoveResponse",
"type": "object"
},
"ThreadAttachmentUpdatedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Notification published after a thread attachment is created or deleted.",
"properties": {
"attachmentId": {
"type": "string"
},
"attachmentType": {
"type": "string"
},
"identityKey": {
"type": "string"
},
"operation": {
"$ref": "#/definitions/ThreadAttachmentOperation"
},
"threadId": {
"type": "string"
}
},
"required": [
"attachmentId",
"attachmentType",
"identityKey",
"operation",
"threadId"
],
"title": "ThreadAttachmentUpdatedNotification",
"type": "object"
},
"ThreadClosedNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {