codex-copilot-dx 0.6.9 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "Use Codex Desktop, Claude Code, and Claude App with GitHub Copilot",
5
5
  "author": "Dale Xiao",
6
6
  "type": "module",
package/src/doctor.mjs CHANGED
@@ -590,6 +590,12 @@ export async function collectDoctorChecks({
590
590
  fix: "stop the running adapter, then run ccdx start",
591
591
  });
592
592
  }
593
+ } else if (pm.app.state === "predecessor") {
594
+ checks.push({
595
+ kind: "warn",
596
+ message: `PM Studio ${version} has a verified predecessor split patch`,
597
+ fix: "ccdx pms setup",
598
+ });
593
599
  } else if (pm.app.state === "clean") {
594
600
  checks.push({
595
601
  kind: "warn",
@@ -9,7 +9,7 @@ const PM_STUDIO_COPILOT_CONFIG_MODULE_LENGTH = 2_121;
9
9
  const PM_STUDIO_COPILOT_CONFIG_MODULE_SOURCE_SHA256 = "3da6245d676015ab6cd26e4d6a0d2f54894a687c370aa419c5ad170533d086aa";
10
10
 
11
11
  function splitOriginConfigModule() {
12
- const source = `47024(I,e,t){"use strict";t.d(e,{qn:()=>l});const l={CLIENT_ID:"Iv1.b507a08c87ecfe98",CLIENT_SECRET:void 0,API_ENDPOINT:"${PM_STUDIO_ORIGIN}",DEVICE_CODE_URL:"https://github.com/login/device/code",ACCESS_TOKEN_URL:"https://github.com/login/oauth/access_token",COPILOT_TOKEN_URL:"https://api.github.com/copilot_internal/v2/token",USER_AGENT:"GitHubCopilotChat/0.26.7",EDITOR_VERSION:"vscode/1.99.3",EDITOR_PLUGIN_VERSION:"copilot-chat/0.26.7",INTEGRATION_ID:"vscode-chat",STANDARD_HEADERS:{Accept:"application/json","Content-Type":"application/json","User-Agent":"GitHubCopilotChat/0.26.7","Editor-Version":"vscode/1.99.3","Editor-Plugin-Version":"copilot-chat/0.26.7","Copilot-Integration-Id":"vscode-chat","X-Request-Id":()=>\`req_\${Date.now()}_\${Math.random().toString(36).substr(2,9)}\`}};const c=fetch.bind(globalThis),n="${CCDX_PM_STUDIO_ORIGIN}",a="${PM_STUDIO_SPLIT_ORIGIN_MARKER}",h="X-CCDX-PM-Relay",d=new Set,m=I=>I.headers.get(h)===a,q=I=>AbortSignal.any([I,AbortSignal.timeout(750)].filter(Boolean)),f=(I,e=400)=>Response.json({error:{code:I}},{status:e,headers:{[h]:a}});globalThis.fetch=async(I,e)=>{const t=I.url||I+"";if(t===\`\${l.API_ENDPOINT}/models\`){try{const t=await c(\`\${n}/models\`,{...e,signal:q(e?.signal)});if(t.ok&&m(t))try{let I=await t.clone().json(),e=I.data||I;if(Array.isArray(e)){d.clear();for(const I of e)/anthropic/i.test(I.vendor||I.owned_by)&&d.add(I.id);return t}}catch{}t.body?.cancel?.()?.catch?.(()=>{})}catch(I){if(e?.signal?.aborted)throw e.signal.reason||I}return c(I,e)}const s=t.startsWith(l.API_ENDPOINT)?t.slice(l.API_ENDPOINT.length):"";if("POST"===e?.method&&["/chat/completions","/responses","/embeddings"].includes(s)){let t="";try{t=(JSON.parse(e.body).model||"").trim()}catch{}if(d.has(t)||/^claude-/i.test(t)){if("/chat/completions"!==s)return f("model_not_supported");try{const I=await c(\`\${n}/models\`,{signal:q(e?.signal)}),t=401===I.status&&m(I);await I.text();if(!t)throw 0;const l=await c(\`\${n}\${s}\`,e);if(!m(l)){await l.body?.cancel?.();throw 0}return l}catch(I){if(e?.signal?.aborted)throw e.signal.reason||I;return f("relay_incompatible",503)}}}return c(I,e)}}`;
12
+ const source = `47024(I,e,t){"use strict";t.d(e,{qn:()=>l});const l={CLIENT_ID:"Iv1.b507a08c87ecfe98",CLIENT_SECRET:void 0,API_ENDPOINT:"${PM_STUDIO_ORIGIN}",DEVICE_CODE_URL:"https://github.com/login/device/code",ACCESS_TOKEN_URL:"https://github.com/login/oauth/access_token",COPILOT_TOKEN_URL:"https://api.github.com/copilot_internal/v2/token",USER_AGENT:"GitHubCopilotChat/0.26.7",EDITOR_VERSION:"vscode/1.99.3",EDITOR_PLUGIN_VERSION:"copilot-chat/0.26.7",INTEGRATION_ID:"vscode-chat",STANDARD_HEADERS:{Accept:"application/json","Content-Type":"application/json","User-Agent":"GitHubCopilotChat/0.26.7","Editor-Version":"vscode/1.99.3","Editor-Plugin-Version":"copilot-chat/0.26.7","Copilot-Integration-Id":"vscode-chat","X-Request-Id":()=>\`req_\${Date.now()}_\${Math.random().toString(36).substr(2,9)}\`}};const c=fetch.bind(globalThis),n="${CCDX_PM_STUDIO_ORIGIN}",a="${PM_STUDIO_SPLIT_ORIGIN_MARKER}",h="X-CCDX-PM-Relay",d=new Set,m=I=>I.headers.get(h)===a,q=I=>AbortSignal.any([I,AbortSignal.timeout(750)].filter(Boolean)),f=(I,e=400)=>Response.json({error:{code:I}},{status:e,headers:{[h]:a}});globalThis.fetch=async(I,e)=>{const t=I.url||I+"";if(t===\`\${l.API_ENDPOINT}/models\`){try{const t=await c(\`\${n}/models\`,e);if(t.ok&&m(t))try{let I=await t.clone().json(),e=I.data||I;if(Array.isArray(e)){d.clear();for(const I of e)/anthropic/i.test(I.vendor||I.owned_by)&&d.add(I.id);return t}}catch{}t.body?.cancel?.()?.catch?.(()=>{})}catch(I){if(e?.signal?.aborted)throw e.signal.reason||I}return c(I,e)}const s=t.startsWith(l.API_ENDPOINT)?t.slice(l.API_ENDPOINT.length):"";if("POST"===e?.method&&["/chat/completions","/responses","/embeddings"].includes(s)){let t="";try{t=(JSON.parse(e.body).model||"").trim()}catch{}if(d.has(t)||/^claude-/i.test(t)){if("/chat/completions"!==s)return f("model_not_supported");try{const I=await c(\`\${n}/models\`,{signal:q(e?.signal)}),t=401===I.status&&m(I);await I.text();if(!t)throw 0;const l=await c(\`\${n}\${s}\`,e);if(!m(l)){await l.body?.cancel?.();throw 0}return l}catch(I){if(e?.signal?.aborted)throw e.signal.reason||I;return f("relay_incompatible",503)}}}return c(I,e)}}`;
13
13
  if (source.length > PM_STUDIO_COPILOT_CONFIG_MODULE_LENGTH) {
14
14
  throw new Error("PM Studio split-origin module exceeds its exact source window");
15
15
  }
@@ -35,8 +35,20 @@ export const PM_STUDIO_2_9_7_RECIPE = Object.freeze({
35
35
  dataOffset: 3_832_764,
36
36
  sourceAsarSha256: "36fb8bf2fc9326ddf49baab5cc0b54b38751a7db041bda3c42a3b01107b54118",
37
37
  sourceHeaderSha256: "87746d92e00db2d40c03830ea06de22ec7cba112d07729801ccd02e226ffd961",
38
- patchedAsarSha256: "d520d115604225c1a3feb749dbe29ad0d2cd175c5233c010de0e1fade527fa0b",
39
- patchedHeaderSha256: "83e5fba3b6751b3098d8f6901db361a9b620ab3b924e0e551d3df612ff412642",
38
+ patchedAsarSha256: "799dc18103a4553daefcaaddbe3410a1bfaf68d84407f4c10ef3d996afd71ad0",
39
+ patchedHeaderSha256: "08253795176d5471c6f406a29ccb134875d503bb55bca909b2cd131cd7708510",
40
+ predecessor: Object.freeze({
41
+ id: "split-origin-v1-model-discovery-750ms",
42
+ kind: "split-origin-predecessor",
43
+ asarSha256: "d520d115604225c1a3feb749dbe29ad0d2cd175c5233c010de0e1fade527fa0b",
44
+ headerSha256: "83e5fba3b6751b3098d8f6901db361a9b620ab3b924e0e551d3df612ff412642",
45
+ signingMetadata: Object.freeze({
46
+ identifier: "com.pm-studio.app",
47
+ flags: "0x10002(adhoc,runtime)",
48
+ runtime_version: "26.0.0",
49
+ entitlements_sha256: "9d4ccbda4fe0c81a70df3db93b3e61fe0500f67f14cdcbee4dea230e6512d05c",
50
+ }),
51
+ }),
40
52
  targets: Object.freeze([
41
53
  Object.freeze({
42
54
  path: "dist/main/main.js",
@@ -44,13 +56,19 @@ export const PM_STUDIO_2_9_7_RECIPE = Object.freeze({
44
56
  size: 9_909_974,
45
57
  blockSize: 4_194_304,
46
58
  sourceSha256: "58383be112886becdb8d8e7cce1b3efce07be6716190aa4940627f456eb8edb2",
47
- patchedSha256: "69156040fb233a835b7be2f86fbe2f653380f03379adfedf46c63ab45de9d39d",
59
+ patchedSha256: "b219d08290b208fe41fa29b1e8ac4a1dee789e15f68fd4ff01eb5cd21249099b",
60
+ predecessorSha256: "69156040fb233a835b7be2f86fbe2f653380f03379adfedf46c63ab45de9d39d",
48
61
  sourceBlocks: Object.freeze([
49
62
  "3ce05a21e8992ccf12966cf9b78fcd28b2f66e6786489b5bb0a06f1d4b2a0c1e",
50
63
  "0a4832413d8d8cdda67c8bb318c0e5c25df20c75802d637097ffd045a31cd443",
51
64
  "bcc10582d8a74717f8f81b98182d3551f1eaf23eb9cadd908d160ce5e4ef8cba",
52
65
  ]),
53
66
  patchedBlocks: Object.freeze([
67
+ "dcbffb0f3aaa79a5ca8f1ee9170826b874d9db237c230abcdc0490335b2dc6a8",
68
+ "0a4832413d8d8cdda67c8bb318c0e5c25df20c75802d637097ffd045a31cd443",
69
+ "bcc10582d8a74717f8f81b98182d3551f1eaf23eb9cadd908d160ce5e4ef8cba",
70
+ ]),
71
+ predecessorBlocks: Object.freeze([
54
72
  "f0b5585d2ff0d403e5c4d35e9bd0b8cbfec5c41a9b9057b8cc808107e889cf46",
55
73
  "0a4832413d8d8cdda67c8bb318c0e5c25df20c75802d637097ffd045a31cd443",
56
74
  "bcc10582d8a74717f8f81b98182d3551f1eaf23eb9cadd908d160ce5e4ef8cba",
@@ -62,7 +80,8 @@ export const PM_STUDIO_2_9_7_RECIPE = Object.freeze({
62
80
  anchor: "47024(I,e,t){",
63
81
  anchorCount: 1,
64
82
  sourceSha256: PM_STUDIO_COPILOT_CONFIG_MODULE_SOURCE_SHA256,
65
- patchedSha256: "a5b9312ed78e8a7fc3bd38f7ea5d4ab008f21bae18ba3dd6aa2d4be6b69191db",
83
+ patchedSha256: "873d2dd276bd12a7dc2876f5eaf8c67d4ae34749ead8911d0588badf4e9fc0cf",
84
+ predecessorSha256: "a5b9312ed78e8a7fc3bd38f7ea5d4ab008f21bae18ba3dd6aa2d4be6b69191db",
66
85
  replacement: PM_STUDIO_SPLIT_ORIGIN_CONFIG_MODULE,
67
86
  }),
68
87
  }),
@@ -124,8 +143,20 @@ export const PM_STUDIO_2_9_10_RECIPE = Object.freeze({
124
143
  dataOffset: 3_833_292,
125
144
  sourceAsarSha256: "d243860770e8b1d8044213924f9704d1fc52f900d6c33461eff6358962330b78",
126
145
  sourceHeaderSha256: "5fc77d5b61e9647fba10fd2bd5752d0889c14da5a00b21d31bc48fdcccf74a69",
127
- patchedAsarSha256: "ea28d998056b32ca4115d208a4d81ce629c83f3f5f89c6a66d50749849beb6fc",
128
- patchedHeaderSha256: "348d80f5f2d1ebb92e6089195b1703b87467678f80d8946b8ab83b3ead03e5be",
146
+ patchedAsarSha256: "f309fc7e86bb4edaf1fecd9061fb4ddc634357c0ba9299f1d13f8b2ff33efd90",
147
+ patchedHeaderSha256: "98c4af8656e61cc1a1a8b2188592a499a987136f8e6b763b0e83abda788e798d",
148
+ predecessor: Object.freeze({
149
+ id: "split-origin-v1-model-discovery-750ms",
150
+ kind: "split-origin-predecessor",
151
+ asarSha256: "ea28d998056b32ca4115d208a4d81ce629c83f3f5f89c6a66d50749849beb6fc",
152
+ headerSha256: "348d80f5f2d1ebb92e6089195b1703b87467678f80d8946b8ab83b3ead03e5be",
153
+ signingMetadata: Object.freeze({
154
+ identifier: "com.pm-studio.app",
155
+ flags: "0x10002(adhoc,runtime)",
156
+ runtime_version: "26.0.0",
157
+ entitlements_sha256: "9d4ccbda4fe0c81a70df3db93b3e61fe0500f67f14cdcbee4dea230e6512d05c",
158
+ }),
159
+ }),
129
160
  targets: Object.freeze([
130
161
  Object.freeze({
131
162
  path: "dist/main/main.js",
@@ -133,13 +164,19 @@ export const PM_STUDIO_2_9_10_RECIPE = Object.freeze({
133
164
  size: 10_150_157,
134
165
  blockSize: 4_194_304,
135
166
  sourceSha256: "03607bb1edc068396b99f02b9c9e0d8e10388fb658afc112e06e875c123dbc27",
136
- patchedSha256: "b5b5e37af3d06ffc603bdef620fc1e0254580f0ee325f1a77096af6c78379b79",
167
+ patchedSha256: "71e936c79c3560ab5bd9813479f8a66f4a7b06629edca74953feaaf7904a6564",
168
+ predecessorSha256: "b5b5e37af3d06ffc603bdef620fc1e0254580f0ee325f1a77096af6c78379b79",
137
169
  sourceBlocks: Object.freeze([
138
170
  "3910a9bc5459f493956aafee7b3644e8d242027758f0dcdea97d7c10a324c6bf",
139
171
  "49fa64c68425d35b1baff0d5745d856d377e014a7a7829322cd1b39aade9532e",
140
172
  "bd7cc14a1d2e02abe66c0ec6fc4e97b263cfc1f67c4ae39e956c06d3bcc2fe67",
141
173
  ]),
142
174
  patchedBlocks: Object.freeze([
175
+ "203ad1efbdcd869314510086632fdb0089196eb25179c46fb4108133cc65412f",
176
+ "49fa64c68425d35b1baff0d5745d856d377e014a7a7829322cd1b39aade9532e",
177
+ "bd7cc14a1d2e02abe66c0ec6fc4e97b263cfc1f67c4ae39e956c06d3bcc2fe67",
178
+ ]),
179
+ predecessorBlocks: Object.freeze([
143
180
  "63aa15d8c44614a7b2d9656ddba23109d40bab66b66b36339154953bd164da75",
144
181
  "49fa64c68425d35b1baff0d5745d856d377e014a7a7829322cd1b39aade9532e",
145
182
  "bd7cc14a1d2e02abe66c0ec6fc4e97b263cfc1f67c4ae39e956c06d3bcc2fe67",
@@ -151,7 +188,8 @@ export const PM_STUDIO_2_9_10_RECIPE = Object.freeze({
151
188
  anchor: "47024(I,e,t){",
152
189
  anchorCount: 1,
153
190
  sourceSha256: PM_STUDIO_COPILOT_CONFIG_MODULE_SOURCE_SHA256,
154
- patchedSha256: "a5b9312ed78e8a7fc3bd38f7ea5d4ab008f21bae18ba3dd6aa2d4be6b69191db",
191
+ patchedSha256: "873d2dd276bd12a7dc2876f5eaf8c67d4ae34749ead8911d0588badf4e9fc0cf",
192
+ predecessorSha256: "a5b9312ed78e8a7fc3bd38f7ea5d4ab008f21bae18ba3dd6aa2d4be6b69191db",
155
193
  replacement: PM_STUDIO_SPLIT_ORIGIN_CONFIG_MODULE,
156
194
  }),
157
195
  }),
@@ -399,8 +437,13 @@ function targetStateIssues(detail, target, state) {
399
437
  addMismatch(issues, `${target.path} block size`, detail.integrity?.blockSize, target.blockSize);
400
438
 
401
439
  const patched = state === "patched";
402
- const expectedHash = patched ? target.patchedSha256 : target.sourceSha256;
403
- const expectedBlocks = patched ? target.patchedBlocks : target.sourceBlocks;
440
+ const predecessor = state === "predecessor";
441
+ const expectedHash = patched
442
+ ? target.patchedSha256
443
+ : predecessor ? target.predecessorSha256 : target.sourceSha256;
444
+ const expectedBlocks = patched
445
+ ? target.patchedBlocks
446
+ : predecessor ? target.predecessorBlocks : target.sourceBlocks;
404
447
 
405
448
  addMismatch(issues, `${target.path} SHA-256`, detail.sha256, expectedHash);
406
449
  addMismatch(issues, `${target.path} integrity hash`, detail.integrity?.hash, expectedHash);
@@ -411,7 +454,9 @@ function targetStateIssues(detail, target, state) {
411
454
  addMismatch(issues, `${target.path} absolute edit offset`, detail.edit?.absoluteOffset, target.edit.absoluteOffset);
412
455
  addMismatch(issues, `${target.path} edit length`, detail.edit?.length, target.edit.length);
413
456
  addMismatch(issues, `${target.path} edit SHA-256`, detail.edit?.sha256,
414
- patched ? target.edit.patchedSha256 : target.edit.sourceSha256);
457
+ patched
458
+ ? target.edit.patchedSha256
459
+ : predecessor ? target.edit.predecessorSha256 : target.edit.sourceSha256);
415
460
  if (!sameArray(detail.edit?.anchorPositions, [target.edit.offset])) {
416
461
  issues.push(`${target.path} module anchor position/count does not match recipe`);
417
462
  }
@@ -432,11 +477,13 @@ export function inspectAsarBuffer(value, recipe = PM_STUDIO_2_9_7_RECIPE) {
432
477
 
433
478
  const issuesFor = (state) => {
434
479
  const patched = state === "patched";
480
+ const predecessor = state === "predecessor";
481
+ const expected = predecessor ? recipe.predecessor : null;
435
482
  const issues = [...commonIssues];
436
483
  addMismatch(issues, "ASAR SHA-256", asarSha256,
437
- patched ? recipe.patchedAsarSha256 : recipe.sourceAsarSha256);
484
+ patched ? recipe.patchedAsarSha256 : predecessor ? expected?.asarSha256 : recipe.sourceAsarSha256);
438
485
  addMismatch(issues, "ASAR header SHA-256", headerSha256,
439
- patched ? recipe.patchedHeaderSha256 : recipe.sourceHeaderSha256);
486
+ patched ? recipe.patchedHeaderSha256 : predecessor ? expected?.headerSha256 : recipe.sourceHeaderSha256);
440
487
  for (let index = 0; index < recipe.targets.length; index += 1) {
441
488
  issues.push(...targetStateIssues(targets[index], recipe.targets[index], state));
442
489
  }
@@ -445,6 +492,9 @@ export function inspectAsarBuffer(value, recipe = PM_STUDIO_2_9_7_RECIPE) {
445
492
 
446
493
  const cleanIssues = issuesFor("clean");
447
494
  const patchedIssues = issuesFor("patched");
495
+ const predecessorIssues = recipe.predecessor
496
+ ? issuesFor("predecessor")
497
+ : ["recipe has no recognized predecessor patch"];
448
498
  const legacyIssues = [...commonIssues];
449
499
  if (!recipe.legacy) {
450
500
  legacyIssues.push("recipe has no recognized legacy patch");
@@ -456,7 +506,9 @@ export function inspectAsarBuffer(value, recipe = PM_STUDIO_2_9_7_RECIPE) {
456
506
  ? "clean"
457
507
  : patchedIssues.length === 0
458
508
  ? "patched"
459
- : legacyIssues.length === 0 ? "legacy" : "drift";
509
+ : predecessorIssues.length === 0
510
+ ? "predecessor"
511
+ : legacyIssues.length === 0 ? "legacy" : "drift";
460
512
  return {
461
513
  recipeId: recipe.id,
462
514
  state,
@@ -466,6 +518,7 @@ export function inspectAsarBuffer(value, recipe = PM_STUDIO_2_9_7_RECIPE) {
466
518
  targets,
467
519
  cleanIssues,
468
520
  patchedIssues,
521
+ predecessorIssues,
469
522
  legacyIssues,
470
523
  };
471
524
  }
@@ -475,6 +528,12 @@ export function patchAsarBuffer(value, recipe = PM_STUDIO_2_9_7_RECIPE) {
475
528
  if (before.state === "patched") {
476
529
  return { changed: false, buffer: Buffer.from(value), before, after: before };
477
530
  }
531
+ if (before.state === "predecessor") {
532
+ const error = new Error("Predecessor PM Studio split-origin patch must be migrated from a verified clean backup");
533
+ error.code = "PM_STUDIO_ASAR_PREDECESSOR";
534
+ error.inspection = before;
535
+ throw error;
536
+ }
478
537
  if (before.state === "legacy") {
479
538
  const error = new Error("Legacy PM Studio global-origin patch must be migrated from a verified clean backup");
480
539
  error.code = "PM_STUDIO_ASAR_LEGACY";
@@ -482,7 +541,7 @@ export function patchAsarBuffer(value, recipe = PM_STUDIO_2_9_7_RECIPE) {
482
541
  throw error;
483
542
  }
484
543
  if (before.state !== "clean") {
485
- const error = new Error("PM Studio ASAR does not match the clean, split-origin, or legacy recipe");
544
+ const error = new Error("PM Studio ASAR does not match the clean, current split-origin, predecessor, or legacy recipe");
486
545
  error.code = "PM_STUDIO_ASAR_DRIFT";
487
546
  error.inspection = before;
488
547
  throw error;
@@ -519,6 +519,10 @@ export function inspectPmStudioApp({
519
519
  && codeSign?.adHoc === true
520
520
  && (!recipe.patchedSigningMetadata
521
521
  || JSON.stringify(signingMetadata(codeSign)) === JSON.stringify(recipe.patchedSigningMetadata));
522
+ const predecessorSignatureAccepted = codeSign?.valid === true
523
+ && codeSign?.adHoc === true
524
+ && Boolean(recipe.predecessor?.signingMetadata)
525
+ && JSON.stringify(signingMetadata(codeSign)) === JSON.stringify(recipe.predecessor.signingMetadata);
522
526
  const clean = issues.length === 0
523
527
  && asar.state === "clean"
524
528
  && integrityMatches(plistIntegrity, "SHA256", recipe.sourceHeaderSha256)
@@ -530,12 +534,16 @@ export function inspectPmStudioApp({
530
534
  && asar.state === "patched"
531
535
  && integrityMatches(plistIntegrity, "SHA256", recipe.patchedHeaderSha256)
532
536
  && patchedSignatureAccepted;
537
+ const predecessor = issues.length === 0
538
+ && asar.state === "predecessor"
539
+ && integrityMatches(plistIntegrity, "SHA256", recipe.predecessor?.headerSha256)
540
+ && predecessorSignatureAccepted;
533
541
  const legacy = issues.length === 0
534
542
  && asar.state === "legacy"
535
543
  && integrityMatches(plistIntegrity, "SHA256", recipe.legacy?.headerSha256)
536
544
  && patchedSignatureAccepted;
537
545
 
538
- if (!clean && !patched && !legacy) {
546
+ if (!clean && !patched && !predecessor && !legacy) {
539
547
  if (asar.state === "drift") issues.push("app.asar is unknown drift");
540
548
  else if (asar.state === "clean"
541
549
  && !integrityMatches(plistIntegrity, "SHA256", recipe.sourceHeaderSha256)) {
@@ -543,6 +551,9 @@ export function inspectPmStudioApp({
543
551
  } else if (asar.state === "patched"
544
552
  && !integrityMatches(plistIntegrity, "SHA256", recipe.patchedHeaderSha256)) {
545
553
  issues.push("patched ASAR does not match ElectronAsarIntegrity");
554
+ } else if (asar.state === "predecessor"
555
+ && !integrityMatches(plistIntegrity, "SHA256", recipe.predecessor?.headerSha256)) {
556
+ issues.push("predecessor ASAR does not match ElectronAsarIntegrity");
546
557
  } else if (asar.state === "legacy"
547
558
  && !integrityMatches(plistIntegrity, "SHA256", recipe.legacy?.headerSha256)) {
548
559
  issues.push("legacy ASAR does not match ElectronAsarIntegrity");
@@ -554,7 +565,7 @@ export function inspectPmStudioApp({
554
565
  issues.push("codesign verification failed");
555
566
  }
556
567
  else if (asar.state === "clean" && codeSign.adHoc) issues.push("clean bundle has an unexpected ad-hoc signature");
557
- else if (["patched", "legacy"].includes(asar.state) && !codeSign.adHoc) {
568
+ else if (["patched", "predecessor", "legacy"].includes(asar.state) && !codeSign.adHoc) {
558
569
  issues.push(`${asar.state} bundle is not ad-hoc signed`);
559
570
  }
560
571
  else if (asar.state === "clean" && codeSign.teamIdentifier !== recipe.sourceTeamIdentifier) {
@@ -573,10 +584,16 @@ export function inspectPmStudioApp({
573
584
  && !patchedSignatureAccepted) {
574
585
  issues.push(`${asar.state} bundle signing metadata does not match the recipe`);
575
586
  }
587
+ if (asar.state === "predecessor" && codeSign?.valid && codeSign?.adHoc
588
+ && !predecessorSignatureAccepted) {
589
+ issues.push("predecessor bundle signing metadata does not match the recipe");
590
+ }
576
591
  }
577
592
 
578
593
  return {
579
- state: clean ? "clean" : patched ? "patched" : legacy ? "legacy" : "drift",
594
+ state: clean
595
+ ? "clean"
596
+ : patched ? "patched" : predecessor ? "predecessor" : legacy ? "legacy" : "drift",
580
597
  metadata: {
581
598
  version: String(metadata.version),
582
599
  build: String(metadata.build),
@@ -688,7 +705,9 @@ function backupManifest(recipe, createdAt) {
688
705
  function patchRecordMatches(record, recipe, state) {
689
706
  const expected = state === "legacy"
690
707
  ? recipe.legacy
691
- : { asarSha256: recipe.patchedAsarSha256, headerSha256: recipe.patchedHeaderSha256 };
708
+ : state === "predecessor"
709
+ ? recipe.predecessor
710
+ : { asarSha256: recipe.patchedAsarSha256, headerSha256: recipe.patchedHeaderSha256 };
692
711
  return Boolean(expected)
693
712
  && record?.asar_sha256 === expected.asarSha256
694
713
  && record?.asar_header_sha256 === expected.headerSha256
@@ -696,8 +715,11 @@ function patchRecordMatches(record, recipe, state) {
696
715
  }
697
716
 
698
717
  function patchRecordForState(manifest, recipe, state) {
699
- if (state === "legacy" && patchRecordMatches(manifest?.legacy_patched, recipe, state)) {
700
- return manifest.legacy_patched;
718
+ const historicalKey = state === "legacy"
719
+ ? "legacy_patched"
720
+ : state === "predecessor" ? "predecessor_patched" : "";
721
+ if (historicalKey && patchRecordMatches(manifest?.[historicalKey], recipe, state)) {
722
+ return manifest[historicalKey];
701
723
  }
702
724
  return patchRecordMatches(manifest?.patched, recipe, state) ? manifest.patched : null;
703
725
  }
@@ -728,8 +750,11 @@ function validateBackup({ backupDir, recipe, operations }) {
728
750
  && JSON.stringify(manifest?.source?.bundle_content) !== JSON.stringify(recipe.sourceBundleContent))
729
751
  || (recipe.sourceArtifact
730
752
  && JSON.stringify(manifest?.source?.artifact) !== JSON.stringify(recipe.sourceArtifact))
753
+ || (manifest?.predecessor_patched !== undefined
754
+ && !patchRecordMatches(manifest.predecessor_patched, recipe, "predecessor"))
731
755
  || (!patchRecordForState(manifest, recipe, "patched")
732
- && !patchRecordForState(manifest, recipe, "legacy"))) {
756
+ && !patchRecordForState(manifest, recipe, "legacy")
757
+ && !patchRecordForState(manifest, recipe, "predecessor"))) {
733
758
  throw setupError("PM_STUDIO_BACKUP_INVALID", `Existing PM Studio backup does not match recipe ${recipe.id}`);
734
759
  }
735
760
  const inspection = inspectPmStudioApp({ appPath: backupAppPath, recipe, operations });
@@ -775,6 +800,14 @@ function ensureBackup({ appPath, backupRoot, recipe, operations }) {
775
800
 
776
801
  function writePatchedBinaryRecord({ backup, inspection, operations, recipe }) {
777
802
  const manifest = JSON.parse(fs.readFileSync(backup.manifestPath, "utf8"));
803
+ if (manifest.predecessor_patched !== undefined
804
+ && !patchRecordMatches(manifest.predecessor_patched, recipe, "predecessor")) {
805
+ throw setupError("PM_STUDIO_BACKUP_INVALID", "Existing PM Studio predecessor record does not match the patch recipe");
806
+ }
807
+ if (manifest.predecessor_patched === undefined
808
+ && patchRecordMatches(manifest.patched, recipe, "predecessor")) {
809
+ manifest.predecessor_patched = manifest.patched;
810
+ }
778
811
  if (!patchRecordMatches(manifest.legacy_patched, recipe, "legacy")
779
812
  && patchRecordMatches(manifest.patched, recipe, "legacy")) {
780
813
  manifest.legacy_patched = manifest.patched;
@@ -804,7 +837,9 @@ export function assertPatchedBinaryRecord({
804
837
  inspection,
805
838
  manifestPath,
806
839
  recipe,
807
- recordState = inspection?.state === "legacy" ? "legacy" : "patched",
840
+ recordState = ["legacy", "predecessor"].includes(inspection?.state)
841
+ ? inspection.state
842
+ : "patched",
808
843
  }) {
809
844
  let manifest;
810
845
  try {
@@ -966,9 +1001,11 @@ export async function runPmStudioSetup({
966
1001
  return { status: "already_patched", changed: false, recipe: recipe.id, backup, inspection: source, messages };
967
1002
  }
968
1003
  const migratingLegacy = source.state === "legacy";
969
- if (source.state !== "clean" && !migratingLegacy) {
1004
+ const migratingPredecessor = source.state === "predecessor";
1005
+ const migratingExistingPatch = migratingLegacy || migratingPredecessor;
1006
+ if (source.state !== "clean" && !migratingExistingPatch) {
970
1007
  throw setupError("PM_STUDIO_BUNDLE_DRIFT",
971
- `PM Studio ${recipe.version} build ${recipe.build} does not match the clean, split-origin, or legacy recipe; no files were changed`, source.issues);
1008
+ `PM Studio ${recipe.version} build ${recipe.build} does not match the clean, current split-origin, predecessor, or legacy recipe; no files were changed`, source.issues);
972
1009
  }
973
1010
 
974
1011
  if (source.sourceVerification === "exact-bundle-content") {
@@ -977,19 +1014,21 @@ export async function runPmStudioSetup({
977
1014
 
978
1015
  const backupExists = fs.existsSync(finalBackupDir);
979
1016
  let verifiedBackup = null;
980
- if (migratingLegacy) {
1017
+ if (migratingExistingPatch) {
981
1018
  if (!backupExists) {
982
1019
  throw setupError("PM_STUDIO_BACKUP_INVALID",
983
- "Legacy PM Studio global-origin patch has no matching verified clean backup; no files were changed");
1020
+ `${migratingLegacy ? "Legacy global-origin" : "Predecessor split-origin"} PM Studio patch has no matching verified clean backup; no files were changed`);
984
1021
  }
985
1022
  verifiedBackup = validateBackup({ backupDir: finalBackupDir, recipe, operations });
986
1023
  assertPatchedBinaryRecord({
987
1024
  inspection: source,
988
1025
  manifestPath: verifiedBackup.manifestPath,
989
1026
  recipe,
990
- recordState: "legacy",
1027
+ recordState: source.state,
991
1028
  });
992
- emit("[WARN] Legacy global-origin patch detected; setup will rebuild from the verified clean backup so GPT and enterprise authentication return to PM Studio's native origin.");
1029
+ emit(migratingLegacy
1030
+ ? "[WARN] Legacy global-origin patch detected; setup will rebuild from the verified clean backup so GPT and enterprise authentication return to PM Studio's native origin."
1031
+ : "[WARN] Predecessor split-origin patch detected; setup will rebuild from the verified clean backup so delayed Claude model discovery no longer falls back to the native catalog.");
993
1032
  }
994
1033
  assertSpace({ appPath, backupRoot, backupExists, operations });
995
1034
 
@@ -999,13 +1038,13 @@ export async function runPmStudioSetup({
999
1038
  let preserveStage = false;
1000
1039
  try {
1001
1040
  operations.copyBundle({
1002
- source: migratingLegacy ? backup.backupAppPath : appPath,
1041
+ source: migratingExistingPatch ? backup.backupAppPath : appPath,
1003
1042
  destination: stagePath,
1004
1043
  processRunner: operations.processRunner,
1005
1044
  });
1006
1045
  const stagedSource = inspectPmStudioApp({ appPath: stagePath, recipe, operations });
1007
1046
  if (stagedSource.state !== "clean"
1008
- || (!migratingLegacy && recipe.sourceBundleContent
1047
+ || (!migratingExistingPatch && recipe.sourceBundleContent
1009
1048
  && stagedSource.bundleContent?.sha256 !== source.bundleContent?.sha256)) {
1010
1049
  throw setupError("PM_STUDIO_STAGE_INVALID", "PM Studio staging copy does not match the exact clean source bundle", stagedSource.issues);
1011
1050
  }
@@ -1078,7 +1117,9 @@ export async function runPmStudioSetup({
1078
1117
 
1079
1118
  emit(migratingLegacy
1080
1119
  ? `[OK] Migrated PM Studio ${recipe.version} build ${recipe.build} from the legacy global-origin patch to the split-origin patch.`
1081
- : `[OK] Patched PM Studio ${recipe.version} build ${recipe.build}.`);
1120
+ : migratingPredecessor
1121
+ ? `[OK] Migrated PM Studio ${recipe.version} build ${recipe.build} from the predecessor split-origin patch to the current split-origin patch.`
1122
+ : `[OK] Patched PM Studio ${recipe.version} build ${recipe.build}.`);
1082
1123
  emit(`[OK] app.asar SHA-256: ${installed.asar.asarSha256}`);
1083
1124
  emit(`[OK] Verified backup: ${backup.backupAppPath}${backup.reused ? " (reused)" : ""}`);
1084
1125
  emit(`[OK] Backup manifest: ${backup.manifestPath}`);
@@ -1088,7 +1129,7 @@ export async function runPmStudioSetup({
1088
1129
  emit(`[INFO] Restore step 3/3: move ${appPath} aside, copy ${backup.backupAppPath} to that exact path, and verify its signature before launch.`);
1089
1130
  emit(`[INFO] Start ${commandName} before using Claude in PM Studio; GPT remains on PM Studio's native GitHub Copilot path.`);
1090
1131
  return {
1091
- status: migratingLegacy ? "migrated" : "patched",
1132
+ status: migratingExistingPatch ? "migrated" : "patched",
1092
1133
  changed: true,
1093
1134
  recipe: recipe.id,
1094
1135
  backup,
@@ -170,10 +170,15 @@ export async function inspectPmStudioStatus({
170
170
  const recipe = selectRecipe(normalized, recipes);
171
171
  if (recipe) {
172
172
  app = { ...inspectApp({ appPath, recipe, operations }), recipe: recipe.id };
173
- if (app.state === "patched") {
173
+ if (["patched", "predecessor"].includes(app.state)) {
174
174
  const manifestPath = pmStudioPatchManifestPath({ home, backupRoot, recipe });
175
175
  try {
176
- verifyPatchRecord({ inspection: app, manifestPath, recipe });
176
+ verifyPatchRecord({
177
+ inspection: app,
178
+ manifestPath,
179
+ recipe,
180
+ recordState: app.state,
181
+ });
177
182
  app.patchRecord = { valid: true, manifestPath, reason: "" };
178
183
  } catch (error) {
179
184
  const reason = safeMessage(error);
@@ -239,6 +244,9 @@ function appStatusItem(result, commandName) {
239
244
  const reason = app.patchRecord?.reason || "installed patch record is missing";
240
245
  return { kind: "err", component: "App patch", detail: `${version}; patch record not verified`, message: `PM Studio ${version} matches the patch recipe, but its installed patch record is not verified: ${reason}` };
241
246
  }
247
+ if (app.state === "predecessor" && app.patchRecord?.valid === true) {
248
+ return { kind: "warn", component: "App patch", detail: `${version}; verified predecessor split patch`, message: `PM Studio ${version} has a verified predecessor split patch; run ${commandName} pms setup to migrate` };
249
+ }
242
250
  if (app.state === "legacy") {
243
251
  return { kind: "err", component: "App patch", detail: `${version}; legacy global-origin patch`, message: `PM Studio ${version} has a legacy global-origin patch; run ${commandName} pms setup to migrate` };
244
252
  }