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

推荐订阅源

P
Proofpoint News Feed
V
V2EX
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
小众软件
小众软件
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
The Cloudflare Blog
T
Tailwind CSS Blog
H
Help Net Security
腾讯CDC
爱范儿
爱范儿
人人都是产品经理
人人都是产品经理
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
Stack Overflow Blog
Stack Overflow Blog
D
DataBreaches.Net
C
Check Point Blog
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

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
feat: improve Codex skill migration selection (#77597) · ...
kevinslin · 2026-05-06 · via Recent Commits to openclaw:main

@@ -34,7 +34,10 @@ vi.mock("../cli/prompt.js", () => ({

3434

vi.mock("@clack/prompts", () => ({

3535

cancel: mocks.clackCancel,

3636

isCancel: mocks.clackIsCancel,

37-

multiselect: mocks.multiselect,

37+

}));

38+39+

vi.mock("./migrate/skill-selection-prompt.js", () => ({

40+

promptMigrationSkillSelectionValues: mocks.multiselect,

3841

}));

39424043

vi.mock("../plugins/migration-provider-runtime.js", () => ({

@@ -47,6 +50,11 @@ vi.mock("./backup.js", () => ({

4750

backupCreateCommand: mocks.backupCreateCommand,

4851

}));

495253+

const {

54+

MIGRATION_SKILL_SELECTION_SKIP,

55+

MIGRATION_SKILL_SELECTION_TOGGLE_ALL_OFF,

56+

MIGRATION_SKILL_SELECTION_TOGGLE_ALL_ON,

57+

} = await import("./migrate/selection.js");

5058

const { migrateApplyCommand, migrateDefaultCommand } = await import("./migrate.js");

51595260

function plan(overrides: Partial<MigrationPlan> = {}): MigrationPlan {

@@ -215,10 +223,22 @@ describe("migrateApplyCommand", () => {

215223

message: expect.stringContaining("Select Codex skills"),

216224

initialValues: ["skill:alpha", "skill:beta"],

217225

required: false,

218-

options: expect.arrayContaining([

226+

options: [

227+

expect.objectContaining({

228+

value: MIGRATION_SKILL_SELECTION_SKIP,

229+

label: "Skip for now",

230+

}),

231+

expect.objectContaining({

232+

value: MIGRATION_SKILL_SELECTION_TOGGLE_ALL_ON,

233+

label: "Toggle all on",

234+

}),

235+

expect.objectContaining({

236+

value: MIGRATION_SKILL_SELECTION_TOGGLE_ALL_OFF,

237+

label: "Toggle all off",

238+

}),

219239

expect.objectContaining({ value: "skill:alpha", label: "alpha" }),

220240

expect.objectContaining({ value: "skill:beta", label: "beta" }),

221-

]),

241+

],

222242

}),

223243

);

224244

expect(mocks.promptYesNo).toHaveBeenCalledWith("Apply this migration now?", false);

@@ -237,6 +257,155 @@ describe("migrateApplyCommand", () => {

237257

);

238258

});

239259260+

it("leaves conflicting Codex skills unchecked by default", async () => {

261+

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

262+

configurable: true,

263+

value: true,

264+

});

265+

const planned = codexSkillPlan({

266+

summary: {

267+

total: 3,

268+

planned: 2,

269+

migrated: 0,

270+

skipped: 0,

271+

conflicts: 1,

272+

errors: 0,

273+

sensitive: 0,

274+

},

275+

items: [

276+

{

277+

id: "skill:alpha",

278+

kind: "skill",

279+

action: "copy",

280+

status: "planned",

281+

details: { skillName: "alpha" },

282+

},

283+

{

284+

id: "skill:beta",

285+

kind: "skill",

286+

action: "copy",

287+

status: "conflict",

288+

reason: "target exists",

289+

details: { skillName: "beta" },

290+

},

291+

{

292+

id: "archive:config.toml",

293+

kind: "archive",

294+

action: "archive",

295+

status: "planned",

296+

},

297+

],

298+

});

299+

mocks.provider.plan.mockResolvedValue(planned);

300+

mocks.multiselect.mockResolvedValue(["skill:alpha"]);

301+

mocks.promptYesNo.mockResolvedValue(false);

302+303+

await migrateDefaultCommand(runtime, { provider: "codex" });

304+305+

expect(mocks.multiselect).toHaveBeenCalledWith(

306+

expect.objectContaining({

307+

initialValues: ["skill:alpha"],

308+

options: expect.arrayContaining([

309+

expect.objectContaining({ value: "skill:beta", label: "beta" }),

310+

]),

311+

}),

312+

);

313+

expect(mocks.promptYesNo).toHaveBeenCalledWith("Apply this migration now?", false);

314+

expect(mocks.provider.apply).not.toHaveBeenCalled();

315+

});

316+317+

it("skips interactive Codex skill migration when Skip for now is selected", async () => {

318+

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

319+

configurable: true,

320+

value: true,

321+

});

322+

const planned = codexSkillPlan();

323+

mocks.provider.plan.mockResolvedValue(planned);

324+

mocks.multiselect.mockResolvedValue([MIGRATION_SKILL_SELECTION_SKIP]);

325+326+

const result = await migrateDefaultCommand(runtime, { provider: "codex" });

327+328+

expect(result).toBe(planned);

329+

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

330+

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

331+

expect(mocks.provider.apply).not.toHaveBeenCalled();

332+

expect(runtime.log).toHaveBeenCalledWith("Codex skill migration skipped for now.");

333+

});

334+335+

it("applies Toggle all off before interactive Codex migration apply", async () => {

336+

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

337+

configurable: true,

338+

value: true,

339+

});

340+

const planned = codexSkillPlan();

341+

mocks.provider.plan.mockResolvedValue(planned);

342+

mocks.multiselect.mockResolvedValue([MIGRATION_SKILL_SELECTION_TOGGLE_ALL_OFF]);

343+

mocks.promptYesNo.mockResolvedValue(true);

344+

mocks.provider.apply.mockImplementation(async (_ctx, selectedPlan: MigrationPlan) => ({

345+

...selectedPlan,

346+

summary: { ...selectedPlan.summary, planned: 0, migrated: 1 },

347+

items: selectedPlan.items.map((item) =>

348+

item.status === "planned" ? { ...item, status: "migrated" as const } : item,

349+

),

350+

}));

351+352+

await migrateDefaultCommand(runtime, { provider: "codex" });

353+354+

const appliedPlan = mocks.provider.apply.mock.calls[0]?.[1] as MigrationPlan;

355+

expect(appliedPlan.summary).toMatchObject({ planned: 1, skipped: 2, conflicts: 0 });

356+

expect(appliedPlan.items).toEqual(

357+

expect.arrayContaining([

358+

expect.objectContaining({

359+

id: "skill:alpha",

360+

status: "skipped",

361+

reason: "not selected for migration",

362+

}),

363+

expect.objectContaining({

364+

id: "skill:beta",

365+

status: "skipped",

366+

reason: "not selected for migration",

367+

}),

368+

expect.objectContaining({ id: "archive:config.toml", status: "planned" }),

369+

]),

370+

);

371+

});

372+373+

it("applies Toggle all on unless Toggle all off is also selected", async () => {

374+

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

375+

configurable: true,

376+

value: true,

377+

});

378+

const planned = codexSkillPlan();

379+

mocks.provider.plan.mockResolvedValue(planned);

380+

mocks.multiselect.mockResolvedValue([MIGRATION_SKILL_SELECTION_TOGGLE_ALL_ON]);

381+

mocks.promptYesNo.mockResolvedValue(true);

382+

mocks.provider.apply.mockImplementation(async (_ctx, selectedPlan: MigrationPlan) => ({

383+

...selectedPlan,

384+

summary: { ...selectedPlan.summary, planned: 0, migrated: 3 },

385+

items: selectedPlan.items.map((item) =>

386+

item.status === "planned" ? { ...item, status: "migrated" as const } : item,

387+

),

388+

}));

389+390+

await migrateDefaultCommand(runtime, { provider: "codex" });

391+392+

let appliedPlan = mocks.provider.apply.mock.calls[0]?.[1] as MigrationPlan;

393+

expect(appliedPlan.summary).toMatchObject({ planned: 3, skipped: 0, conflicts: 0 });

394+395+

mocks.provider.plan.mockResolvedValue(planned);

396+

mocks.multiselect.mockResolvedValue([

397+

MIGRATION_SKILL_SELECTION_TOGGLE_ALL_ON,

398+

MIGRATION_SKILL_SELECTION_TOGGLE_ALL_OFF,

399+

]);

400+

mocks.promptYesNo.mockResolvedValue(true);

401+

mocks.provider.apply.mockClear();

402+403+

await migrateDefaultCommand(runtime, { provider: "codex" });

404+405+

appliedPlan = mocks.provider.apply.mock.calls[0]?.[1] as MigrationPlan;

406+

expect(appliedPlan.summary).toMatchObject({ planned: 1, skipped: 2, conflicts: 0 });

407+

});

408+240409

it("does not apply when interactive apply confirmation is declined", async () => {

241410

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

242411

configurable: true,