{"meta":{"title":"会话生命周期挂钩","intro":"会话生命周期挂钩使你能够响应会话开始和结束事件。 使用它们来：","product":"GitHub Copilot","breadcrumbs":[{"href":"/zh/enterprise-cloud@latest/copilot","title":"GitHub Copilot"},{"href":"/zh/enterprise-cloud@latest/copilot/how-tos","title":"操作方法"},{"href":"/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks","title":"使用挂钩"},{"href":"/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/session-lifecycle","title":"会话生命周期"}],"documentType":"article"},"body":"# 会话生命周期挂钩\n\n会话生命周期挂钩使你能够响应会话开始和结束事件。 使用它们来：\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n* 在会话开始时初始化上下文\n* 会话结束时清理资源\n* 跟踪会话指标和分析\n* 动态配置会话行为\n\n## 会话启动钩子 {#session-start}\n\n当会话开始时（新建或恢复），会调用 `onSessionStart` 钩子。\n\n### 挂钩签名\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\ntype SessionStartHandler = (\n  input: SessionStartHookInput,\n  invocation: HookInvocation\n) => Promise<SessionStartHookOutput | null | undefined>;\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nSessionStartHandler = Callable[\n    [SessionStartHookInput, dict[str, str]],\n    Awaitable[SessionStartHookOutput | None]\n]\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\ntype SessionStartHandler func(\n    input SessionStartHookInput,\n    invocation HookInvocation,\n) (*SessionStartHookOutput, error)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\npublic delegate Task<SessionStartHookOutput?> SessionStartHandler(\n    SessionStartHookInput input,\n    HookInvocation invocation);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n```java\n@FunctionalInterface\npublic interface SessionStartHandler {\n    CompletableFuture<SessionStartHookOutput> handle(\n        SessionStartHookInput input,\n        HookInvocation invocation);\n}\n```\n\n</div>\n\n</div>\n\n### 输入\n\n| 领域              | 类型         | 说明              |\n| --------------- | ---------- | --------------- |\n| `timestamp`     | number     | 触发挂钩时的 Unix 时间戳 |\n| `cwd`           | 字符串        | 当前工作目录          |\n| `source`        |            |                 |\n| `\"startup\"`     |            |                 |\n| \\|              |            |                 |\n| `\"resume\"`      |            |                 |\n| \\|              |            |                 |\n| `\"new\"`         |            |                 |\n| 会话的启动方式         |            |                 |\n| `initialPrompt` | 字符串 \\| 未定义 | 提供的初始提示         |\n\n### 输出\n\n| 领域                  | 类型  | 说明           |\n| ------------------- | --- | ------------ |\n| `additionalContext` | 字符串 | 在会话开始时添加的上下文 |\n| `modifiedConfig`    | 对象  | 替代会话配置       |\n\n### 例子\n\n#### 在启动时添加项目上下文\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input, invocation) => {\n      console.log(`Session ${invocation.sessionId} started (${input.source})`);\n      \n      const projectInfo = await detectProjectType(input.cwd);\n      \n      return {\n        additionalContext: `\nThis is a ${projectInfo.type} project.\nMain language: ${projectInfo.language}\nPackage manager: ${projectInfo.packageManager}\n        `.trim(),\n      };\n    },\n  },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot.session import PermissionHandler\n\nasync def on_session_start(input_data, invocation):\n    print(f\"Session {invocation['session_id']} started ({input_data['source']})\")\n    \n    project_info = await detect_project_type(input_data[\"cwd\"])\n    \n    return {\n        \"additionalContext\": f\"\"\"\nThis is a {project_info['type']} project.\nMain language: {project_info['language']}\nPackage manager: {project_info['packageManager']}\n        \"\"\".strip()\n    }\n\nsession = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={\"on_session_start\": on_session_start})\n```\n\n</div>\n\n</div>\n\n#### 处理会话恢复\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input, invocation) => {\n      if (input.source === \"resume\") {\n        // Load previous session state\n        const previousState = await loadSessionState(invocation.sessionId);\n        \n        return {\n          additionalContext: `\nSession resumed. Previous context:\n- Last topic: ${previousState.lastTopic}\n- Open files: ${previousState.openFiles.join(\", \")}\n          `.trim(),\n        };\n      }\n      return null;\n    },\n  },\n});\n```\n\n#### 加载用户首选项\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async () => {\n      const preferences = await loadUserPreferences();\n      \n      const contextParts = [];\n      \n      if (preferences.language) {\n        contextParts.push(`Preferred language: ${preferences.language}`);\n      }\n      if (preferences.codeStyle) {\n        contextParts.push(`Code style: ${preferences.codeStyle}`);\n      }\n      if (preferences.verbosity === \"concise\") {\n        contextParts.push(\"Keep responses brief and to the point.\");\n      }\n      \n      return {\n        additionalContext: contextParts.join(\"\\n\"),\n      };\n    },\n  },\n});\n```\n\n## 会话结束钩子 {#session-end}\n\n会话结束时调用`onSessionEnd`钩子。\n\n### 挂钩签名\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\ntype SessionEndHandler = (\n  input: SessionEndHookInput,\n  invocation: HookInvocation\n) => Promise<SessionEndHookOutput | null | undefined>;\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nSessionEndHandler = Callable[\n    [SessionEndHookInput, dict[str, str]],\n    Awaitable[SessionEndHookOutput | None]\n]\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\ntype SessionEndHandler func(\n    input SessionEndHookInput,\n    invocation HookInvocation,\n) (*SessionEndHookOutput, error)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\npublic delegate Task<SessionEndHookOutput?> SessionEndHandler(\n    SessionEndHookInput input,\n    HookInvocation invocation);\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n```java\n@FunctionalInterface\npublic interface SessionEndHandler {\n    CompletableFuture<SessionEndHookOutput> handle(\n        SessionEndHookInput input,\n        HookInvocation invocation);\n}\n```\n\n</div>\n\n</div>\n\n### 输入\n\n| 领域             | 类型         | 说明              |\n| -------------- | ---------- | --------------- |\n| `timestamp`    | number     | 触发挂钩时的 Unix 时间戳 |\n| `cwd`          | 字符串        | 当前工作目录          |\n| `reason`       | 字符串        | 会话结束的原因（请参阅下文）  |\n| `finalMessage` | 字符串 \\| 未定义 | 会话中的最后一条消息      |\n| `error`        | 字符串 \\| 未定义 | 会话因错误结束时的错误消息   |\n\n#### 结束原因\n\n| 原因            | 说明          |\n| ------------- | ----------- |\n| `\"complete\"`  | 会话正常完成      |\n| `\"error\"`     | 会话因错误而结束    |\n| `\"abort\"`     | 会话已由用户或代码中止 |\n| `\"timeout\"`   | 会话超时        |\n| `\"user_exit\"` | 用户显式结束会话    |\n\n### 输出\n\n| 领域               | 类型        | 说明          |\n| ---------------- | --------- | ----------- |\n| `suppressOutput` | boolean   | 抑制最终会话输出    |\n| `cleanupActions` | string\\[] | 要执行的清理操作列表  |\n| `sessionSummary` | 字符串       | 日志记录/分析会话摘要 |\n\n### 例子\n\n#### 跟踪会话指标\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nconst sessionStartTimes = new Map<string, number>();\n\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input, invocation) => {\n      sessionStartTimes.set(invocation.sessionId, input.timestamp);\n      return null;\n    },\n    onSessionEnd: async (input, invocation) => {\n      const startTime = sessionStartTimes.get(invocation.sessionId);\n      const duration = startTime ? input.timestamp - startTime : 0;\n      \n      await recordMetrics({\n        sessionId: invocation.sessionId,\n        duration,\n        endReason: input.reason,\n      });\n      \n      sessionStartTimes.delete(invocation.sessionId);\n      return null;\n    },\n  },\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot.session import PermissionHandler\n\nsession_start_times = {}\n\nasync def on_session_start(input_data, invocation):\n    session_start_times[invocation[\"session_id\"]] = input_data[\"timestamp\"]\n    return None\n\nasync def on_session_end(input_data, invocation):\n    start_time = session_start_times.get(invocation[\"session_id\"])\n    duration = input_data[\"timestamp\"] - start_time if start_time else 0\n    \n    await record_metrics({\n        \"session_id\": invocation[\"session_id\"],\n        \"duration\": duration,\n        \"end_reason\": input_data[\"reason\"],\n    })\n    \n    session_start_times.pop(invocation[\"session_id\"], None)\n    return None\n\nsession = await client.create_session(on_permission_request=PermissionHandler.approve_all, hooks={\n        \"on_session_start\": on_session_start,\n        \"on_session_end\": on_session_end,\n    })\n```\n\n</div>\n\n</div>\n\n#### 清理资源\n\n```typescript\nconst sessionResources = new Map<string, { tempFiles: string[] }>();\n\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input, invocation) => {\n      sessionResources.set(invocation.sessionId, { tempFiles: [] });\n      return null;\n    },\n    onSessionEnd: async (input, invocation) => {\n      const resources = sessionResources.get(invocation.sessionId);\n      \n      if (resources) {\n        // Clean up temp files\n        for (const file of resources.tempFiles) {\n          await fs.unlink(file).catch(() => {});\n        }\n        sessionResources.delete(invocation.sessionId);\n      }\n      \n      console.log(`Session ${invocation.sessionId} ended: ${input.reason}`);\n      return null;\n    },\n  },\n});\n```\n\n#### 保存会话状态以供恢复\n\n```typescript\nconst session = await client.createSession({\n  hooks: {\n    onSessionEnd: async (input, invocation) => {\n      if (input.reason !== \"error\") {\n        // Save state for potential resume\n        await saveSessionState(invocation.sessionId, {\n          endTime: input.timestamp,\n          cwd: input.cwd,\n          reason: input.reason,\n        });\n      }\n      return null;\n    },\n  },\n});\n```\n\n#### 日志会话摘要\n\n```typescript\nconst sessionData: Record<string, { prompts: number; tools: number; startTime: number }> = {};\n\nconst session = await client.createSession({\n  hooks: {\n    onSessionStart: async (input, invocation) => {\n      sessionData[invocation.sessionId] = { \n        prompts: 0, \n        tools: 0, \n        startTime: input.timestamp \n      };\n      return null;\n    },\n    onUserPromptSubmitted: async (_, invocation) => {\n      sessionData[invocation.sessionId].prompts++;\n      return null;\n    },\n    onPreToolUse: async (_, invocation) => {\n      sessionData[invocation.sessionId].tools++;\n      return { permissionDecision: \"allow\" };\n    },\n    onSessionEnd: async (input, invocation) => {\n      const data = sessionData[invocation.sessionId];\n      console.log(`\nSession Summary:\n  ID: ${invocation.sessionId}\n  Duration: ${(input.timestamp - data.startTime) / 1000}s\n  Prompts: ${data.prompts}\n  Tool calls: ${data.tools}\n  End reason: ${input.reason}\n      `.trim());\n      \n      delete sessionData[invocation.sessionId];\n      return null;\n    },\n  },\n});\n```\n\n## 代理停止钩子 {#agent-stop}\n\n当顶级代理自然到达轮次末尾时，代理停止挂钩将运行。 它与`onSessionEnd`分开：会话仍处于活动状态，并且该挂钩可以请求另一个代理轮次。\n\n| 语言                 | 处理程序             |\n| ------------------ | ---------------- |\n| Node.js/TypeScript | `onAgentStop`    |\n| Python             | `on_agent_stop`  |\n| Go                 | `OnAgentStop`    |\n| .NET               | `OnAgentStop`    |\n| Rust               | `on_agent_stop`  |\n| Java               | `setOnAgentStop` |\n\n### 输入\n\n公共成员名称遵循各语言的大小写约定：\n\n| Meaning                | Node.js/Python   | Go / .NET        | Rust               | Java                  |\n| ---------------------- | ---------------- | ---------------- | ------------------ | --------------------- |\n| : 代理停止的原因，例如`end_turn` | `stopReason`     | `StopReason`     | `stop_reason`      | `getStopReason()`     |\n| 磁盘上会话记录的路径             | `transcriptPath` | `TranscriptPath` | `transcript_path`  | `getTranscriptPath()` |\n| 先前的阻止决定是否已迫使其继续进行      | `stopHookActive` | `StopHookActive` | `stop_hook_active` | `getStopHookActive()` |\n\n### 输出\n\n不要返回任何输出，以使代理停止。 返回阻止决策，以将另一条用户消息加入队列并继续：\n\n```json\n{\n  \"decision\": \"block\",\n  \"reason\": \"Run the final validation and fix any failures.\"\n}\n```\n\n使用上面列出的活动停止成员，以避免重复阻止已因该挂钩而继续运行的代理。 运行时还会对连续的块决策次数设定上限。\n\n## 最佳做法\n\n1. **保持`onSessionStart`快速** - 用户正在等待会话准备就绪。\n\n2. **处理所有可能的结束原因** - 不要假定会话都会正常结束；要处理错误和中止情况。\n\n3. **清理资源** - 使用 `onSessionEnd` 释放会话期间分配的任何资源。\n\n4. **存储最小状态** - 如果跟踪会话数据，请保持轻量级。\n\n5. **使清理操作** - `onSessionEnd`具有幂等性；如果进程崩溃，可能不会调用该操作。\n\n## 另见\n\n* [使用挂钩](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks)\n* [错误处理挂钩](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/error-handling)\n* [调试指南](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/troubleshooting/debugging)"}