Skip to content

Add shell notification types and handlers across all SDKs#790

Open
jmoseley wants to merge 3 commits intomainfrom
jm/shell-notifications
Open

Add shell notification types and handlers across all SDKs#790
jmoseley wants to merge 3 commits intomainfrom
jm/shell-notifications

Conversation

@jmoseley
Copy link
Contributor

Motivation

The runtime is adding session.shell.exec and session.shell.kill RPC methods (copilot-agent-runtime#4596) which stream shell command output via two new JSON-RPC notifications: shell.output and shell.exit. This PR adds the SDK-side plumbing so consumers can subscribe to these notifications at the session level.

Approach

Each SDK gets three layers of changes:

  1. TypesShellOutputNotification (processId, stream, data) and ShellExitNotification (processId, exitCode), plus typed handler callbacks.

  2. Session-level handlersonShellOutput(handler) / onShellExit(handler) subscription methods that return an unsubscribe function (or IDisposable in .NET). Follows the same patterns as the existing session.on() event handlers.

  3. Client-level routing — The client maintains a processId → session map. When shell.output or shell.exit notifications arrive, the client looks up the owning session by processId and dispatches to that session's handlers. On shell.exit, the mapping is automatically cleaned up.

Sessions track their process IDs via internal _trackShellProcess() / _untrackShellProcess() methods, which the client wires up during createSession() / resumeSession().

Consumer API (example)

session.onShellOutput((n) => {
    console.log(`[${n.processId}:${n.stream}] ${n.data}`);
});
session.onShellExit((n) => {
    console.log(`Process ${n.processId} exited with code ${n.exitCode}`);
});

Files changed (15 files, 4 SDKs)

SDK Types Session Client Exports
Node.js types.ts session.ts client.ts index.ts
Python types.py session.py client.py __init__.py
Go types.go session.go client.go
.NET Types.cs Session.cs Client.cs

Plus nodejs/test/session-shell.test.ts — 7 unit tests covering handler registration, multi-handler dispatch, unsubscribe, error resilience, and process tracking.

Follow-up

Once the @github/copilot npm package ships the updated api.schema.json, we'll run codegen to generate the typed RPC wrappers for session.rpc.shell.exec() / session.rpc.shell.kill() and add a convenience wrapper that auto-calls _trackShellProcess().

Verification

  • ✅ Node.js: 40/40 tests pass (33 existing + 7 new)
  • ✅ Go: builds, unit tests pass
  • ✅ .NET: builds with 0 warnings (TreatWarningsAsErrors + Nullable enabled)
  • ✅ Python: builds

Created with GitHub Desktop

Add support for shell.output and shell.exit JSON-RPC notifications
that stream output from shell commands started via session.shell.exec.

Each SDK gets:
- ShellOutputNotification and ShellExitNotification types
- Session-level onShellOutput/onShellExit subscription methods
- Client-level processId-to-session routing with auto-cleanup on exit
- Process tracking via _trackShellProcess/_untrackShellProcess

The RPC method wrappers (session.rpc.shell.exec and session.rpc.shell.stop)
will be auto-generated once the @github/copilot package is updated with
the new api.schema.json.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jmoseley jmoseley requested a review from a team as a code owner March 11, 2026 18:50
Copilot AI review requested due to automatic review settings March 11, 2026 18:50
@github-actions
Copy link
Contributor

Cross-SDK Consistency Review ✅

This PR demonstrates excellent cross-language consistency in implementing shell notification support across all four SDKs. The implementation follows established patterns and maintains feature parity with only minor gaps.

Strengths

  1. Consistent Type Definitions: All SDKs define ShellOutputNotification and ShellExitNotification with equivalent fields
  2. Parallel Handler APIs: All SDKs implement onShellOutput/on_shell_output/OnShellOutput and onShellExit/on_shell_exit/OnShellExit with matching semantics
  3. Unified Architecture: Process tracking, client-level routing via shellProcessMap, and automatic cleanup on exit are implemented consistently
  4. Test Coverage: Comprehensive Node.js tests (7 new tests covering handlers, unsubscribe, error resilience, and process tracking)

⚠️ Minor Inconsistencies

I found two small gaps that would improve cross-SDK consistency:

1. Python: Missing Type Exports

The following types are defined but not exported in python/copilot/__init__.py:

  • ShellOutputStream (the Literal["stdout", "stderr"] type alias)
  • ShellOutputHandler (the Callable[[ShellOutputNotification], None] type alias)
  • ShellExitHandler (the Callable[[ShellExitNotification], None] type alias)

Comparison:

  • Node.js: Exports all three (ShellOutputStream, ShellOutputHandler, ShellExitHandler) via index.ts
  • Go: All types are public (capitalized names)
  • Python: Only exports the notification classes, not the type aliases

Impact: Python consumers cannot import these types for type hints (e.g., from copilot import ShellOutputStream, ShellOutputHandler).

Suggested fix: Add to python/copilot/__init__.py:

from .types import (
    # ... existing imports ...
    ShellExitHandler,
    ShellExitNotification,
    ShellOutputStream,
    ShellOutputHandler,
    ShellOutputNotification,
    # ... rest ...
)

__all__ = [
    # ... existing exports ...
    "ShellExitHandler",
    "ShellExitNotification",
    "ShellOutputStream",
    "ShellOutputHandler",
    "ShellOutputNotification",
    # ... rest ...
]

2. .NET: Stream Property Type

The ShellOutputNotification.Stream property uses string instead of a typed enum/literal like other SDKs.

Comparison:

  • Node.js: stream: ShellOutputStream (typed as "stdout" | "stderr")
  • Python: stream: ShellOutputStream (typed as Literal["stdout", "stderr"])
  • Go: Stream ShellOutputStream (custom type with constants ShellStreamStdout/ShellStreamStderr)
  • .NET: Stream is string (comment says "stdout" or "stderr" but not enforced)

Impact: Less type safety in .NET consumers; IDEs won't autocomplete valid stream values.

Suggested fix: Consider adding an enum or using string constants:

public static class ShellOutputStream
{
    public const string Stdout = "stdout";
    public const string Stderr = "stderr";
}

// Then in ShellOutputNotification:
public string Stream { get; set; } = string.Empty;  // Still string for JSON compat, but consumers can use ShellOutputStream.Stdout

Or a more C#-idiomatic approach with an enum + JSON converter.


📝 Summary

Both issues are non-blocking and don't affect functionality—the core implementation is solid and consistent. Addressing these would bring the SDKs to 100% API parity. Great work on maintaining such tight cross-language alignment! 🎉

Generated by SDK Consistency Review Agent for issue #790 ·

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds cross-SDK support for shell process output/exit notifications so consumers can subscribe to streamed stdout/stderr chunks and completion events, routed per-session via a processId mapping.

Changes:

  • Introduces ShellOutputNotification / ShellExitNotification (and handler types) across Python/Node/Go/.NET SDKs.
  • Adds per-session subscription APIs (onShellOutput/onShellExit equivalents) and client-side notification routing for shell.output / shell.exit.
  • Adds a Node unit test covering handler registration, unsubscribe, and dispatch behavior.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
python/copilot/types.py Adds Python shell notification dataclasses + handler type aliases.
python/copilot/session.py Adds session-level subscribe/dispatch + process tracking helpers for shell notifications.
python/copilot/client.py Adds routing for incoming shell.output/shell.exit notifications to the right session.
python/copilot/init.py Exports the new Python notification types from the package.
nodejs/src/types.ts Adds TypeScript shell notification and handler types.
nodejs/src/session.ts Adds session subscribe/dispatch + process tracking helpers for shell notifications.
nodejs/src/client.ts Adds routing for incoming shell.output/shell.exit notifications to the right session.
nodejs/src/index.ts Re-exports new shell-related types from the Node entrypoint.
nodejs/test/session-shell.test.ts Unit tests for Node session shell notification subscriptions/dispatch.
go/types.go Adds Go shell notification structs/enums and handler types.
go/session.go Adds Go session subscription APIs, dispatchers, and process tracking helpers.
go/client.go Adds Go client routing for shell.output/shell.exit notifications and process→session map.
dotnet/src/Types.cs Adds .NET shell notification types + JSON source-gen registrations.
dotnet/src/Session.cs Adds .NET session subscription APIs and process tracking helpers.
dotnet/src/Client.cs Adds .NET client routing for shell.output/shell.exit to sessions via process→session map.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +295 to +323
/**
* Subscribe to shell output notifications for this session.
*
* Shell output notifications are streamed in chunks when commands started
* via `session.rpc.shell.exec()` produce stdout or stderr output.
*
* @param handler - Callback receiving shell output notifications
* @returns A function that, when called, unsubscribes the handler
*
* @example
* ```typescript
* const unsubscribe = session.onShellOutput((notification) => {
* console.log(`[${notification.processId}:${notification.stream}] ${notification.data}`);
* });
* ```
*/
onShellOutput(handler: ShellOutputHandler): () => void {
this.shellOutputHandlers.add(handler);
return () => {
this.shellOutputHandlers.delete(handler);
};
}

/**
* Subscribe to shell exit notifications for this session.
*
* Shell exit notifications are sent when commands started via
* `session.rpc.shell.exec()` complete (after all output has been streamed).
*
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

session.rpc is created from createSessionRpc (generated) and currently has no shell namespace / exec() method. The JSDoc examples here reference session.rpc.shell.exec(), which will be a broken example for users unless the RPC surface is added or the docs are updated to point at the real API that starts a shell process and returns processId.

Copilot uses AI. Check for mistakes.
Comment on lines +1497 to +1511
private handleShellOutputNotification(notification: unknown): void {
if (
typeof notification !== "object" ||
!notification ||
!("processId" in notification) ||
typeof (notification as { processId?: unknown }).processId !== "string"
) {
return;
}

const { processId } = notification as { processId: string };
const session = this.shellProcessMap.get(processId);
if (session) {
session._dispatchShellOutput(notification as ShellOutputNotification);
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shell notifications are routed via shellProcessMap, but nothing in the codebase ever calls CopilotSession._trackShellProcess() (search shows only the method definition + unit test). As a result, shellProcessMap will remain empty and shell.output/shell.exit notifications will be dropped, making onShellOutput/onShellExit ineffective. Consider wiring process tracking into whatever API starts shell commands (e.g., immediately after receiving the processId from the exec RPC), or include sessionId in the notification params and route by session instead of a separate registration step.

Copilot uses AI. Check for mistakes.
Comment on lines +260 to +290
def on_shell_output(self, handler: ShellOutputHandler) -> Callable[[], None]:
"""Subscribe to shell output notifications for this session.

Shell output notifications are streamed in chunks when commands started
via ``session.rpc.shell.exec()`` produce stdout or stderr output.

Args:
handler: A callback that receives shell output notifications.

Returns:
A function that, when called, unsubscribes the handler.

Example:
>>> def handle_output(notification):
... print(f"[{notification.processId}:{notification.stream}] {notification.data}")
>>> unsubscribe = session.on_shell_output(handle_output)
"""
with self._shell_output_handlers_lock:
self._shell_output_handlers.add(handler)

def unsubscribe():
with self._shell_output_handlers_lock:
self._shell_output_handlers.discard(handler)

return unsubscribe

def on_shell_exit(self, handler: ShellExitHandler) -> Callable[[], None]:
"""Subscribe to shell exit notifications for this session.

Shell exit notifications are sent when commands started via
``session.rpc.shell.exec()`` complete (after all output has been streamed).
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring mentions commands started via session.rpc.shell.exec(), but SessionRpc (python/copilot/generated/rpc.py) currently has no shell API. This example will mislead users unless the shell exec RPC is added to the typed RPC surface or the docs are updated to match the actual API that produces processId.

Copilot uses AI. Check for mistakes.
Comment on lines +312 to +322
def _dispatch_shell_output(self, notification: ShellOutputNotification) -> None:
"""Dispatch a shell output notification to all registered handlers."""
with self._shell_output_handlers_lock:
handlers = list(self._shell_output_handlers)

for handler in handlers:
try:
handler(notification)
except Exception:
pass # Ignore handler errors

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_dispatch_event logs exceptions from user handlers, but _dispatch_shell_output/_dispatch_shell_exit silently swallow all exceptions. That makes debugging handler failures much harder and is inconsistent with other event dispatching in this class. Consider at least logging the exception (or reusing the same logging strategy as _dispatch_event).

Copilot uses AI. Check for mistakes.
Comment on lines +1450 to +1465
process_id = params.get("processId")
if process_id:
with self._shell_process_map_lock:
session = self._shell_process_map.get(process_id)
if session:
notification = ShellOutputNotification.from_dict(params)
session._dispatch_shell_output(notification)
elif method == "shell.exit":
process_id = params.get("processId")
if process_id:
with self._shell_process_map_lock:
session = self._shell_process_map.get(process_id)
if session:
notification = ShellExitNotification.from_dict(params)
session._dispatch_shell_exit(notification)
# Clean up the mapping after exit
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shell notifications are looked up via _shell_process_map, but there is no code path that ever calls CopilotSession._track_shell_process() to populate that map (search shows only the method definition). That means shell.output/shell.exit notifications will be ignored and the new on_shell_output/on_shell_exit subscriptions won’t fire. Process tracking likely needs to be wired into the shell exec/start API (track immediately after obtaining processId), or notifications need to include sessionId so they can be routed without explicit registration.

Suggested change
process_id = params.get("processId")
if process_id:
with self._shell_process_map_lock:
session = self._shell_process_map.get(process_id)
if session:
notification = ShellOutputNotification.from_dict(params)
session._dispatch_shell_output(notification)
elif method == "shell.exit":
process_id = params.get("processId")
if process_id:
with self._shell_process_map_lock:
session = self._shell_process_map.get(process_id)
if session:
notification = ShellExitNotification.from_dict(params)
session._dispatch_shell_exit(notification)
# Clean up the mapping after exit
# Prefer routing by sessionId when available; fall back to processId map.
session: CopilotSession | None = None
session_id = params.get("sessionId")
if session_id:
with self._sessions_lock:
session = self._sessions.get(session_id)
if session is None:
process_id = params.get("processId")
if process_id:
with self._shell_process_map_lock:
session = self._shell_process_map.get(process_id)
if session:
notification = ShellOutputNotification.from_dict(params)
session._dispatch_shell_output(notification)
elif method == "shell.exit":
# Prefer routing by sessionId when available; fall back to processId map.
session: CopilotSession | None = None
session_id = params.get("sessionId")
if session_id:
with self._sessions_lock:
session = self._sessions.get(session_id)
process_id = params.get("processId")
if session is None and process_id:
with self._shell_process_map_lock:
session = self._shell_process_map.get(process_id)
if session:
notification = ShellExitNotification.from_dict(params)
session._dispatch_shell_exit(notification)
# Clean up the mapping after exit
if process_id:

Copilot uses AI. Check for mistakes.
Comment on lines +315 to +316
// Shell exit notifications are sent when commands started via
// session.RPC.Shell.Exec() complete (after all output has been streamed).
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments/examples reference session.RPC.Shell.Exec(), but rpc.NewSessionRpc doesn’t expose a Shell API in this repo (no generated shell RPC methods found). Either add the shell exec RPC surface or update the docs to point at the actual way to start shell commands and obtain the ProcessID used for notifications.

Suggested change
// Shell exit notifications are sent when commands started via
// session.RPC.Shell.Exec() complete (after all output has been streamed).
// Shell exit notifications are sent when shell commands associated with
// this session complete (after all output has been streamed).

Copilot uses AI. Check for mistakes.
Comment on lines +1391 to +1413
func (c *Client) handleShellOutput(notification ShellOutputNotification) {
c.shellProcessMapMux.Lock()
session, ok := c.shellProcessMap[notification.ProcessID]
c.shellProcessMapMux.Unlock()

if ok {
session.dispatchShellOutput(notification)
}
}

func (c *Client) handleShellExit(notification ShellExitNotification) {
c.shellProcessMapMux.Lock()
session, ok := c.shellProcessMap[notification.ProcessID]
c.shellProcessMapMux.Unlock()

if ok {
session.dispatchShellExit(notification)
// Clean up the mapping after exit
c.shellProcessMapMux.Lock()
delete(c.shellProcessMap, notification.ProcessID)
c.shellProcessMapMux.Unlock()
session.untrackShellProcess(notification.ProcessID)
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shell notifications are routed using shellProcessMap, but there is no call site that ever invokes session.trackShellProcess(...) to register a process ID (search shows only the method definition). As-is, handleShellOutput/handleShellExit will almost always find no session and drop notifications. The process ID registration needs to be wired into the API that starts shell commands (track immediately after obtaining processID), or the notification payload should include sessionId so routing doesn’t depend on an out-of-band registration.

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +302
/// Shell exit notifications are sent when commands started via
/// <c>session.Rpc.Shell.ExecAsync()</c> complete (after all output has been streamed).
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML docs mention commands started via session.Rpc.Shell.ExecAsync(), but there is no Shell API exposed on the session RPC surface in this repo. Either add the missing RPC API or update the docs to reference the real method that starts shell commands and yields the processId used for notifications.

Suggested change
/// Shell exit notifications are sent when commands started via
/// <c>session.Rpc.Shell.ExecAsync()</c> complete (after all output has been streamed).
/// Shell exit notifications are sent when shell commands started via the session's RPC
/// surface complete (after all output has been streamed). The notification's
/// <see cref="ShellExitNotification.ProcessId"/> matches the <c>processId</c> returned
/// when the corresponding shell command was started.

Copilot uses AI. Check for mistakes.
Comment on lines +1417 to +1442
public void OnShellOutput(string processId, string stream, string data)
{
if (client._shellProcessMap.TryGetValue(processId, out var session))
{
session.DispatchShellOutput(new ShellOutputNotification
{
ProcessId = processId,
Stream = stream,
Data = data,
});
}
}

public void OnShellExit(string processId, int exitCode)
{
if (client._shellProcessMap.TryGetValue(processId, out var session))
{
session.DispatchShellExit(new ShellExitNotification
{
ProcessId = processId,
ExitCode = exitCode,
});
// Clean up the mapping after exit
client._shellProcessMap.TryRemove(processId, out _);
session.UntrackShellProcess(processId);
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_shellProcessMap is the only routing mechanism for shell.output/shell.exit, but nothing in the .NET SDK calls CopilotSession.TrackShellProcess(...) (search shows only the method definition). This means these notifications will be dropped and OnShellOutput/OnShellExit won’t fire. The process registration needs to happen when starting a shell command (track immediately after receiving processId), or the server needs to include sessionId in these notifications so routing doesn’t depend on prior registration.

Copilot uses AI. Check for mistakes.
Bumps the CLI package in both nodejs/ and test/harness/ to pick up
the shell exec API schema. Runs codegen to produce typed RPC wrappers
and updated session-event types across all four SDKs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

Cross-SDK Consistency Review ✅

I've reviewed this PR for consistency across all four SDK implementations (Node.js, Python, Go, and .NET). Overall, this is an excellent example of consistent cross-SDK feature implementation! 🎉

✅ Consistent Implementation

The PR successfully adds shell notification support across all four SDKs with:

  1. Parallel Type Definitions - All SDKs define equivalent notification types:

    • ShellOutputNotification (processId, stream, data)
    • ShellExitNotification (processId, exitCode)
  2. Consistent Session Handler APIs - Following language idioms:

    • Node.js: session.onShellOutput(handler) → returns () => void
    • Python: session.on_shell_output(handler) → returns Callable[[], None]
    • Go: session.OnShellOutput(handler) → returns func()
    • .NET: session.OnShellOutput(handler) → returns IDisposable
  3. Uniform Client-Level Routing - All SDKs:

    • Register shell.output and shell.exit notification handlers
    • Maintain a processId → session mapping
    • Route notifications to the owning session
    • Clean up mappings on shell.exit
  4. Handler Type Definitions - All SDKs provide typed callbacks (accounting for language differences)

⚠️ Minor Inconsistency Found

Python missing helper type exports: Python's __init__.py does not export ShellOutputStream, ShellOutputHandler, or ShellExitHandler, while Node.js exports all five shell-related types.

Impact: Low - users can still import these from copilot.types directly, but it breaks consistency with the Node.js SDK's public API surface.

Recommendation: Add the missing exports to maintain parity with Node.js.

📊 Test Coverage Note

Only the Node.js SDK includes dedicated unit tests (nodejs/test/session-shell.test.ts with 7 test cases). Consider adding equivalent tests to Python, Go, and .NET for full feature parity, though this is not blocking for this PR.

Summary

This PR demonstrates excellent cross-language API design. The single inconsistency is minor and easy to fix. Great work maintaining consistency! 👏

Generated by SDK Consistency Review Agent for issue #790 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #790

SessionListFilter,
SessionMetadata,
ShellExitNotification,
ShellOutputNotification,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing exports for shell notification helper types

For consistency with the Node.js SDK (which exports ShellOutputStream, ShellOutputHandler, and ShellExitHandler), consider adding these types to the exports:

from .types import (
    # ... existing imports ...
    ShellExitHandler,
    ShellExitNotification,
    ShellOutputHandler,
    ShellOutputNotification,
    ShellOutputStream,
    # ... rest of imports ...
)

And add to __all__:

__all__ = [
    # ... existing exports ...
    "ShellExitHandler",
    "ShellExitNotification", 
    "ShellOutputHandler",
    "ShellOutputNotification",
    "ShellOutputStream",
    # ... rest of exports ...
]

This provides better developer experience and matches the Node.js SDK's public API surface.

- Run prettier on Node.js files (client.ts, session.ts, test)
- Add 'from __future__ import annotations' to Python session.py and
  remove quoted forward references (ruff UP037)
- Remove unused trackShellProcess function in Go (golangci-lint)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

✅ Cross-SDK Consistency Review: Excellent

I've reviewed PR #790 for consistency across all four SDK implementations (Node.js, Python, Go, .NET), and I'm pleased to report that this PR maintains exceptional feature parity and API design consistency.

Summary of Changes

This PR adds shell command execution support (session.shell.exec / session.shell.kill) with streaming output notifications across all SDKs. The implementation is highly consistent:

Type Definitions — Fully Aligned

All four SDKs define identical notification structures:

  • ShellOutputNotification: processId (string), stream (ShellOutputStream enum), data (string)
  • ShellExitNotification: processId (string), exitCode (int/number)
  • RPC Parameters: SessionShellExecParams (command, cwd?, timeout?) and SessionShellKillParams (processId, signal?)
  • Enums: ShellOutputStream (stdout, stderr) and Signal (SIGTERM, SIGKILL)

Session-Level Handler APIs — Consistent Patterns

All SDKs follow the same subscription pattern with proper unsubscribe mechanisms:

SDK Handler Method Unsubscribe Pattern
Node.js session.onShellOutput(handler) Returns () => void
Python session.on_shell_output(handler) Returns Callable[[], None]
Go session.OnShellOutput(handler) Returns func()
.NET session.OnShellOutput(handler) Returns IDisposable

The naming correctly follows language conventions (camelCase in TS, snake_case in Python, PascalCase in Go/C#).

Client-Level Routing — Identical Architecture

All SDKs implement:

  • A processId → session map for routing notifications to the correct session
  • handleShellOutput() / handleShellExit() methods that look up the session and dispatch
  • Automatic cleanup of the process mapping on shell.exit
  • Internal _trackShellProcess() / _untrackShellProcess() methods wired during session creation

Generated RPC Wrappers

All SDKs correctly generate session.rpc.shell.exec() and session.rpc.shell.kill() methods with proper async signatures.


Minor Observation: Test Coverage

Node.js includes comprehensive unit tests (nodejs/test/session-shell.test.ts) covering:

  • ✅ Handler registration and dispatch
  • ✅ Multiple handler support
  • ✅ Unsubscribe functionality
  • ✅ Error resilience (handler throws)
  • ✅ Process tracking callbacks

Python, Go, .NET do not include dedicated test files for the shell notification feature in this PR.

Recommendation: Consider adding equivalent test coverage to the other SDKs to ensure the same level of verification. However, this is a non-blocking observation — the implementations are correct and consistent. Tests can be added in a follow-up PR if desired.


Verdict

No consistency issues found. This PR exemplifies best practices for multi-language SDK development:

  • ✅ All four SDKs modified in parallel
  • ✅ API surface is semantically identical across languages
  • ✅ Language-specific idioms are respected (naming conventions, async patterns, disposal patterns)
  • ✅ Documentation is thorough and consistent (docstrings, XML comments, GoDoc)
  • ✅ Generated code is properly updated for all SDKs

Great work maintaining feature parity! 🎉

Generated by SDK Consistency Review Agent for issue #790 ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants