Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion copilot-sdk
Submodule copilot-sdk updated 77 files
+14 −14 .github/aw/github-agentic-workflows.md
+1 −1 .github/workflows/copilot-setup-steps.yml
+2 −0 .github/workflows/dotnet-sdk-tests.yml
+3 −1 .github/workflows/go-sdk-tests.yml
+5 −5 .github/workflows/issue-triage.lock.yml
+2 −0 .github/workflows/nodejs-sdk-tests.yml
+2 −0 .github/workflows/python-sdk-tests.yml
+5 −5 .github/workflows/sdk-consistency-review.lock.yml
+1 −1 CONTRIBUTING.md
+109 −2 docs/getting-started.md
+44 −7 dotnet/README.md
+193 −1 dotnet/src/Client.cs
+131 −3 dotnet/src/Generated/SessionEvents.cs
+3 −3 dotnet/src/GitHub.Copilot.SDK.csproj
+74 −0 dotnet/src/Types.cs
+1 −1 dotnet/test/AskUserTests.cs
+2 −2 dotnet/test/GitHub.Copilot.SDK.Test.csproj
+1 −0 dotnet/test/Harness/CapiProxy.cs
+2 −1 dotnet/test/Harness/E2ETestContext.cs
+1 −1 dotnet/test/McpAndAgentsTests.cs
+44 −23 go/README.md
+305 −112 go/client.go
+1 −1 go/client_test.go
+60 −12 go/generated_session_events.go
+1 −1 go/go.mod
+10 −11 go/internal/e2e/ask_user_test.go
+32 −33 go/internal/e2e/client_test.go
+8 −9 go/internal/e2e/compaction_test.go
+13 −14 go/internal/e2e/hooks_test.go
+22 −23 go/internal/e2e/mcp_and_agents_test.go
+13 −14 go/internal/e2e/permissions_test.go
+62 −62 go/internal/e2e/session_test.go
+11 −12 go/internal/e2e/skills_test.go
+27 −6 go/internal/e2e/testharness/context.go
+6 −5 go/internal/e2e/testharness/helper.go
+1 −1 go/internal/e2e/testharness/proxy.go
+13 −14 go/internal/e2e/tools_test.go
+41 −45 go/internal/jsonrpc2/jsonrpc2.go
+23 −21 go/session.go
+45 −0 go/types.go
+35 −0 nodejs/README.md
+402 −357 nodejs/package-lock.json
+6 −6 nodejs/package.json
+185 −1 nodejs/src/client.ts
+57 −2 nodejs/src/generated/session-events.ts
+5 −0 nodejs/src/index.ts
+53 −0 nodejs/src/types.ts
+0 −0 nodejs/test/e2e/ask_user.test.ts
+4 −1 nodejs/test/e2e/harness/sdkTestContext.ts
+0 −0 nodejs/test/e2e/mcp_and_agents.test.ts
+6 −7 nodejs/test/e2e/session.test.ts
+29 −0 python/README.md
+151 −2 python/copilot/client.py
+175 −14 python/copilot/generated/session_events.py
+1 −1 python/copilot/session.py
+52 −0 python/copilot/types.py
+3 −3 python/e2e/test_ask_user.py
+10 −4 python/e2e/test_hooks.py
+13 −92 python/e2e/test_mcp_and_agents.py
+9 −28 python/e2e/test_permissions.py
+9 −1 python/e2e/test_session.py
+3 −0 python/e2e/testharness/context.py
+0 −1 python/pyproject.toml
+216 −171 test/harness/package-lock.json
+3 −3 test/harness/package.json
+59 −21 test/harness/replayingCapiProxy.ts
+21 −0 test/snapshots/ask_user/should_handle_freeform_user_input_response.yaml
+21 −0 test/snapshots/ask_user/should_invoke_user_input_handler_when_model_uses_ask_user_tool.yaml
+21 −0 test/snapshots/ask_user/should_receive_choices_in_user_input_request.yaml
+10 −0 test/snapshots/mcp_and_agents/should_accept_both_mcp_servers_and_custom_agents.yaml
+10 −0 test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_create.yaml
+14 −0 test/snapshots/mcp_and_agents/should_accept_custom_agent_configuration_on_session_resume.yaml
+10 −0 test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_create.yaml
+14 −0 test/snapshots/mcp_and_agents/should_accept_mcp_server_configuration_on_session_resume.yaml
+1 −1 test/snapshots/permissions/should_receive_toolcallid_in_permission_requests.yaml
+7 −5 test/snapshots/session/send_returns_immediately_while_events_stream_in_background.yaml
+8 −0 test/snapshots/session/sendandwait_throws_on_timeout.yaml
154 changes: 154 additions & 0 deletions docs/jp/session-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# セッションライフサイクルイベント

