惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Blog — PlanetScale
Blog — PlanetScale
爱范儿
爱范儿
MongoDB | Blog
MongoDB | Blog
腾讯CDC
aimingoo的专栏
aimingoo的专栏
月光博客
月光博客
Engineering at Meta
Engineering at Meta
C
Check Point Blog
N
Netflix TechBlog - Medium
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
大猫的无限游戏
大猫的无限游戏
IT之家
IT之家
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
The Blog of Author Tim Ferriss
Last Week in AI
Last Week in AI
B
Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
WordPress大学
WordPress大学
博客园 - 司徒正美

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(github-copilot): reuse existing auth profiles · openc...
obviyus · 2026-04-29 · via Recent Commits to openclaw:main

@@ -4,16 +4,20 @@ import path from "node:path";

44

import {

55

clearRuntimeAuthProfileStoreSnapshots,

66

ensureAuthProfileStore,

7+

upsertAuthProfile,

78

} from "openclaw/plugin-sdk/agent-runtime";

89

import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";

910

import { afterEach, describe, expect, it, vi } from "vitest";

101111-

const resolveCopilotApiTokenMock = vi.hoisted(() => vi.fn());

12+

const mocks = vi.hoisted(() => ({

13+

githubCopilotLoginCommand: vi.fn(),

14+

resolveCopilotApiToken: vi.fn(),

15+

}));

12161317

vi.mock("./register.runtime.js", () => ({

1418

DEFAULT_COPILOT_API_BASE_URL: "https://api.githubcopilot.test",

15-

resolveCopilotApiToken: resolveCopilotApiTokenMock,

16-

githubCopilotLoginCommand: vi.fn(),

19+

resolveCopilotApiToken: mocks.resolveCopilotApiToken,

20+

githubCopilotLoginCommand: mocks.githubCopilotLoginCommand,

1721

fetchCopilotUsage: vi.fn(),

1822

}));

1923

@@ -22,6 +26,7 @@ import plugin from "./index.js";

2226

const tempDirs: string[] = [];

23272428

afterEach(async () => {

29+

vi.clearAllMocks();

2530

clearRuntimeAuthProfileStoreSnapshots();

2631

await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));

2732

});

@@ -98,11 +103,11 @@ describe("github-copilot plugin", () => {

98103

} as never);

99104100105

expect(result).toBeNull();

101-

expect(resolveCopilotApiTokenMock).not.toHaveBeenCalled();

106+

expect(mocks.resolveCopilotApiToken).not.toHaveBeenCalled();

102107

});

103108104109

it("uses live plugin config to re-enable discovery after startup disable", async () => {

105-

resolveCopilotApiTokenMock.mockResolvedValueOnce({

110+

mocks.resolveCopilotApiToken.mockResolvedValueOnce({

106111

token: "copilot_api_token",

107112

baseUrl: "https://api.githubcopilot.live",

108113

});

@@ -125,7 +130,7 @@ describe("github-copilot plugin", () => {

125130

resolveProviderApiKey: () => ({ apiKey: "gh_test_token" }),

126131

} as never);

127132128-

expect(resolveCopilotApiTokenMock).toHaveBeenCalledWith({

133+

expect(mocks.resolveCopilotApiToken).toHaveBeenCalledWith({

129134

githubToken: "gh_test_token",

130135

env: { GH_TOKEN: "gh_test_token" },

131136

});

@@ -137,6 +142,135 @@ describe("github-copilot plugin", () => {

137142

});

138143

});

139144145+

it("offers to reuse an existing token profile during interactive onboarding", async () => {

146+

const provider = registerProviderWithPluginConfig({});

147+

const method = provider.auth[0];

148+

const agentDir = await createAgentDir();

149+

await fs.writeFile(

150+

path.join(agentDir, "auth-profiles.json"),

151+

JSON.stringify({

152+

version: 1,

153+

profiles: {

154+

"github-copilot:github": {

155+

type: "token",

156+

provider: "github-copilot",

157+

token: "existing-token",

158+

},

159+

},

160+

}),

161+

);

162+

const prompter = {

163+

confirm: vi.fn(async () => false),

164+

note: vi.fn(),

165+

};

166+167+

const result = await method.run({

168+

config: {},

169+

env: {},

170+

agentDir,

171+

workspaceDir: "/tmp/workspace",

172+

prompter,

173+

runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },

174+

opts: {},

175+

secretInputMode: "plaintext",

176+

allowSecretRefPrompt: false,

177+

isRemote: false,

178+

openUrl: vi.fn(),

179+

oauth: { createVpsAwareHandlers: vi.fn() },

180+

} as never);

181+182+

expect(prompter.confirm).toHaveBeenCalledWith({

183+

message: "GitHub Copilot auth already exists. Re-run login?",

184+

initialValue: false,

185+

});

186+

expect(mocks.githubCopilotLoginCommand).not.toHaveBeenCalled();

187+

expect(result).toEqual({

188+

profiles: [

189+

{

190+

profileId: "github-copilot:github",

191+

credential: {

192+

type: "token",

193+

provider: "github-copilot",

194+

token: "existing-token",

195+

},

196+

},

197+

],

198+

defaultModel: "github-copilot/claude-opus-4.7",

199+

});

200+

});

201+202+

it("can refresh an existing token profile during interactive onboarding", async () => {

203+

const provider = registerProviderWithPluginConfig({});

204+

const method = provider.auth[0];

205+

const agentDir = await createAgentDir();

206+

await fs.writeFile(

207+

path.join(agentDir, "auth-profiles.json"),

208+

JSON.stringify({

209+

version: 1,

210+

profiles: {

211+

"github-copilot:github": {

212+

type: "token",

213+

provider: "github-copilot",

214+

token: "existing-token",

215+

},

216+

},

217+

}),

218+

);

219+

mocks.githubCopilotLoginCommand.mockImplementationOnce(async (opts: { agentDir?: string }) => {

220+

upsertAuthProfile({

221+

profileId: "github-copilot:github",

222+

credential: {

223+

type: "token",

224+

provider: "github-copilot",

225+

token: "refreshed-token",

226+

},

227+

agentDir: opts.agentDir,

228+

});

229+

});

230+

const prompter = {

231+

confirm: vi.fn(async () => true),

232+

note: vi.fn(),

233+

};

234+

const isTtyDescriptor = Object.getOwnPropertyDescriptor(process.stdin, "isTTY");

235+

Object.defineProperty(process.stdin, "isTTY", {

236+

configurable: true,

237+

value: true,

238+

});

239+240+

try {

241+

const result = await method.run({

242+

config: {},

243+

env: {},

244+

agentDir,

245+

workspaceDir: "/tmp/workspace",

246+

prompter,

247+

runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() },

248+

opts: {},

249+

secretInputMode: "plaintext",

250+

allowSecretRefPrompt: false,

251+

isRemote: false,

252+

openUrl: vi.fn(),

253+

oauth: { createVpsAwareHandlers: vi.fn() },

254+

} as never);

255+256+

expect(mocks.githubCopilotLoginCommand).toHaveBeenCalledWith(

257+

{ yes: true, profileId: "github-copilot:github", agentDir },

258+

expect.any(Object),

259+

);

260+

expect(result.profiles[0]?.credential).toEqual({

261+

type: "token",

262+

provider: "github-copilot",

263+

token: "refreshed-token",

264+

});

265+

} finally {

266+

if (isTtyDescriptor) {

267+

Object.defineProperty(process.stdin, "isTTY", isTtyDescriptor);

268+

} else {

269+

delete (process.stdin as { isTTY?: boolean }).isTTY;

270+

}

271+

}

272+

});

273+140274

it("stores GitHub Copilot token from non-interactive onboarding", async () => {

141275

const provider = registerProviderWithPluginConfig({});

142276

const method = provider.auth[0];