privateer-agent 0.15.4 → 0.15.6

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.
Files changed (229) hide show
  1. package/README.md +11 -0
  2. package/bin/boot-bundle.d.mts +2 -0
  3. package/bin/boot-bundle.mjs +44 -0
  4. package/bin/fresh-supervisor.d.mts +5 -1
  5. package/bin/fresh-supervisor.mjs +47 -10
  6. package/bin/privateer-launch.mjs +27 -3
  7. package/bin/privateer-subagent.mjs +5 -2
  8. package/bin/privateer-verify.mjs +12 -0
  9. package/bin/session-recovery.d.mts +53 -0
  10. package/bin/session-recovery.mjs +240 -0
  11. package/bin/session-seal-preload.mjs +17 -0
  12. package/bin/session-seal.d.mts +25 -0
  13. package/bin/session-seal.mjs +242 -0
  14. package/dist-boot/boot.mjs +3 -0
  15. package/dist-boot/c-27JKSMWL.mjs +1460 -0
  16. package/dist-boot/c-2BMRFV24.mjs +269 -0
  17. package/dist-boot/c-2CTF6JA3.mjs +166 -0
  18. package/dist-boot/c-2G3BBHRJ.mjs +84 -0
  19. package/dist-boot/c-2IDLKXX6.mjs +250 -0
  20. package/dist-boot/c-2NJD5VYM.mjs +8 -0
  21. package/dist-boot/c-2S5EEQPW.mjs +1602 -0
  22. package/dist-boot/c-2WRDCUVI.mjs +25 -0
  23. package/dist-boot/c-2YN3M256.mjs +14 -0
  24. package/dist-boot/c-37GM3GMU.mjs +187 -0
  25. package/dist-boot/c-3STKRSVO.mjs +2480 -0
  26. package/dist-boot/c-3TPV6TQQ.mjs +2320 -0
  27. package/dist-boot/c-3UVHLQDJ.mjs +225 -0
  28. package/dist-boot/c-42PUUZYC.mjs +697 -0
  29. package/dist-boot/c-4D5UMWVW.mjs +683 -0
  30. package/dist-boot/c-4ETZRMTA.mjs +46 -0
  31. package/dist-boot/c-4QT25BD5.mjs +1482 -0
  32. package/dist-boot/c-4WVR32AP.mjs +2579 -0
  33. package/dist-boot/c-5EMM567G.mjs +152 -0
  34. package/dist-boot/c-5FA4CV63.mjs +145 -0
  35. package/dist-boot/c-5GV3X3RB.mjs +14 -0
  36. package/dist-boot/c-5JCEPRIY.mjs +52 -0
  37. package/dist-boot/c-5NDKIIWT.mjs +16 -0
  38. package/dist-boot/c-5QL3AR26.mjs +4665 -0
  39. package/dist-boot/c-5XY6MFKN.mjs +5142 -0
  40. package/dist-boot/c-65UZIZC7.mjs +2897 -0
  41. package/dist-boot/c-6FUXKHOK.mjs +1969 -0
  42. package/dist-boot/c-6JHWQE4V.mjs +18242 -0
  43. package/dist-boot/c-6LOBR4I6.mjs +5280 -0
  44. package/dist-boot/c-6QHOZXKQ.mjs +14394 -0
  45. package/dist-boot/c-6ZO2SADQ.mjs +178 -0
  46. package/dist-boot/c-6ZORVVZP.mjs +1449 -0
  47. package/dist-boot/c-76QH4UCP.mjs +2441 -0
  48. package/dist-boot/c-77GO5L5Y.mjs +20982 -0
  49. package/dist-boot/c-7M23XFTN.mjs +5052 -0
  50. package/dist-boot/c-7TC367E2.mjs +32 -0
  51. package/dist-boot/c-7UOG7POX.mjs +1524 -0
  52. package/dist-boot/c-7VHP3FLN.mjs +376 -0
  53. package/dist-boot/c-A73JHZP2.mjs +13207 -0
  54. package/dist-boot/c-ABXULAFL.mjs +71 -0
  55. package/dist-boot/c-ANDD2AQO.mjs +74 -0
  56. package/dist-boot/c-AOY77AZL.mjs +1430 -0
  57. package/dist-boot/c-AXEPZ22I.mjs +108 -0
  58. package/dist-boot/c-B2Y7VNHU.mjs +427 -0
  59. package/dist-boot/c-B7VJUH73.mjs +77 -0
  60. package/dist-boot/c-BFW6CDVL.mjs +24 -0
  61. package/dist-boot/c-BHNIF2AR.mjs +188 -0
  62. package/dist-boot/c-BKF2O6BL.mjs +23 -0
  63. package/dist-boot/c-BOZXFTZN.mjs +8 -0
  64. package/dist-boot/c-BRNYJWZL.mjs +28 -0
  65. package/dist-boot/c-C3N4Z2HO.mjs +93 -0
  66. package/dist-boot/c-C4IV7ZFG.mjs +717 -0
  67. package/dist-boot/c-C5NK6UYV.mjs +793 -0
  68. package/dist-boot/c-C67644MB.mjs +1659 -0
  69. package/dist-boot/c-C6S2YB53.mjs +620 -0
  70. package/dist-boot/c-CDBEJSBF.mjs +37 -0
  71. package/dist-boot/c-CG47HBKU.mjs +435 -0
  72. package/dist-boot/c-CKCTWCT5.mjs +51 -0
  73. package/dist-boot/c-CN77DCGF.mjs +60 -0
  74. package/dist-boot/c-CRBXNKFO.mjs +1795 -0
  75. package/dist-boot/c-CRIHASQ6.mjs +973 -0
  76. package/dist-boot/c-CU4NGAUO.mjs +3786 -0
  77. package/dist-boot/c-CWRFLGQU.mjs +1883 -0
  78. package/dist-boot/c-CYQB2USO.mjs +32 -0
  79. package/dist-boot/c-DEE67DJO.mjs +379 -0
  80. package/dist-boot/c-DIFDBYOP.mjs +1258 -0
  81. package/dist-boot/c-DOSGVXDZ.mjs +34879 -0
  82. package/dist-boot/c-DR7XQI32.mjs +381 -0
  83. package/dist-boot/c-DTGPSB46.mjs +14666 -0
  84. package/dist-boot/c-DTHDNA4P.mjs +320 -0
  85. package/dist-boot/c-E4Z3Y5B5.mjs +3864 -0
  86. package/dist-boot/c-E66Z2TI7.mjs +203 -0
  87. package/dist-boot/c-EBU6RLRV.mjs +1527 -0
  88. package/dist-boot/c-EBZZNGSF.mjs +432 -0
  89. package/dist-boot/c-EC3LJ2KF.mjs +1032 -0
  90. package/dist-boot/c-EQBKXWNX.mjs +15829 -0
  91. package/dist-boot/c-EUEPIW4S.mjs +574 -0
  92. package/dist-boot/c-EX4YVWAT.mjs +378 -0
  93. package/dist-boot/c-EYZGSAJU.mjs +98 -0
  94. package/dist-boot/c-F75VRKY4.mjs +172 -0
  95. package/dist-boot/c-FH4NXKHV.mjs +177 -0
  96. package/dist-boot/c-FHNUCTEM.mjs +140 -0
  97. package/dist-boot/c-FJ5T42DS.mjs +5349 -0
  98. package/dist-boot/c-FPCNRL7G.mjs +1728 -0
  99. package/dist-boot/c-G4DLN7Q4.mjs +8 -0
  100. package/dist-boot/c-G56GYQFZ.mjs +2507 -0
  101. package/dist-boot/c-GLIOERDQ.mjs +1200 -0
  102. package/dist-boot/c-GMJ5HUBM.mjs +14 -0
  103. package/dist-boot/c-H3HSGMAQ.mjs +461 -0
  104. package/dist-boot/c-HIKTO6P7.mjs +275 -0
  105. package/dist-boot/c-I2MCJ34O.mjs +464 -0
  106. package/dist-boot/c-I3C6LBW4.mjs +765 -0
  107. package/dist-boot/c-I54OKQHW.mjs +387 -0
  108. package/dist-boot/c-IFSP3R4D.mjs +80 -0
  109. package/dist-boot/c-IZK77VB7.mjs +171 -0
  110. package/dist-boot/c-J3Y25UGE.mjs +54 -0
  111. package/dist-boot/c-J5IESS7W.mjs +27750 -0
  112. package/dist-boot/c-JJWZBY7L.mjs +254 -0
  113. package/dist-boot/c-JKTFXTGF.mjs +463 -0
  114. package/dist-boot/c-JN57AXQ4.mjs +77 -0
  115. package/dist-boot/c-KFIO6LUW.mjs +1096 -0
  116. package/dist-boot/c-KIEMEFWW.mjs +478 -0
  117. package/dist-boot/c-KIVXWBYD.mjs +28 -0
  118. package/dist-boot/c-KP6CRIP4.mjs +580 -0
  119. package/dist-boot/c-L26VOJX4.mjs +11558 -0
  120. package/dist-boot/c-L5GHWG5G.mjs +5988 -0
  121. package/dist-boot/c-M7NSQAKK.mjs +353 -0
  122. package/dist-boot/c-MCEWOPBW.mjs +56 -0
  123. package/dist-boot/c-MGPIEG7I.mjs +20 -0
  124. package/dist-boot/c-NREXJV3M.mjs +393 -0
  125. package/dist-boot/c-O5PP6CTX.mjs +162 -0
  126. package/dist-boot/c-OBQDHSXM.mjs +1774 -0
  127. package/dist-boot/c-OLQHHDD4.mjs +21 -0
  128. package/dist-boot/c-OXW4JQQH.mjs +319 -0
  129. package/dist-boot/c-P3D7ZUWN.mjs +1288 -0
  130. package/dist-boot/c-P4PQZVV5.mjs +948 -0
  131. package/dist-boot/c-P63T3XL2.mjs +26373 -0
  132. package/dist-boot/c-P6N5DQGZ.mjs +194 -0
  133. package/dist-boot/c-P7MLKQFC.mjs +58 -0
  134. package/dist-boot/c-PB3PZJL5.mjs +1276 -0
  135. package/dist-boot/c-PTN4S6G2.mjs +1259 -0
  136. package/dist-boot/c-PYCE5J7U.mjs +1083 -0
  137. package/dist-boot/c-Q4HYCRLB.mjs +19410 -0
  138. package/dist-boot/c-QUXMETGG.mjs +68 -0
  139. package/dist-boot/c-RDMJZCOX.mjs +356 -0
  140. package/dist-boot/c-ROGJK76M.mjs +17 -0
  141. package/dist-boot/c-RQJJ26T4.mjs +708 -0
  142. package/dist-boot/c-RSKCUPJF.mjs +7559 -0
  143. package/dist-boot/c-RVJU4O6P.mjs +37 -0
  144. package/dist-boot/c-S2RVHR3M.mjs +165 -0
  145. package/dist-boot/c-SKMK3PEE.mjs +91120 -0
  146. package/dist-boot/c-TI7SM4EG.mjs +20 -0
  147. package/dist-boot/c-TILJ6GXD.mjs +1235 -0
  148. package/dist-boot/c-TT6BCUJD.mjs +114 -0
  149. package/dist-boot/c-U4ITOXAN.mjs +2107 -0
  150. package/dist-boot/c-UGERXVO2.mjs +7120 -0
  151. package/dist-boot/c-UPBH5AXY.mjs +1363 -0
  152. package/dist-boot/c-US2PUNZL.mjs +597 -0
  153. package/dist-boot/c-UU4WZPPE.mjs +20 -0
  154. package/dist-boot/c-UWVIYPQS.mjs +39 -0
  155. package/dist-boot/c-V4RHQBPQ.mjs +17683 -0
  156. package/dist-boot/c-V5MRVK47.mjs +351 -0
  157. package/dist-boot/c-V6CL266R.mjs +1755 -0
  158. package/dist-boot/c-VJTMFKYE.mjs +14975 -0
  159. package/dist-boot/c-VJTRUKIB.mjs +773 -0
  160. package/dist-boot/c-W4IFSHPW.mjs +43 -0
  161. package/dist-boot/c-WETGB2CF.mjs +113 -0
  162. package/dist-boot/c-WO3QSZUU.mjs +1337 -0
  163. package/dist-boot/c-WQLH73TY.mjs +242 -0
  164. package/dist-boot/c-XL735JW5.mjs +500 -0
  165. package/dist-boot/c-Y7HADL5B.mjs +1107 -0
  166. package/dist-boot/c-YGQHSVSK.mjs +41 -0
  167. package/dist-boot/c-YMP2AMEB.mjs +10350 -0
  168. package/dist-boot/c-YRDO2WEF.mjs +12012 -0
  169. package/dist-boot/c-YUSTRATJ.mjs +8 -0
  170. package/dist-boot/c-Z2262R22.mjs +2697 -0
  171. package/dist-boot/c-Z4ALQMJC.mjs +709 -0
  172. package/dist-boot/c-Z5ZUQVUY.mjs +62 -0
  173. package/dist-boot/c-ZDZUKXF7.mjs +874 -0
  174. package/dist-boot/c-ZJYLGIC3.mjs +32 -0
  175. package/dist-boot/c-ZNA5CDMI.mjs +77 -0
  176. package/dist-boot/c-ZSO6DI3M.mjs +204 -0
  177. package/dist-boot/c-ZWUVHKMK.mjs +100 -0
  178. package/dist-boot/c-ZY4MESEW.mjs +832 -0
  179. package/dist-boot/c-ZZ5REI4L.mjs +329 -0
  180. package/dist-boot/cli.mjs +51 -0
  181. package/dist-boot/ext-anthropic-attribution.mjs +207 -0
  182. package/dist-boot/ext-pi-background-tasks.mjs +7234 -0
  183. package/dist-boot/ext-pi-subagents.mjs +37 -0
  184. package/dist-boot/ext-privateer-account.mjs +4426 -0
  185. package/dist-boot/ext-privateer-brand.mjs +5274 -0
  186. package/dist-boot/ext-privateer-codemode.mjs +37 -0
  187. package/dist-boot/ext-privateer-computer.mjs +626 -0
  188. package/dist-boot/ext-privateer-connect.mjs +1862 -0
  189. package/dist-boot/ext-privateer-context.mjs +323 -0
  190. package/dist-boot/ext-privateer-desktop.mjs +92 -0
  191. package/dist-boot/ext-privateer-final-audit.mjs +48 -0
  192. package/dist-boot/ext-privateer-fresh.mjs +81 -0
  193. package/dist-boot/ext-privateer-gate.mjs +5959 -0
  194. package/dist-boot/ext-privateer-hints.mjs +236 -0
  195. package/dist-boot/ext-privateer-media.mjs +2626 -0
  196. package/dist-boot/ext-privateer-models.mjs +3397 -0
  197. package/dist-boot/ext-privateer-posture.mjs +3006 -0
  198. package/dist-boot/ext-privateer-privacy.mjs +4578 -0
  199. package/dist-boot/ext-privateer-recovery.mjs +287 -0
  200. package/dist-boot/ext-privateer-speak.mjs +4754 -0
  201. package/dist-boot/ext-privateer-terminals.mjs +725 -0
  202. package/dist-boot/ext-privateer-tools.mjs +528 -0
  203. package/dist-boot/ext-privateer-truncation.mjs +74 -0
  204. package/dist-boot/ext-privateer-update.mjs +210 -0
  205. package/dist-boot/ext-privateer-vision.mjs +148 -0
  206. package/dist-boot/ext-privateer-web.mjs +543 -0
  207. package/dist-boot/ext-rpiv-ask-user-question.mjs +710 -0
  208. package/dist-boot/manifest.json +9 -0
  209. package/extensions/privateer-brand.ts +3 -1
  210. package/extensions/privateer-context.ts +4 -2
  211. package/extensions/privateer-final-audit.ts +17 -0
  212. package/extensions/privateer-gate.ts +55 -9
  213. package/extensions/privateer-recovery.ts +175 -0
  214. package/extensions/privateer-web.ts +4 -4
  215. package/package.json +3 -1
  216. package/patches/@earendil-works+pi-coding-agent+1.3.0.patch +199 -2
  217. package/patches/pi-background-tasks+2.6.10.patch +91 -7
  218. package/src/cli/chat.ts +7 -1
  219. package/src/config/moatManifest.json +2 -0
  220. package/src/config/whatsNew.json +4 -4
  221. package/src/context.ts +33 -5
  222. package/src/engine/finalAudit.ts +38 -0
  223. package/src/engine/stallWatchdog.ts +8 -1
  224. package/src/providers/account.ts +34 -3
  225. package/src/remote/attentionPush.ts +40 -0
  226. package/src/remote/liveTaskSession.ts +7 -1
  227. package/src/remote/relayClient.ts +38 -4
  228. package/src/remote/remoteBridge.ts +17 -0
  229. package/src/remote/transcriptSnapshot.ts +104 -0
@@ -0,0 +1,1883 @@
1
+ import{createRequire as __pvCR}from"node:module";import{fileURLToPath as __pvF}from"node:url";import{dirname as __pvD}from"node:path";const require=__pvCR(import.meta.url);const __pvUrl=(r)=>new URL(r,import.meta.url).href;const __pvFile=(r)=>__pvF(new URL(r,import.meta.url));
2
+ import {
3
+ acquireAccountCredential,
4
+ authedFetch,
5
+ currentUser,
6
+ describeAccountBalanceError,
7
+ hasCredentials,
8
+ isConnectionFailureText,
9
+ logout,
10
+ notifySignedIn,
11
+ refreshAccountCredentials,
12
+ runDeviceLogin,
13
+ serverBaseUrl
14
+ } from "./c-EC3LJ2KF.mjs";
15
+ import {
16
+ agentDir,
17
+ globalDir
18
+ } from "./c-YGQHSVSK.mjs";
19
+ import {
20
+ interpretReport,
21
+ teePosture,
22
+ tierFromTeePosture
23
+ } from "./c-76QH4UCP.mjs";
24
+ import {
25
+ SecureClient
26
+ } from "./c-FJ5T42DS.mjs";
27
+ import {
28
+ __name
29
+ } from "./c-CN77DCGF.mjs";
30
+
31
+ // src/providers/account.ts?ext=privateer-gate
32
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
33
+ import { join as join4 } from "node:path";
34
+
35
+ // src/util/openBrowser.ts?ext=privateer-gate
36
+ import { spawn } from "node:child_process";
37
+ function canOpenBrowser(env = process.env, platform = process.platform) {
38
+ if (env.PRIVATEER_NO_BROWSER?.trim()) return false;
39
+ if (env.SSH_TTY || env.SSH_CONNECTION) return false;
40
+ if (platform !== "darwin" && platform !== "win32" && !env.DISPLAY && !env.WAYLAND_DISPLAY) return false;
41
+ return true;
42
+ }
43
+ __name(canOpenBrowser, "canOpenBrowser");
44
+ function browsableUrl(raw) {
45
+ const s = (raw ?? "").trim();
46
+ if (!s) return void 0;
47
+ try {
48
+ const u = new URL(s);
49
+ if (u.protocol !== "https:" && u.protocol !== "http:") return void 0;
50
+ return u.href;
51
+ } catch {
52
+ return void 0;
53
+ }
54
+ }
55
+ __name(browsableUrl, "browsableUrl");
56
+ function openInBrowser(rawUrl) {
57
+ const url = browsableUrl(rawUrl);
58
+ if (!url) return Promise.resolve(false);
59
+ let cmd;
60
+ let args;
61
+ if (process.platform === "darwin") {
62
+ cmd = "open";
63
+ args = [url];
64
+ } else if (process.platform === "win32") {
65
+ cmd = "cmd";
66
+ args = ["/c", "start", "", url.replace(/&/g, "^&")];
67
+ } else {
68
+ cmd = "xdg-open";
69
+ args = [url];
70
+ }
71
+ return new Promise((resolve) => {
72
+ let settled = false;
73
+ const done = /* @__PURE__ */ __name((ok) => {
74
+ if (!settled) {
75
+ settled = true;
76
+ resolve(ok);
77
+ }
78
+ }, "done");
79
+ try {
80
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
81
+ child.once("error", () => done(false));
82
+ child.once("exit", (code) => done(code === 0));
83
+ child.unref();
84
+ setTimeout(() => done(true), 2e3).unref?.();
85
+ } catch {
86
+ done(false);
87
+ }
88
+ });
89
+ }
90
+ __name(openInBrowser, "openInBrowser");
91
+
92
+ // src/util/gzipRequestBody.ts?ext=privateer-gate
93
+ import { gzipSync } from "node:zlib";
94
+ var INFERENCE_PATH = "/api/agent/v1";
95
+ var REJECTS_ENCODING = /* @__PURE__ */ new Set([400, 411, 415]);
96
+ var installed = null;
97
+ var enabled = true;
98
+ function compressible(body) {
99
+ return typeof body === "string" || body instanceof ArrayBuffer || ArrayBuffer.isView(body);
100
+ }
101
+ __name(compressible, "compressible");
102
+ function toBuffer(body) {
103
+ if (typeof body === "string") return Buffer.from(body, "utf8");
104
+ if (body instanceof ArrayBuffer) return Buffer.from(body);
105
+ return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
106
+ }
107
+ __name(toBuffer, "toBuffer");
108
+ function targetsInference(url) {
109
+ try {
110
+ const base = new URL(serverBaseUrl());
111
+ const u = new URL(url, base);
112
+ if (u.origin !== base.origin) return false;
113
+ const mount = base.pathname === "/" ? "" : base.pathname.replace(/\/+$/, "");
114
+ return u.pathname.startsWith(`${mount}${INFERENCE_PATH}`);
115
+ } catch {
116
+ return false;
117
+ }
118
+ }
119
+ __name(targetsInference, "targetsInference");
120
+ function urlOf(input) {
121
+ if (typeof input === "string") return input;
122
+ if (input instanceof URL) return input.href;
123
+ return input.url;
124
+ }
125
+ __name(urlOf, "urlOf");
126
+ function installGzipRequestBodies() {
127
+ if (installed) return;
128
+ if (process.env.PRIVATEER_NO_GZIP === "1") return;
129
+ const inner = globalThis.fetch;
130
+ installed = inner;
131
+ globalThis.fetch = async (input, init) => {
132
+ const body = init?.body;
133
+ if (!enabled || !init || !compressible(body) || !targetsInference(urlOf(input))) {
134
+ return await inner(input, init);
135
+ }
136
+ const headers = new Headers(init.headers);
137
+ if (headers.has("content-encoding")) return await inner(input, init);
138
+ headers.set("content-encoding", "gzip");
139
+ headers.delete("content-length");
140
+ const gz = gzipSync(toBuffer(body), { level: 1 });
141
+ const res = await inner(input, { ...init, headers, body: gz });
142
+ if (!REJECTS_ENCODING.has(res.status)) return res;
143
+ const plain = await inner(input, init);
144
+ if (plain.ok) {
145
+ enabled = false;
146
+ void res.body?.cancel().catch(() => {
147
+ });
148
+ return plain;
149
+ }
150
+ void plain.body?.cancel().catch(() => {
151
+ });
152
+ return res;
153
+ };
154
+ }
155
+ __name(installGzipRequestBodies, "installGzipRequestBodies");
156
+
157
+ // src/providers/defaultModel.ts?ext=privateer-gate
158
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
159
+ import { join } from "node:path";
160
+ var TINFOIL_MODEL_ID = "tinfoil/gemma4-31b";
161
+ var TINFOIL_DEFAULT_SPEC = TINFOIL_MODEL_ID;
162
+ var ACCOUNT_DEFAULT_MODEL_ID = TINFOIL_MODEL_ID;
163
+ var ACCOUNT_DEFAULT_SPEC = `privateer/${ACCOUNT_DEFAULT_MODEL_ID}`;
164
+ var ACCOUNT_NEAR_MODEL_ID = "near/zai-org/GLM-5.1-FP8";
165
+ var LEGACY_BYO_FALLBACK = "openrouter/openai/gpt-4o-mini";
166
+ var BYO_BY_KEY = [
167
+ { env: "ANTHROPIC_API_KEY", spec: "anthropic/claude-opus-4-8" },
168
+ { env: "OPENAI_API_KEY", spec: "openai/gpt-5.5" },
169
+ { env: "OPENROUTER_API_KEY", spec: LEGACY_BYO_FALLBACK }
170
+ ];
171
+ function savedPiDefaultSpec() {
172
+ try {
173
+ const raw = readFileSync(join(agentDir(), "settings.json"), "utf8").trim();
174
+ if (!raw) return null;
175
+ const s = JSON.parse(raw);
176
+ const provider = typeof s.defaultProvider === "string" ? s.defaultProvider.trim() : "";
177
+ const modelId = typeof s.defaultModel === "string" ? s.defaultModel.trim() : "";
178
+ return provider && modelId ? `${provider}/${modelId}` : null;
179
+ } catch {
180
+ return null;
181
+ }
182
+ }
183
+ __name(savedPiDefaultSpec, "savedPiDefaultSpec");
184
+ function resolveDefaultModel(opts = {}) {
185
+ const env = opts.env ?? process.env;
186
+ const explicit = opts.explicit?.trim();
187
+ if (explicit) return explicit;
188
+ const fromEnv = env.PRIVATEER_MODEL?.trim();
189
+ if (fromEnv) return fromEnv;
190
+ const saved = opts.saved === void 0 ? savedPiDefaultSpec() : opts.saved;
191
+ if (saved) return saved;
192
+ if (env.TINFOIL_API_KEY?.trim()) return TINFOIL_DEFAULT_SPEC;
193
+ const signedIn = opts.signedIn ?? hasCredentials();
194
+ if (signedIn) return ACCOUNT_DEFAULT_SPEC;
195
+ for (const { env: keyName, spec } of BYO_BY_KEY) {
196
+ if (env[keyName]?.trim()) return spec;
197
+ }
198
+ return ACCOUNT_DEFAULT_SPEC;
199
+ }
200
+ __name(resolveDefaultModel, "resolveDefaultModel");
201
+ function resolveSignedInModel(env = process.env) {
202
+ return resolveDefaultModel({ env, signedIn: true, saved: null });
203
+ }
204
+ __name(resolveSignedInModel, "resolveSignedInModel");
205
+ function splitSpec(spec) {
206
+ const slash = spec.indexOf("/");
207
+ if (slash <= 0 || slash === spec.length - 1) return null;
208
+ return { provider: spec.slice(0, slash), modelId: spec.slice(slash + 1) };
209
+ }
210
+ __name(splitSpec, "splitSpec");
211
+ function ensurePiDefaultModel(spec = resolveSignedInModel()) {
212
+ return writeSettingsDefaultModel(spec, true);
213
+ }
214
+ __name(ensurePiDefaultModel, "ensurePiDefaultModel");
215
+ function writeSettingsDefaultModel(spec, onlyIfUnset) {
216
+ const parts = splitSpec(spec);
217
+ if (!parts) return null;
218
+ const dir = agentDir();
219
+ const settingsPath = join(dir, "settings.json");
220
+ try {
221
+ mkdirSync(dir, { recursive: true });
222
+ let settings = {};
223
+ if (existsSync(settingsPath)) {
224
+ const raw = readFileSync(settingsPath, "utf8").trim();
225
+ if (raw) settings = JSON.parse(raw);
226
+ }
227
+ if (onlyIfUnset && typeof settings.defaultModel === "string" && settings.defaultModel.trim()) {
228
+ return null;
229
+ }
230
+ settings.defaultProvider = parts.provider;
231
+ settings.defaultModel = parts.modelId;
232
+ writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
233
+ `);
234
+ return spec;
235
+ } catch {
236
+ return null;
237
+ }
238
+ }
239
+ __name(writeSettingsDefaultModel, "writeSettingsDefaultModel");
240
+
241
+ // src/providers/vision.ts?ext=privateer-gate
242
+ var VISION_PATTERNS = [
243
+ // ── Named in the id ────────────────────────────────────────────────────────
244
+ // Qwen's vision line (qwen3-vl-*, qwen2.5-vl-72b, near/Qwen/Qwen3-VL-30B-A3B) and
245
+ // Baidu's (ernie-4.5-vl-424b). Qwen ships VL as a SEPARATE line, which is also why
246
+ // the plain qwen3.x ids below are deliberately absent.
247
+ /-vl[-_.]/i,
248
+ // Z.ai marks vision with a trailing v on the version: glm-4.5v, glm-4.6v,
249
+ // glm-5v-turbo. Same reasoning — the unsuffixed glm-5.x are their text siblings.
250
+ /\bglm-\d+(?:\.\d+)?v\b/i,
251
+ // ByteDance's GUI agent reads screenshots; that is the whole point of it.
252
+ /\bui-tars\b/i,
253
+ // ── Families that are multimodal throughout ────────────────────────────────
254
+ // Every Claude from 3 onwards takes images, and 3 is the oldest in the catalog.
255
+ /(^|\/)anthropic\/claude-/i,
256
+ // Gemini has been multimodal since 1.0.
257
+ /(^|\/)google\/gemini-/i,
258
+ // Gemma from 3 on is multimodal; gemma-2 is not, so the version is part of the
259
+ // match. Covers google/gemma-3-27b-it, google/gemma-4-31b-it, tinfoil/gemma4-31b,
260
+ // near/google/gemma-4-31B-it, phala/google/gemma-4-31b-it and the Phala
261
+ // gemma-4 derivatives.
262
+ /gemma-?[34]/i,
263
+ // gpt-4o, gpt-4.1 and the whole gpt-5 line. Deliberately NOT `openai/gpt-4`
264
+ // (the original is text-only), not gpt-3.5, and not gpt-oss — the open-weights
265
+ // models are text-only, which is what our own default was until this change.
266
+ /(^|\/)openai\/gpt-(?:4o|4\.1|5)/i,
267
+ // Llama 4 (maverick, scout) is natively multimodal; 3.x is not.
268
+ /(^|\/)meta-llama\/llama-4-/i,
269
+ // Nova lite/pro/premier take images; nova-micro is text-only.
270
+ /(^|\/)amazon\/nova-(?:2-)?(?:lite|pro|premier)/i,
271
+ // Grok 4 and up.
272
+ /(^|\/)x-ai\/grok-4/i,
273
+ // Mistral's multimodal tiers: small 3.2 and the medium 3 line.
274
+ /(^|\/)mistralai\/mistral-(?:small-3\.2|medium-3)/i
275
+ ];
276
+ function acceptsImages(modelId) {
277
+ return VISION_PATTERNS.some((re) => re.test(modelId));
278
+ }
279
+ __name(acceptsImages, "acceptsImages");
280
+ function visionInput(modelId) {
281
+ return acceptsImages(modelId) ? ["text", "image"] : ["text"];
282
+ }
283
+ __name(visionInput, "visionInput");
284
+
285
+ // src/providers/piAuthStore.ts?ext=privateer-gate
286
+ import { join as join2 } from "node:path";
287
+ function resolveAgentDir(agentDir2) {
288
+ return agentDir2 ?? process.env.PI_CODING_AGENT_DIR ?? agentDir();
289
+ }
290
+ __name(resolveAgentDir, "resolveAgentDir");
291
+ var cached;
292
+ var override;
293
+ function piAuthStore(agentDir2) {
294
+ if (override) return override;
295
+ const path = join2(resolveAgentDir(agentDir2), "auth.json");
296
+ if (cached?.path !== path) {
297
+ cached = {
298
+ path,
299
+ store: import("./c-RDMJZCOX.mjs").then((pi) => {
300
+ const ctor = pi.AuthStorage;
301
+ if (!ctor) {
302
+ throw new Error(
303
+ "pi's AuthStorage export is missing \u2014 the privateer patch for @earendil-works/pi-coding-agent has not been applied to this install. Run `privateer` once with write access to node_modules (a root-owned global install needs sudo), or reinstall with a release bundle."
304
+ );
305
+ }
306
+ return ctor.create(path);
307
+ })
308
+ };
309
+ }
310
+ return cached.store;
311
+ }
312
+ __name(piAuthStore, "piAuthStore");
313
+
314
+ // src/providers/sealedShim.ts?ext=privateer-gate
315
+ import http from "node:http";
316
+ import { Readable } from "node:stream";
317
+
318
+ // src/providers/phala/aci-verifier/errors.ts?ext=privateer-gate
319
+ var AciError = class extends Error {
320
+ static {
321
+ __name(this, "AciError");
322
+ }
323
+ constructor(message) {
324
+ super(message);
325
+ this.name = "AciError";
326
+ }
327
+ };
328
+ var AciFormatError = class extends AciError {
329
+ static {
330
+ __name(this, "AciFormatError");
331
+ }
332
+ constructor(message) {
333
+ super(message);
334
+ this.name = "AciFormatError";
335
+ }
336
+ };
337
+
338
+ // src/providers/phala/aci-verifier/jcs.ts?ext=privateer-gate
339
+ function canonicalize(value) {
340
+ if (value === null) return "null";
341
+ switch (typeof value) {
342
+ case "boolean":
343
+ return value ? "true" : "false";
344
+ case "number":
345
+ if (!Number.isInteger(value)) {
346
+ throw new AciFormatError(
347
+ `JCS: ACI restricts numbers to integers, got ${value} (\xA73)`
348
+ );
349
+ }
350
+ return Object.is(value, -0) ? "0" : String(value);
351
+ case "string":
352
+ return JSON.stringify(value);
353
+ case "object":
354
+ if (Array.isArray(value)) {
355
+ return "[" + value.map(canonicalize).join(",") + "]";
356
+ }
357
+ return serializeObject(value);
358
+ default:
359
+ throw new AciFormatError(`JCS: unsupported type ${typeof value}`);
360
+ }
361
+ }
362
+ __name(canonicalize, "canonicalize");
363
+ function serializeObject(obj) {
364
+ const keys = Object.keys(obj).filter((k) => obj[k] !== void 0).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
365
+ let out = "{";
366
+ for (let i = 0; i < keys.length; i++) {
367
+ const k = keys[i];
368
+ if (i > 0) out += ",";
369
+ out += JSON.stringify(k) + ":" + canonicalize(obj[k]);
370
+ }
371
+ return out + "}";
372
+ }
373
+ __name(serializeObject, "serializeObject");
374
+ function jcsBytes(value) {
375
+ return new TextEncoder().encode(canonicalize(value));
376
+ }
377
+ __name(jcsBytes, "jcsBytes");
378
+
379
+ // src/providers/phala/aci-verifier/crypto.ts?ext=privateer-gate
380
+ var subtle = globalThis.crypto.subtle;
381
+ function toHex(bytes) {
382
+ let out = "";
383
+ for (const b of bytes) out += b.toString(16).padStart(2, "0");
384
+ return out;
385
+ }
386
+ __name(toHex, "toHex");
387
+ function fromHex(hex) {
388
+ const h = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
389
+ if (h.length % 2 !== 0) {
390
+ throw new AciFormatError(`hex string has odd length: ${hex.length} chars`);
391
+ }
392
+ const out = new Uint8Array(h.length / 2);
393
+ for (let i = 0; i < out.length; i++) {
394
+ const byte = Number.parseInt(h.slice(i * 2, i * 2 + 2), 16);
395
+ if (Number.isNaN(byte)) {
396
+ throw new AciFormatError(`invalid hex at offset ${i * 2}: "${h.slice(i * 2, i * 2 + 2)}"`);
397
+ }
398
+ out[i] = byte;
399
+ }
400
+ return out;
401
+ }
402
+ __name(fromHex, "fromHex");
403
+ async function sha256(bytes) {
404
+ return new Uint8Array(await subtle.digest("SHA-256", bytes));
405
+ }
406
+ __name(sha256, "sha256");
407
+ async function sha256Hex(bytes) {
408
+ return toHex(await sha256(bytes));
409
+ }
410
+ __name(sha256Hex, "sha256Hex");
411
+ async function sha256Prefixed(bytes) {
412
+ return "sha256:" + await sha256Hex(bytes);
413
+ }
414
+ __name(sha256Prefixed, "sha256Prefixed");
415
+
416
+ // src/providers/phala/aci-verifier/digest.ts?ext=privateer-gate
417
+ var DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
418
+ var NONCE_RE = /^[0-9a-f]{64}$/;
419
+ async function computeKeysetDigest(keyset) {
420
+ return sha256Prefixed(jcsBytes(keyset));
421
+ }
422
+ __name(computeKeysetDigest, "computeKeysetDigest");
423
+ function attestationStatement(keysetDigest, nonce) {
424
+ if (!DIGEST_RE.test(keysetDigest)) {
425
+ throw new AciFormatError(`keyset digest is not sha256:<64-hex>: "${keysetDigest}"`);
426
+ }
427
+ if (nonce != null && !NONCE_RE.test(nonce)) {
428
+ throw new AciFormatError("nonce must be exactly 64 lowercase hex characters (\xA73.2)");
429
+ }
430
+ const noncePart = nonce == null ? "null" : `"${nonce}"`;
431
+ return new TextEncoder().encode(
432
+ `{"keyset_digest":"${keysetDigest}","nonce":${noncePart},"purpose":"aci.report_data.v1"}`
433
+ );
434
+ }
435
+ __name(attestationStatement, "attestationStatement");
436
+ async function computeReportData(keysetDigest, nonce) {
437
+ return sha256Hex(attestationStatement(keysetDigest, nonce));
438
+ }
439
+ __name(computeReportData, "computeReportData");
440
+
441
+ // src/providers/phala/aci-verifier/e2ee.ts?ext=privateer-gate
442
+ function requestAad(params) {
443
+ return jcsBytes({
444
+ purpose: "aci.e2ee.request.v2",
445
+ algo: params.algo,
446
+ model: params.model,
447
+ field: params.field,
448
+ nonce: params.nonce,
449
+ ts: params.ts
450
+ });
451
+ }
452
+ __name(requestAad, "requestAad");
453
+ function responseAad(params) {
454
+ return jcsBytes({
455
+ purpose: "aci.e2ee.response.v2",
456
+ algo: params.algo,
457
+ model: params.model,
458
+ id: params.id,
459
+ field: params.field,
460
+ nonce: params.nonce,
461
+ ts: params.ts
462
+ });
463
+ }
464
+ __name(responseAad, "responseAad");
465
+
466
+ // src/providers/phala/aci-verifier/e2ee-channel.ts?ext=privateer-gate
467
+ var ALGO = "x25519-aes-256-gcm-hkdf-sha256";
468
+ var subtle2 = globalThis.crypto.subtle;
469
+ var enc = new TextEncoder();
470
+ var dec = new TextDecoder();
471
+ var HKDF_INFO = enc.encode("aci.e2ee.v2.x25519");
472
+ async function openE2eeChannel(report, verification) {
473
+ if (!verification.ok || verification.workloadKeysetDigest !== report.workload_keyset_digest) {
474
+ throw new Error("openE2eeChannel: report is not verified \u2014 call verifyReportBinding and check .ok");
475
+ }
476
+ const keys = verification.keyset?.e2ee_public_keys ?? [];
477
+ const service = keys.find((k) => k?.algo === ALGO);
478
+ if (!service) throw new Error(`openE2eeChannel: no attested ${ALGO} key in the keyset`);
479
+ const serviceRaw = fromHex(service.public_key);
480
+ const client2 = await subtle2.generateKey({ name: "X25519" }, true, ["deriveBits"]);
481
+ const clientPubHex = toHex(new Uint8Array(await subtle2.exportKey("raw", client2.publicKey)));
482
+ let sent;
483
+ return {
484
+ async seal(request) {
485
+ const model = request.model;
486
+ if (typeof model !== "string") throw new Error("seal: request.model must be a string");
487
+ const nonce = toHex(crypto.getRandomValues(new Uint8Array(32)));
488
+ const ts = Math.floor(Date.now() / 1e3);
489
+ sent = { model, nonce, ts };
490
+ const encField = /* @__PURE__ */ __name((text, field) => sealField(serviceRaw, enc.encode(text), requestAad({ algo: ALGO, model, field, nonce, ts })), "encField");
491
+ const body = { ...request };
492
+ if (Array.isArray(request.messages)) {
493
+ body.messages = await Promise.all(
494
+ request.messages.map(async (m, i) => {
495
+ if (m?.content == null) return m;
496
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
497
+ return { ...m, content: await encField(text, `messages.${i}.content`) };
498
+ })
499
+ );
500
+ } else if (request.prompt !== void 0) {
501
+ body.prompt = await sealStringOrArray(request.prompt, "prompt", encField);
502
+ } else if (request.input !== void 0) {
503
+ body.input = await sealStringOrArray(request.input, "input", encField);
504
+ }
505
+ return {
506
+ body,
507
+ headers: {
508
+ "X-E2EE-Version": "2",
509
+ "X-Client-Pub-Key": clientPubHex,
510
+ "X-Model-Pub-Key": service.public_key,
511
+ "X-E2EE-Nonce": nonce,
512
+ "X-E2EE-Timestamp": String(ts)
513
+ }
514
+ };
515
+ },
516
+ async open(response) {
517
+ const decField = responseDecryptor(client2.privateKey, sent, textFrom(response.id));
518
+ const body = { ...response };
519
+ if (Array.isArray(response.choices)) {
520
+ body.choices = await Promise.all(
521
+ response.choices.map(async (c, pos) => {
522
+ const i = indexOf(c, pos);
523
+ const out = { ...c };
524
+ if (out.message && typeof out.message === "object") {
525
+ const m = { ...out.message };
526
+ await openStr(m, "content", `choices.${i}.message.content`, decField);
527
+ await openStr(m, "reasoning_content", `choices.${i}.message.reasoning_content`, decField);
528
+ if (m.audio && typeof m.audio === "object") {
529
+ const a = { ...m.audio };
530
+ await openStr(a, "data", `choices.${i}.message.audio.data`, decField);
531
+ m.audio = a;
532
+ }
533
+ out.message = m;
534
+ } else {
535
+ await openStr(out, "text", `choices.${i}.text`, decField);
536
+ }
537
+ return out;
538
+ })
539
+ );
540
+ }
541
+ if (Array.isArray(response.data)) {
542
+ body.data = await Promise.all(
543
+ response.data.map(async (d, pos) => {
544
+ const i = indexOf(d, pos);
545
+ const out = { ...d };
546
+ if (typeof out.embedding === "string") {
547
+ out.embedding = JSON.parse(await decField(out.embedding, `data.${i}.embedding`));
548
+ }
549
+ return out;
550
+ })
551
+ );
552
+ }
553
+ return body;
554
+ },
555
+ async openChunk(chunk) {
556
+ const decField = responseDecryptor(client2.privateKey, sent, textFrom(chunk.id));
557
+ const body = { ...chunk };
558
+ if (Array.isArray(chunk.choices)) {
559
+ body.choices = await Promise.all(
560
+ chunk.choices.map(async (c, pos) => {
561
+ const i = indexOf(c, pos);
562
+ const out = { ...c };
563
+ if (out.delta && typeof out.delta === "object") {
564
+ const d = { ...out.delta };
565
+ await openStr(d, "content", `choices.${i}.delta.content`, decField);
566
+ await openStr(d, "reasoning_content", `choices.${i}.delta.reasoning_content`, decField);
567
+ out.delta = d;
568
+ } else {
569
+ await openStr(out, "text", `choices.${i}.text`, decField);
570
+ }
571
+ return out;
572
+ })
573
+ );
574
+ }
575
+ return body;
576
+ }
577
+ };
578
+ }
579
+ __name(openE2eeChannel, "openE2eeChannel");
580
+ function responseDecryptor(clientPriv, sent, id) {
581
+ if (!sent) throw new Error("open: call seal first");
582
+ const { model, nonce, ts } = sent;
583
+ return async (blobHex, field) => dec.decode(await openField(clientPriv, blobHex, responseAad({ algo: ALGO, model, id, field, nonce, ts })));
584
+ }
585
+ __name(responseDecryptor, "responseDecryptor");
586
+ function indexOf(entry, position) {
587
+ return typeof entry.index === "number" ? entry.index : position;
588
+ }
589
+ __name(indexOf, "indexOf");
590
+ function textFrom(v) {
591
+ return typeof v === "string" ? v : "";
592
+ }
593
+ __name(textFrom, "textFrom");
594
+ async function openStr(obj, key, field, decField) {
595
+ if (typeof obj[key] === "string") obj[key] = await decField(obj[key], field);
596
+ }
597
+ __name(openStr, "openStr");
598
+ async function sealStringOrArray(value, name, encField) {
599
+ if (typeof value === "string") return encField(value, name);
600
+ if (Array.isArray(value)) {
601
+ return Promise.all(value.map((v, i) => typeof v === "string" ? encField(v, `${name}.${i}`) : v));
602
+ }
603
+ return value;
604
+ }
605
+ __name(sealStringOrArray, "sealStringOrArray");
606
+ var bs = /* @__PURE__ */ __name((u) => u, "bs");
607
+ async function aesKey(shared, usage) {
608
+ const hk = await subtle2.importKey("raw", bs(shared), "HKDF", false, ["deriveKey"]);
609
+ return subtle2.deriveKey(
610
+ { name: "HKDF", hash: "SHA-256", salt: bs(new Uint8Array(0)), info: bs(HKDF_INFO) },
611
+ hk,
612
+ { name: "AES-GCM", length: 256 },
613
+ false,
614
+ [usage]
615
+ );
616
+ }
617
+ __name(aesKey, "aesKey");
618
+ async function sealField(serviceRaw, plaintext, aad) {
619
+ const eph = await subtle2.generateKey({ name: "X25519" }, true, ["deriveBits"]);
620
+ const ephPub = new Uint8Array(await subtle2.exportKey("raw", eph.publicKey));
621
+ const service = await subtle2.importKey("raw", bs(serviceRaw), { name: "X25519" }, false, []);
622
+ const shared = new Uint8Array(await subtle2.deriveBits({ name: "X25519", public: service }, eph.privateKey, 256));
623
+ const iv = crypto.getRandomValues(new Uint8Array(12));
624
+ const ct = new Uint8Array(await subtle2.encrypt({ name: "AES-GCM", iv: bs(iv), additionalData: bs(aad) }, await aesKey(shared, "encrypt"), bs(plaintext)));
625
+ const blob = new Uint8Array(ephPub.length + iv.length + ct.length);
626
+ blob.set(ephPub);
627
+ blob.set(iv, ephPub.length);
628
+ blob.set(ct, ephPub.length + iv.length);
629
+ return toHex(blob);
630
+ }
631
+ __name(sealField, "sealField");
632
+ async function openField(clientPriv, blobHex, aad) {
633
+ const blob = fromHex(blobHex);
634
+ const ephPub = await subtle2.importKey("raw", bs(blob.slice(0, 32)), { name: "X25519" }, false, []);
635
+ const shared = new Uint8Array(await subtle2.deriveBits({ name: "X25519", public: ephPub }, clientPriv, 256));
636
+ const pt = await subtle2.decrypt({ name: "AES-GCM", iv: bs(blob.slice(32, 44)), additionalData: bs(aad) }, await aesKey(shared, "decrypt"), bs(blob.slice(44)));
637
+ return new Uint8Array(pt);
638
+ }
639
+ __name(openField, "openField");
640
+
641
+ // src/providers/phala/aci-verifier/report.ts?ext=privateer-gate
642
+ async function verifyReportBinding(report, nonce, options = {}) {
643
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
644
+ const checks = [];
645
+ const versionOk = report.api_version === "aci/1";
646
+ checks.push({
647
+ name: "api_version",
648
+ ok: versionOk,
649
+ ...versionOk ? {} : { detail: `api_version "${report.api_version}" is not "aci/1"` }
650
+ });
651
+ const keysetValue = report.attestation.workload_keyset;
652
+ if (keysetValue === null || typeof keysetValue !== "object" || Array.isArray(keysetValue)) {
653
+ const detail = "workload_keyset is not a JSON object";
654
+ for (const name of ["workload_keyset_digest", "report_data", "not_after"]) {
655
+ checks.push({ name, ok: false, detail });
656
+ }
657
+ return { ok: false, checks };
658
+ }
659
+ const digest = await computeKeysetDigest(keysetValue);
660
+ pushEqual(checks, "workload_keyset_digest", report.workload_keyset_digest, digest);
661
+ const expectedReportData = await computeReportData(digest, nonce);
662
+ pushEqual(checks, "report_data", report.attestation.report_data, expectedReportData);
663
+ const keyset = keysetValue;
664
+ if (typeof keyset.not_after !== "number") {
665
+ checks.push({
666
+ name: "not_after",
667
+ ok: false,
668
+ detail: "keyset has no numeric not_after"
669
+ });
670
+ } else {
671
+ const ok = now < keyset.not_after;
672
+ checks.push({
673
+ name: "not_after",
674
+ ok,
675
+ ...ok ? {} : { detail: `now ${now} >= not_after ${keyset.not_after}` }
676
+ });
677
+ }
678
+ return {
679
+ ok: checks.every((c) => c.ok),
680
+ checks,
681
+ workloadKeysetDigest: digest,
682
+ keyset
683
+ };
684
+ }
685
+ __name(verifyReportBinding, "verifyReportBinding");
686
+ function pushEqual(checks, name, actual, expected) {
687
+ const ok = actual === expected;
688
+ checks.push({ name, ok, ...ok ? {} : { detail: `report ${actual} != recomputed ${expected}` } });
689
+ }
690
+ __name(pushEqual, "pushEqual");
691
+
692
+ // src/providers/phala/measurements.ts?ext=privateer-gate
693
+ import { createHash } from "node:crypto";
694
+ var RTMR_KEYS = ["rtMr0", "rtMr1", "rtMr2", "rtMr3"];
695
+ function parseEventLog(raw) {
696
+ let value = raw;
697
+ if (typeof raw === "string") {
698
+ try {
699
+ value = JSON.parse(raw);
700
+ } catch {
701
+ return [];
702
+ }
703
+ }
704
+ if (!Array.isArray(value)) return [];
705
+ return value.filter(
706
+ (e) => !!e && typeof e === "object" && typeof e.imr === "number" && typeof e.digest === "string"
707
+ );
708
+ }
709
+ __name(parseEventLog, "parseEventLog");
710
+ function replayRtmrs(events) {
711
+ const out = {};
712
+ RTMR_KEYS.forEach((key, imr) => {
713
+ let acc = Buffer.alloc(48);
714
+ for (const ev of events) {
715
+ if (ev.imr !== imr) continue;
716
+ let digest;
717
+ try {
718
+ digest = Buffer.from(ev.digest, "hex");
719
+ } catch {
720
+ continue;
721
+ }
722
+ acc = createHash("sha384").update(Buffer.concat([acc, digest])).digest();
723
+ }
724
+ out[key] = acc.toString("hex");
725
+ });
726
+ return out;
727
+ }
728
+ __name(replayRtmrs, "replayRtmrs");
729
+ function appIdentityFrom(events) {
730
+ const byName = /* @__PURE__ */ new Map();
731
+ for (const ev of events) {
732
+ if (ev.imr === 3 && ev.event && typeof ev.event_payload === "string" && ev.event_payload) {
733
+ byName.set(ev.event, ev.event_payload);
734
+ }
735
+ }
736
+ return {
737
+ appId: byName.get("app-id"),
738
+ composeHash: byName.get("compose-hash"),
739
+ osImageHash: byName.get("os-image-hash"),
740
+ instanceId: byName.get("instance-id"),
741
+ mrKms: byName.get("mr-kms"),
742
+ keyProvider: byName.get("key-provider")
743
+ };
744
+ }
745
+ __name(appIdentityFrom, "appIdentityFrom");
746
+ function computeComposeHash(appCompose) {
747
+ if (typeof appCompose !== "string" || !appCompose) return void 0;
748
+ return createHash("sha256").update(Buffer.from(appCompose, "utf8")).digest("hex");
749
+ }
750
+ __name(computeComposeHash, "computeComposeHash");
751
+ function checkReportConsistency(args) {
752
+ const checks = [];
753
+ const skipped = [];
754
+ if (args.events.length === 0) {
755
+ skipped.push("rtmr-replay");
756
+ } else {
757
+ const replayed = replayRtmrs(args.events);
758
+ for (const key of RTMR_KEYS) {
759
+ const ok = replayed[key] === args.quoted[key];
760
+ checks.push({
761
+ name: `rtmr-replay:${key}`,
762
+ ok,
763
+ detail: ok ? void 0 : `replayed ${replayed[key].slice(0, 16)}\u2026 but the quote signed ${args.quoted[key].slice(0, 16)}\u2026`
764
+ });
765
+ }
766
+ }
767
+ const computed = computeComposeHash(args.appCompose);
768
+ if (!computed || !args.identity.composeHash) {
769
+ skipped.push("compose-hash");
770
+ } else {
771
+ const ok = computed === args.identity.composeHash;
772
+ checks.push({
773
+ name: "compose-hash",
774
+ ok,
775
+ detail: ok ? void 0 : `sha256(app_compose)=${computed.slice(0, 16)}\u2026 but the log attests ${args.identity.composeHash.slice(0, 16)}\u2026`
776
+ });
777
+ }
778
+ return { checks, ok: checks.every((c) => c.ok), skipped };
779
+ }
780
+ __name(checkReportConsistency, "checkReportConsistency");
781
+
782
+ // src/providers/phala/pin.ts?ext=privateer-gate
783
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
784
+ import { join as join3 } from "node:path";
785
+ var PIN_VERSION = 1;
786
+ function pinPath() {
787
+ return join3(globalDir(), "phala-enclave-pin.json");
788
+ }
789
+ __name(pinPath, "pinPath");
790
+ function readPin() {
791
+ try {
792
+ const parsed = JSON.parse(readFileSync2(pinPath(), "utf8"));
793
+ if (parsed?.v !== PIN_VERSION || !parsed.pin || typeof parsed.pin !== "object") return null;
794
+ return parsed;
795
+ } catch {
796
+ return null;
797
+ }
798
+ }
799
+ __name(readPin, "readPin");
800
+ function writePin(pin, firstSeenAt) {
801
+ try {
802
+ mkdirSync2(globalDir(), { recursive: true });
803
+ writeFileSync2(pinPath(), JSON.stringify({ v: PIN_VERSION, firstSeenAt, pin }, null, 2) + "\n", "utf8");
804
+ } catch {
805
+ }
806
+ }
807
+ __name(writePin, "writePin");
808
+ function checkPin(current, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
809
+ const stored = readPin();
810
+ if (!stored?.pin) {
811
+ const firstSeenAt = now();
812
+ writePin(current, firstSeenAt);
813
+ return { state: "first-seen", changed: [], firstSeenAt };
814
+ }
815
+ const changed = [];
816
+ for (const key of Object.keys(current)) {
817
+ const was = stored.pin[key];
818
+ const is = current[key];
819
+ if (was && is && was !== is) changed.push(`${key}: ${was.slice(0, 12)}\u2026 \u2192 ${is.slice(0, 12)}\u2026`);
820
+ }
821
+ if (changed.length === 0) return { state: "unchanged", changed: [], firstSeenAt: stored.firstSeenAt };
822
+ writePin(current, stored.firstSeenAt ?? now());
823
+ return { state: "changed", changed, firstSeenAt: stored.firstSeenAt };
824
+ }
825
+ __name(checkPin, "checkPin");
826
+
827
+ // src/providers/phalaSeal.ts?ext=privateer-gate
828
+ var DEFAULT_ACCEPTABLE_TCB = ["UpToDate"];
829
+ function relayBase() {
830
+ return `${serverBaseUrl().replace(/\/+$/, "")}/api/sealed/phala`;
831
+ }
832
+ __name(relayBase, "relayBase");
833
+ function requireQuote() {
834
+ const v = process.env.PRIVATEER_PHALA_REQUIRE_QUOTE;
835
+ return !(v === "0" || v === "false");
836
+ }
837
+ __name(requireQuote, "requireQuote");
838
+ function pccsUrl() {
839
+ return process.env.PRIVATEER_PHALA_PCCS_URL || void 0;
840
+ }
841
+ __name(pccsUrl, "pccsUrl");
842
+ function acceptableTcb() {
843
+ const v = process.env.PRIVATEER_PHALA_TCB;
844
+ const list = v ? v.split(",").map((s) => s.trim()).filter(Boolean) : DEFAULT_ACCEPTABLE_TCB;
845
+ return new Set(list);
846
+ }
847
+ __name(acceptableTcb, "acceptableTcb");
848
+ function extractQuoteMeasurements(report) {
849
+ const td = report.asTd10?.() ?? report.asTd15?.()?.base;
850
+ const fields = [td?.mrTd, td?.rtMr0, td?.rtMr1, td?.rtMr2, td?.rtMr3];
851
+ if (fields.some((f) => !f?.length)) return void 0;
852
+ const [mrTd, rtMr0, rtMr1, rtMr2, rtMr3] = fields.map((f) => toHex(new Uint8Array(f)));
853
+ return { mrTd, rtMr0, rtMr1, rtMr2, rtMr3 };
854
+ }
855
+ __name(extractQuoteMeasurements, "extractQuoteMeasurements");
856
+ function extractQuoteReportData(report) {
857
+ const td10 = report.asTd10?.();
858
+ if (td10?.reportData) return new Uint8Array(td10.reportData);
859
+ const td15 = report.asTd15?.();
860
+ if (td15?.base?.reportData) return new Uint8Array(td15.base.reportData);
861
+ const data = report.data;
862
+ if (data?.reportData) return new Uint8Array(data.reportData);
863
+ throw new Error("phala: verified quote report has no reportData");
864
+ }
865
+ __name(extractQuoteReportData, "extractQuoteReportData");
866
+ var attestationPromise = null;
867
+ function attest() {
868
+ if (!attestationPromise) {
869
+ attestationPromise = establishAttestation().catch((err) => {
870
+ attestationPromise = null;
871
+ throw err;
872
+ });
873
+ }
874
+ return attestationPromise;
875
+ }
876
+ __name(attest, "attest");
877
+ async function establishAttestation() {
878
+ const nonce = toHex(globalThis.crypto.getRandomValues(new Uint8Array(32)));
879
+ const res = await fetch(`${relayBase()}/attestation?nonce=${nonce}`, { method: "GET" });
880
+ if (!res.ok) throw new Error(`phala attestation HTTP ${res.status}`);
881
+ const report = await res.json();
882
+ const verification = await verifyReportBinding(report, nonce);
883
+ if (!verification.ok) {
884
+ const failed = verification.checks.filter((c) => !c.ok).map((c) => c.name).join(", ");
885
+ throw new Error(`phala attestation binding failed: ${failed}`);
886
+ }
887
+ const enclave = await verifyHardwareQuote(report);
888
+ return { report, verification, enclave };
889
+ }
890
+ __name(establishAttestation, "establishAttestation");
891
+ async function verifyHardwareQuote(report) {
892
+ if (!requireQuote()) return void 0;
893
+ const attestation = report.attestation;
894
+ const teeType = String(attestation?.tee_type || "");
895
+ if (teeType !== "tdx") throw new Error(`phala: unsupported/absent tee_type "${teeType}" (only tdx is wired)`);
896
+ const quoteHex = attestation.evidence?.quote;
897
+ if (typeof quoteHex !== "string" || !quoteHex) throw new Error("phala: attestation evidence has no TDX quote");
898
+ const reportDataHex = String(attestation.report_data || "").toLowerCase();
899
+ if (!reportDataHex) throw new Error("phala: report has no report_data");
900
+ const { getCollateralAndVerify } = await import("./c-77GO5L5Y.mjs");
901
+ const verified = await getCollateralAndVerify(fromHex(quoteHex), pccsUrl());
902
+ const status = String(verified.status);
903
+ if (!acceptableTcb().has(status)) throw new Error(`phala: TDX quote TCB status not accepted: "${status}"`);
904
+ const quoteReportData = extractQuoteReportData(verified.report);
905
+ if (toHex(quoteReportData.slice(0, 32)) !== reportDataHex) {
906
+ throw new Error("phala: TDX quote report_data does not bind the attested report_data");
907
+ }
908
+ const declared = attestation.evidence?.quote_report_data;
909
+ if (typeof declared === "string" && declared && toHex(quoteReportData) !== declared.toLowerCase()) {
910
+ throw new Error("phala: evidence.quote_report_data does not match the verified quote");
911
+ }
912
+ const measurements = extractQuoteMeasurements(verified.report);
913
+ if (!measurements) return void 0;
914
+ const events = parseEventLog(attestation.evidence?.event_log);
915
+ const identity = appIdentityFrom(events);
916
+ const consistency = checkReportConsistency({
917
+ events,
918
+ quoted: measurements,
919
+ appCompose: attestation.evidence?.app_compose,
920
+ identity
921
+ });
922
+ if (!consistency.ok) {
923
+ const failed = consistency.checks.filter((c) => !c.ok).map((c) => `${c.name} (${c.detail})`).join("; ");
924
+ throw new Error(`phala: attestation self-consistency failed: ${failed}`);
925
+ }
926
+ const pin = checkPin({
927
+ mrTd: measurements.mrTd,
928
+ rtMr0: measurements.rtMr0,
929
+ rtMr1: measurements.rtMr1,
930
+ rtMr2: measurements.rtMr2,
931
+ appId: identity.appId,
932
+ composeHash: identity.composeHash,
933
+ osImageHash: identity.osImageHash,
934
+ mrKms: identity.mrKms
935
+ });
936
+ const provenance = attestation.source_provenance;
937
+ const downstream = attestation.evidence?.downstream_tls_binding;
938
+ return {
939
+ measurements,
940
+ identity,
941
+ pin,
942
+ skippedChecks: consistency.skipped,
943
+ // Named so a human can go read the source; self-declared by the enclave and NOT
944
+ // proven by the quote (image_digest/image_provenance come back null), so it is a
945
+ // pointer to audit, never evidence that this binary came from that commit.
946
+ repoUrl: provenance?.repo_url,
947
+ repoCommit: provenance?.repo_commit,
948
+ // The attested enclave forwards to this downstream host over TLS. Surfaced because
949
+ // it marks where our attested boundary ENDS — that host is a separate trust domain
950
+ // we do not attest.
951
+ downstreamDomain: downstream?.domain,
952
+ downstreamSpkiSha256: downstream?.spki_sha256
953
+ };
954
+ }
955
+ __name(verifyHardwareQuote, "verifyHardwareQuote");
956
+ async function attestPhala() {
957
+ try {
958
+ const { enclave } = await attest();
959
+ return { ok: true, enclaveIdentity: enclave };
960
+ } catch (e) {
961
+ return { ok: false, error: e.message };
962
+ }
963
+ }
964
+ __name(attestPhala, "attestPhala");
965
+ async function phalaSealedFetch(rawBody, authHeader, signal) {
966
+ const { report, verification } = await attest();
967
+ const channel = await openE2eeChannel(report, verification);
968
+ let request;
969
+ try {
970
+ request = JSON.parse(rawBody);
971
+ } catch {
972
+ throw new Error("phala: request body is not JSON");
973
+ }
974
+ const fullModel = typeof request.model === "string" ? request.model : "unknown";
975
+ const streaming = request.stream !== false;
976
+ request.model = fullModel.replace(/^phala\//, "");
977
+ request.stream = streaming;
978
+ const { body, headers: e2ee } = await channel.seal(request);
979
+ const headers = {
980
+ "Content-Type": "application/json",
981
+ "X-Sealed-Model": fullModel,
982
+ ...e2ee
983
+ };
984
+ if (authHeader) headers.Authorization = authHeader;
985
+ const res = await fetch(`${relayBase()}/v1/chat/completions`, {
986
+ method: "POST",
987
+ headers,
988
+ body: JSON.stringify(body),
989
+ signal
990
+ });
991
+ return { res, channel, streaming };
992
+ }
993
+ __name(phalaSealedFetch, "phalaSealedFetch");
994
+
995
+ // src/providers/phala/sse.ts?ext=privateer-gate
996
+ async function* iterateSSE(body) {
997
+ const reader = body.getReader();
998
+ const decoder = new TextDecoder();
999
+ let buf = "";
1000
+ try {
1001
+ while (true) {
1002
+ const { done, value } = await reader.read();
1003
+ if (done) break;
1004
+ buf += decoder.decode(value, { stream: true });
1005
+ const lines = buf.split("\n");
1006
+ buf = lines.pop() ?? "";
1007
+ for (const obj of parseDataLines(lines)) yield obj;
1008
+ }
1009
+ if (buf.trim()) for (const obj of parseDataLines([buf])) yield obj;
1010
+ } finally {
1011
+ try {
1012
+ await reader.cancel();
1013
+ } catch {
1014
+ }
1015
+ }
1016
+ }
1017
+ __name(iterateSSE, "iterateSSE");
1018
+ function parseDataLines(lines) {
1019
+ const out = [];
1020
+ for (const line of lines) {
1021
+ if (!line.startsWith("data:")) continue;
1022
+ const t = line.slice(5).trim();
1023
+ if (!t || t === "[DONE]") continue;
1024
+ try {
1025
+ out.push(JSON.parse(t));
1026
+ } catch {
1027
+ }
1028
+ }
1029
+ return out;
1030
+ }
1031
+ __name(parseDataLines, "parseDataLines");
1032
+
1033
+ // src/providers/sealedShim.ts?ext=privateer-gate
1034
+ function sealedEnabled() {
1035
+ const v = process.env.PRIVATEER_SEALED;
1036
+ return !(v === "0" || v === "false");
1037
+ }
1038
+ __name(sealedEnabled, "sealedEnabled");
1039
+ function sealedProviderFor(modelId) {
1040
+ if (modelId.startsWith("tinfoil/")) return "tinfoil";
1041
+ if (modelId.startsWith("phala/")) return "phala";
1042
+ return null;
1043
+ }
1044
+ __name(sealedProviderFor, "sealedProviderFor");
1045
+ function relayBase2(provider) {
1046
+ return `${serverBaseUrl().replace(/\/+$/, "")}/api/sealed/${provider}`;
1047
+ }
1048
+ __name(relayBase2, "relayBase");
1049
+ var clients = /* @__PURE__ */ new Map();
1050
+ var readyPromises = /* @__PURE__ */ new Map();
1051
+ function client(provider) {
1052
+ let c = clients.get(provider);
1053
+ if (!c) {
1054
+ const base = relayBase2(provider);
1055
+ c = new SecureClient({ baseURL: base, attestationBundleURL: base, transport: "ehbp" });
1056
+ clients.set(provider, c);
1057
+ }
1058
+ return c;
1059
+ }
1060
+ __name(client, "client");
1061
+ function ready(provider) {
1062
+ let p = readyPromises.get(provider);
1063
+ if (!p) {
1064
+ p = client(provider).ready().catch((err) => {
1065
+ readyPromises.delete(provider);
1066
+ throw err;
1067
+ });
1068
+ readyPromises.set(provider, p);
1069
+ }
1070
+ return p;
1071
+ }
1072
+ __name(ready, "ready");
1073
+ async function attestSealed(provider) {
1074
+ if (provider === "phala") return attestPhala();
1075
+ try {
1076
+ await ready(provider);
1077
+ return { ok: true, enclave: client(provider).getEnclaveURL() };
1078
+ } catch (e) {
1079
+ return { ok: false, error: e.message };
1080
+ }
1081
+ }
1082
+ __name(attestSealed, "attestSealed");
1083
+ function buildForward(provider, rawBody, authHeader) {
1084
+ let body = rawBody;
1085
+ let sealedModel = "unknown";
1086
+ try {
1087
+ const parsed = JSON.parse(rawBody);
1088
+ if (parsed && typeof parsed.model === "string") {
1089
+ sealedModel = parsed.model;
1090
+ const prefix = `${provider}/`;
1091
+ if (parsed.model.startsWith(prefix)) parsed.model = parsed.model.slice(prefix.length);
1092
+ body = JSON.stringify(parsed);
1093
+ }
1094
+ } catch {
1095
+ }
1096
+ const headers = {
1097
+ // MUST carry the charset. EHBP seals the body but headers travel in cleartext
1098
+ // (tinfoil/dist/encrypted-body-fetch.js: "EHBP only seals the body"), so this
1099
+ // Content-Type is what the enclave's router actually validates — and it rejects a
1100
+ // bare `application/json` with "Unsupported Media Type: Only 'application/json' is
1101
+ // allowed" (verified live 2026-07-31: bare → 400, any `charset=` variant → 200).
1102
+ "Content-Type": "application/json; charset=utf-8",
1103
+ "X-Sealed-Model": sealedModel
1104
+ };
1105
+ if (authHeader) headers.Authorization = authHeader;
1106
+ return { url: `${relayBase2(provider)}/v1/chat/completions`, headers, body, sealedModel };
1107
+ }
1108
+ __name(buildForward, "buildForward");
1109
+ var PATH_RE = /^\/(tinfoil|phala)\/v1\/chat\/completions$/;
1110
+ var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "::ffff:127.0.0.1"]);
1111
+ var shimBase = null;
1112
+ var shimStarting = null;
1113
+ var shimServer = null;
1114
+ function sealedShimBase() {
1115
+ return shimBase;
1116
+ }
1117
+ __name(sealedShimBase, "sealedShimBase");
1118
+ function ensureSealedShim() {
1119
+ if (shimBase) return Promise.resolve(shimBase);
1120
+ if (!shimStarting) shimStarting = startShim();
1121
+ return shimStarting;
1122
+ }
1123
+ __name(ensureSealedShim, "ensureSealedShim");
1124
+ function stopSealedShim() {
1125
+ const server = shimServer;
1126
+ shimServer = null;
1127
+ shimBase = null;
1128
+ shimStarting = null;
1129
+ if (!server) return Promise.resolve();
1130
+ return new Promise((resolve) => server.close(() => resolve()));
1131
+ }
1132
+ __name(stopSealedShim, "stopSealedShim");
1133
+ function startShim() {
1134
+ return new Promise((resolve, reject) => {
1135
+ const server = http.createServer((req, res) => {
1136
+ void handle(req, res).catch((e) => {
1137
+ if (!res.headersSent) res.writeHead(502, { "Content-Type": "application/json" });
1138
+ res.end(JSON.stringify({ error: { message: `sealed shim: ${e.message}` } }));
1139
+ });
1140
+ });
1141
+ server.on("error", reject);
1142
+ server.listen(0, "127.0.0.1", () => {
1143
+ const addr = server.address();
1144
+ if (addr && typeof addr === "object") {
1145
+ shimServer = server;
1146
+ shimBase = `http://127.0.0.1:${addr.port}`;
1147
+ resolve(shimBase);
1148
+ } else {
1149
+ reject(new Error("sealed shim: could not determine listen port"));
1150
+ }
1151
+ });
1152
+ server.unref();
1153
+ });
1154
+ }
1155
+ __name(startShim, "startShim");
1156
+ async function handle(req, res) {
1157
+ if (!LOOPBACK.has(req.socket.remoteAddress ?? "")) {
1158
+ res.writeHead(403).end();
1159
+ return;
1160
+ }
1161
+ const path = (req.url ?? "").split("?")[0];
1162
+ const m = PATH_RE.exec(path);
1163
+ if (req.method !== "POST" || !m) {
1164
+ res.writeHead(404).end();
1165
+ return;
1166
+ }
1167
+ const provider = m[1];
1168
+ const raw = await readBody(req);
1169
+ const authHeader = typeof req.headers["authorization"] === "string" ? req.headers["authorization"] : void 0;
1170
+ if (provider === "phala") {
1171
+ await handlePhala(raw.toString("utf8"), authHeader, res);
1172
+ return;
1173
+ }
1174
+ const plan = buildForward(provider, raw.toString("utf8"), authHeader);
1175
+ await ready(provider);
1176
+ const upstream = await client(provider).fetch(plan.url, {
1177
+ method: "POST",
1178
+ headers: plan.headers,
1179
+ body: plan.body
1180
+ });
1181
+ res.writeHead(upstream.status, {
1182
+ "Content-Type": upstream.headers.get("content-type") ?? "application/json"
1183
+ });
1184
+ if (upstream.body) {
1185
+ const nodeStream = Readable.fromWeb(upstream.body);
1186
+ res.on("close", () => nodeStream.destroy());
1187
+ nodeStream.pipe(res);
1188
+ nodeStream.on("error", () => {
1189
+ if (!res.writableEnded) res.end();
1190
+ });
1191
+ } else {
1192
+ res.end(await upstream.text());
1193
+ }
1194
+ }
1195
+ __name(handle, "handle");
1196
+ async function handlePhala(rawBody, authHeader, res) {
1197
+ const ac = new AbortController();
1198
+ res.on("close", () => ac.abort());
1199
+ const { res: up, channel, streaming } = await phalaSealedFetch(rawBody, authHeader, ac.signal);
1200
+ if (!up.ok) {
1201
+ res.writeHead(up.status, { "Content-Type": up.headers.get("content-type") ?? "application/json" });
1202
+ res.end(await up.text());
1203
+ return;
1204
+ }
1205
+ if (streaming && up.body) {
1206
+ res.writeHead(up.status, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform" });
1207
+ for await (const obj of iterateSSE(up.body)) {
1208
+ const opened2 = await channel.openChunk(obj);
1209
+ res.write(`data: ${JSON.stringify(opened2)}
1210
+
1211
+ `);
1212
+ }
1213
+ res.write("data: [DONE]\n\n");
1214
+ res.end();
1215
+ return;
1216
+ }
1217
+ const opened = await channel.open(await up.json());
1218
+ res.writeHead(up.status, { "Content-Type": "application/json" });
1219
+ res.end(JSON.stringify(opened));
1220
+ }
1221
+ __name(handlePhala, "handlePhala");
1222
+ function readBody(req) {
1223
+ return new Promise((resolve, reject) => {
1224
+ const chunks = [];
1225
+ req.on("data", (c) => chunks.push(c));
1226
+ req.on("end", () => resolve(Buffer.concat(chunks)));
1227
+ req.on("error", reject);
1228
+ });
1229
+ }
1230
+ __name(readBody, "readBody");
1231
+
1232
+ // src/providers/account.ts?ext=privateer-gate
1233
+ var DEFAULT_MODELS = [
1234
+ ACCOUNT_DEFAULT_MODEL_ID,
1235
+ ACCOUNT_NEAR_MODEL_ID,
1236
+ // All three former defaults (see TINFOIL_MODEL_ID for the dates). They stay in the
1237
+ // floor so a user who saved any of them as their own default still resolves it
1238
+ // synchronously at launch, rather than falling through to "first model with
1239
+ // configured auth" — the BYO dead end this seed list exists to prevent.
1240
+ "tinfoil/gpt-oss-120b",
1241
+ "tinfoil/kimi-k2-6",
1242
+ "tinfoil/glm-5-2",
1243
+ "anthropic/claude-opus-5",
1244
+ "anthropic/claude-sonnet-5",
1245
+ "openai/gpt-5.6-sol",
1246
+ "deepseek/deepseek-v4-flash",
1247
+ // 2026-08 releases, confirmed live on OpenRouter (ZDR-covered ids reach the
1248
+ // account catalog automatically; these seeds just make them resolve at launch).
1249
+ "moonshotai/kimi-k3",
1250
+ "z-ai/glm-5.2",
1251
+ // Both verified 2026-08-19 against /endpoints/zdr and GET /api/models: ZDR-covered
1252
+ // and servable. Same reason as the line above — the live catalog already carries
1253
+ // them, this only closes the first-launch window where a saved default that isn't
1254
+ // yet in the cache would fall through to a BYO provider.
1255
+ "x-ai/grok-4.6",
1256
+ "openai/gpt-5.6-luna"
1257
+ ];
1258
+ var DEFAULT_CONTEXT_WINDOW = 128e3;
1259
+ var MIN_PUBLISHED_CONTEXT_WINDOW = 8192;
1260
+ var MAX_PUBLISHED_CONTEXT_WINDOW = 1e7;
1261
+ function validContextWindow(value) {
1262
+ return typeof value === "number" && Number.isFinite(value) && value >= MIN_PUBLISHED_CONTEXT_WINDOW && value <= MAX_PUBLISHED_CONTEXT_WINDOW ? Math.floor(value) : void 0;
1263
+ }
1264
+ __name(validContextWindow, "validContextWindow");
1265
+ var DEFAULT_MAX_OUTPUT_TOKENS = 16384;
1266
+ var ACCOUNT_MAX_OUTPUT_TOKENS = 32e3;
1267
+ var MIN_PUBLISHED_MAX_OUTPUT = 1024;
1268
+ function validMaxOutputTokens(value) {
1269
+ return typeof value === "number" && Number.isFinite(value) && value >= MIN_PUBLISHED_MAX_OUTPUT ? Math.min(Math.floor(value), ACCOUNT_MAX_OUTPUT_TOKENS) : void 0;
1270
+ }
1271
+ __name(validMaxOutputTokens, "validMaxOutputTokens");
1272
+ function seedModel(id) {
1273
+ return {
1274
+ id,
1275
+ name: id,
1276
+ // reasoning + how to steer it, for the enclave models where we verified the
1277
+ // control shape live; `reasoning: false` (Pi's "not a thinking model") for the rest.
1278
+ ...thinkingProfile(id) ?? { reasoning: false },
1279
+ // Honest modalities. This was hardcoded to text for the whole catalog, which made
1280
+ // Pi strip every image from every account request — including the ones `read`
1281
+ // attaches when the user points at a screenshot. See providers/vision.ts.
1282
+ input: visionInput(id),
1283
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1284
+ // The model's real window when the server published one, else the fallback.
1285
+ // This is not cosmetic: pi sizes each turn's answer budget as
1286
+ // (contextWindow − estimated prompt − 4096) and clamps max_tokens to it
1287
+ // (pi-ai clampMaxTokensToContext, and see src/engine/contextBudget.ts). A window
1288
+ // that under-states the model's real one therefore throttles the answer — and
1289
+ // used to collapse it to a single token — while the model still has room.
1290
+ contextWindow: accountContextWindow(id) ?? DEFAULT_CONTEXT_WINDOW,
1291
+ // The model's real output cap when published. Not cosmetic either: reasoning and
1292
+ // the answer share this one budget, and pi only compact-and-retries a length stop
1293
+ // that ended BELOW it — a thinking model that spends all of a too-small budget
1294
+ // reasoning ends the run with "Response was truncated before completion."
1295
+ // (measured: glm-5.3-flash spending all 16384 on 50k+ chars of reasoning, whose
1296
+ // real cap is far higher). See also ext/truncationRecovery.ts.
1297
+ maxTokens: accountMaxOutputTokens(id) ?? DEFAULT_MAX_OUTPUT_TOKENS
1298
+ };
1299
+ }
1300
+ __name(seedModel, "seedModel");
1301
+ var CHAT_TEMPLATE_THINKING = {
1302
+ reasoning: true,
1303
+ thinkingLevelMap: { minimal: null, low: null, high: null, xhigh: null },
1304
+ compat: { thinkingFormat: "qwen-chat-template" }
1305
+ };
1306
+ var REASONING_EFFORT_THINKING = {
1307
+ reasoning: true,
1308
+ thinkingLevelMap: { off: "low", minimal: "low", xhigh: null }
1309
+ };
1310
+ var OPENROUTER_THINKING = {
1311
+ reasoning: true,
1312
+ thinkingLevelMap: { off: "low", minimal: "low" },
1313
+ compat: { thinkingFormat: "openrouter" }
1314
+ };
1315
+ var PROXIED_THINKING_MODEL = [
1316
+ /^google\/gemini-/i,
1317
+ // gemini-2.5+ all take an effort; gemma-* deliberately excluded
1318
+ /^z-ai\/glm-/i,
1319
+ // the whole GLM line reasons, vision variants included
1320
+ /^deepseek\/deepseek-(r1|v3\.[12]|v4)/i,
1321
+ // NOT deepseek-chat*, which is the non-thinking split
1322
+ /^x-ai\/grok-4/i,
1323
+ // grok-4.x; grok-build-0.1 is unprobed
1324
+ /^openai\/gpt-(5|6|oss)/i
1325
+ // gpt-4*/gpt-3.5* do not reason
1326
+ ];
1327
+ var NON_THINKING_VARIANT = /(?:^|[-/])(?:chat|instruct)(?:[-.]|$)/i;
1328
+ var TEE_MODEL = /^(tinfoil|phala|near)\//;
1329
+ function teeThinkingProfile(id) {
1330
+ if (/instruct/i.test(id)) return null;
1331
+ if (/gpt-oss/i.test(id)) return REASONING_EFFORT_THINKING;
1332
+ if (/glm|qwen/i.test(id)) return CHAT_TEMPLATE_THINKING;
1333
+ return null;
1334
+ }
1335
+ __name(teeThinkingProfile, "teeThinkingProfile");
1336
+ function proxiedThinkingProfile(id) {
1337
+ if (NON_THINKING_VARIANT.test(id)) return null;
1338
+ return PROXIED_THINKING_MODEL.some((re) => re.test(id)) ? OPENROUTER_THINKING : null;
1339
+ }
1340
+ __name(proxiedThinkingProfile, "proxiedThinkingProfile");
1341
+ function thinkingProfile(id) {
1342
+ const profile = TEE_MODEL.test(id) ? teeThinkingProfile(id) : proxiedThinkingProfile(id);
1343
+ return profile && { ...profile, thinkingLevelMap: { ...profile.thinkingLevelMap }, ...profile.compat ? { compat: { ...profile.compat } } : {} };
1344
+ }
1345
+ __name(thinkingProfile, "thinkingProfile");
1346
+ var CATALOG_CACHE_MAX = 2e3;
1347
+ function catalogCachePath() {
1348
+ return join4(globalDir(), "account-models.json");
1349
+ }
1350
+ __name(catalogCachePath, "catalogCachePath");
1351
+ function saveCachedCatalog(infos) {
1352
+ try {
1353
+ mkdirSync3(globalDir(), { recursive: true });
1354
+ const kept = infos.slice(0, CATALOG_CACHE_MAX);
1355
+ const windows = {};
1356
+ const maxOutputs = {};
1357
+ for (const info of kept) {
1358
+ if (info.contextWindow !== void 0) windows[info.id] = info.contextWindow;
1359
+ if (info.maxOutputTokens !== void 0) maxOutputs[info.id] = info.maxOutputTokens;
1360
+ }
1361
+ const payload = {
1362
+ v: 2,
1363
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
1364
+ ids: kept.map((info) => info.id),
1365
+ windows,
1366
+ // Same reasoning as `windows`: a capability needed at launch. Absent in older
1367
+ // files, which then fall back per model.
1368
+ maxOutputs
1369
+ };
1370
+ writeFileSync3(catalogCachePath(), JSON.stringify(payload) + "\n", "utf8");
1371
+ forgetCachedContextWindows();
1372
+ } catch {
1373
+ }
1374
+ }
1375
+ __name(saveCachedCatalog, "saveCachedCatalog");
1376
+ var cachedCapabilities = null;
1377
+ function cachedCapabilityMaps() {
1378
+ if (cachedCapabilities) return cachedCapabilities;
1379
+ cachedCapabilities = { windows: /* @__PURE__ */ new Map(), maxOutputs: /* @__PURE__ */ new Map() };
1380
+ try {
1381
+ const path = catalogCachePath();
1382
+ if (existsSync2(path)) {
1383
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
1384
+ readCapability(parsed.windows, validContextWindow, cachedCapabilities.windows);
1385
+ readCapability(parsed.maxOutputs, validMaxOutputTokens, cachedCapabilities.maxOutputs);
1386
+ }
1387
+ } catch {
1388
+ }
1389
+ return cachedCapabilities;
1390
+ }
1391
+ __name(cachedCapabilityMaps, "cachedCapabilityMaps");
1392
+ function readCapability(raw, valid, into) {
1393
+ if (!raw || typeof raw !== "object") return;
1394
+ for (const [id, value] of Object.entries(raw)) {
1395
+ const n = valid(value);
1396
+ if (n !== void 0 && into.size < CATALOG_CACHE_MAX) into.set(id, n);
1397
+ }
1398
+ }
1399
+ __name(readCapability, "readCapability");
1400
+ function cachedContextWindows() {
1401
+ return cachedCapabilityMaps().windows;
1402
+ }
1403
+ __name(cachedContextWindows, "cachedContextWindows");
1404
+ function forgetCachedContextWindows() {
1405
+ cachedCapabilities = null;
1406
+ }
1407
+ __name(forgetCachedContextWindows, "forgetCachedContextWindows");
1408
+ function loadCachedCatalogIds() {
1409
+ try {
1410
+ const path = catalogCachePath();
1411
+ if (!existsSync2(path)) return [];
1412
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
1413
+ if (!Array.isArray(parsed.ids)) return [];
1414
+ return parsed.ids.filter((id) => typeof id === "string" && id.length > 0).slice(0, CATALOG_CACHE_MAX);
1415
+ } catch {
1416
+ return [];
1417
+ }
1418
+ }
1419
+ __name(loadCachedCatalogIds, "loadCachedCatalogIds");
1420
+ function isServableAccountModel(id) {
1421
+ if (!id.startsWith("phala/")) return true;
1422
+ return sealedEnabled() && sealedShimBase() !== null;
1423
+ }
1424
+ __name(isServableAccountModel, "isServableAccountModel");
1425
+ function seedCatalogIds() {
1426
+ const ids = [...DEFAULT_MODELS];
1427
+ const seen = new Set(ids);
1428
+ for (const id of loadCachedCatalogIds()) {
1429
+ if (!seen.has(id)) {
1430
+ seen.add(id);
1431
+ ids.push(id);
1432
+ }
1433
+ }
1434
+ return ids;
1435
+ }
1436
+ __name(seedCatalogIds, "seedCatalogIds");
1437
+ var VALID_TIERS = /* @__PURE__ */ new Set([
1438
+ "tee-verified",
1439
+ "tee-unverified",
1440
+ "local",
1441
+ "zdr-enforced",
1442
+ "zdr-policy",
1443
+ "standard"
1444
+ ]);
1445
+ function tierFromPrefix(modelId) {
1446
+ return modelId.startsWith("near/") || modelId.startsWith("tinfoil/") || modelId.startsWith("phala/") ? "tee-unverified" : "standard";
1447
+ }
1448
+ __name(tierFromPrefix, "tierFromPrefix");
1449
+ function normalizeTier(tier, modelId) {
1450
+ return tier && VALID_TIERS.has(tier) ? tier : tierFromPrefix(modelId);
1451
+ }
1452
+ __name(normalizeTier, "normalizeTier");
1453
+ var accountTierMap = /* @__PURE__ */ new Map();
1454
+ var accountWindowMap = /* @__PURE__ */ new Map();
1455
+ function accountContextWindow(id) {
1456
+ return accountWindowMap.get(id) ?? cachedContextWindows().get(id);
1457
+ }
1458
+ __name(accountContextWindow, "accountContextWindow");
1459
+ var accountMaxOutputMap = /* @__PURE__ */ new Map();
1460
+ function accountMaxOutputTokens(id) {
1461
+ return accountMaxOutputMap.get(id) ?? cachedCapabilityMaps().maxOutputs.get(id);
1462
+ }
1463
+ __name(accountMaxOutputTokens, "accountMaxOutputTokens");
1464
+ function accountBaselineTier(modelId) {
1465
+ return accountTierMap.get(modelId);
1466
+ }
1467
+ __name(accountBaselineTier, "accountBaselineTier");
1468
+ function accountCatalogLoaded() {
1469
+ return accountTierMap.size > 0;
1470
+ }
1471
+ __name(accountCatalogLoaded, "accountCatalogLoaded");
1472
+ async function fetchAccountCatalog() {
1473
+ const fallback = /* @__PURE__ */ __name(() => DEFAULT_MODELS.map((id) => ({ id, tier: tierFromPrefix(id) })), "fallback");
1474
+ let infos;
1475
+ try {
1476
+ const res = await fetch(`${serverBaseUrl()}/api/models`);
1477
+ if (!res.ok) {
1478
+ infos = fallback();
1479
+ } else {
1480
+ const data = await res.json();
1481
+ const parsed = (data.models ?? []).map(
1482
+ (m) => m.modelId ? {
1483
+ id: m.modelId,
1484
+ tier: normalizeTier(m.privacy?.tier, m.modelId),
1485
+ // `contextLength` is null for a model upstream has no window for, and
1486
+ // absent entirely on a server older than the field — both mean
1487
+ // "unknown", and validContextWindow collapses them to undefined.
1488
+ contextWindow: validContextWindow(m.contextLength),
1489
+ maxOutputTokens: validMaxOutputTokens(m.maxCompletionTokens)
1490
+ } : null
1491
+ ).filter((x) => !!x);
1492
+ if (parsed.length) saveCachedCatalog(parsed);
1493
+ infos = parsed.length ? parsed : fallback();
1494
+ }
1495
+ } catch {
1496
+ infos = fallback();
1497
+ }
1498
+ accountTierMap.clear();
1499
+ accountWindowMap.clear();
1500
+ accountMaxOutputMap.clear();
1501
+ for (const info of infos) {
1502
+ accountTierMap.set(info.id, info.tier);
1503
+ if (info.contextWindow !== void 0) accountWindowMap.set(info.id, info.contextWindow);
1504
+ if (info.maxOutputTokens !== void 0) accountMaxOutputMap.set(info.id, info.maxOutputTokens);
1505
+ }
1506
+ return infos;
1507
+ }
1508
+ __name(fetchAccountCatalog, "fetchAccountCatalog");
1509
+ async function fetchAccountModels() {
1510
+ return (await fetchAccountCatalog()).map((m) => m.id);
1511
+ }
1512
+ __name(fetchAccountModels, "fetchAccountModels");
1513
+ function verificationLink(raw) {
1514
+ const uri = (raw ?? "").trim();
1515
+ if (!uri) return "";
1516
+ if (/^https?:\/\//i.test(uri)) return uri;
1517
+ return `https://${uri.replace(/^\/+/, "")}`;
1518
+ }
1519
+ __name(verificationLink, "verificationLink");
1520
+ var privateerOAuthProvider = {
1521
+ name: "Privateer account",
1522
+ usesCallbackServer: false,
1523
+ // Pi's login dialog passes `signal` (its cancel AbortController) alongside the
1524
+ // callbacks. We MUST thread it into runDeviceLogin — otherwise escape/ctrl+c
1525
+ // aborts the dialog's signal but our poll loop never sees it, the login()
1526
+ // promise never settles, and Pi never restores the editor: the "Waiting for
1527
+ // authentication…" screen hangs with no way out. See auth/privateer.ts
1528
+ // pollForToken, which checks the signal and rejects with "Login cancelled.".
1529
+ async login(cb) {
1530
+ let wasLinked = hasCredentials();
1531
+ if (wasLinked && cb.onSelect) {
1532
+ const user = currentUser();
1533
+ const who = user?.email ?? user?.id ?? "this account";
1534
+ const choice = await cb.onSelect({
1535
+ message: `Already signed in as ${who}.`,
1536
+ options: [
1537
+ { id: "keep", label: `Stay signed in as ${who}` },
1538
+ { id: "switch", label: "Sign in as a different account (signs this machine out first)" }
1539
+ ]
1540
+ });
1541
+ if (choice === void 0) throw new Error("Login cancelled");
1542
+ if (choice === "switch") {
1543
+ await logout();
1544
+ wasLinked = false;
1545
+ }
1546
+ }
1547
+ if (!wasLinked) {
1548
+ try {
1549
+ await runDeviceLogin({
1550
+ signal: cb.signal,
1551
+ onCode: /* @__PURE__ */ __name((code) => {
1552
+ const uri = verificationLink(code.verification_uri_complete ?? code.verification_uri);
1553
+ if (uri && canOpenBrowser()) void openInBrowser(uri);
1554
+ cb.onDeviceCode?.({
1555
+ userCode: code.user_code,
1556
+ verificationUri: uri,
1557
+ intervalSeconds: code.interval,
1558
+ expiresInSeconds: code.expires_in
1559
+ });
1560
+ }, "onCode")
1561
+ });
1562
+ } catch (e) {
1563
+ if (cb.signal?.aborted) throw new Error("Login cancelled");
1564
+ throw e;
1565
+ }
1566
+ }
1567
+ if (cb.signal?.aborted) throw new Error("Login cancelled");
1568
+ const creds = await accountCredential();
1569
+ ensurePiDefaultModel();
1570
+ notifySignedIn();
1571
+ return creds;
1572
+ },
1573
+ async refreshToken(creds) {
1574
+ const mine = armSlot().cred?.refresh;
1575
+ const refresh = mine && creds.refresh !== mine ? mine : creds.refresh;
1576
+ let next;
1577
+ try {
1578
+ next = await refreshAccountCredentials(refresh);
1579
+ } catch {
1580
+ next = await acquireAccountCredential();
1581
+ }
1582
+ rememberAccountCredential(next);
1583
+ return next;
1584
+ },
1585
+ getApiKey(creds) {
1586
+ return creds.access;
1587
+ }
1588
+ };
1589
+ var TEE_PREFIXES = ["near/", "tinfoil/", "phala/"];
1590
+ function privateerChannel(modelId) {
1591
+ return TEE_PREFIXES.some((p) => modelId.startsWith(p)) ? "tee" : "zdr";
1592
+ }
1593
+ __name(privateerChannel, "privateerChannel");
1594
+ async function accountPosture(modelId) {
1595
+ if (privateerChannel(modelId) === "zdr") {
1596
+ return { tier: "zdr-policy" };
1597
+ }
1598
+ const sealedProvider = sealedEnabled() ? sealedProviderFor(modelId) : null;
1599
+ if (sealedProvider) {
1600
+ const att = await attestSealed(sealedProvider);
1601
+ return att.ok ? { tier: "tee-verified", enclaveIdentity: att.enclaveIdentity } : { tier: "tee-unverified", error: att.error };
1602
+ }
1603
+ if (!modelId.startsWith("near/")) {
1604
+ return { tier: "tee-unverified" };
1605
+ }
1606
+ try {
1607
+ const res = await authedFetch(
1608
+ `${serverBaseUrl()}/api/models/near/attestation?model=${encodeURIComponent(modelId)}`
1609
+ );
1610
+ if (!res.ok) return { tier: "tee-unverified", error: `HTTP ${res.status}` };
1611
+ const data = await res.json();
1612
+ const att = interpretReport(modelId, data.nonce ?? "", data.report ?? {});
1613
+ const tp = teePosture(att);
1614
+ const tier = tierFromTeePosture(tp);
1615
+ return { tier, teePosture: tp };
1616
+ } catch (e) {
1617
+ return { tier: "tee-unverified", error: e.message };
1618
+ }
1619
+ }
1620
+ __name(accountPosture, "accountPosture");
1621
+ function modelEntry(id) {
1622
+ const base = seedModel(id);
1623
+ const provider = sealedEnabled() ? sealedProviderFor(id) : null;
1624
+ const shim = sealedShimBase();
1625
+ return provider && shim ? { ...base, baseUrl: `${shim}/${provider}/v1` } : base;
1626
+ }
1627
+ __name(modelEntry, "modelEntry");
1628
+ function accountProviderConfig(ids) {
1629
+ return {
1630
+ name: "Privateer account",
1631
+ baseUrl: `${serverBaseUrl()}/api/agent/v1`,
1632
+ api: "openai-completions",
1633
+ oauth: privateerOAuthProvider,
1634
+ // Filter HERE rather than at the catalog, so callers keep passing the server's
1635
+ // full list and every registration re-evaluates servability against the CURRENT
1636
+ // shim state. That is what lets the post-shim re-registration in makeAccountProvider
1637
+ // put `phala/*` back: had the ids been filtered upstream, the sealed-only models
1638
+ // would have been dropped from `lastIds` before the shim ever finished starting and
1639
+ // nothing would have brought them back.
1640
+ models: ids.filter(isServableAccountModel).map(modelEntry)
1641
+ };
1642
+ }
1643
+ __name(accountProviderConfig, "accountProviderConfig");
1644
+ function registerAccountModels(pi) {
1645
+ pi.registerProvider?.("privateer", accountProviderConfig(seedCatalogIds()));
1646
+ }
1647
+ __name(registerAccountModels, "registerAccountModels");
1648
+ function makeAccountProvider(opts = {}) {
1649
+ return (pi) => {
1650
+ if (typeof pi.registerProvider !== "function") return;
1651
+ installGzipRequestBodies();
1652
+ let lastIds = seedCatalogIds();
1653
+ const register = /* @__PURE__ */ __name((ids) => {
1654
+ lastIds = ids;
1655
+ pi.registerProvider("privateer", accountProviderConfig(ids));
1656
+ }, "register");
1657
+ register(lastIds);
1658
+ if (sealedEnabled()) {
1659
+ void ensureSealedShim().then(() => register(lastIds)).catch(() => {
1660
+ });
1661
+ }
1662
+ void fetchAccountCatalog().then((infos) => infos.length && register(infos.map((m) => m.id))).catch(() => {
1663
+ });
1664
+ if (opts.armOnStart !== false) {
1665
+ pi.on?.("session_start", async (_e, ctx) => {
1666
+ const arming = armAccountCredential(ctx);
1667
+ if (!ctx?.hasUI) await arming;
1668
+ });
1669
+ }
1670
+ pi.on?.("before_agent_start", async (_e, ctx) => {
1671
+ try {
1672
+ await ensureAccountArmed(ctx);
1673
+ } catch {
1674
+ }
1675
+ });
1676
+ pi.on?.("message_end", (e, ctx) => {
1677
+ const msg = e?.message;
1678
+ if (msg?.role !== "assistant" || msg.stopReason !== "error" || !msg.errorMessage) return;
1679
+ if (msg.provider !== "privateer") return;
1680
+ const balance = hasCredentials() ? describeAccountBalanceError(msg.errorMessage) : null;
1681
+ if (balance) {
1682
+ return { message: { ...msg, errorMessage: `402 ${balance.message}
1683
+ ${balance.hint}` } };
1684
+ }
1685
+ void recoverAccountSession(ctx, msg.errorMessage);
1686
+ if (isConnectionFailureText(msg.errorMessage)) {
1687
+ void recoverAccountConnection(ctx, msg.model ?? "", () => register(lastIds));
1688
+ }
1689
+ });
1690
+ };
1691
+ }
1692
+ __name(makeAccountProvider, "makeAccountProvider");
1693
+ var ARMED = /* @__PURE__ */ Symbol.for("privateer.accountCredential");
1694
+ function armSlot() {
1695
+ const g = globalThis;
1696
+ return g[ARMED] ??= {};
1697
+ }
1698
+ __name(armSlot, "armSlot");
1699
+ function rememberAccountCredential(cred) {
1700
+ armSlot().cred = cred;
1701
+ }
1702
+ __name(rememberAccountCredential, "rememberAccountCredential");
1703
+ function ownedAccountCredential() {
1704
+ return armSlot().cred;
1705
+ }
1706
+ __name(ownedAccountCredential, "ownedAccountCredential");
1707
+ function liveAccountCredential() {
1708
+ const cred = armSlot().cred;
1709
+ return cred && cred.expires > Date.now() + 6e4 ? cred : void 0;
1710
+ }
1711
+ __name(liveAccountCredential, "liveAccountCredential");
1712
+ async function accountCredential() {
1713
+ const live = liveAccountCredential();
1714
+ if (live) return live;
1715
+ const slot = armSlot();
1716
+ slot.inFlight ??= acquireAccountCredential().then((cred) => {
1717
+ slot.cred = cred;
1718
+ return cred;
1719
+ }).finally(() => {
1720
+ slot.inFlight = void 0;
1721
+ });
1722
+ return slot.inFlight;
1723
+ }
1724
+ __name(accountCredential, "accountCredential");
1725
+ async function armAccountCredential(ctx, opts = {}) {
1726
+ if (!hasCredentials()) return false;
1727
+ try {
1728
+ const cred = await accountCredential();
1729
+ await (await piAuthStore()).modify("privateer", async () => ({ type: "oauth", ...cred }));
1730
+ return true;
1731
+ } catch (e) {
1732
+ const c = ctx;
1733
+ if (opts.notify !== false && hasCredentials()) {
1734
+ const message = `Privateer account channel unavailable \u2014 ${e.message}`;
1735
+ if (c?.hasUI) c.ui?.notify?.(message, "error");
1736
+ else console.error(message);
1737
+ }
1738
+ return false;
1739
+ }
1740
+ }
1741
+ __name(armAccountCredential, "armAccountCredential");
1742
+ async function persistAccountCredential(creds) {
1743
+ await (await piAuthStore()).modify("privateer", async () => ({ type: "oauth", ...creds }));
1744
+ }
1745
+ __name(persistAccountCredential, "persistAccountCredential");
1746
+ async function dropPersistedAccountCredential(opts = {}) {
1747
+ const store = await piAuthStore();
1748
+ const mine = ownedAccountCredential()?.access;
1749
+ armSlot().cred = void 0;
1750
+ try {
1751
+ if (!opts.force) {
1752
+ const persisted = await store.read("privateer");
1753
+ if (!persisted) return false;
1754
+ if (typeof persisted.access === "string" && persisted.access !== mine) return false;
1755
+ }
1756
+ await store.delete("privateer");
1757
+ return true;
1758
+ } catch {
1759
+ return false;
1760
+ }
1761
+ }
1762
+ __name(dropPersistedAccountCredential, "dropPersistedAccountCredential");
1763
+ async function dropPersistedAccountCredentialBeforeExit(timeoutMs = 2e3) {
1764
+ let timer;
1765
+ await Promise.race([
1766
+ dropPersistedAccountCredential().catch(() => false),
1767
+ new Promise((resolve) => {
1768
+ timer = setTimeout(resolve, timeoutMs);
1769
+ })
1770
+ ]);
1771
+ clearTimeout(timer);
1772
+ }
1773
+ __name(dropPersistedAccountCredentialBeforeExit, "dropPersistedAccountCredentialBeforeExit");
1774
+ async function ensureAccountArmed(ctx) {
1775
+ if (!hasCredentials()) return;
1776
+ try {
1777
+ if (await (await piAuthStore()).read("privateer")) return;
1778
+ } catch {
1779
+ return;
1780
+ }
1781
+ await armAccountCredential(ctx, { notify: false });
1782
+ }
1783
+ __name(ensureAccountArmed, "ensureAccountArmed");
1784
+ var ACCOUNT_AUTH_FAILURE = /\b401\b|SESSION_REVOKED|Authentication required|Invalid token|Failed to refresh OAuth token|No API key\b[^\n]*privateer/i;
1785
+ var RECOVERY_COOLDOWN_MS = 3e4;
1786
+ async function probeAccountToken(access) {
1787
+ try {
1788
+ const res = await fetch(`${serverBaseUrl()}/auth/me`, {
1789
+ headers: { Authorization: `Bearer ${access}` },
1790
+ signal: AbortSignal.timeout(8e3)
1791
+ });
1792
+ void res.body?.cancel().catch(() => {
1793
+ });
1794
+ return res.status;
1795
+ } catch {
1796
+ return null;
1797
+ }
1798
+ }
1799
+ __name(probeAccountToken, "probeAccountToken");
1800
+ async function recoverAccountConnection(ctx, modelId, reRegister, probe = probeAccountToken) {
1801
+ const renewed = { shim: false, session: false };
1802
+ if (!hasCredentials()) return renewed;
1803
+ const slot = armSlot();
1804
+ const now = Date.now();
1805
+ if (slot.connectionRecoveredAt !== void 0 && now - slot.connectionRecoveredAt < RECOVERY_COOLDOWN_MS) return renewed;
1806
+ slot.connectionRecoveredAt = now;
1807
+ if (sealedEnabled() && sealedProviderFor(modelId)) {
1808
+ try {
1809
+ await stopSealedShim();
1810
+ await ensureSealedShim();
1811
+ reRegister();
1812
+ renewed.shim = true;
1813
+ } catch {
1814
+ try {
1815
+ reRegister();
1816
+ } catch {
1817
+ }
1818
+ }
1819
+ }
1820
+ const held = slot.cred;
1821
+ const status = held ? await probe(held.access) : null;
1822
+ if (status === 401 || status === 403) {
1823
+ slot.cred = void 0;
1824
+ renewed.session = await armAccountCredential(ctx, { notify: false });
1825
+ }
1826
+ const c = ctx;
1827
+ if (c?.hasUI && (renewed.shim || renewed.session)) {
1828
+ c.ui?.notify?.(
1829
+ `Couldn't reach Privateer \u2014 renewed this session's ${[renewed.shim ? "sealed connection" : "", renewed.session ? "credential" : ""].filter(Boolean).join(" and ")}. Send that message again.`,
1830
+ "warning"
1831
+ );
1832
+ }
1833
+ return renewed;
1834
+ }
1835
+ __name(recoverAccountConnection, "recoverAccountConnection");
1836
+ async function recoverAccountSession(ctx, errorMessage) {
1837
+ if (!hasCredentials()) return false;
1838
+ if (!ACCOUNT_AUTH_FAILURE.test(errorMessage)) return false;
1839
+ const slot = armSlot();
1840
+ const now = Date.now();
1841
+ if (slot.recoveredAt !== void 0 && now - slot.recoveredAt < RECOVERY_COOLDOWN_MS) return false;
1842
+ slot.recoveredAt = now;
1843
+ slot.cred = void 0;
1844
+ const armed = await armAccountCredential(ctx, { notify: false });
1845
+ const c = ctx;
1846
+ if (c?.hasUI) {
1847
+ c.ui?.notify?.(
1848
+ armed ? "Your Privateer account session had expired \u2014 opened a new one. Send that message again." : "Your Privateer account session isn't valid any more and couldn't be renewed. Run /login to sign back in.",
1849
+ armed ? "warning" : "error"
1850
+ );
1851
+ }
1852
+ return armed;
1853
+ }
1854
+ __name(recoverAccountSession, "recoverAccountSession");
1855
+
1856
+ export {
1857
+ thinkingProfile,
1858
+ loadCachedCatalogIds,
1859
+ isServableAccountModel,
1860
+ seedCatalogIds,
1861
+ accountContextWindow,
1862
+ accountMaxOutputTokens,
1863
+ accountBaselineTier,
1864
+ accountCatalogLoaded,
1865
+ fetchAccountCatalog,
1866
+ fetchAccountModels,
1867
+ verificationLink,
1868
+ privateerOAuthProvider,
1869
+ privateerChannel,
1870
+ accountPosture,
1871
+ accountProviderConfig,
1872
+ registerAccountModels,
1873
+ makeAccountProvider,
1874
+ rememberAccountCredential,
1875
+ ownedAccountCredential,
1876
+ armAccountCredential,
1877
+ persistAccountCredential,
1878
+ dropPersistedAccountCredential,
1879
+ dropPersistedAccountCredentialBeforeExit,
1880
+ ensureAccountArmed,
1881
+ recoverAccountConnection,
1882
+ recoverAccountSession
1883
+ };