mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 05:45:51 +00:00
* feat: acp-link 支持 --group 参数指定 channel group - 添加 --group CLI flag,校验格式 [a-zA-Z0-9_-]+ - 支持 ACP_RCS_GROUP 环境变量 fallback - 传递 channelGroupId 到 RcsUpstreamClient - 更新 README 文档说明 --group 和相关环境变量 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: RCS 后端 session 复用与 group 绑定 - storeFindEnvironmentByMachineName 匹配 offline 状态,防止重连创建重复 session - registerEnvironment 复用已有 session 而非每次新建 - EnvironmentResponse 返回 channel_group_id 字段 - 注册时将 session 绑定到 group ID,支持 web UI 按 group 查询 - apiKeyAuth 不再设置 uuid,由 uuidAuth 统一处理 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: Web UI Token Manager — 多 token 切换与 session 隔离 - 新增 useTokens hook 管理 localStorage token CRUD - 新增 TokenManagerDialog 弹窗组件(添加/编辑/删除/切换 token) - api client 支持Bearer token 认证,UUID 跟随 token 变化 - Navbar 添加 token 切换按钮 - 切换 token 时自动 reload,实现 session 数据隔离 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复 useTokens useState 初始化函数签名错误 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
import type { Session, Environment, ControlResponse, SessionEvent } from "../types";
|
|
|
|
const BASE = "";
|
|
|
|
function generateUuid(): string {
|
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
return crypto.randomUUID();
|
|
}
|
|
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) =>
|
|
(Number(c) ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (Number(c) / 4)))).toString(16),
|
|
);
|
|
}
|
|
|
|
export function getUuid(): string {
|
|
let uuid = localStorage.getItem("rcs_uuid");
|
|
if (!uuid) {
|
|
uuid = generateUuid();
|
|
localStorage.setItem("rcs_uuid", uuid);
|
|
}
|
|
return uuid;
|
|
}
|
|
|
|
export function setUuid(uuid: string): void {
|
|
localStorage.setItem("rcs_uuid", uuid);
|
|
}
|
|
|
|
/** Active API token for Authorization header (set by useTokens) */
|
|
let _activeToken: string | null = null;
|
|
|
|
export function setActiveApiToken(token: string | null): void {
|
|
_activeToken = token;
|
|
}
|
|
|
|
export function getActiveApiToken(): string | null {
|
|
return _activeToken;
|
|
}
|
|
|
|
async function api<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
|
|
if (_activeToken) {
|
|
headers["Authorization"] = `Bearer ${_activeToken}`;
|
|
}
|
|
|
|
// When using Bearer token auth, backend derives UUID from the token — no need to send query param.
|
|
// Otherwise fall back to UUID auth via query param.
|
|
let url: string;
|
|
if (_activeToken) {
|
|
const sep = path.includes("?") ? "&" : "?";
|
|
url = `${BASE}${path}${sep}uuid=${encodeURIComponent(_activeToken)}`;
|
|
} else {
|
|
const uuid = getUuid();
|
|
const sep = path.includes("?") ? "&" : "?";
|
|
url = `${BASE}${path}${sep}uuid=${encodeURIComponent(uuid)}`;
|
|
}
|
|
const opts: RequestInit = { method, headers };
|
|
if (body !== undefined) opts.body = JSON.stringify(body);
|
|
|
|
const res = await fetch(url, opts);
|
|
const data = await res.json();
|
|
if (!res.ok) {
|
|
const err = data.error || { type: "unknown", message: res.statusText };
|
|
throw new Error(err.message || err.type);
|
|
}
|
|
return data as T;
|
|
}
|
|
|
|
export function apiBind(sessionId: string) {
|
|
return api<void>("POST", "/web/bind", { sessionId });
|
|
}
|
|
|
|
export function apiFetchSessions() {
|
|
return api<Session[]>("GET", "/web/sessions");
|
|
}
|
|
|
|
export function apiFetchAllSessions() {
|
|
return api<Session[]>("GET", "/web/sessions/all");
|
|
}
|
|
|
|
export function apiFetchSession(id: string) {
|
|
return api<Session>("GET", `/web/sessions/${id}`);
|
|
}
|
|
|
|
export function apiFetchSessionHistory(id: string) {
|
|
return api<{ events: SessionEvent[] }>("GET", `/web/sessions/${id}/history`);
|
|
}
|
|
|
|
export function apiFetchEnvironments() {
|
|
return api<Environment[]>("GET", "/web/environments");
|
|
}
|
|
|
|
export function apiSendEvent(sessionId: string, body: Record<string, unknown>) {
|
|
return api<void>("POST", `/web/sessions/${sessionId}/events`, body);
|
|
}
|
|
|
|
export function apiSendControl(sessionId: string, body: ControlResponse) {
|
|
return api<void>("POST", `/web/sessions/${sessionId}/control`, body);
|
|
}
|
|
|
|
export function apiInterrupt(sessionId: string) {
|
|
return api<void>("POST", `/web/sessions/${sessionId}/interrupt`);
|
|
}
|
|
|
|
export function apiCreateSession(body: { title?: string; environment_id?: string }) {
|
|
return api<Session>("POST", "/web/sessions", body);
|
|
}
|