diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go index 8520b6643..20056d56e 100644 --- a/clients/go/ahptypes/state.generated.go +++ b/clients/go/ahptypes/state.generated.go @@ -291,7 +291,6 @@ const ( ToolResultContentTypeResource ToolResultContentType = "resource" ToolResultContentTypeFileEdit ToolResultContentType = "fileEdit" ToolResultContentTypeTerminal ToolResultContentType = "terminal" - ToolResultContentTypeTerminalComplete ToolResultContentType = "terminalComplete" ToolResultContentTypeSubagent ToolResultContentType = "subagent" ) @@ -2151,37 +2150,23 @@ type ToolResultFileEditContent struct { // // Clients can subscribe to the terminal's URI to stream its output in real // time, providing live feedback while a tool is executing. +// +// When the command exits, {@link result} is filled in on the completed +// result, retaining the outcome for clients that did not subscribe. This +// records the command's exit, not the terminal's — the terminal may keep +// running afterwards. type ToolResultTerminalContent struct { Type ToolResultContentType `json:"type"` // Terminal URI (subscribable for full terminal state) Resource URI `json:"resource"` // Display title for the terminal content Title string `json:"title"` -} - -// Record of a command executed by a terminal-style tool (e.g. a shell tool), -// appended to the tool result when the command exits. -// -// This records the command's exit, not the terminal's — the terminal may -// keep running afterwards. -// -// When live output was exposed through a terminal channel (a -// {@link ToolResultTerminalContent} block in the same tool result), -// {@link resource} identifies that channel; otherwise this block stands alone -// as the retained command result. -type ToolResultTerminalCompleteContent struct { - Type ToolResultContentType `json:"type"` - // URI of the `ahp-terminal:` channel that carried live output for this - // command, if one was exposed. - Resource *URI `json:"resource,omitempty"` - // Exit code from the completed command, if reported by the runtime - ExitCode *int64 `json:"exitCode,omitempty"` - // Working directory where the command was executed - Cwd *URI `json:"cwd,omitempty"` - // Preview of the command's output, if available - Preview *string `json:"preview,omitempty"` - // Whether `preview` is known to be incomplete or truncated - Truncated *bool `json:"truncated,omitempty"` + // Whether this terminal-style resource is backed by a pseudoterminal. + // When `false`, output is plain text and clients do not need to parse + // VT sequences. + IsPty *bool `json:"isPty,omitempty"` + // Outcome of the command, present once it has exited. + Result *TerminalCommandResult `json:"result,omitempty"` } // A reference, embedded in a tool result, to a worker chat spawned by the tool @@ -2945,6 +2930,20 @@ type FileEdit struct { Diff *json.RawMessage `json:"diff,omitempty"` } +// Outcome of a command run in a terminal-style tool, filled in on +// {@link ToolResultTerminalContent.result} once the command exits. +type TerminalCommandResult struct { + // Exit code from the completed command, if reported by the runtime + ExitCode *int64 `json:"exitCode,omitempty"` + // Preview of the command's output, for clients that are not subscribed + // to the terminal or that arrive after it is disposed. When `isPty` is + // `true` the preview may contain VT sequences; when `false` it is plain + // text. + Preview *string `json:"preview,omitempty"` + // Whether `preview` is known to be incomplete or truncated + Truncated *bool `json:"truncated,omitempty"` +} + // Lightweight terminal metadata exposed on the root state. type TerminalInfo struct { // Terminal URI (subscribable for full terminal state) @@ -3005,6 +3004,10 @@ type TerminalState struct { // Do NOT use the presence of a `command` part as a feature flag — parts // are absent in the normal idle state. SupportsCommandDetection *bool `json:"supportsCommandDetection,omitempty"` + // Whether this terminal-style resource is backed by a pseudoterminal. + // When `false`, output is plain text and clients do not need to parse + // VT sequences. + IsPty *bool `json:"isPty,omitempty"` } // Unstructured terminal output — content before, between, or after commands, @@ -4010,7 +4013,6 @@ func (*ToolResultEmbeddedResourceContent) isToolResultContent() {} func (*ToolResultResourceContent) isToolResultContent() {} func (*ToolResultFileEditContent) isToolResultContent() {} func (*ToolResultTerminalContent) isToolResultContent() {} -func (*ToolResultTerminalCompleteContent) isToolResultContent() {} func (*ToolResultSubagentContent) isToolResultContent() {} // ToolResultContentUnknown carries an unrecognized ToolResultContent variant — typically a discriminator value introduced by a newer protocol version. The original JSON object is preserved verbatim so that re-encoding round-trips faithfully. @@ -4057,12 +4059,6 @@ func (u *ToolResultContent) UnmarshalJSON(data []byte) error { return err } u.Value = &value - case "terminalComplete": - var value ToolResultTerminalCompleteContent - if err := json.Unmarshal(data, &value); err != nil { - return err - } - u.Value = &value case "subagent": var value ToolResultSubagentContent if err := json.Unmarshal(data, &value); err != nil { diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt index a84af21f8..25d1312c0 100644 --- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt +++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt @@ -512,8 +512,6 @@ enum class ToolResultContentType { FILE_EDIT, @SerialName("terminal") TERMINAL, - @SerialName("terminalComplete") - TERMINAL_COMPLETE, @SerialName("subagent") SUBAGENT } @@ -3019,33 +3017,17 @@ data class ToolResultTerminalContent( /** * Display title for the terminal content */ - val title: String -) - -@Serializable -data class ToolResultTerminalCompleteContent( - val type: ToolResultContentType, - /** - * URI of the `ahp-terminal:` channel that carried live output for this - * command, if one was exposed. - */ - val resource: String? = null, - /** - * Exit code from the completed command, if reported by the runtime - */ - val exitCode: Long? = null, - /** - * Working directory where the command was executed - */ - val cwd: String? = null, + val title: String, /** - * Preview of the command's output, if available + * Whether this terminal-style resource is backed by a pseudoterminal. + * When `false`, output is plain text and clients do not need to parse + * VT sequences. */ - val preview: String? = null, + val isPty: Boolean? = null, /** - * Whether `preview` is known to be incomplete or truncated + * Outcome of the command, present once it has exited. */ - val truncated: Boolean? = null + val result: TerminalCommandResult? = null ) @Serializable @@ -3940,6 +3922,25 @@ data class FileEdit( val diff: JsonElement? = null ) +@Serializable +data class TerminalCommandResult( + /** + * Exit code from the completed command, if reported by the runtime + */ + val exitCode: Long? = null, + /** + * Preview of the command's output, for clients that are not subscribed + * to the terminal or that arrive after it is disposed. When `isPty` is + * `true` the preview may contain VT sequences; when `false` it is plain + * text. + */ + val preview: String? = null, + /** + * Whether `preview` is known to be incomplete or truncated + */ + val truncated: Boolean? = null +) + @Serializable data class TerminalInfo( /** @@ -4035,7 +4036,13 @@ data class TerminalState( * Do NOT use the presence of a `command` part as a feature flag — parts * are absent in the normal idle state. */ - val supportsCommandDetection: Boolean? = null + val supportsCommandDetection: Boolean? = null, + /** + * Whether this terminal-style resource is backed by a pseudoterminal. + * When `false`, output is plain text and clients do not need to parse + * VT sequences. + */ + val isPty: Boolean? = null ) @Serializable @@ -5465,7 +5472,6 @@ sealed interface ToolResultContent { @JvmInline value class Resource(val value: ToolResultResourceContent) : ToolResultContent @JvmInline value class FileEdit(val value: ToolResultFileEditContent) : ToolResultContent @JvmInline value class Terminal(val value: ToolResultTerminalContent) : ToolResultContent - @JvmInline value class TerminalComplete(val value: ToolResultTerminalCompleteContent) : ToolResultContent @JvmInline value class Subagent(val value: ToolResultSubagentContent) : ToolResultContent /** @@ -5495,7 +5501,6 @@ internal object ToolResultContentSerializer : KSerializer { "resource" -> ToolResultContent.Resource(input.json.decodeFromJsonElement(ToolResultResourceContent.serializer(), element)) "fileEdit" -> ToolResultContent.FileEdit(input.json.decodeFromJsonElement(ToolResultFileEditContent.serializer(), element)) "terminal" -> ToolResultContent.Terminal(input.json.decodeFromJsonElement(ToolResultTerminalContent.serializer(), element)) - "terminalComplete" -> ToolResultContent.TerminalComplete(input.json.decodeFromJsonElement(ToolResultTerminalCompleteContent.serializer(), element)) "subagent" -> ToolResultContent.Subagent(input.json.decodeFromJsonElement(ToolResultSubagentContent.serializer(), element)) else -> ToolResultContent.Unknown(obj) } @@ -5510,7 +5515,6 @@ internal object ToolResultContentSerializer : KSerializer { is ToolResultContent.Resource -> output.json.encodeToJsonElement(ToolResultResourceContent.serializer(), value.value) is ToolResultContent.FileEdit -> output.json.encodeToJsonElement(ToolResultFileEditContent.serializer(), value.value) is ToolResultContent.Terminal -> output.json.encodeToJsonElement(ToolResultTerminalContent.serializer(), value.value) - is ToolResultContent.TerminalComplete -> output.json.encodeToJsonElement(ToolResultTerminalCompleteContent.serializer(), value.value) is ToolResultContent.Subagent -> output.json.encodeToJsonElement(ToolResultSubagentContent.serializer(), value.value) is ToolResultContent.Unknown -> value.raw } diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs index 1bc1f6635..d1bfef63d 100644 --- a/clients/rust/crates/ahp-types/src/state.rs +++ b/clients/rust/crates/ahp-types/src/state.rs @@ -407,8 +407,6 @@ pub enum ToolResultContentType { FileEdit, #[serde(rename = "terminal")] Terminal, - #[serde(rename = "terminalComplete")] - TerminalComplete, #[serde(rename = "subagent")] Subagent, } @@ -2667,6 +2665,11 @@ pub struct ToolResultFileEditContent { /// /// Clients can subscribe to the terminal's URI to stream its output in real /// time, providing live feedback while a tool is executing. +/// +/// When the command exits, {@link result} is filled in on the completed +/// result, retaining the outcome for clients that did not subscribe. This +/// records the command's exit, not the terminal's — the terminal may keep +/// running afterwards. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToolResultTerminalContent { @@ -2674,37 +2677,14 @@ pub struct ToolResultTerminalContent { pub resource: Uri, /// Display title for the terminal content pub title: String, -} - -/// Record of a command executed by a terminal-style tool (e.g. a shell tool), -/// appended to the tool result when the command exits. -/// -/// This records the command's exit, not the terminal's — the terminal may -/// keep running afterwards. -/// -/// When live output was exposed through a terminal channel (a -/// {@link ToolResultTerminalContent} block in the same tool result), -/// {@link resource} identifies that channel; otherwise this block stands alone -/// as the retained command result. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct ToolResultTerminalCompleteContent { - /// URI of the `ahp-terminal:` channel that carried live output for this - /// command, if one was exposed. + /// Whether this terminal-style resource is backed by a pseudoterminal. + /// When `false`, output is plain text and clients do not need to parse + /// VT sequences. #[serde(default, skip_serializing_if = "Option::is_none")] - pub resource: Option, - /// Exit code from the completed command, if reported by the runtime - #[serde(default, skip_serializing_if = "Option::is_none")] - pub exit_code: Option, - /// Working directory where the command was executed - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cwd: Option, - /// Preview of the command's output, if available - #[serde(default, skip_serializing_if = "Option::is_none")] - pub preview: Option, - /// Whether `preview` is known to be incomplete or truncated + pub is_pty: Option, + /// Outcome of the command, present once it has exited. #[serde(default, skip_serializing_if = "Option::is_none")] - pub truncated: Option, + pub result: Option, } /// A reference, embedded in a tool result, to a worker chat spawned by the tool @@ -3568,6 +3548,25 @@ pub struct FileEdit { pub diff: Option, } +/// Outcome of a command run in a terminal-style tool, filled in on +/// {@link ToolResultTerminalContent.result} once the command exits. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct TerminalCommandResult { + /// Exit code from the completed command, if reported by the runtime + #[serde(default, skip_serializing_if = "Option::is_none")] + pub exit_code: Option, + /// Preview of the command's output, for clients that are not subscribed + /// to the terminal or that arrive after it is disposed. When `isPty` is + /// `true` the preview may contain VT sequences; when `false` it is plain + /// text. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub preview: Option, + /// Whether `preview` is known to be incomplete or truncated + #[serde(default, skip_serializing_if = "Option::is_none")] + pub truncated: Option, +} + /// Lightweight terminal metadata exposed on the root state. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -3640,6 +3639,11 @@ pub struct TerminalState { /// are absent in the normal idle state. #[serde(default, skip_serializing_if = "Option::is_none")] pub supports_command_detection: Option, + /// Whether this terminal-style resource is backed by a pseudoterminal. + /// When `false`, output is plain text and clients do not need to parse + /// VT sequences. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_pty: Option, } /// Unstructured terminal output — content before, between, or after commands, @@ -4286,8 +4290,6 @@ pub enum ToolResultContent { FileEdit(ToolResultFileEditContent), #[serde(rename = "terminal")] Terminal(ToolResultTerminalContent), - #[serde(rename = "terminalComplete")] - TerminalComplete(ToolResultTerminalCompleteContent), #[serde(rename = "subagent")] Subagent(ToolResultSubagentContent), /// Unknown or future variant — preserved as raw JSON for round-trip fidelity. diff --git a/clients/rust/crates/ahp/src/reducers.rs b/clients/rust/crates/ahp/src/reducers.rs index 2a6553450..187e4ff01 100644 --- a/clients/rust/crates/ahp/src/reducers.rs +++ b/clients/rust/crates/ahp/src/reducers.rs @@ -2117,6 +2117,7 @@ mod tests { }, ), supports_command_detection: None, + is_pty: None, }; let a = StateAction::TerminalData(ahp_types::actions::TerminalDataAction { data: "hello".into(), diff --git a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift index b1f7d1689..62dbc15a6 100644 --- a/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift +++ b/clients/swift/AgentHostProtocol/Sources/AgentHostProtocol/Generated/State.generated.swift @@ -267,7 +267,6 @@ public enum ToolResultContentType: String, Codable, Sendable { case resource = "resource" case fileEdit = "fileEdit" case terminal = "terminal" - case terminalComplete = "terminalComplete" case subagent = "subagent" } @@ -3220,46 +3219,25 @@ public struct ToolResultTerminalContent: Codable, Sendable { public var resource: String /// Display title for the terminal content public var title: String + /// Whether this terminal-style resource is backed by a pseudoterminal. + /// When `false`, output is plain text and clients do not need to parse + /// VT sequences. + public var isPty: Bool? + /// Outcome of the command, present once it has exited. + public var result: TerminalCommandResult? public init( type: ToolResultContentType, resource: String, - title: String + title: String, + isPty: Bool? = nil, + result: TerminalCommandResult? = nil ) { self.type = type self.resource = resource self.title = title - } -} - -public struct ToolResultTerminalCompleteContent: Codable, Sendable { - public var type: ToolResultContentType - /// URI of the `ahp-terminal:` channel that carried live output for this - /// command, if one was exposed. - public var resource: String? - /// Exit code from the completed command, if reported by the runtime - public var exitCode: Int? - /// Working directory where the command was executed - public var cwd: String? - /// Preview of the command's output, if available - public var preview: String? - /// Whether `preview` is known to be incomplete or truncated - public var truncated: Bool? - - public init( - type: ToolResultContentType, - resource: String? = nil, - exitCode: Int? = nil, - cwd: String? = nil, - preview: String? = nil, - truncated: Bool? = nil - ) { - self.type = type - self.resource = resource - self.exitCode = exitCode - self.cwd = cwd - self.preview = preview - self.truncated = truncated + self.isPty = isPty + self.result = result } } @@ -4394,6 +4372,28 @@ public struct FileEdit: Codable, Sendable { } } +public struct TerminalCommandResult: Codable, Sendable { + /// Exit code from the completed command, if reported by the runtime + public var exitCode: Int? + /// Preview of the command's output, for clients that are not subscribed + /// to the terminal or that arrive after it is disposed. When `isPty` is + /// `true` the preview may contain VT sequences; when `false` it is plain + /// text. + public var preview: String? + /// Whether `preview` is known to be incomplete or truncated + public var truncated: Bool? + + public init( + exitCode: Int? = nil, + preview: String? = nil, + truncated: Bool? = nil + ) { + self.exitCode = exitCode + self.preview = preview + self.truncated = truncated + } +} + public struct TerminalInfo: Codable, Sendable { /// Terminal URI (subscribable for full terminal state) public var resource: String @@ -4482,6 +4482,10 @@ public struct TerminalState: Codable, Sendable { /// Do NOT use the presence of a `command` part as a feature flag — parts /// are absent in the normal idle state. public var supportsCommandDetection: Bool? + /// Whether this terminal-style resource is backed by a pseudoterminal. + /// When `false`, output is plain text and clients do not need to parse + /// VT sequences. + public var isPty: Bool? public init( title: String, @@ -4491,7 +4495,8 @@ public struct TerminalState: Codable, Sendable { content: [TerminalContentPart], exitCode: Int? = nil, claim: TerminalClaim, - supportsCommandDetection: Bool? = nil + supportsCommandDetection: Bool? = nil, + isPty: Bool? = nil ) { self.title = title self.cwd = cwd @@ -4501,6 +4506,7 @@ public struct TerminalState: Codable, Sendable { self.exitCode = exitCode self.claim = claim self.supportsCommandDetection = supportsCommandDetection + self.isPty = isPty } } @@ -5766,7 +5772,6 @@ public enum ToolResultContent: Codable, Sendable { case resource(ToolResultResourceContent) case fileEdit(ToolResultFileEditContent) case terminal(ToolResultTerminalContent) - case terminalComplete(ToolResultTerminalCompleteContent) case subagent(ToolResultSubagentContent) /// Unknown or future tool result content type; the raw payload is preserved /// and re-encoded verbatim for forward-compatibility. @@ -5790,8 +5795,6 @@ public enum ToolResultContent: Codable, Sendable { self = .fileEdit(try ToolResultFileEditContent(from: decoder)) case "terminal": self = .terminal(try ToolResultTerminalContent(from: decoder)) - case "terminalComplete": - self = .terminalComplete(try ToolResultTerminalCompleteContent(from: decoder)) case "subagent": self = .subagent(try ToolResultSubagentContent(from: decoder)) default: @@ -5812,7 +5815,6 @@ public enum ToolResultContent: Codable, Sendable { case .resource(let v): try v.encode(to: encoder) case .fileEdit(let v): try v.encode(to: encoder) case .terminal(let v): try v.encode(to: encoder) - case .terminalComplete(let v): try v.encode(to: encoder) case .subagent(let v): try v.encode(to: encoder) case .unknown(let v): try v.encode(to: encoder) } diff --git a/docs/.changes/20260722-remove-terminal-complete.json b/docs/.changes/20260722-remove-terminal-complete.json new file mode 100644 index 000000000..89ff1ecf9 --- /dev/null +++ b/docs/.changes/20260722-remove-terminal-complete.json @@ -0,0 +1,5 @@ +{ + "type": "removed", + "message": "`ToolResultTerminalCompleteContent`; command completion data now lives on `ToolResultTerminalContent`.", + "issues": [322, 323] +} diff --git a/docs/.changes/20260722-terminal-completion-data.json b/docs/.changes/20260722-terminal-completion-data.json new file mode 100644 index 000000000..7b2da8b86 --- /dev/null +++ b/docs/.changes/20260722-terminal-completion-data.json @@ -0,0 +1,5 @@ +{ + "type": "added", + "message": "`result` on `ToolResultTerminalContent` (`exitCode`, `preview`, `truncated`), present once the command exits.", + "issues": [323] +} diff --git a/docs/.changes/20260722-terminal-ispty.json b/docs/.changes/20260722-terminal-ispty.json new file mode 100644 index 000000000..51956e392 --- /dev/null +++ b/docs/.changes/20260722-terminal-ispty.json @@ -0,0 +1,5 @@ +{ + "type": "added", + "message": "Optional `isPty` metadata on `ToolResultTerminalContent` and `TerminalState` indicating whether a terminal resource is PTY-backed.", + "issues": [322] +} diff --git a/schema/actions.schema.json b/schema/actions.schema.json index 795cc5b9d..46224c38d 100644 --- a/schema/actions.schema.json +++ b/schema/actions.schema.json @@ -6285,7 +6285,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -6297,6 +6297,14 @@ "title": { "type": "string", "description": "Display title for the terminal content" + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." } }, "required": [ @@ -6305,37 +6313,23 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { + "TerminalCommandResult": { "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, "preview": { "type": "string", - "description": "Preview of the command's output, if available" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type" - ] + } }, "ToolResultSubagentContent": { "type": "object", @@ -6476,6 +6470,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -7256,14 +7254,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "TerminalClaim": { "oneOf": [ diff --git a/schema/commands.schema.json b/schema/commands.schema.json index 0335c18ea..221b89d6c 100644 --- a/schema/commands.schema.json +++ b/schema/commands.schema.json @@ -5491,7 +5491,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -5503,6 +5503,14 @@ "title": { "type": "string", "description": "Display title for the terminal content" + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." } }, "required": [ @@ -5511,37 +5519,23 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { + "TerminalCommandResult": { "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, "preview": { "type": "string", - "description": "Preview of the command's output, if available" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type" - ] + } }, "ToolResultSubagentContent": { "type": "object", @@ -5682,6 +5676,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -8887,14 +8885,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/errors.schema.json b/schema/errors.schema.json index 468b76256..c0dea916a 100644 --- a/schema/errors.schema.json +++ b/schema/errors.schema.json @@ -4275,7 +4275,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -4287,6 +4287,14 @@ "title": { "type": "string", "description": "Display title for the terminal content" + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." } }, "required": [ @@ -4295,37 +4303,23 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { + "TerminalCommandResult": { "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, "preview": { "type": "string", - "description": "Preview of the command's output, if available" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type" - ] + } }, "ToolResultSubagentContent": { "type": "object", @@ -4466,6 +4460,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -6636,14 +6634,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/notifications.schema.json b/schema/notifications.schema.json index 96351393b..5e89283e4 100644 --- a/schema/notifications.schema.json +++ b/schema/notifications.schema.json @@ -4435,7 +4435,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -4447,6 +4447,14 @@ "title": { "type": "string", "description": "Display title for the terminal content" + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." } }, "required": [ @@ -4455,37 +4463,23 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { + "TerminalCommandResult": { "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, "preview": { "type": "string", - "description": "Preview of the command's output, if available" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type" - ] + } }, "ToolResultSubagentContent": { "type": "object", @@ -4626,6 +4620,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -5499,14 +5497,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "ToolCallRiskAssessment": { "oneOf": [ diff --git a/schema/state.schema.json b/schema/state.schema.json index 7ff9dcb31..7e13f096d 100644 --- a/schema/state.schema.json +++ b/schema/state.schema.json @@ -4186,7 +4186,7 @@ }, "ToolResultTerminalContent": { "type": "object", - "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.", + "description": "A reference to a terminal whose output is relevant to this tool result.\n\nClients can subscribe to the terminal's URI to stream its output in real\ntime, providing live feedback while a tool is executing.\n\nWhen the command exits, {@link result} is filled in on the completed\nresult, retaining the outcome for clients that did not subscribe. This\nrecords the command's exit, not the terminal's — the terminal may keep\nrunning afterwards.", "properties": { "type": { "const": "terminal" @@ -4198,6 +4198,14 @@ "title": { "type": "string", "description": "Display title for the terminal content" + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." + }, + "result": { + "$ref": "#/$defs/TerminalCommandResult", + "description": "Outcome of the command, present once it has exited." } }, "required": [ @@ -4206,37 +4214,23 @@ "title" ] }, - "ToolResultTerminalCompleteContent": { + "TerminalCommandResult": { "type": "object", - "description": "Record of a command executed by a terminal-style tool (e.g. a shell tool),\nappended to the tool result when the command exits.\n\nThis records the command's exit, not the terminal's — the terminal may\nkeep running afterwards.\n\nWhen live output was exposed through a terminal channel (a\n{@link ToolResultTerminalContent} block in the same tool result),\n{@link resource} identifies that channel; otherwise this block stands alone\nas the retained command result.", + "description": "Outcome of a command run in a terminal-style tool, filled in on\n{@link ToolResultTerminalContent.result} once the command exits.", "properties": { - "type": { - "const": "terminalComplete" - }, - "resource": { - "$ref": "#/$defs/URI", - "description": "URI of the `ahp-terminal:` channel that carried live output for this\ncommand, if one was exposed." - }, "exitCode": { "type": "number", "description": "Exit code from the completed command, if reported by the runtime" }, - "cwd": { - "$ref": "#/$defs/URI", - "description": "Working directory where the command was executed" - }, "preview": { "type": "string", - "description": "Preview of the command's output, if available" + "description": "Preview of the command's output, for clients that are not subscribed\nto the terminal or that arrive after it is disposed. When `isPty` is\n`true` the preview may contain VT sequences; when `false` it is plain\ntext." }, "truncated": { "type": "boolean", "description": "Whether `preview` is known to be incomplete or truncated" } - }, - "required": [ - "type" - ] + } }, "ToolResultSubagentContent": { "type": "object", @@ -4377,6 +4371,10 @@ "supportsCommandDetection": { "type": "boolean", "description": "Whether this terminal emits `terminal/commandExecuted` and\n`terminal/commandFinished` actions and populates `command`-typed parts.\n\nClients MUST check this flag before relying on command detection.\nDo NOT use the presence of a `command` part as a feature flag — parts\nare absent in the normal idle state." + }, + "isPty": { + "type": "boolean", + "description": "Whether this terminal-style resource is backed by a pseudoterminal.\nWhen `false`, output is plain text and clients do not need to parse\nVT sequences." } }, "required": [ @@ -5157,14 +5155,11 @@ { "$ref": "#/$defs/ToolResultTerminalContent" }, - { - "$ref": "#/$defs/ToolResultTerminalCompleteContent" - }, { "$ref": "#/$defs/ToolResultSubagentContent" } ], - "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output,\n`ToolResultTerminalCompleteContent` for terminal-style completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." + "description": "Content block in a tool result.\n\nMirrors the content blocks in MCP `CallToolResult.content`, plus\n`ToolResultResourceContent` for lazy-loading large results,\n`ToolResultFileEditContent` for file edit diffs,\n`ToolResultTerminalContent` for live terminal output and\ncommand completion metadata, and\n`ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions)." }, "TerminalClaim": { "oneOf": [ diff --git a/scripts/generate-go.ts b/scripts/generate-go.ts index 02fa0334c..0f92fd3c7 100644 --- a/scripts/generate-go.ts +++ b/scripts/generate-go.ts @@ -731,7 +731,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ToolResultResourceContent' }, { name: 'ToolResultFileEditContent' }, { name: 'ToolResultTerminalContent' }, - { name: 'ToolResultTerminalCompleteContent' }, { name: 'ToolResultSubagentContent' }, { name: 'CustomizationLoadingState' }, { name: 'CustomizationLoadedState' }, @@ -758,6 +757,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; goName?: strin { name: 'ToolCallClientContributor' }, { name: 'ToolCallMcpContributor' }, { name: 'FileEdit' }, + { name: 'TerminalCommandResult' }, { name: 'TerminalInfo' }, { name: 'TerminalClientClaim' }, { name: 'TerminalSessionClaim' }, @@ -896,7 +896,6 @@ const TOOL_RESULT_CONTENT_UNION: UnionConfig = { { variantName: 'Resource', innerType: 'ToolResultResourceContent', wireValue: 'resource' }, { variantName: 'FileEdit', innerType: 'ToolResultFileEditContent', wireValue: 'fileEdit' }, { variantName: 'Terminal', innerType: 'ToolResultTerminalContent', wireValue: 'terminal' }, - { variantName: 'TerminalComplete', innerType: 'ToolResultTerminalCompleteContent', wireValue: 'terminalComplete' }, { variantName: 'Subagent', innerType: 'ToolResultSubagentContent', wireValue: 'subagent' }, ], unknown: true, diff --git a/scripts/generate-kotlin.ts b/scripts/generate-kotlin.ts index 49468b017..d45144167 100644 --- a/scripts/generate-kotlin.ts +++ b/scripts/generate-kotlin.ts @@ -730,7 +730,6 @@ sealed interface ToolResultContent { @JvmInline value class Resource(val value: ToolResultResourceContent) : ToolResultContent @JvmInline value class FileEdit(val value: ToolResultFileEditContent) : ToolResultContent @JvmInline value class Terminal(val value: ToolResultTerminalContent) : ToolResultContent - @JvmInline value class TerminalComplete(val value: ToolResultTerminalCompleteContent) : ToolResultContent @JvmInline value class Subagent(val value: ToolResultSubagentContent) : ToolResultContent /** @@ -760,7 +759,6 @@ internal object ToolResultContentSerializer : KSerializer { "resource" -> ToolResultContent.Resource(input.json.decodeFromJsonElement(ToolResultResourceContent.serializer(), element)) "fileEdit" -> ToolResultContent.FileEdit(input.json.decodeFromJsonElement(ToolResultFileEditContent.serializer(), element)) "terminal" -> ToolResultContent.Terminal(input.json.decodeFromJsonElement(ToolResultTerminalContent.serializer(), element)) - "terminalComplete" -> ToolResultContent.TerminalComplete(input.json.decodeFromJsonElement(ToolResultTerminalCompleteContent.serializer(), element)) "subagent" -> ToolResultContent.Subagent(input.json.decodeFromJsonElement(ToolResultSubagentContent.serializer(), element)) else -> ToolResultContent.Unknown(obj) } @@ -775,7 +773,6 @@ internal object ToolResultContentSerializer : KSerializer { is ToolResultContent.Resource -> output.json.encodeToJsonElement(ToolResultResourceContent.serializer(), value.value) is ToolResultContent.FileEdit -> output.json.encodeToJsonElement(ToolResultFileEditContent.serializer(), value.value) is ToolResultContent.Terminal -> output.json.encodeToJsonElement(ToolResultTerminalContent.serializer(), value.value) - is ToolResultContent.TerminalComplete -> output.json.encodeToJsonElement(ToolResultTerminalCompleteContent.serializer(), value.value) is ToolResultContent.Subagent -> output.json.encodeToJsonElement(ToolResultSubagentContent.serializer(), value.value) is ToolResultContent.Unknown -> value.raw } @@ -833,7 +830,7 @@ const STATE_STRUCTS = [ 'ToolDefinition', 'ToolAnnotations', 'ToolResultTextContent', 'ToolResultEmbeddedResourceContent', 'ToolResultResourceContent', 'ToolResultFileEditContent', - 'ToolResultTerminalContent', 'ToolResultTerminalCompleteContent', + 'ToolResultTerminalContent', 'ToolResultSubagentContent', 'CustomizationLoadingState', 'CustomizationLoadedState', 'CustomizationDegradedState', 'CustomizationErrorState', @@ -844,7 +841,7 @@ const STATE_STRUCTS = [ 'McpServerStartingState', 'McpServerReadyState', 'McpServerAuthRequiredState', 'McpServerErrorState', 'McpServerStoppedState', 'McpOAuthClient', 'McpAuthRequirement', 'ToolCallClientContributor', 'ToolCallMcpContributor', - 'FileEdit', 'TerminalInfo', + 'FileEdit', 'TerminalCommandResult', 'TerminalInfo', 'TerminalClientClaim', 'TerminalSessionClaim', 'TerminalState', 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', diff --git a/scripts/generate-rust.ts b/scripts/generate-rust.ts index 4773ddb55..de8b36357 100644 --- a/scripts/generate-rust.ts +++ b/scripts/generate-rust.ts @@ -762,7 +762,6 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ToolResultResourceContent', omitDiscriminants: true }, { name: 'ToolResultFileEditContent', omitDiscriminants: true }, { name: 'ToolResultTerminalContent', omitDiscriminants: true }, - { name: 'ToolResultTerminalCompleteContent', omitDiscriminants: true }, { name: 'ToolResultSubagentContent', omitDiscriminants: true }, { name: 'CustomizationLoadingState', omitDiscriminants: true }, { name: 'CustomizationLoadedState', omitDiscriminants: true }, @@ -789,6 +788,7 @@ const STATE_STRUCTS: { name: string; omitDiscriminants?: boolean; rustName?: str { name: 'ToolCallClientContributor', omitDiscriminants: true }, { name: 'ToolCallMcpContributor', omitDiscriminants: true }, { name: 'FileEdit' }, + { name: 'TerminalCommandResult' }, { name: 'TerminalInfo' }, { name: 'TerminalClientClaim', omitDiscriminants: true }, { name: 'TerminalSessionClaim', omitDiscriminants: true }, @@ -927,7 +927,6 @@ const TOOL_RESULT_CONTENT_UNION: UnionConfig = { { variantName: 'Resource', innerType: 'ToolResultResourceContent', wireValue: 'resource' }, { variantName: 'FileEdit', innerType: 'ToolResultFileEditContent', wireValue: 'fileEdit' }, { variantName: 'Terminal', innerType: 'ToolResultTerminalContent', wireValue: 'terminal' }, - { variantName: 'TerminalComplete', innerType: 'ToolResultTerminalCompleteContent', wireValue: 'terminalComplete' }, { variantName: 'Subagent', innerType: 'ToolResultSubagentContent', wireValue: 'subagent' }, ], unknown: true, diff --git a/scripts/generate-swift.ts b/scripts/generate-swift.ts index f86be48c0..3ca5b664a 100644 --- a/scripts/generate-swift.ts +++ b/scripts/generate-swift.ts @@ -583,7 +583,7 @@ const STATE_STRUCTS = [ 'ToolDefinition', 'ToolAnnotations', 'ToolResultTextContent', 'ToolResultEmbeddedResourceContent', 'ToolResultResourceContent', 'ToolResultFileEditContent', - 'ToolResultTerminalContent', 'ToolResultTerminalCompleteContent', + 'ToolResultTerminalContent', 'ToolResultSubagentContent', 'CustomizationLoadingState', 'CustomizationLoadedState', 'CustomizationDegradedState', 'CustomizationErrorState', @@ -594,7 +594,7 @@ const STATE_STRUCTS = [ 'McpServerStartingState', 'McpServerReadyState', 'McpServerAuthRequiredState', 'McpServerErrorState', 'McpServerStoppedState', 'McpOAuthClient', 'McpAuthRequirement', 'ToolCallClientContributor', 'ToolCallMcpContributor', - 'FileEdit', 'TerminalInfo', + 'FileEdit', 'TerminalCommandResult', 'TerminalInfo', 'TerminalClientClaim', 'TerminalSessionClaim', 'TerminalState', 'TerminalUnclassifiedPart', 'TerminalCommandPart', 'UsageInfo', 'ErrorInfo', 'Snapshot', @@ -821,7 +821,6 @@ function generateToolResultContentUnion(): string { case resource(ToolResultResourceContent) case fileEdit(ToolResultFileEditContent) case terminal(ToolResultTerminalContent) - case terminalComplete(ToolResultTerminalCompleteContent) case subagent(ToolResultSubagentContent) /// Unknown or future tool result content type; the raw payload is preserved /// and re-encoded verbatim for forward-compatibility. @@ -845,8 +844,6 @@ function generateToolResultContentUnion(): string { self = .fileEdit(try ToolResultFileEditContent(from: decoder)) case "terminal": self = .terminal(try ToolResultTerminalContent(from: decoder)) - case "terminalComplete": - self = .terminalComplete(try ToolResultTerminalCompleteContent(from: decoder)) case "subagent": self = .subagent(try ToolResultSubagentContent(from: decoder)) default: @@ -867,7 +864,6 @@ function generateToolResultContentUnion(): string { case .resource(let v): try v.encode(to: encoder) case .fileEdit(let v): try v.encode(to: encoder) case .terminal(let v): try v.encode(to: encoder) - case .terminalComplete(let v): try v.encode(to: encoder) case .subagent(let v): try v.encode(to: encoder) case .unknown(let v): try v.encode(to: encoder) } diff --git a/types/channels-chat/state.ts b/types/channels-chat/state.ts index 198772533..b7a6df956 100644 --- a/types/channels-chat/state.ts +++ b/types/channels-chat/state.ts @@ -1331,7 +1331,6 @@ export const enum ToolResultContentType { Resource = 'resource', FileEdit = 'fileEdit', Terminal = 'terminal', - TerminalComplete = 'terminalComplete', Subagent = 'subagent', } @@ -1389,6 +1388,11 @@ export interface ToolResultFileEditContent extends FileEdit { * Clients can subscribe to the terminal's URI to stream its output in real * time, providing live feedback while a tool is executing. * + * When the command exits, {@link result} is filled in on the completed + * result, retaining the outcome for clients that did not subscribe. This + * records the command's exit, not the terminal's — the terminal may keep + * running afterwards. + * * @category Tool Result Content */ export interface ToolResultTerminalContent { @@ -1397,34 +1401,31 @@ export interface ToolResultTerminalContent { resource: URI; /** Display title for the terminal content */ title: string; + /** + * Whether this terminal-style resource is backed by a pseudoterminal. + * When `false`, output is plain text and clients do not need to parse + * VT sequences. + */ + isPty?: boolean; + /** Outcome of the command, present once it has exited. */ + result?: TerminalCommandResult; } /** - * Record of a command executed by a terminal-style tool (e.g. a shell tool), - * appended to the tool result when the command exits. - * - * This records the command's exit, not the terminal's — the terminal may - * keep running afterwards. - * - * When live output was exposed through a terminal channel (a - * {@link ToolResultTerminalContent} block in the same tool result), - * {@link resource} identifies that channel; otherwise this block stands alone - * as the retained command result. + * Outcome of a command run in a terminal-style tool, filled in on + * {@link ToolResultTerminalContent.result} once the command exits. * * @category Tool Result Content */ -export interface ToolResultTerminalCompleteContent { - type: ToolResultContentType.TerminalComplete; - /** - * URI of the `ahp-terminal:` channel that carried live output for this - * command, if one was exposed. - */ - resource?: URI; +export interface TerminalCommandResult { /** Exit code from the completed command, if reported by the runtime */ exitCode?: number; - /** Working directory where the command was executed */ - cwd?: URI; - /** Preview of the command's output, if available */ + /** + * Preview of the command's output, for clients that are not subscribed + * to the terminal or that arrive after it is disposed. When `isPty` is + * `true` the preview may contain VT sequences; when `false` it is plain + * text. + */ preview?: string; /** Whether `preview` is known to be incomplete or truncated */ truncated?: boolean; @@ -1458,8 +1459,8 @@ export interface ToolResultSubagentContent { * Mirrors the content blocks in MCP `CallToolResult.content`, plus * `ToolResultResourceContent` for lazy-loading large results, * `ToolResultFileEditContent` for file edit diffs, - * `ToolResultTerminalContent` for live terminal output, - * `ToolResultTerminalCompleteContent` for terminal-style completion metadata, and + * `ToolResultTerminalContent` for live terminal output and + * command completion metadata, and * `ToolResultSubagentContent` for tool-spawned worker chats (AHP extensions). * * @category Tool Result Content @@ -1470,5 +1471,4 @@ export type ToolResultContent = | ToolResultResourceContent | ToolResultFileEditContent | ToolResultTerminalContent - | ToolResultTerminalCompleteContent | ToolResultSubagentContent; diff --git a/types/channels-terminal/state.ts b/types/channels-terminal/state.ts index 4484089bc..28cd36e4e 100644 --- a/types/channels-terminal/state.ts +++ b/types/channels-terminal/state.ts @@ -107,6 +107,12 @@ export interface TerminalState { * are absent in the normal idle state. */ supportsCommandDetection?: boolean; + /** + * Whether this terminal-style resource is backed by a pseudoterminal. + * When `false`, output is plain text and clients do not need to parse + * VT sequences. + */ + isPty?: boolean; } // ─── Terminal Content Parts ────────────────────────────────────────────────── diff --git a/types/index.ts b/types/index.ts index fd5d54216..d25e2aae1 100644 --- a/types/index.ts +++ b/types/index.ts @@ -68,7 +68,7 @@ export type { FileEdit, ToolResultFileEditContent, ToolResultTerminalContent, - ToolResultTerminalCompleteContent, + TerminalCommandResult, ToolResultSubagentContent, SessionActiveClient, SessionInputRequest, diff --git a/types/test-cases/round-trips/030-terminal-content-ispty.json b/types/test-cases/round-trips/030-terminal-content-ispty.json new file mode 100644 index 000000000..c3f6cf820 --- /dev/null +++ b/types/test-cases/round-trips/030-terminal-content-ispty.json @@ -0,0 +1,42 @@ +{ + "name": "terminal-content-ispty", + "group": "A", + "description": "ActionEnvelope carries a terminal reference marked as non-PTY (isPty false), allowing clients to identify its backing before subscribing.", + "type": "ActionEnvelope", + "input": { + "channel": "ahp-chat:/s1/c1", + "action": { + "type": "chat/toolCallContentChanged", + "turnId": "turn-1", + "toolCallId": "tool-1", + "content": [ + { + "type": "terminal", + "resource": "ahp-terminal:/s1/t1", + "title": "npm test", + "isPty": false + } + ] + }, + "serverSeq": 10 + }, + "acceptableOutputs": [ + { + "action": { + "content": [ + { + "isPty": false, + "resource": "ahp-terminal:/s1/t1", + "title": "npm test", + "type": "terminal" + } + ], + "toolCallId": "tool-1", + "turnId": "turn-1", + "type": "chat/toolCallContentChanged" + }, + "channel": "ahp-chat:/s1/c1", + "serverSeq": 10 + } + ] +} diff --git a/types/test-cases/round-trips/031-terminal-content-completed.json b/types/test-cases/round-trips/031-terminal-content-completed.json new file mode 100644 index 000000000..5400ed87f --- /dev/null +++ b/types/test-cases/round-trips/031-terminal-content-completed.json @@ -0,0 +1,52 @@ +{ + "name": "terminal-content-completed", + "group": "A", + "description": "ActionEnvelope carries a terminal block whose result object is present after the command exited, with exit code, bounded preview, and truncation marker.", + "type": "ActionEnvelope", + "input": { + "channel": "ahp-chat:/s1/c1", + "action": { + "type": "chat/toolCallContentChanged", + "turnId": "turn-1", + "toolCallId": "tool-1", + "content": [ + { + "type": "terminal", + "resource": "ahp-terminal:/s1/t1", + "title": "npm test", + "isPty": false, + "result": { + "exitCode": 1, + "preview": "line 2\nline 3\n", + "truncated": true + } + } + ] + }, + "serverSeq": 11 + }, + "acceptableOutputs": [ + { + "action": { + "content": [ + { + "isPty": false, + "resource": "ahp-terminal:/s1/t1", + "result": { + "exitCode": 1, + "preview": "line 2\nline 3\n", + "truncated": true + }, + "title": "npm test", + "type": "terminal" + } + ], + "toolCallId": "tool-1", + "turnId": "turn-1", + "type": "chat/toolCallContentChanged" + }, + "channel": "ahp-chat:/s1/c1", + "serverSeq": 11 + } + ] +}