调度器代理管理指南
在一个界面中管理按项目(CSN)注册的自动化运行器(gissue 调度器等后台代理):在线/离线状态、运行状态与历史、以及实时日志。
🤖 让 AI 帮你创建 Gissue 调度器
把这个文档交给 AI,AI 会 clone 或更新 giip-fde-agent 仓库(https://github.com/LowyShin/giip-fde-agent.git),然后自动在这台 PC 上完成 gissue 处理调度器的安装、注册和验证。不要从零开始写脚本,使用此步骤。
提示词(复制后直接使用):
读取本文档(/help/catquest-schedulers.zh-CN.md),并按照下面的"安装步骤"在此 PC 上安装 gissue 调度器。
如果没有 giip-fde-agent 则 clone,如果有则执行 git pull --ff-only 更新。如果存在本地更改,不要覆盖,先停止并报告。
目标 CSN 指定为 <CSN>,工作文件夹指定为 <路径>。
从 giipAgent.cnf(或 giipAgent.cfg)读取 sk= 的值,不要询问用户。
报告每个步骤的确认结果。
安装步骤
- 预检 — 执行 →
git --version、node --version、claude --version,Windows 执行powershell -Command $PSVersionTable.PSVersion,Linux 执行pwsh --version。全部在 PATH 中后才能进入下一步。 - 获取 giip-fde-agent — 执行 → 没有则
git clone https://github.com/LowyShin/giip-fde-agent.git,有则git pull --ff-only。如有本地更改则停止并报告。 - 读取规范文档 — 读取
docs/60-operations/hourly-issue-scheduler.md§4~§5,确认 CSN→workdir 映射、心跳块和 SK 位置。 - 编写 csn-projects.json — 将
scripts/gissue/csn-projects.json.example复制为csn-projects.json。填写目标 CSN 的workdir/enabled: true,并在顶层heartbeat块中必须指定lssn(服务器编号)/hostname(本机标识符)/skFile(sk= 格式文件路径)。缺失则此屏幕上不会显示该行。 - 准备密钥 — 从上级文件夹的
giipAgent.cnf(或giipAgent.cfg)读取sk=值。不向用户询问,也不记录到输出中。同时准备好slack-bot/.secrets/giip-accounts.json(从 .sample.json 复制)。 - 试运行 — 执行
scripts/gissue/manage-gissue-scheduler.ps1 -Action DryRun -OnlyCsn <CSN>→ 如果结束无[PREFLIGHT-FAIL]日志则成功。 - 注册
- Windows —
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\register-hourly-issue-scheduler.ps1 -Action Register -RepoRoot <giip-fde-agent 根目录> - Linux — 没有专用注册脚本,用类似
/etc/cron.d/gissue-scheduler的一行 cron 注册(与实际 docker 部署格式相同):7,27,47 * * * * <普通用户> cd <根目录> && pwsh -NoProfile -NonInteractive -File "<根目录>/scripts/gissue/run-gissue-claude.ps1" -OnlyCsn <CSN> >> <根目录>/scripts/gissue/logs/cron.log 2>&1。必须以普通用户注册,不能用 root(claude 在 root 下会拒绝--dangerously-skip-permissions,导致所有 issue 均失败)。该用户的 claude 登录信息也必须提供。 - Docker 实例 — 容器启动时
docker/entrypoint.sh会自动注册上述 cron(GIIP_ENABLE_SCHEDULER默认为 true,-OnlyCsn从csn-projects.json派生)。此时步骤 7 已完成,只需确认/etc/cron.d/gissue-scheduler存在,并用scripts/bootstrap-instance.sh检查环境(pwsh 链接等)。
- Windows —
- 验证 — 执行
manage-gissue-scheduler.ps1 -Action RunOnce -OnlyCsn <CSN>一次,或等待下一个 :07,然后刷新本界面 → 如果gissue_csn<CSN>行显示 online 且 History 有执行记录则成功。若未显示,检查scripts/gissue/logs/gissue_csn<CSN>.log中的[WARN][SchedulerState-...]行。
AI 不得做的事
- 不得新建 giip-fde-agent 的 runner(
run-gissue-claude.ps1) - 不得另行编写直接调用 SP 的代码(已安装的 runner 会处理)
- 不得在文档或日志中留下 SK 值
- 不得修改 giip-fde-agent 代码本身
📋 概览
调度器代理管理 显示当前所选项目(CSN)下注册的所有自动化代理 —— 例如 gissue 的每小时调度器、交互式 Claude Code 会话等。与将 giip 问题 可视化为猫咪的 /admin/catquest 不同,本界面是用于管理 运行自动化任务的代理本身 的运维仪表盘:查看每个代理是否存活、当前运行是否健康、以及在哪里查看其日志。
🎨 状态颜色
每个代理会显示两个相互独立的状态信号——请勿混淆。
身份状态(代理名称旁的小圆点)
表示代理进程本身是否可达(tSchedulerAgent.status),与单次运行无关。
| 颜色 | 状态 | 含义 |
|---|---|---|
| 🟢 绿色 | online | 代理可达且正在正常上报 |
| 🔴 红色 | error | 代理上报了错误状态 |
| ⚪ 灰色(slate-400) | offline | 当前代理不可达 |
| ⚪ 浅灰色(slate-300) | unknown | 尚未上报任何状态 |
运行状态(Status 列徽章)
反映最近一次调度器 运行(run) 的状态(currentStatus),以及 stale(僵滞)检测结果。
| 颜色 | 显示标签 | 条件 | 含义 |
|---|---|---|---|
| 🟢 绿色 | RUNNING | currentStatus = RUNNING 且未 stale | 正在正常执行 |
| 🔴 红色 | STALE | currentStatus = RUNNING 且 isStale = true | 标记为运行中,但心跳长时间无响应 —— 看起来像卡死/僵死运行 |
| 🟠 橙色 | STALE | currentStatus = STALE(显式值) | 后端将该次运行本身标记为 stale |
| ⚪ 灰色 | ENDED | currentStatus = ENDED | 已结束(正常完成或受控停止) |
| ⚪ 灰色 | (原始值) | 其他情况 / 尚无运行记录 | 未知 / 无数据 |
在下方的运行历史弹窗中,已完成的运行还可能以红色的 FAILED 显示——这是以错误结束的运行,与 ENDED(正常结束)或 stale/超时不同。
🔍 界面结构
1. 顶部标题栏
- 返回
/admin/catquest的箭头(←)。 - Refresh:重新加载当前 CSN 的代理列表。
2. Open Interactive Sessions
仅当至少存在一个活跃的 claude_interactive_session 代理时(即当前在某主机上注册为运行中的交互式 Claude Code 会话),才会单独显示此卡片。每一行显示会话名称、主机、最后通信时间,以及当该代理的 capabilities 中注册了会话 URL 时显示的 "Open session" 链接。
3. 代理列表(表格)
每个调度器代理占一行,包含:
- Name —— 身份状态圆点、代理名称、操作系统/代理类型/版本徽章、
lssn(关联服务器)、截断显示的 capabilities 摘要,以及最近记录的错误(如有)。 - Host ——
hostIdentifier。 - Windows Task —— 若适用,运行该代理的 Windows 任务计划程序任务名称。
- Project —— 项目名称。
- Active —— ON/OFF 徽章(
isActive)。 - Last Comm —— 最后一次心跳/通信时间戳。
- Status —— 上述运行状态徽章,以及(如有上报)当前阶段(phase)文本。
底部统计栏汇总列表中所有代理的 已处理(Processed)/ 已跳过(Skipped)/ 失败(Failed) 计数。
4. 运行历史(Run History)
表格下方的 "View History" 卡片再次列出所有代理;点击某行的 History 按钮会打开弹窗,显示该代理最近的运行记录——执行模式、状态(含 FAILED)、开始/结束时间、耗时、已处理/已跳过/失败计数、阶段、以及摘要。
5. 实时日志查看器(Live Log Viewer)
双栏实时追踪界面:
- 左侧(代理 → 日志流树):代理按分组显示,展开后可看到已知的日志流列表(日志流以
streamKey标识,具有streamType和轮转代数)。代理会根据最近通信时间显示 online/offline。 - 右侧(日志流卡片):选中某个日志流后,会显示其最近的日志行,并持续刷新以实现实时追踪。
➕ 添加其他主机
本界面没有手动"添加"主机的按钮或表单——列表完全由下面两条自动注册路径产生。两条路径都使用**按 CSN(项目)签发的密钥(SK)**调用 giipApiSk4 端点,将自身 upsert 到 tSchedulerAgent/tSchedulerAgentRun 中;若没有目标 CSN 的有效 SK,调用本身就会以 401 Unauthorized 失败,因此即使知道下面的步骤,也无法擅自向他人的项目注册虚假代理。
⚠️ 下面出现的 SK/AK 值始终是占位符。切勿将真实值硬编码到文档或代码中,签发后的值请各自以安全的方式保存(密钥文件、环境变量、密钥管理器等)。
通用前提条件
| 项目 | 说明 |
|---|---|
| 目标 CSN | 要注册代理的项目编号。本页按会话当前的 CSN 显示列表。 |
| SK(密钥) | 对该 CSN 具有写入(注册)权限的密钥。请向项目管理员申请签发该 CSN 的 SK——无法通过本界面或 API 自行签发。 |
| 注册端点 | https://giipfaw.azurewebsites.net/api/giipApiSk4(匿名认证——无需 Function key,通过请求表单中的 SK 令牌进行认证) |
将控制台注册到此页面 — 原样复制并粘贴到控制台
如果此页面的 "Open Interactive Sessions" 中看不到您正在使用的 Claude Code 控制台,请把下面的请求文原样粘贴到控制台(Claude Code)。控制台仅依据下方"方法 A"的规范(agentKey 规则、SP 调用)完成注册,因此不需要额外脚本或其他仓库。请把 <CSN> 替换为要注册的项目编号。
前提条件:目标 CSN 的 SK 必须以
sk=的形式存在于这台 PC 上级文件夹的giipAgent.cnf(或giipAgent.cfg)中(没有的话,请项目管理员为该 CSN 签发 SK)。控制台会从该文件读取 SK,不会向用户反问。
请求文 1 — 一次性注册当前打开的这个控制台
请把我当前打开的这个控制台会话,作为 CSN <CSN> 的交互式会话,一次性注册到 giip 调度器代理页面(/zh-CN/admin/catquest/schedulers)。
请阅读本文档(/help/catquest-schedulers.zh-CN.md)中的"方法 A —— 注册交互式 Claude Code 会话",并严格按其规范执行。请假定不存在 register-interactive-session.ps1 之类的单独脚本,直接执行文档中写明的 giipApiSk4 调用(HTTP POST,表单字段 text/token/jsondata)。
1. agentKey:按文档"agentKey 生成规则"的 5 个步骤生成。原始 ID 优先用本会话的 session_id(若已知),否则用 fallback_<COMPUTERNAME>_<PID>。如果没有 COMPUTERNAME 环境变量,就使用 hostname 命令的结果。
2. SchedulerAgentUpsert:text 为 "SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version"。jsondata 包含 agentKey、displayName="Claude Interactive Session (<主机名>)"、hostIdentifier=<主机名>、windowsTaskName/projectName/scheduleDesc 为空字符串、isActive="1"、lssn="0"、osType=Windows 或 Linux、agentType="claude_interactive_session"、version=""。如果存在环境变量 CLAUDE_CODE_BRIDGE_SESSION_ID,就在 text 和 jsondata 的最末尾追加 capabilities={"sessionUrl":"https://claude.ai/code/<该值>"};如果不存在,就整个省略 capabilities。不要用其他值编造 sessionUrl。
3. SchedulerAgentRunStart:text 为 "SchedulerAgentRunStart runIdKey agentKey executionMode totalIssueCount"。runIdKey=<agentKey>_<当前时间 yyyyMMddHHmmss>,executionMode="interactive",totalIssueCount="0"。
4. SK 从上级文件夹中 giipAgent.cnf(没有则 giipAgent.cfg)的 sk= 值读取,作为 token 使用。不要向我反问,也不要在屏幕、日志或任何文件中输出或记录 SK 的值。
5. 确认:用同一个 giipApiSk4 调用 text "SchedulerAgentList csn"、jsondata {"csn":"<CSN>"},确认刚创建的 agentKey 对应的行 is_active=true 且 current_status=RUNNING。把结果和所用的 agentKey/runIdKey 告诉我(不含 SK)。
请求文 2 — 设置钩子,让今后所有会话自动注册
请设置 SessionStart/SessionEnd 钩子,使这台 PC 上每次打开或关闭 Claude Code 会话时,都自动在 giip 调度器代理页面中注册/注销。目标 CSN 为 <CSN>。
请阅读本文档(/help/catquest-schedulers.zh-CN.md)中的"方法 A —— 注册交互式 Claude Code 会话",仅依据该规范自行实现。不要试图从其他仓库获取脚本。
1. 在用户主目录下的某处创建钩子处理脚本(例如 ~/.claude/giip-console-hook.ps1)。它接收 Start 或 End 作为参数,并从标准输入读取钩子事件 JSON。非 Windows 环境下也可以用 pwsh 或 bash 编写。
2. 钩子注册按文档中的 JSON,在 ~/.claude/settings.json(全局)的 hooks.SessionStart 和 hooks.SessionEnd 中添加 command 钩子。不要加 matcher。不要覆盖已有的 hooks 条目,而是追加到数组中,并原样使用文档 JSON 中的 timeout 值。
3. 排除无头运行:脚本先沿父进程向上查找,如果 claude 进程的命令行中含有 -p 或 --print,就什么都不做直接退出(文档中的无头判定)。不要根据 CLAUDE_CODE_BRIDGE_SESSION_ID 是否存在来判定无头。
4. Start:按文档"agentKey 生成规则"生成 agentKey(原始 ID = stdin 中的 session_id,没有则 fallback_<COMPUTERNAME>_<PID>),然后调用 SchedulerAgentUpsert(agentType="claude_interactive_session",isActive="1",字段及顺序与文档示例相同)和 SchedulerAgentRunStart(executionMode="interactive",totalIssueCount="0")。runIdKey=<agentKey>_<当前时间 yyyyMMddHHmmss>,并保存到临时文件夹中的文件里供 End 使用。只有存在 CLAUDE_CODE_BRIDGE_SESSION_ID 时才加入 capabilities.sessionUrl;不存在则省略 capabilities 继续注册。
5. End:按同样规则重新计算 agentKey,调用 SchedulerAgentRunEnd(status="ENDED",processedCount/skippedCount/failedCount="0"),然后以 isActive="0" 调用 SchedulerAgentUpsert,切换为离线。
6. 所有调用都通过 giipApiSk4(text/token/jsondata)进行,并且始终以退出码 0 结束,避免钩子失败时阻塞会话。
7. 不要把 SK 的值写进脚本,只写入 giipAgent.cnf(没有则 giipAgent.cfg)的绝对路径,每次运行时从中读取 sk=。不要向我反问,也不要在输出、日志或文件中留下 SK 的值。
8. 完成后,告诉我创建或修改的文件路径列表,以及在新会话中确认是否已注册的方法。
- 用请求文 1 注册的会话没有结束钩子,如需在关闭时注销,请对控制台说:"用你告诉我的 agentKey/runIdKey 调用 SchedulerAgentRunEnd,再以 isActive=0 调用 SchedulerAgentUpsert。"使用钩子(请求文 2)则会在退出时自动注销。
- 成功确认:刷新页面后,该会话会出现在 "Open Interactive Sessions" 卡片和代理列表中。详细规范请参阅下方的"方法 A"。
方法 A —— 注册交互式 Claude Code 会话
要让 Claude Code 这样的交互式 AI 编程会话出现在 "Open Interactive Sessions" 卡片中,需要在会话的 SessionStart/SessionEnd 钩子中配置执行以下步骤。
- 前提条件:上述通用前提条件(目标 CSN 的 SK),以及在运行会话的工具(如 Claude Code)中配置 SessionStart/SessionEnd 钩子的权限。
- 会话开始时(SessionStart)——先调用
SchedulerAgentUpsert注册/刷新身份,再调用SchedulerAgentRunStart记录运行开始。 - 会话结束时(SessionEnd)——调用
SchedulerAgentRunEnd记录运行结束,然后以isActive=0再次调用SchedulerAgentUpsert将代理切换为离线。
agentKey 生成规则(照原样执行,无需判断):数据库中 csn + agentKey 组合是 UNIQUE 约束,因此同一会话重启后也必须始终得到相同的值(否则每次重启都会插入新行而不是更新已有行)。请按以下步骤原样计算:
- 原始 ID:若钩子 stdin 的 JSON 中存在
session_id字段,则使用该值;若不存在,则使用fallback_<COMPUTERNAME 环境变量的值>_<当前进程 PID>作为原始 ID。 - 规范化:从原始 ID 中删除所有非英数字字符(
a-z、A-Z、0-9)(正则替换:[^a-zA-Z0-9]→'')。 - 截断(第一次):若规范化后的值超过24个字符,只保留前24个字符。
- 组合:
agentKey = "claude_interactive_" + <COMPUTERNAME 环境变量的值> + "_" + <步骤3的结果> - 截断(第二次):若步骤4的结果超过100个字符,只保留前100个字符。将该最终值作为
agentKey。
SessionStart/SessionEnd 钩子的注册位置(可直接粘贴的 JSON):以 Claude Code 为例,选择项目根目录的 .claude/settings.json(仅对该项目生效)或用户主目录的 ~/.claude/settings.json(对所有项目全局生效)二者之一,将下面的 hooks 键合并到该文件的顶层。若文件中已存在 hooks 键,则在 SessionStart/SessionEnd 数组内添加下面的对象(不要覆盖已有条目)。不要添加 matcher 字段——该钩子对所有会话全局生效。
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "<会话开始时要执行的 command。例如:powershell -NoProfile -ExecutionPolicy Bypass -File \"<注册脚本的绝对路径>\" -Action Start>",
"timeout": 15000
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "<会话结束时要执行的 command。例如:powershell -NoProfile -ExecutionPolicy Bypass -File \"<注册脚本的绝对路径>\" -Action End>",
"timeout": 15000
}
]
}
]
}
}
⚠️ 以全局(无 matcher)钩子注册时要警惕无头误判(giip #1837):上面的钩子没有
matcher,因此该主机上运行的所有 claude 进程都会触发它——不仅是交互式会话,无人值守调度器以claude -p打开的每一次无头执行也会触发。这意味着你的注册脚本实际上必须包含"当前这次执行是交互式还是无头"的判断逻辑。此时请勿使用$env:CLAUDE_CODE_BRIDGE_SESSION_ID(Remote Control 桥接是否已连接)作为判断依据——该值只反映桥接连接状态,与是否无头无关;一个真正的交互式会话,如果还没有执行/连接/remote-control,同样可能没有这个值(这个具体的缺陷已经真实导致交互式会话被误判为无头而被静默跳过注册——giip #1837)。正确的判断方法:从执行该钩子的进程本身向上追溯父进程,找到真正的claude/claude.exe进程,检查该进程的命令行中是否包含-p/function Test-IsHeadlessClaudeInvocation { try { $checkPid = $PID for ($i = 0; $i -lt 6; $i++) { $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$checkPid" -ErrorAction Stop if (-not $proc) { return $false } if ($proc.Name -eq 'claude.exe') { $cmd = $proc.CommandLine if ($cmd -and ($cmd -match '(^|\s)(-p|--print)(\s|$)')) { return $true } return $false } $checkPid = $proc.ParentProcessId if (-not $checkPid -or $checkPid -eq 0) { return $false } } return $false } catch { return $false } }在非 Windows 环境下,请使用该操作系统对应的进程查询方式实现同样的原理(检查祖先进程命令行中是否包含
-p/
该 command 会从标准输入(stdin)接收钩子事件的 JSON(包含 session_id 等)。仅凭本文档中记载的 SP 调用规范(调用约定、字段列表、agentKey 生成规则)即可自行完整实现——即使维护本文档的运维者个人有一份参考实现,那个仓库也是私有的,其他会话无法访问,因此不要试图从任何地方获取代码,请仅依据本文档的规范自行编写。
请注意,agentKey 和 capabilities.sessionUrl 来自两个不同的 ID——把它们混为一谈,可能会让你误以为"没有会话 ID 所以无法构造 URL",从而放弃整个注册流程:
agentKey——基于钩子 stdin 中的session_id(工具内部的 UUID,即~/.claude/projects/*/<uuid>.jsonl文件名中使用的值)构造。其唯一作用是让同一会话重启后得到相同的键,它不是 URL。capabilities.sessionUrl——若存在的话,基于$env:CLAUDE_CODE_BRIDGE_SESSION_ID(例如 ULID 风格的值session_01JGmSiTajXu9iZmjVZC27Nt——与钩子 stdin 中的session_id是不同的 ID)构造为https://claude.ai/code/<该值>。
并非每个会话都存在这个环境变量——例如从未经过 claude.ai/code 桥接的本地 CLI 会话可能没有它。如果 $env:CLAUDE_CODE_BRIDGE_SESSION_ID 不存在,只是意味着你无法构造 sessionUrl,并不意味着注册被阻塞——此时直接从调用中完全去掉 capabilities 字段,用其余字段继续完成注册即可(参见下文"无法构造 sessionUrl 时")。
CLAUDE_CODE_BRIDGE_SESSION_ID 何时会被设置,缺失时事后能否补上(依据官方文档 https://code.claude.com/docs/en/remote-control ,以下是可直接照做的步骤):
这个环境变量只有在会话已连接到 Anthropic 的"Remote Control"功能时才存在。只要下面任一条件不满足,就从根本上无法获得,这是正常状态,不是缺陷——此时不要再尝试别的办法,直接省略 capabilities 继续完成注册即可。
- 无头(
claude -p)会话根本没有这个功能。 Remote Control 仅限交互式会话使用,因此像 gissue 调度器那样以claude -p运行的无人值守进程绝不可能拥有这个值。 - 使用 API 密钥(
ANTHROPIC_API_KEY)认证的会话不支持 Remote Control。 需要登录 claude.ai 账号(运行claude后执行/login)。 - 若经由 Amazon Bedrock / Google Vertex / Microsoft Foundry 接入,或
ANTHROPIC_BASE_URL被设置为api.anthropic.com以外的值(LLM 网关、代理等),也不受支持。 - 若设置了
DISABLE_TELEMETRY、DO_NOT_TRACK、CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC、DISABLE_GROWTHBOOK中的任意一个,也不受支持(若已设置,需要取消该变量)。
如果上述条件全部满足,却仍然是普通交互式会话且这个变量缺失,那只是 Remote Control 尚未连接而已。无需重启会话,在该会话内执行以下命令即可立即连接,从那一刻起 $env:CLAUDE_CODE_BRIDGE_SESSION_ID 就会被设置:
/remote-control
(/rc 效果相同)若想让之后每次会话都自动连接,可在会话内执行 /config 并开启"Enable Remote Control for all sessions",或在用户主目录的 ~/.claude/settings.json 中添加以下内容:
{
"remoteControlAtStartup": true
}
这里同样绝对禁止(与本指南第147行的原则相同):当因不满足上述条件(无头运行、API 密钥认证等)而无法获得 CLAUDE_CODE_BRIDGE_SESSION_ID 时,不要用钩子 stdin 的 session_id 或 $env:CLAUDE_CODE_SESSION_ID 等其他值来"编造" sessionUrl——这些值与 claude.ai/code 的实际会话毫无关联,会形成无法打开(404)的链接。此时唯一正确的做法是省略 capabilities 并继续完成注册,不需要尝试其他办法。
这个判断完全是机械式的——绝不询问用户。 只需检查自身进程中 $env:CLAUDE_CODE_BRIDGE_SESSION_ID 是否存在即可分支,绝不将"用哪个值"作为选项呈现给用户或向用户提问。绝对禁止把钩子 stdin 的 session_id(agentKey 的来源)当作 sessionUrl 重复使用——两者是不同的 ID,会产生失效/404 链接。也禁止用其他任意值(内部工具链接、猜测的 URL 等)替代 sessionUrl——sessionUrl 只能是真正的 claude.ai/code 桥接链接,或者干脆省略。
如果一定要填上 sessionUrl:执行本次注册的会话"自身"必须已连接到 Remote Control 桥接,$env:CLAUDE_CODE_BRIDGE_SESSION_ID 才会被设置。这是每个会话自己具备的状态,不能依赖其他运维者的电脑设置(例如某台电脑设置了会话启动时自动连接桥接)——别人电脑上的设置对本会话没有任何影响。如果尚未连接,请在交互式会话内执行 /remote-control 命令,或者带上 claude --remote-control/--rc 参数重新启动,自行完成连接。在等待连接的同时,注册本身(Upsert/RunStart)仍应继续进行——按照下文"无法构造 sessionUrl 时(省略 capabilities)"一节的方式操作,不要把整个注册都推迟。
调用约定(giipApiSk4 通用):HTTP POST,3 个 application/x-www-form-urlencoded 表单字段。
⚠️ 位置参数:这些 SP(如
pApiSchedulerAgentUpsertBySK)会把jsondata中的字段按顺序绑定到 SP 的各个位置参数(@p0、@p1、……)——中间的字段不能跳过。要填到后面的字段(例如capabilities),就必须把它前面的所有字段一起发送,实际不使用的字段用安全的占位值填充(例如windowsTaskName=''、projectName=''、scheduleDesc=''、lssn='0'、version='')。整数型参数(lssn等)如果传空字符串会转换出错,因此必须传数字字符串。不过capabilities位于该字段列表的最末尾,因此当它没有值时(即上述$env:CLAUDE_CODE_BRIDGE_SESSION_ID不存在的情况),可以安全地整体省略该字段——因为它后面没有其他字段,位置不会发生偏移。位置参数的限制仅意味着不能跳过中间字段,并不意味着末尾字段也必须始终填写。
| 表单字段 | 值 |
|---|---|
text | "<SP名称> <字段1> <字段2> ..."(SP 名称去掉 BySK 后缀,后接本次调用要填写的字段名,以空格分隔) |
token | <YOUR_SECRET_KEY>(目标 CSN 的 SK) |
jsondata | 字段名→值的映射 JSON 字符串,例如:{"agentKey":"...","displayName":"..."} |
各步骤实际填写的字段:
| 步骤 | SP 名称(text 的第一个词) | 主要字段 |
|---|---|---|
| 开始——注册/刷新身份 | SchedulerAgentUpsert | agentKey(每台主机+每个会话唯一的标识符)、displayName、hostIdentifier(主机名)、windowsTaskName、projectName、scheduleDesc、isActive=1、lssn、osType、agentType(例如 claude_interactive_session)、version、capabilities(JSON 字符串,例如 {"sessionUrl":"<可打开会话的 URL>"}) |
| 开始——启动运行(run) | SchedulerAgentRunStart | runIdKey(本次运行的唯一键)、agentKey、executionMode=interactive、totalIssueCount |
| 结束——结束运行(run) | SchedulerAgentRunEnd | runIdKey、agentKey、status=ENDED、processedCount、skippedCount、failedCount |
| 结束——身份切换为离线 | SchedulerAgentUpsert | agentKey、displayName、hostIdentifier、windowsTaskName、projectName、scheduleDesc、isActive=0 |
示例(PowerShell,所有值均为占位符):
$body = @{
text = 'SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version capabilities'
token = '<YOUR_SECRET_KEY>'
jsondata = (@{
agentKey = 'claude_interactive_<YOUR_HOST>_<UNIQUE_ID>'
displayName = 'Claude Interactive Session (<YOUR_HOST>)'
hostIdentifier = '<YOUR_HOST>'
windowsTaskName = ''
projectName = ''
scheduleDesc = ''
isActive = '1'
lssn = '0'
osType = 'Windows'
agentType = 'claude_interactive_session'
version = ''
capabilities = (@{ sessionUrl = '<可选的会话 URL>' } | ConvertTo-Json -Compress)
} | ConvertTo-Json -Compress)
}
Invoke-WebRequest -Uri 'https://giipfaw.azurewebsites.net/api/giipApiSk4' -Method Post -Body $body
无法构造 sessionUrl 时(省略 capabilities):如果 $env:CLAUDE_CODE_BRIDGE_SESSION_ID 不存在,导致无法构造 capabilities,只需从上面的示例中去掉 text 里的 capabilities 词元和 jsondata 中的 capabilities 字段,其余11个字段照常使用即可。没有 sessionUrl 也能完全正常地成功注册——唯一的区别是 "Open Interactive Sessions" 卡片中不会出现 "Open session" 链接(参见下文"故障排查")。
$body = @{
text = 'SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version'
token = '<YOUR_SECRET_KEY>'
jsondata = (@{
agentKey = 'claude_interactive_<YOUR_HOST>_<UNIQUE_ID>'
displayName = 'Claude Interactive Session (<YOUR_HOST>)'
hostIdentifier = '<YOUR_HOST>'
windowsTaskName = ''
projectName = ''
scheduleDesc = ''
isActive = '1'
lssn = '0'
osType = 'Windows'
agentType = 'claude_interactive_session'
version = ''
} | ConvertTo-Json -Compress)
}
Invoke-WebRequest -Uri 'https://giipfaw.azurewebsites.net/api/giipApiSk4' -Method Post -Body $body
要在其他主机上复现此机制,需要原样满足以下两点(二者均为固定要求,没有自由裁量空间):(a) 该主机运行会话的工具中配置与上文相同的 hooks.SessionStart/hooks.SessionEnd;(b) 拥有要注册的 CSN 的有效 SK。
SK 保存在何处是(与本流程无关的)纯安全判断事项——仅供参考,本运维者的个人环境(lowyworkenv 仓库)将该 SK 保存在一个名为 slack-bot/.secrets/giip-accounts.json 的 JSON 文件中(结构为 Slack 频道 ID → {login_id, sk, csn} 的映射),但这仅是示例结构——并非要求照搬这个具体文件路径;其他主机/运维者可以各自选择安全的方式(操作系统凭据存储、密钥管理器、环境变量等)保存 SK。
确认成功:刷新页面后,会话会出现在 "Open Interactive Sessions" 卡片中。
方法 B —— 注册调度器代理(自动化运行器)
注册后台自动化运行器(如每小时的 gissue 调度器)使用与方法 A 相同的 SP 系列,但不是一次性调用,而是**周期性地(心跳)**刷新自身的身份和运行状态。
标准实现是 giip-fde-agent。 不要从零开始编写,遵循上面的"让 AI 帮你创建 Gissue 调度器"步骤。下面的 SP 规范是 runner 内部调用的方式,仅在直接实现时参考。
- 前提条件:上述通用前提条件(目标 CSN 的 SK)。
- 初次注册/周期性身份刷新:进程启动时调用一次
SchedulerAgentUpsert(字段与方法 A 相同——agentKey/displayName/hostIdentifier/osType/agentType/version/capabilities等),此后以不超过 5 分钟的间隔(推荐:每 2 分钟)重复调用。 - 启动运行:
SchedulerAgentRunStart(runIdKey、agentKey、executionMode=scheduled、totalIssueCount)。 - 运行期间更新进度(心跳):
SchedulerAgentHeartbeatPut——runIdKey、agentKey、processedCount、skippedCount、failedCount、currentPhase、currentIssueNum、totalIssueCount。每完成一个处理单元(例如处理完一个 issue)调用一次;即使处于空闲、没有可处理项时,也要以不超过 2 分钟的间隔调用——若心跳中断达到或超过 5 分钟,界面会将该运行显示为 STALE。 - 结束运行:
SchedulerAgentRunEnd(runIdKey、agentKey、status、processedCount、skippedCount、failedCount、exitCode、summary)。status值为SUCCEEDED/TIMED_OUT/FAILED,exitCode为数字字符串('0'或'1')。
| SP 名称 | 认证方式 | 用途 |
|---|---|---|
SchedulerAgentUpsert | SK | 注册/刷新身份 |
SchedulerAgentRunStart | SK | 记录运行开始 |
SchedulerAgentHeartbeatPut | SK | 运行期间更新进度/心跳 |
SchedulerAgentRunEnd | SK | 记录运行结束 |
SchedulerAgentList | AK(登录会话) | 本界面用于获取列表——注册时不需要 |
位置参数顺序(方式 1):
- Upsert 参数顺序:
agentKey、displayName、hostIdentifier、windowsTaskName、projectName、scheduleDesc、isActive - RunStart 参数顺序:
runIdKey、agentKey、executionMode、totalIssueCount - HeartbeatPut 参数顺序:
runIdKey、agentKey、processedCount、skippedCount、failedCount、currentPhase、currentIssueNum
调用约定有两种方式,均有效。方式 1(runner 内部):giipApiSk2,token=SK,text="SP名称 '值1' '值2' ..."(SP 参数声明顺序的位置参数,值为单引号字符串字面量),jsondata 为空。方式 2(直接实现,与方法 A 相同格式):giipApiSk4,token=SK,text 中填字段名以空格分隔,jsondata 中填 {"字段名":"值",...} JSON。
说明:本项目管理的 giipAgentWin/giipAgentLinux(服务器基础设施代理)使用与
tSchedulerAgent体系完全不同的注册机制(基于 x-api-key 的agent-register/agent-heartbeatAPI),与本界面无关。要注册到本界面(admin/catquest/schedulers),需要自行实现对上述 SP 系列的直接调用;gissue 每小时调度器的标准实现是 giip-fde-agent 的run-gissue-claude.ps1。
确认成功:下一次心跳/刷新后,代理会出现在代理列表表格中。
💡 注意事项
- 本界面为 管理员专属,需要
AdminGuard(uLevel ≥ 70)权限。 - 代理列表、运行历史和日志目录均按会话当前的 CSN 范围获取——如需查看其他项目的代理,请在顶部导航中切换项目。
- 加载数据需要已认证的会话(AK 令牌);若请求开始因认证错误而失败,请重新登录后点击 Refresh。
- 本页面与
/admin/catquest是彼此独立的工具——通过 CatQuest 界面的导航到达本页,并不意味着共享 CatQuest 的猫咪可视化数据或受众。
故障排查
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 已让交互式会话去注册,但 "Open Interactive Sessions" 中始终没有出现 | 最有可能的原因是全局钩子的无头判断逻辑仅凭 $env:CLAUDE_CODE_BRIDGE_SESSION_ID 的有无来判断,把尚未连接桥接的交互式会话误判为无头,从而整体跳过了注册(giip #1837) | 请按照上方"以全局钩子注册时要警惕无头误判"提示框中的进程命令行检查方式,替换掉判断逻辑。 |
| 出现 "Authentication required" 错误 | 未找到 AK/会话令牌 | 重新登录后刷新页面。 |
| 代理列表为空 | 该 CSN 下没有注册的调度器代理,或所选项目不正确 | 确认顶部导航中已选择正确的项目(CSN)。 |
| 明明任务已空闲,某行却显示红色 STALE | 代理在运行过程中停止发送心跳(isStale = true)且未转为 ENDED —— 可能是进程崩溃或被强制终止 | 直接检查代理所在主机;一旦其重启并重新上报,徽章会自动更新。 |
| Live Log Viewer 显示 "No streams yet." | 该代理尚未注册任何日志流,或自上次目录刷新以来没有新输出 | 等待代理下一次运行,或确认代理进程确实在写入日志。 |
| Open Interactive Sessions 中缺少 "Open session" 链接 | 该代理注册的 capabilities 中未包含 sessionUrl | 没有可打开的内容——该交互式会话未注册可访问的 URL。 |
版本:1.1
最后更新:2026-09-01
源文件:giipv3/public/help/catquest-schedulers.zh-CN.md