セッションライフサイクルイベントは、セッションの作成、削除、更新、およびフォアグラウンド/バックグラウンドの状態変更(TUI+サーバーモード時)を通知するイベントです。

## イベントタイプ

`SessionLifecycleEventType` enumで定義されている5つのイベントタイプがあります:

| イベント | 説明 |
|---------|------|
| `session.created` | 新しいセッションが作成された |
| `session.deleted` | セッションが削除された |
| `session.updated` | セッションが更新された |
| `session.foreground` | セッションがフォアグラウンドに移動した |
| `session.background` | セッションがバックグラウンドに移動した |

```php
use Revolution\Copilot\Enums\SessionLifecycleEventType;

// イベントタイプの値を取得
echo SessionLifecycleEventType::SESSION_CREATED->value; // 'session.created'
```

## イベントの購読

`onLifecycle()` メソッドを使用してセッションライフサイクルイベントを購読できます:

```php
use Revolution\Copilot\Facades\Copilot;
use Revolution\Copilot\Types\SessionLifecycleEvent;

// Clientを取得してライフサイクルイベントを購読
$client = Copilot::client();

// 全てのライフサイクルイベントを購読
$unsubscribe = $client->onLifecycle(function (SessionLifecycleEvent $event) {
match ($event->type) {
SessionLifecycleEventType::SESSION_CREATED => info("セッション作成: {$event->sessionId}"),
SessionLifecycleEventType::SESSION_DELETED => info("セッション削除: {$event->sessionId}"),
SessionLifecycleEventType::SESSION_FOREGROUND => info("フォアグラウンド: {$event->sessionId}"),
SessionLifecycleEventType::SESSION_BACKGROUND => info("バックグラウンド: {$event->sessionId}"),
default => null,
};
});

// 購読を解除
$unsubscribe();
```

## SessionLifecycleEvent

`SessionLifecycleEvent` はライフサイクルイベントの詳細を含むreadonly classです:

```php
use Revolution\Copilot\Types\SessionLifecycleEvent;
use Revolution\Copilot\Types\SessionLifecycleEventMetadata;

readonly class SessionLifecycleEvent implements Arrayable
{
public function __construct(
public SessionLifecycleEventType $type, // イベントタイプ
public string $sessionId, // セッションID
public ?SessionLifecycleEventMetadata $metadata = null, // メタデータ(削除時は含まれない)
) {}
}
```

### SessionLifecycleEventMetadata

セッションのメタデータを含むクラスです(削除イベントには含まれません):

```php
readonly class SessionLifecycleEventMetadata implements Arrayable
{
public function __construct(
public string $startTime, // セッション開始時刻
public string $modifiedTime, // 最終更新時刻
public ?string $summary = null, // セッションの要約
) {}
}
```

## フォアグラウンドセッション管理

TUI+サーバーモード(`--ui-server`)で動作しているサーバーに接続している場合、フォアグラウンドセッションを管理できます。

### 現在のフォアグラウンドセッションを取得

```php
use Revolution\Copilot\Facades\Copilot;

$client = Copilot::client();

// TUIに表示されている現在のセッションIDを取得
$sessionId = $client->getForegroundSessionId();

if ($sessionId !== null) {
echo "現在のフォアグラウンドセッション: {$sessionId}";
}
```

### フォアグラウンドセッションを設定

```php
use Revolution\Copilot\Facades\Copilot;

$client = Copilot::client();

// TUIに特定のセッションを表示
$client->setForegroundSessionId('session-123');
```

## ForegroundSessionInfo

