paseo-acp-agy 1.2.2 → 1.2.4

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/dist/asar.d.ts CHANGED
@@ -4,6 +4,11 @@ export interface AsarNode {
4
4
  offset?: string;
5
5
  executable?: boolean;
6
6
  unpacked?: boolean;
7
+ link?: string;
8
+ }
9
+ export interface ExtractResult {
10
+ unpackedPaths: string[];
11
+ originalHeader: AsarNode;
7
12
  }
8
13
  /**
9
14
  * Reads and parses the ASAR archive header and returns the root node and base offset for data.
@@ -20,9 +25,13 @@ export declare function listPackage(archivePath: string): string[];
20
25
  * Pure TypeScript, zero-dependency ASAR archive extractor.
21
26
  * Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
22
27
  */
23
- export declare function extractAll(archivePath: string, destDir: string): void;
28
+ export declare function extractAll(archivePath: string, destDir: string): ExtractResult;
29
+ export interface CreatePackageOptions {
30
+ unpackedPaths?: string[];
31
+ originalHeader?: AsarNode;
32
+ }
24
33
  /**
25
34
  * Pure TypeScript, zero-dependency ASAR archive builder.
26
35
  * Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
27
36
  */
28
- export declare function createPackage(srcDir: string, destFile: string): Promise<void>;
37
+ export declare function createPackage(srcDir: string, destFile: string, options?: CreatePackageOptions): Promise<void>;
package/dist/asar.js CHANGED
@@ -61,6 +61,7 @@ export function listPackage(archivePath) {
61
61
  export function extractAll(archivePath, destDir) {
62
62
  const { header, dataBaseOffset } = readArchiveHeader(archivePath);
63
63
  const fd = fs.openSync(archivePath, "r");
64
+ const unpackedPaths = [];
64
65
  try {
65
66
  function extractNode(node, currentPath, relParts = []) {
66
67
  if (node.files) {
@@ -69,6 +70,20 @@ export function extractAll(archivePath, destDir) {
69
70
  extractNode(child, path.join(currentPath, name), [...relParts, name]);
70
71
  }
71
72
  }
73
+ else if (node.link) {
74
+ fs.mkdirSync(path.dirname(currentPath), { recursive: true });
75
+ try {
76
+ fs.unlinkSync(currentPath);
77
+ }
78
+ catch { }
79
+ fs.symlinkSync(node.link, currentPath);
80
+ }
81
+ else if (node.unpacked) {
82
+ // Unpacked files belong in archive.unpacked and must NOT be copied into destDir,
83
+ // so that re-packing does not duplicate external binaries into the asar payload.
84
+ const relPath = relParts.join("/");
85
+ unpackedPaths.push(relPath);
86
+ }
72
87
  else if (node.size !== undefined && node.offset !== undefined) {
73
88
  const fileOffset = dataBaseOffset + parseInt(node.offset, 10);
74
89
  const fileBuf = Buffer.alloc(node.size);
@@ -80,15 +95,9 @@ export function extractAll(archivePath, destDir) {
80
95
  mode: node.executable ? 0o755 : 0o644,
81
96
  });
82
97
  }
83
- else if (node.unpacked) {
84
- const unpackedSrc = path.join(archivePath + ".unpacked", ...relParts);
85
- if (fs.existsSync(unpackedSrc)) {
86
- fs.mkdirSync(path.dirname(currentPath), { recursive: true });
87
- fs.copyFileSync(unpackedSrc, currentPath);
88
- }
89
- }
90
98
  }
91
99
  extractNode(header, destDir);
100
+ return { unpackedPaths, originalHeader: header };
92
101
  }
93
102
  finally {
94
103
  fs.closeSync(fd);
@@ -98,7 +107,7 @@ export function extractAll(archivePath, destDir) {
98
107
  * Pure TypeScript, zero-dependency ASAR archive builder.
99
108
  * Compatible with all Node.js versions (Node 18, 20, 22, 24+) across Windows, macOS, and Linux.
100
109
  */
101
- export async function createPackage(srcDir, destFile) {
110
+ export async function createPackage(srcDir, destFile, options) {
102
111
  const files = [];
103
112
  function walk(dir, rel = "") {
104
113
  const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -106,16 +115,25 @@ export async function createPackage(srcDir, destFile) {
106
115
  for (const e of entries) {
107
116
  const entryRel = rel ? `${rel}/${e.name}` : e.name;
108
117
  const full = path.join(dir, e.name);
109
- if (e.isDirectory()) {
118
+ const lstat = fs.lstatSync(full);
119
+ if (lstat.isSymbolicLink()) {
120
+ files.push({
121
+ relPath: entryRel,
122
+ fullPath: full,
123
+ size: 0,
124
+ isLink: true,
125
+ linkTarget: fs.readlinkSync(full),
126
+ });
127
+ }
128
+ else if (lstat.isDirectory()) {
110
129
  walk(full, entryRel);
111
130
  }
112
- else if (e.isFile()) {
113
- const stat = fs.statSync(full);
114
- const executable = Boolean(stat.mode & 0o111);
131
+ else if (lstat.isFile()) {
132
+ const executable = Boolean(lstat.mode & 0o111);
115
133
  files.push({
116
134
  relPath: entryRel,
117
135
  fullPath: full,
118
- size: stat.size,
136
+ size: lstat.size,
119
137
  executable: executable || undefined,
120
138
  });
121
139
  }
@@ -134,12 +152,39 @@ export async function createPackage(srcDir, destFile) {
134
152
  curr = curr[parts[i]].files;
135
153
  }
136
154
  const leaf = parts[parts.length - 1];
137
- curr[leaf] = {
138
- size: f.size,
139
- offset: String(currentOffset),
140
- executable: f.executable,
141
- };
142
- currentOffset += f.size;
155
+ if (f.isLink) {
156
+ curr[leaf] = { link: f.linkTarget };
157
+ }
158
+ else {
159
+ curr[leaf] = {
160
+ size: f.size,
161
+ offset: String(currentOffset),
162
+ executable: f.executable,
163
+ };
164
+ currentOffset += f.size;
165
+ }
166
+ }
167
+ // Restore unpacked file references from original archive if provided
168
+ if (options?.unpackedPaths && options?.originalHeader) {
169
+ for (const relPath of options.unpackedPaths) {
170
+ const parts = relPath.split("/");
171
+ let origNode = options.originalHeader;
172
+ for (const p of parts) {
173
+ origNode = origNode?.files?.[p];
174
+ }
175
+ let curr = header.files;
176
+ for (let i = 0; i < parts.length - 1; i++) {
177
+ if (!curr[parts[i]]) {
178
+ curr[parts[i]] = { files: {} };
179
+ }
180
+ curr = curr[parts[i]].files;
181
+ }
182
+ const leaf = parts[parts.length - 1];
183
+ curr[leaf] = {
184
+ size: origNode?.size ?? 0,
185
+ unpacked: true,
186
+ };
187
+ }
143
188
  }
144
189
  const jsonBuf = Buffer.from(JSON.stringify(header), "utf8");
145
190
  const alignedJsonLen = (jsonBuf.length + 3) & ~3;
@@ -159,7 +204,7 @@ export async function createPackage(srcDir, destFile) {
159
204
  fs.writeSync(fd, sizePickle);
160
205
  fs.writeSync(fd, headerPickle);
161
206
  for (const f of files) {
162
- if (f.size > 0) {
207
+ if (!f.isLink && f.size > 0) {
163
208
  const content = fs.readFileSync(f.fullPath);
164
209
  fs.writeSync(fd, content);
165
210
  }
@@ -129,7 +129,7 @@ export function findPaseoServerInstallations() {
129
129
  "/Applications/Paseo.app/Contents/Resources/app/node_modules/@getpaseo/server",
130
130
  ];
131
131
  if (home) {
132
- posixLocations.push(path.join(home, ".local", "share", "pnpm", "global", "5", "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"), path.join(home, ".bun", "install", "global", "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"));
132
+ posixLocations.push(path.join(home, ".local", "lib", "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"), path.join(home, ".local", "lib", "node_modules", "@getpaseo", "server"), path.join(home, ".local", "share", "pnpm", "global", "5", "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"), path.join(home, ".bun", "install", "global", "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"));
133
133
  // Check nvm paths if available
134
134
  const nvmDir = path.join(home, ".nvm", "versions", "node");
135
135
  if (fs.existsSync(nvmDir)) {
@@ -146,6 +146,38 @@ export function findPaseoServerInstallations() {
146
146
  if (fs.existsSync(loc))
147
147
  candidates.add(path.resolve(loc));
148
148
  }
149
+ // Inspect which paseo on POSIX
150
+ try {
151
+ const whichOut = execFileSync("which", ["paseo"], {
152
+ encoding: "utf-8",
153
+ timeout: 3000,
154
+ }).trim();
155
+ for (const line of whichOut.split(/\r?\n/).map((l) => l.trim()).filter(Boolean)) {
156
+ let realLine = line;
157
+ try {
158
+ realLine = fs.realpathSync(line);
159
+ }
160
+ catch { }
161
+ const dirs = [path.dirname(line), path.dirname(realLine)];
162
+ for (const dir of dirs) {
163
+ const checks = [
164
+ path.join(dir, "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"),
165
+ path.join(dir, "node_modules", "@getpaseo", "server"),
166
+ path.join(path.dirname(dir), "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"),
167
+ path.join(path.dirname(dir), "node_modules", "@getpaseo", "server"),
168
+ path.join(path.dirname(path.dirname(dir)), "node_modules", "@getpaseo", "cli", "node_modules", "@getpaseo", "server"),
169
+ path.join(path.dirname(path.dirname(dir)), "node_modules", "@getpaseo", "server"),
170
+ path.join(dir, "resources", "app.asar.unpacked", "node_modules", "@getpaseo", "server"),
171
+ path.join(path.dirname(dir), "resources", "app.asar.unpacked", "node_modules", "@getpaseo", "server"),
172
+ ];
173
+ for (const c of checks) {
174
+ if (fs.existsSync(c))
175
+ candidates.add(path.resolve(c));
176
+ }
177
+ }
178
+ }
179
+ }
180
+ catch { }
149
181
  try {
150
182
  const npmRoot = execFileSync("npm", ["root", "-g"], { encoding: "utf-8", timeout: 2000 }).trim();
151
183
  if (npmRoot && fs.existsSync(npmRoot)) {
@@ -618,39 +650,43 @@ export function patchPaseoServer(serverDir) {
618
650
  }
619
651
  }
620
652
  // Patch handleUsageUpdate
621
- if (acpCode.includes("handleUsageUpdate") && (!acpCode.includes("this.deliverTranslatedEvents") || acpCode.includes("this.notifySubscribers"))) {
622
- const startIdx = acpCode.search(/\bhandleUsageUpdate\s*\(/);
623
- if (startIdx !== -1) {
624
- const openBrace = acpCode.indexOf("{", startIdx);
625
- if (openBrace !== -1) {
626
- let depth = 1;
627
- let i = openBrace + 1;
628
- while (i < acpCode.length && depth > 0) {
629
- if (acpCode[i] === "{")
630
- depth++;
631
- else if (acpCode[i] === "}")
632
- depth--;
633
- i++;
634
- }
635
- if (depth === 0) {
636
- const newHandler = `handleUsageUpdate(update) {
637
- if (!update) return;
638
- const usage = mapACPUsage(update);
639
- if (usage) {
640
- this.currentTurnUsage = { ...this.currentTurnUsage, ...usage };
641
- const event = {
642
- type: "usage_updated",
643
- provider: this.provider,
644
- usage: this.currentTurnUsage,
645
- ...(this.activeForegroundTurnId ? { turnId: this.activeForegroundTurnId } : {}),
646
- };
647
- if (typeof this.deliverTranslatedEvents === "function") {
648
- this.deliverTranslatedEvents([event]);
649
- } else if (typeof this.pushEvent === "function") {
650
- this.pushEvent(event);
651
- }
652
- }
653
- }`;
653
+ const methodMatch = acpCode.match(/(?:^|\n)([ \t]*)handleUsageUpdate\s*\([^)]*\)\s*\{/);
654
+ if (methodMatch && methodMatch.index !== undefined) {
655
+ const startIdx = methodMatch.index + (methodMatch[0].startsWith("\n") ? 1 : 0);
656
+ const openBrace = acpCode.indexOf("{", startIdx);
657
+ if (openBrace !== -1) {
658
+ let depth = 1;
659
+ let i = openBrace + 1;
660
+ while (i < acpCode.length && depth > 0) {
661
+ if (acpCode[i] === "{")
662
+ depth++;
663
+ else if (acpCode[i] === "}")
664
+ depth--;
665
+ i++;
666
+ }
667
+ if (depth === 0) {
668
+ const currentMethodBody = acpCode.slice(startIdx, i);
669
+ const needsPatch = !currentMethodBody.includes('type: "usage_updated"') || currentMethodBody.includes("this.notifySubscribers");
670
+ if (needsPatch) {
671
+ const indent = methodMatch[1] || " ";
672
+ const newHandler = `${indent}handleUsageUpdate(update) {
673
+ ${indent} if (!update) return;
674
+ ${indent} const usage = mapACPUsage(update);
675
+ ${indent} if (usage) {
676
+ ${indent} this.currentTurnUsage = { ...this.currentTurnUsage, ...usage };
677
+ ${indent} const event = {
678
+ ${indent} type: "usage_updated",
679
+ ${indent} provider: this.provider,
680
+ ${indent} usage: this.currentTurnUsage,
681
+ ${indent} ...(this.activeForegroundTurnId ? { turnId: this.activeForegroundTurnId } : {}),
682
+ ${indent} };
683
+ ${indent} if (typeof this.pushEvent === "function") {
684
+ ${indent} this.pushEvent(event);
685
+ ${indent} } else if (typeof this.deliverTranslatedEvents === "function") {
686
+ ${indent} this.deliverTranslatedEvents([event]);
687
+ ${indent} }
688
+ ${indent} }
689
+ ${indent}}`;
654
690
  acpCode = acpCode.slice(0, startIdx) + newHandler + acpCode.slice(i);
655
691
  acpModified = true;
656
692
  }
@@ -762,7 +798,7 @@ export async function patchPaseoAsar(asarPath) {
762
798
  return { success: false, changes: [], error: `Asar archive not found: ${asarPath}` };
763
799
  }
764
800
  tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "paseo-asar-extract-"));
765
- extractAll(asarPath, tempDir);
801
+ const { unpackedPaths, originalHeader } = extractAll(asarPath, tempDir);
766
802
  // Look for server directory in extracted files
767
803
  const serverCandidates = [
768
804
  path.join(tempDir, "node_modules", "@getpaseo", "server"),
@@ -805,8 +841,27 @@ export async function patchPaseoAsar(asarPath) {
805
841
  return { success: true, changes: [] };
806
842
  }
807
843
  changes.push(...patchResult.changes);
808
- // Create backup if not already present
809
- const backupPath = `${asarPath}.bak`;
844
+ // Create backup if not already present.
845
+ // On macOS, never write backup inside .app bundle (e.g. app.asar.bak) because it breaks code signing seals.
846
+ let backupPath;
847
+ if (process.platform === "darwin" && asarPath.includes(".app")) {
848
+ const backupDir = path.join(os.homedir(), ".paseo", "backups");
849
+ if (!fs.existsSync(backupDir)) {
850
+ fs.mkdirSync(backupDir, { recursive: true });
851
+ }
852
+ backupPath = path.join(backupDir, "app.asar.bak");
853
+ // Clean up any orphan backup inside the .app bundle from previous runs
854
+ const orphanBak = `${asarPath}.bak`;
855
+ if (fs.existsSync(orphanBak)) {
856
+ try {
857
+ fs.unlinkSync(orphanBak);
858
+ }
859
+ catch { }
860
+ }
861
+ }
862
+ else {
863
+ backupPath = `${asarPath}.bak`;
864
+ }
810
865
  if (!fs.existsSync(backupPath)) {
811
866
  try {
812
867
  fs.copyFileSync(asarPath, backupPath);
@@ -817,7 +872,7 @@ export async function patchPaseoAsar(asarPath) {
817
872
  }
818
873
  }
819
874
  tempAsar = path.join(os.tmpdir(), `app-${Date.now()}.asar`);
820
- await createPackage(tempDir, tempAsar);
875
+ await createPackage(tempDir, tempAsar, { unpackedPaths, originalHeader });
821
876
  // Replace original archive with locked file handling for Windows
822
877
  try {
823
878
  fs.copyFileSync(tempAsar, asarPath);
@@ -839,6 +894,21 @@ export async function patchPaseoAsar(asarPath) {
839
894
  throw copyErr;
840
895
  }
841
896
  }
897
+ // On macOS, re-sign application bundle if inside an .app directory to satisfy Gatekeeper
898
+ if (process.platform === "darwin" && asarPath.includes(".app")) {
899
+ const appIndex = asarPath.indexOf(".app");
900
+ const appBundlePath = asarPath.slice(0, appIndex + 4);
901
+ try {
902
+ execFileSync("codesign", ["--force", "--deep", "--sign", "-", appBundlePath], {
903
+ stdio: "ignore",
904
+ timeout: 15000,
905
+ });
906
+ changes.push(`Re-signed macOS bundle at ${appBundlePath}`);
907
+ }
908
+ catch (signErr) {
909
+ logger.warn(`Could not re-sign ${appBundlePath}`, { error: String(signErr) });
910
+ }
911
+ }
842
912
  return { success: true, changes };
843
913
  }
844
914
  catch (err) {
@@ -914,6 +984,7 @@ export function isPaseoServerPatched(serverDir) {
914
984
  try {
915
985
  if (!fs.existsSync(serverDir))
916
986
  return false;
987
+ // 1. Antigravity quota provider file
917
988
  const antigravityJsCandidates = [
918
989
  path.join(serverDir, "dist", "server", "services", "quota-fetcher", "providers", "antigravity.js"),
919
990
  path.join(serverDir, "dist", "services", "quota-fetcher", "providers", "antigravity.js"),
@@ -940,20 +1011,80 @@ export function isPaseoServerPatched(serverDir) {
940
1011
  };
941
1012
  hasProvider = checkRecursive(path.join(serverDir, "dist"));
942
1013
  }
943
- if (hasProvider)
944
- return true;
1014
+ if (!hasProvider)
1015
+ return false;
1016
+ // 2. Manifest check
945
1017
  const manifestCandidates = [
946
1018
  path.join(serverDir, "dist", "server", "services", "quota-fetcher", "manifest.js"),
947
1019
  path.join(serverDir, "dist", "services", "quota-fetcher", "manifest.js"),
948
1020
  ];
949
- for (const cand of manifestCandidates) {
950
- if (fs.existsSync(cand)) {
951
- const content = fs.readFileSync(cand, "utf-8");
952
- if (content.includes('providerId: "antigravity"'))
953
- return true;
1021
+ let manifestFile = manifestCandidates.find((f) => fs.existsSync(f));
1022
+ if (!manifestFile && fs.existsSync(path.join(serverDir, "dist"))) {
1023
+ const findManifest = (dir, depth = 0) => {
1024
+ if (depth > 5)
1025
+ return null;
1026
+ try {
1027
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1028
+ for (const e of entries) {
1029
+ const full = path.join(dir, e.name);
1030
+ if (e.isDirectory() && e.name !== "node_modules") {
1031
+ const found = findManifest(full, depth + 1);
1032
+ if (found)
1033
+ return found;
1034
+ }
1035
+ else if (e.isFile() && e.name === "manifest.js" && dir.replace(/\\/g, "/").includes("quota-fetcher")) {
1036
+ return full;
1037
+ }
1038
+ }
1039
+ }
1040
+ catch { }
1041
+ return null;
1042
+ };
1043
+ manifestFile = findManifest(path.join(serverDir, "dist")) || undefined;
1044
+ }
1045
+ if (manifestFile) {
1046
+ const content = fs.readFileSync(manifestFile, "utf-8");
1047
+ if (!content.includes('providerId: "antigravity"')) {
1048
+ return false;
1049
+ }
1050
+ }
1051
+ // 3. ACP Agent check (context window telemetry & usage updates)
1052
+ const acpCandidates = [
1053
+ path.join(serverDir, "dist", "server", "server", "agent", "providers", "acp-agent.js"),
1054
+ path.join(serverDir, "dist", "server", "agent", "providers", "acp-agent.js"),
1055
+ path.join(serverDir, "dist", "agent", "providers", "acp-agent.js"),
1056
+ ];
1057
+ let acpFile = acpCandidates.find((f) => fs.existsSync(f));
1058
+ if (!acpFile && fs.existsSync(path.join(serverDir, "dist"))) {
1059
+ const findAcp = (dir, depth = 0) => {
1060
+ if (depth > 5)
1061
+ return null;
1062
+ try {
1063
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1064
+ for (const e of entries) {
1065
+ const full = path.join(dir, e.name);
1066
+ if (e.isDirectory() && e.name !== "node_modules") {
1067
+ const found = findAcp(full, depth + 1);
1068
+ if (found)
1069
+ return found;
1070
+ }
1071
+ else if (e.isFile() && e.name === "acp-agent.js") {
1072
+ return full;
1073
+ }
1074
+ }
1075
+ }
1076
+ catch { }
1077
+ return null;
1078
+ };
1079
+ acpFile = findAcp(path.join(serverDir, "dist")) || undefined;
1080
+ }
1081
+ if (acpFile) {
1082
+ const content = fs.readFileSync(acpFile, "utf-8");
1083
+ if (!content.includes('type: "usage_updated"') || !content.includes("contextWindowMaxTokens")) {
1084
+ return false;
954
1085
  }
955
1086
  }
956
- return false;
1087
+ return true;
957
1088
  }
958
1089
  catch {
959
1090
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paseo-acp-agy",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "ACP (Agent Client Protocol) provider for Google Antigravity in Paseo and Zed",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",