フォアグラウンドセッションの情報を含むreadonly classです:

```php
use Revolution\Copilot\Types\ForegroundSessionInfo;

readonly class ForegroundSessionInfo implements Arrayable
{
public function __construct(
public ?string $sessionId = null, // フォアグラウンドセッションID
public ?string $workspacePath = null, // ワークスペースパス
) {}
}
```

## TCPモードでの使用

TUI+サーバーモードでCopilot CLIを起動し、SDKから接続することでライフサイクルイベントを活用できます:

```bash
# TUI+サーバーモードでCopilot CLIを起動
copilot --ui-server --port 8080
```

```php
use Revolution\Copilot\Facades\Copilot;

// TCPモードでサーバーに接続
$client = Copilot::useTcp('tcp://127.0.0.1:8080')->client();

// ライフサイクルイベントを購読
$client->onLifecycle(function (SessionLifecycleEvent $event) {
// イベント処理
});
```

## 注意事項

- `session.foreground` と `session.background` イベントは、TUI+サーバーモードでのみ発生します
- セッション削除時(`session.deleted`)には `metadata` プロパティは `null` になります
- `onLifecycle()` は購読解除用のコールバック関数を返します
102 changes: 102 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
use Revolution\Copilot\JsonRpc\JsonRpcClient;
use Revolution\Copilot\Process\ProcessManager;
use Revolution\Copilot\Transport\TcpTransport;
use Revolution\Copilot\Types\ForegroundSessionInfo;
use Revolution\Copilot\Types\GetAuthStatusResponse;
use Revolution\Copilot\Types\GetStatusResponse;
use Revolution\Copilot\Types\ModelInfo;
use Revolution\Copilot\Types\ResumeSessionConfig;
use Revolution\Copilot\Types\SessionConfig;
use Revolution\Copilot\Types\SessionEvent;
use Revolution\Copilot\Types\SessionLifecycleEvent;
use Revolution\Copilot\Types\SessionMetadata;
use Revolution\Copilot\Types\ToolResultObject;
use Revolution\Copilot\Types\UserInputRequest;
Expand Down Expand Up @@ -63,6 +65,13 @@ class Client implements CopilotClient
*/
protected array $sessions = [];

/**
* Session lifecycle event handlers.
*
* @var array<callable(SessionLifecycleEvent): void>
*/
protected array $lifecycleHandlers = [];

/**
* Create a new CopilotClient.
*/
Expand Down Expand Up @@ -499,6 +508,65 @@ public function listSessions(): array
);
}

/**
* Gets the foreground session ID in TUI+server mode.
*
* This returns the ID of the session currently displayed in the TUI.
* Only available when connecting to a server running in TUI+server mode (--ui-server).
*
* @throws JsonRpcException
*/
public function getForegroundSessionId(): ?string
{
$this->ensureConnected();

$response = $this->rpcClient->request('session.getForeground', []);

return ForegroundSessionInfo::fromArray($response)->sessionId;
}

/**
* Sets the foreground session in TUI+server mode.
*
* This requests the TUI to switch to displaying the specified session.
* Only available when connecting to a server running in TUI+server mode (--ui-server).
*
* @throws RuntimeException|JsonRpcException
*/
public function setForegroundSessionId(string $sessionId): void
{
$this->ensureConnected();

$response = $this->rpcClient->request('session.setForeground', [
'sessionId' => $sessionId,
]);

if (! ($response['success'] ?? false)) {
throw new RuntimeException($response['error'] ?? 'Failed to set foreground session');
}
}

/**
* Subscribes to session lifecycle events.
*
* Lifecycle events are emitted when sessions are created, deleted, updated,
* or change foreground/background state (in TUI+server mode).
*
* @param callable(SessionLifecycleEvent): void $handler
* @return callable(): void A function that, when called, unsubscribes the handler
*/
public function onLifecycle(callable $handler): callable
{
$this->lifecycleHandlers[] = $handler;

return function () use ($handler) {
$this->lifecycleHandlers = array_filter(
$this->lifecycleHandlers,
fn ($h) => $h !== $handler,
);
};
}

/**
* Ensure the client is connected.
*
Expand Down Expand Up @@ -551,6 +619,40 @@ protected function handleNotification(string $method, array $params): void
$this->sessions[$sessionId]->dispatchEvent($event);
}
}

if ($method === 'session.lifecycle') {
$this->handleLifecycleNotification($params);
}
}

/**
* Handle session lifecycle notifications.
*/
protected function handleLifecycleNotification(array $params): void
{
// Validate required fields
if (! isset($params['type']) || ! is_string($params['type'])) {
return;
}

if (! isset($params['sessionId']) || ! is_string($params['sessionId'])) {
return;
}

try {
$event = SessionLifecycleEvent::fromArray($params);

// Dispatch to all registered handlers
foreach ($this->lifecycleHandlers as $handler) {
try {
$handler($event);
} catch (Throwable) {
// Ignore handler errors
}
}
} catch (Throwable) {
// Ignore parsing errors for invalid event types
}
}

/**
Expand Down
23 changes: 23 additions & 0 deletions src/Contracts/CopilotClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Revolution\Copilot\Types\ModelInfo;
use Revolution\Copilot\Types\ResumeSessionConfig;
use Revolution\Copilot\Types\SessionConfig;
use Revolution\Copilot\Types\SessionLifecycleEvent;
use Throwable;

/**
Expand Down Expand Up @@ -68,6 +69,28 @@ public function getAuthStatus(): GetAuthStatusResponse;
*/
public function listModels(): array;

/**
* Gets the foreground session ID in TUI+server mode.
*
* @throws JsonRpcException
*/
public function getForegroundSessionId(): ?string;

/**
* Sets the foreground session in TUI+server mode.
*
* @throws JsonRpcException
*/
public function setForegroundSessionId(string $sessionId): void;

/**
* Subscribes to session lifecycle events.
*
* @param callable(SessionLifecycleEvent): void $handler
* @return callable(): void A function that, when called, unsubscribes the handler
*/
public function onLifecycle(callable $handler): callable;

/**
* Stop the CLI server and close all sessions.
*
Expand Down
5 changes: 5 additions & 0 deletions src/Enums/SessionEventType.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ enum SessionEventType: string
case SESSION_MODEL_CHANGE = 'session.model_change';
case SESSION_HANDOFF = 'session.handoff';
case SESSION_TRUNCATION = 'session.truncation';
case SESSION_SNAPSHOT_REWIND = 'session.snapshot_rewind';
case SESSION_SHUTDOWN = 'session.shutdown';
case SESSION_USAGE_INFO = 'session.usage_info';
case SESSION_COMPACTION_START = 'session.compaction_start';
case SESSION_COMPACTION_COMPLETE = 'session.compaction_complete';
Expand Down Expand Up @@ -46,6 +48,9 @@ enum SessionEventType: string
case TOOL_EXECUTION_PROGRESS = 'tool.execution_progress';
case TOOL_EXECUTION_COMPLETE = 'tool.execution_complete';

// Skill events
case SKILL_INVOKED = 'skill.invoked';

// Subagent events
case SUBAGENT_STARTED = 'subagent.started';
case SUBAGENT_COMPLETED = 'subagent.completed';
Expand Down
20 changes: 20 additions & 0 deletions src/Enums/SessionLifecycleEventType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Revolution\Copilot\Enums;

/**
* Types of session lifecycle events.
*
* These events are emitted when sessions are created, deleted, updated,
* or change foreground/background state (in TUI+server mode).
*/
enum SessionLifecycleEventType: string
{
case SESSION_CREATED = 'session.created';
case SESSION_DELETED = 'session.deleted';
case SESSION_UPDATED = 'session.updated';
case SESSION_FOREGROUND = 'session.foreground';
case SESSION_BACKGROUND = 'session.background';
}
2 changes: 1 addition & 1 deletion src/Process/ProcessManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ protected function startProcess(): void
$env['COPILOT_SDK_AUTH_TOKEN'] = $this->githubToken;
}

$args = ['--server', '--stdio', '--log-level', $this->logLevel];
$args = ['--headless', '--stdio', '--log-level', $this->logLevel];

// Add auth-related flags
if (filled($this->githubToken)) {
Expand Down
Loading