free-pi-cli 0.6.20 → 0.6.22

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.
@@ -515,7 +515,7 @@ function folderMatches(cwd, sessionCwd) {
515
515
  // package.json
516
516
  var package_default = {
517
517
  name: "free-pi-cli",
518
- version: "0.6.20",
518
+ version: "0.6.22",
519
519
  description: "Free AI coding agent for your terminal. Ads and a daily free allowance pay for the models; sign in with GitHub and run npx free-pi-cli.",
520
520
  keywords: [
521
521
  "coding agent",
@@ -557,7 +557,7 @@ var package_default = {
557
557
  "@earendil-works/pi-ai": "1.2.0",
558
558
  "@earendil-works/pi-coding-agent": "1.2.0",
559
559
  "@earendil-works/pi-tui": "1.2.0",
560
- "@gravity-ai/api": "1.2.1"
560
+ "@gravity-ai/api": "1.4.0"
561
561
  },
562
562
  devDependencies: {
563
563
  "@freepi/pi-ads": "workspace:*",
@@ -5822,6 +5822,8 @@ var AuthConsentRequestSchema = object({
5822
5822
  signup_src: string2().optional(),
5823
5823
  referral_code: string2().optional()
5824
5824
  });
5825
+ var AuthConsentResponseSchema = object({ ok: literal(true) });
5826
+ var MeConsentResponseSchema = object({ ok: literal(true) });
5825
5827
  var ConnectStartRequestSchema = object({
5826
5828
  consent_version: string2().min(1).max(32),
5827
5829
  signup_src: string2().optional(),
@@ -6021,11 +6023,14 @@ var AdNextResponseSchema = object({
6021
6023
  });
6022
6024
  var GRAVITY_CONTEXT_HEADER = "x-gravity-context";
6023
6025
  var GRAVITY_CONTEXT_MAX_BYTES = 8192;
6026
+ var AD_CONTEXT_HEADER = "x-ad-context";
6027
+ var AD_CONTEXT_MAX_CHARS = 1000;
6024
6028
  var AdImpressionRequestSchema = object({
6025
6029
  ad_id: string2(),
6026
6030
  click_token: string2(),
6027
6031
  imp_fired: boolean2().optional()
6028
6032
  });
6033
+ var AdImpressionResponseSchema = object({ ok: literal(true) });
6029
6034
  var AdminAdRequestSchema = object({
6030
6035
  slot: AdSlotSchema,
6031
6036
  creative: AdCreativeSchema,
@@ -6052,10 +6057,26 @@ var ClientVersionResponseSchema = object({
6052
6057
  latest: string2(),
6053
6058
  model: string2().optional(),
6054
6059
  notice: string2().optional(),
6055
- models: array(object({ id: string2(), name: string2(), section: string2().optional(), tier: _enum(["regular", "premium"]).optional() })).optional(),
6060
+ models: array(object({
6061
+ id: string2(),
6062
+ name: string2(),
6063
+ section: string2().optional(),
6064
+ tier: _enum(["regular", "premium"]).optional(),
6065
+ display_name: string2().optional(),
6066
+ free: boolean2().optional(),
6067
+ usage_x: string2().nullable().optional(),
6068
+ note: string2().nullable().optional(),
6069
+ unavailable: boolean2().optional(),
6070
+ stats_id: string2().optional()
6071
+ })).optional(),
6056
6072
  fusion: object({ planner: string2(), executor: string2() }).optional(),
6057
- experiments: record(string2(), object({ variant: _enum(["control", "treatment"]) }).passthrough()).optional(),
6058
- cost_experiment: object({ arm: _enum(COST_ARMS) }).optional()
6073
+ experiments: record(string2(), object({ variant: _enum(["control", "treatment"]), recent_hours: number2().optional() }).passthrough()).optional(),
6074
+ cost_experiment: object({ arm: _enum(COST_ARMS) }).optional(),
6075
+ min_desktop_version: string2().optional(),
6076
+ latest_desktop_version: string2().optional(),
6077
+ min_web_version: string2().optional(),
6078
+ latest_web_version: string2().optional(),
6079
+ desktop_download_url: string2().optional()
6059
6080
  });
6060
6081
  var SessionResetResponseSchema = object({
6061
6082
  released: boolean2()
@@ -6073,6 +6094,7 @@ var MeReferralResponseSchema = object({
6073
6094
  share_line: string2(),
6074
6095
  leaderboard_opt_in: boolean2()
6075
6096
  });
6097
+ var ReferralOptInResponseSchema = object({ ok: literal(true), opt_in: boolean2() });
6076
6098
  var ReferralStatusResponseSchema = object({
6077
6099
  signups: number2(),
6078
6100
  qualified: number2(),
@@ -6084,6 +6106,62 @@ var ReferralStatusResponseSchema = object({
6084
6106
  var ReferralLeaderboardResponseSchema = object({
6085
6107
  entries: array(object({ rank: number2(), qualified_count: number2(), handle: string2().nullable() }))
6086
6108
  });
6109
+ var HealthResponseSchema = object({ ok: literal(true), sha: string2() });
6110
+ var HealthDeepResponseSchema = object({
6111
+ ok: boolean2(),
6112
+ db: _enum(["ok", "timeout", "error"]),
6113
+ sha: string2(),
6114
+ last_db_ok_at: string2().nullable()
6115
+ });
6116
+ var RedeemResponseSchema = object({
6117
+ ok: literal(true),
6118
+ credits_added: number2().int(),
6119
+ balance: number2().int()
6120
+ });
6121
+ var LoginFunnelResponseSchema = union([
6122
+ object({ attempt_id: string2() }),
6123
+ object({ ok: literal(true) })
6124
+ ]);
6125
+ var TelemetryOkResponseSchema = object({ ok: literal(true) });
6126
+ var BlogPostsResponseSchema = object({
6127
+ posts: array(object({
6128
+ slug: string2(),
6129
+ title: string2(),
6130
+ category: string2(),
6131
+ description: string2().nullable(),
6132
+ author: string2().nullable(),
6133
+ published: string2(),
6134
+ revised: string2().nullable(),
6135
+ updated_at: string2(),
6136
+ body: string2()
6137
+ }))
6138
+ });
6139
+ var ToolWebSearchResponseSchema = object({
6140
+ results: array(object({
6141
+ title: string2(),
6142
+ url: string2(),
6143
+ published_date: string2().nullable(),
6144
+ text: string2()
6145
+ })),
6146
+ cost_usd: string2()
6147
+ });
6148
+ var ToolDocsResponseSchema = object({
6149
+ library_id: string2(),
6150
+ title: string2(),
6151
+ docs: string2(),
6152
+ cost_usd: string2()
6153
+ });
6154
+ var ToolImageResponseSchema = object({
6155
+ image_b64: string2(),
6156
+ media_type: _enum(["image/png", "image/jpeg", "image/webp"]),
6157
+ cost_usd: string2(),
6158
+ model: string2()
6159
+ });
6160
+ var ToolVisionResponseSchema = object({
6161
+ text: string2(),
6162
+ cost_usd: string2(),
6163
+ model: string2()
6164
+ });
6087
6165
 
6088
6166
  // ../shared/src/errors.ts
6089
6167
  function assertExhaustiveErrorCode(code) {
@@ -6641,7 +6719,7 @@ function dropPixelSignalHandlers() {
6641
6719
  process.removeListener("newListener", onNew);
6642
6720
  };
6643
6721
  }
6644
- function startGravityPixel(jwt, storageDir, env = process.env, load = () => import("./index-xkn61g24.js")) {
6722
+ function startGravityPixel(jwt, storageDir, env = process.env, load = () => import("./index-vz2s5sjf.js")) {
6645
6723
  if (env.FREEPI_NO_GRAVITY_PIXEL === "1")
6646
6724
  return;
6647
6725
  const userId = userIdFromJwt(jwt);
@@ -6767,6 +6845,52 @@ function gravityHeader(deps) {
6767
6845
  return {};
6768
6846
  }
6769
6847
  }
6848
+ function textParts(content) {
6849
+ if (typeof content === "string")
6850
+ return content;
6851
+ if (!Array.isArray(content))
6852
+ return "";
6853
+ return content.map((p) => p && p.type === "text" && typeof p.text === "string" ? p.text : "").filter(Boolean).join(`
6854
+ `);
6855
+ }
6856
+ function cut(redact, text) {
6857
+ const full = redact(text.slice(0, 20000));
6858
+ const s = full.slice(0, AD_CONTEXT_MAX_CHARS);
6859
+ return full.length > s.length && /\S/.test(full[s.length]) ? s.replace(/\S+$/, "").trimEnd() : s;
6860
+ }
6861
+ function adContextHeader(deps, ctx, prompt) {
6862
+ if (!deps.adContext || !ctx)
6863
+ return;
6864
+ try {
6865
+ const out = {};
6866
+ const entries = ctx.sessionManager.getBranch();
6867
+ if (prompt?.trim())
6868
+ out.user = cut(deps.adContext.redact, prompt.trim());
6869
+ for (let i = entries.length - 1;i >= 0 && !(out.user && out.assistant); i--) {
6870
+ const m = entries[i].type === "message" ? entries[i].message : undefined;
6871
+ if (m?.role !== "user" && m?.role !== "assistant" || out[m.role])
6872
+ continue;
6873
+ const text = textParts(m.content).trim();
6874
+ if (text)
6875
+ out[m.role] = cut(deps.adContext.redact, text);
6876
+ }
6877
+ if (!out.user && !out.assistant)
6878
+ return;
6879
+ const limit = Math.min(4096, 16 * 1024 - 2500 - (deps.getGravityContext?.()?.length ?? 0));
6880
+ if (limit < 600)
6881
+ return;
6882
+ let encoded = Buffer.from(JSON.stringify(out)).toString("base64url");
6883
+ while (encoded.length > limit && (out.user || out.assistant)) {
6884
+ for (const k of ["user", "assistant"])
6885
+ if (out[k])
6886
+ out[k] = out[k].slice(0, Math.floor(out[k].length / 2));
6887
+ encoded = Buffer.from(JSON.stringify(out)).toString("base64url");
6888
+ }
6889
+ return encoded;
6890
+ } catch {
6891
+ return;
6892
+ }
6893
+ }
6770
6894
  var IMP_TIMEOUT_MS = 3000;
6771
6895
  function isGravityImpUrl(raw) {
6772
6896
  if (!raw)
@@ -6786,9 +6910,11 @@ function fireGravityImpression(deps, ad) {
6786
6910
  } catch {}
6787
6911
  return true;
6788
6912
  }
6789
- async function fetchAdNext(deps, slot, trigger) {
6913
+ async function fetchAdNext(deps, slot, trigger, ctx, prompt) {
6790
6914
  try {
6791
- const res = await authedFetch(deps, `/ads/next?slot=${slot}`, trigger ? { headers: { "x-ad-trigger": trigger } } : undefined);
6915
+ const pair = adContextHeader(deps, ctx, prompt);
6916
+ const headers = { ...trigger ? { "x-ad-trigger": trigger } : {}, ...pair ? { [AD_CONTEXT_HEADER]: pair } : {} };
6917
+ const res = await authedFetch(deps, `/ads/next?slot=${slot}`, { headers });
6792
6918
  if (res.status === 204 || !res.ok)
6793
6919
  return;
6794
6920
  return await res.json();
@@ -6888,8 +7014,8 @@ function createBannerRenderer(opts = {}) {
6888
7014
  let lastPostedToken;
6889
7015
  const screen = opts.screen ?? {};
6890
7016
  return {
6891
- async render(deps, ctx, config, trigger) {
6892
- const ad = await fetchAdNext(deps, "banner", trigger);
7017
+ async render(deps, ctx, config, trigger, prompt) {
7018
+ const ad = await fetchAdNext(deps, "banner", trigger, ctx, prompt);
6893
7019
  if (opts.isActive && !opts.isActive())
6894
7020
  return;
6895
7021
  if (!ad) {
@@ -6947,7 +7073,7 @@ function createInlineRenderer(screen = {}) {
6947
7073
  return;
6948
7074
  if (screen.banner === "gravity")
6949
7075
  return;
6950
- const ad = await fetchAdNext(deps, "inline");
7076
+ const ad = await fetchAdNext(deps, "inline", undefined, ctx);
6951
7077
  if (!ad)
6952
7078
  return;
6953
7079
  const isGravity = ad.source === "gravity";
@@ -7024,6 +7150,20 @@ function mapErrorCode(code) {
7024
7150
  return "This model is only for reward holders. Finish an EARN task from your terminal to unlock it, or switch models with /model.";
7025
7151
  case "subagents_unavailable":
7026
7152
  return "Subagents run on the free Particle model, or with credits. Switch with /model or run /buy-credits.";
7153
+ case "bad_request":
7154
+ case "payload_too_large":
7155
+ case "rate_limited":
7156
+ case "unauthorized":
7157
+ case "denied":
7158
+ case "expired":
7159
+ case "account_ceiling":
7160
+ case "misconfigured":
7161
+ case "bug_report_limit":
7162
+ case "ip_blocked":
7163
+ case "request_aborted":
7164
+ case "insufficient_credits":
7165
+ case "upstream_rate_limited":
7166
+ throw new Error(`unhandled error code: ${code}`);
7027
7167
  default:
7028
7168
  return assertExhaustiveErrorCode(code);
7029
7169
  }
@@ -7165,6 +7305,7 @@ function createAdsExtension(opts) {
7165
7305
  clientVersion: opts.clientVersion,
7166
7306
  fetchImpl: opts.fetchImpl,
7167
7307
  getGravityContext: opts.getGravityContext,
7308
+ adContext: opts.adContext,
7168
7309
  onMe: opts.onMe
7169
7310
  };
7170
7311
  const config = loadConfig();
@@ -7180,14 +7321,14 @@ function createAdsExtension(opts) {
7180
7321
  let launchAt = 0;
7181
7322
  let lastBannerAt = 0;
7182
7323
  let bannerInFlight = false;
7183
- const renderBanner = async (ctx, trigger, force = false) => {
7324
+ const renderBanner = async (ctx, trigger, force = false, prompt) => {
7184
7325
  if (stopped)
7185
7326
  return;
7186
7327
  if (!force && (bannerInFlight || Date.now() - lastBannerAt < BANNER_MIN_REFRESH_MS))
7187
7328
  return;
7188
7329
  bannerInFlight = true;
7189
7330
  try {
7190
- await banner.render(deps, ctx, config, trigger);
7331
+ await banner.render(deps, ctx, config, trigger, prompt);
7191
7332
  } finally {
7192
7333
  bannerInFlight = false;
7193
7334
  lastBannerAt = Date.now();
@@ -7211,7 +7352,7 @@ function createAdsExtension(opts) {
7211
7352
  await Promise.all([renderBanner(ctx, "launch", true), renderMeter(deps, ctx)]);
7212
7353
  });
7213
7354
  const LAUNCH_AD_MAX_AGE_MS = 60 * 60000;
7214
- const newMessage = async (ctx) => {
7355
+ const newMessage = async (ctx, prompt) => {
7215
7356
  if (launchTurnUnused) {
7216
7357
  launchTurnUnused = false;
7217
7358
  const token = await Promise.resolve(deps.getToken()).catch(() => {
@@ -7221,17 +7362,17 @@ function createAdsExtension(opts) {
7221
7362
  return;
7222
7363
  }
7223
7364
  startTurn();
7224
- renderBanner(ctx, "user_message", true);
7365
+ renderBanner(ctx, "user_message", true, prompt);
7225
7366
  };
7226
7367
  pi.on("before_agent_start", async (event, ctx) => {
7227
7368
  if (event.messageType !== "user")
7228
7369
  return;
7229
- await newMessage(ctx);
7370
+ await newMessage(ctx, event.prompt);
7230
7371
  });
7231
7372
  pi.on("input", (event, ctx) => {
7232
7373
  const e = event;
7233
7374
  if (e.streamingBehavior && e.source !== "extension")
7234
- return newMessage(ctx).then(() => ({ action: "continue" }));
7375
+ return newMessage(ctx, e.text).then(() => ({ action: "continue" }));
7235
7376
  return { action: "continue" };
7236
7377
  });
7237
7378
  pi.on("turn_end", async (_event, ctx) => {
@@ -8572,7 +8713,6 @@ session.
8572
8713
 
8573
8714
  ## Commands
8574
8715
  - \`/usage\` — spend and remaining budget today
8575
- - \`/support\` — visit today's advertiser
8576
8716
  - \`/feedback <text>\` — send feedback to the free-pi team
8577
8717
  - \`/tos\` — open the Terms of Service
8578
8718
  - \`/privacy-policy\` — open the Privacy Policy
@@ -8608,9 +8748,6 @@ in to a server that uses OAuth, and \`free-pi-cli mcp logout <server>\`. Add
8608
8748
  Inside a session, \`/mcp\` shows each server and its tools, and lets you sign
8609
8749
  in, reconnect, or turn a server on or off.
8610
8750
 
8611
- ## Support
8612
- Today's sponsor keeps free-pi free. When the user asks how to help the
8613
- project, or seems to like free-pi, suggest \`/support\` to visit the sponsor.
8614
8751
  More at https://freepi.ai
8615
8752
  `;
8616
8753
 
@@ -9277,7 +9414,19 @@ function routeFusion(request, models) {
9277
9414
  return handedOffThisTurn(request.messages) ? route(models.planner, "plan") : route(models.executor, "explore");
9278
9415
  }
9279
9416
  }
9280
- function createFusionExtension(defaultModelId, ids = resolveFusionIds([])) {
9417
+ var FUSION_PREMIUM_REFUSAL = "At capacity for free users. Available with premium credits: /buy-credits. Or switch to Particle-1, DeepSeek V4.1 Flash or GLM 5.3 Flash with /model.";
9418
+ async function fetchHasPremium(opts) {
9419
+ try {
9420
+ const res = await (opts.fetchImpl ?? fetch)(`${opts.baseUrl}/me`, { headers: { Authorization: `Bearer ${await opts.getToken()}` }, signal: AbortSignal.timeout(5000) });
9421
+ if (!res.ok)
9422
+ return true;
9423
+ const me = await res.json();
9424
+ return typeof me.premium_credits === "number" ? me.premium_credits > 0 : true;
9425
+ } catch {
9426
+ return true;
9427
+ }
9428
+ }
9429
+ function createFusionExtension(defaultModelId, ids = resolveFusionIds([]), hasPremium) {
9281
9430
  const name = ids.planner === FUSION_PLANNER_ID && ids.executor === FUSION_EXECUTOR_ID ? FUSION_NAME : ids.planner === FUSION_FALLBACK_PLANNER_ID ? FUSION_FALLBACK_NAME : "\uD83E\uDDE8 Fusion";
9282
9431
  return {
9283
9432
  name: "free-pi-fusion",
@@ -9289,8 +9438,21 @@ function createFusionExtension(defaultModelId, ids = resolveFusionIds([])) {
9289
9438
  return;
9290
9439
  pi.setActiveTools(on ? [...active, FUSION_PLAN_TOOL] : active.filter((name) => name !== FUSION_PLAN_TOOL));
9291
9440
  };
9292
- pi.on("session_start", (_event, ctx) => syncPlanTool(ctx.model));
9293
- pi.on("model_select", (event) => syncPlanTool(event.model));
9441
+ const refuseWithoutPremium = async (model, ctx) => {
9442
+ if (!hasPremium || !isFusionModel(model) || await hasPremium())
9443
+ return false;
9444
+ ctx.ui.notify(FUSION_PREMIUM_REFUSAL, "warning");
9445
+ const fallback = ctx.modelRegistry.find(PROVIDER_NAME, defaultModelId);
9446
+ if (fallback)
9447
+ await pi.setModel(fallback);
9448
+ return true;
9449
+ };
9450
+ const onSelect = (model, ctx) => hasPremium && isFusionModel(model) ? refuseWithoutPremium(model, ctx).then((refused) => {
9451
+ if (!refused)
9452
+ syncPlanTool(model);
9453
+ }) : syncPlanTool(model);
9454
+ pi.on("session_start", (_event, ctx) => onSelect(ctx.model, ctx));
9455
+ pi.on("model_select", (event, ctx) => onSelect(event.model, ctx));
9294
9456
  pi.registerTool({
9295
9457
  name: FUSION_PLAN_TOOL,
9296
9458
  label: "Fusion plan",
@@ -9682,9 +9844,16 @@ function createUpgradeNoticeExtension(opts) {
9682
9844
 
9683
9845
  // src/tool-outcomes.ts
9684
9846
  var MAX_IDS = 50;
9847
+ var TOOL_META_HEADER = "x-freepi-tool-meta";
9848
+ var COMPACTED_HEADER = "x-freepi-compacted";
9849
+ var META_BUDGET = 1500;
9685
9850
  function createToolOutcomesExtension() {
9686
9851
  const failed = [];
9687
9852
  let evicted = false;
9853
+ const meta = [];
9854
+ let metaBytes = 3;
9855
+ let droppedMessages = 0;
9856
+ let pendingDropped = 0;
9688
9857
  return {
9689
9858
  name: "free-pi-tool-outcomes",
9690
9859
  factory(pi) {
@@ -9698,7 +9867,37 @@ function createToolOutcomesExtension() {
9698
9867
  evicted = true;
9699
9868
  }
9700
9869
  });
9870
+ pi.on("tool_execution_end", (event) => {
9871
+ const e = event;
9872
+ if (!e.toolCallId || !/^[\w.:-]{1,100}$/.test(e.toolCallId) || typeof e.durationMs !== "number")
9873
+ return;
9874
+ let exit = "-";
9875
+ if (e.toolName === "bash" || e.toolName === "powershell") {
9876
+ const m = e.isError ? /Command exited with code (\d{1,3})\s*$/.exec(e.result?.content?.map((b) => b.text ?? "").join("") ?? "") : null;
9877
+ exit = m ? m[1] : e.isError ? "-" : "0";
9878
+ }
9879
+ const entry = `${e.toolCallId}~${Math.max(0, Math.round(e.durationMs))}~${exit}~${e.result?.details?.truncation?.truncated ? 1 : 0}`;
9880
+ if (entry.length > 150)
9881
+ return;
9882
+ meta.push(entry);
9883
+ metaBytes += entry.length + 1;
9884
+ while (metaBytes > META_BUDGET && meta.length > 1)
9885
+ metaBytes -= meta.shift().length + 1;
9886
+ });
9887
+ pi.on("session_before_compact", (event) => {
9888
+ const p = event.preparation;
9889
+ pendingDropped = (p?.messagesToSummarize?.length ?? 0) + (p?.turnPrefixMessages?.length ?? 0);
9890
+ });
9891
+ pi.on("session_compact", () => {
9892
+ droppedMessages = pendingDropped;
9893
+ });
9701
9894
  pi.on("before_provider_headers", (event) => {
9895
+ if (meta.length)
9896
+ event.headers[TOOL_META_HEADER] = `v1:${meta.join(",")}`;
9897
+ if (droppedMessages) {
9898
+ event.headers[COMPACTED_HEADER] = `v1:${droppedMessages}`;
9899
+ droppedMessages = 0;
9900
+ }
9702
9901
  event.headers["x-freepi-tool-errors"] = `${evicted ? "v1x" : "v1"}:${failed.join(",")}`;
9703
9902
  });
9704
9903
  }
@@ -10141,6 +10340,17 @@ function mergeAttribution(a, b) {
10141
10340
 
10142
10341
  // src/changelog.ts
10143
10342
  var CHANGELOG_HIGHLIGHTS = [
10343
+ {
10344
+ version: "0.6.22",
10345
+ highlights: [
10346
+ 'The /model picker now shows "DeepSeek V4.1 Flash 600tps" instead of fire emojis.',
10347
+ "Fusion needs premium credits; without them the CLI explains how to get credits or switch models."
10348
+ ]
10349
+ },
10350
+ {
10351
+ version: "0.6.21",
10352
+ highlights: ["The /support command is removed."]
10353
+ },
10144
10354
  {
10145
10355
  version: "0.6.20",
10146
10356
  highlights: ["Error messages are now plain sentences instead of raw server text."]
@@ -11634,15 +11844,6 @@ async function buyCredits(opts, ctx) {
11634
11844
  (opts.openBrowserImpl ?? openBrowser)(page.url);
11635
11845
  ctx.ui.notify(buyPageText(page.url), "info");
11636
11846
  }
11637
- async function openAdvertiser(opts, ctx) {
11638
- const url = opts.getAdvertiserUrl?.();
11639
- if (!url) {
11640
- ctx.ui.notify("No advertiser loaded yet.", "info");
11641
- return;
11642
- }
11643
- (opts.openBrowserImpl ?? openBrowser)(url);
11644
- ctx.ui.notify("Opening today's advertiser in your browser. Thank you for supporting free-pi.", "info");
11645
- }
11646
11847
  var TERMS_URL = "https://freepi.ai/terms";
11647
11848
  var PRIVACY_URL = "https://freepi.ai/privacy";
11648
11849
  async function openTos(opts, ctx) {
@@ -11988,10 +12189,6 @@ function createFreePiCommandsExtension(opts) {
11988
12189
  description: FEEDBACK_DESCRIPTION,
11989
12190
  handler: (args, ctx) => sendFeedback(opts, args, ctx)
11990
12191
  });
11991
- pi.registerCommand("support", {
11992
- description: "Open today's advertiser in your browser to support free-pi.",
11993
- handler: (_args, ctx) => openAdvertiser(opts, ctx)
11994
- });
11995
12192
  pi.registerCommand("invite", {
11996
12193
  description: "Get your invite code and link: you and a friend both get +5k credits/day.",
11997
12194
  handler: (_args, ctx) => showInvite(opts, ctx)
@@ -12254,7 +12451,6 @@ var ALL_COMMANDS = [
12254
12451
  { name: "/usage", description: "credits left today and refill time" },
12255
12452
  { name: "/import", description: "continue a Claude Code or Codex session" },
12256
12453
  { name: "/hive", description: "share this session with friends" },
12257
- { name: "/support", description: "visit today's advertiser" },
12258
12454
  { name: "/tos", description: "terms of service" },
12259
12455
  { name: "/privacy-policy", description: "privacy policy" },
12260
12456
  { name: "/buy-credits", description: "get more usage" },
@@ -12385,7 +12581,8 @@ function createHeaderExtension(opts) {
12385
12581
  // src/ultrafast.ts
12386
12582
  var ULTRAFAST_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
12387
12583
  var ULTRAFAST_CATALOG_ID = "deepseek-v4.1-flash-1017-ultrafast";
12388
- var ULTRAFAST_PICKER_ID = "\uD83D\uDD25\uD83D\uDD25\uD83D\uDD25 ultrafast";
12584
+ var ULTRAFAST_PICKER_ID = "deepseek-v4.1-flash-600tps";
12585
+ var ULTRAFAST_LEGACY_PICKER_ID = "\uD83D\uDD25\uD83D\uDD25\uD83D\uDD25 ultrafast";
12389
12586
  function createUltrafastExtension(model) {
12390
12587
  return {
12391
12588
  name: "free-pi-ultrafast",
@@ -13477,6 +13674,10 @@ function createHiveExtension(opts) {
13477
13674
  roomWaiter = undefined;
13478
13675
  }
13479
13676
  async function start(ctx) {
13677
+ if (opts.isRemoteAttached?.()) {
13678
+ notify("Hive: turn the Telegram remote off first (/remote off).");
13679
+ return;
13680
+ }
13480
13681
  if (ws || starting) {
13481
13682
  notify(roomUrl ? `Hive room: ${roomUrl}` : "Hive: still connecting...");
13482
13683
  return;
@@ -13805,6 +14006,414 @@ ${decision.reason}`, ACTION_DIALOG_MS);
13805
14006
  };
13806
14007
  }
13807
14008
 
14009
+ // src/remote.ts
14010
+ import { randomUUID as randomUUID2 } from "node:crypto";
14011
+ import { basename as basename5 } from "node:path";
14012
+
14013
+ // ../shared/src/remote.ts
14014
+ var REMOTE_MAX_TITLE_CHARS = 80;
14015
+ var REMOTE_MAX_COMMAND_CHARS = 2000;
14016
+ var IdSchema = string2().min(1).max(64);
14017
+ var RemoteChoiceSchema = _enum(["once", "turn", "deny"]);
14018
+ var RemoteSessionFrameSchema = discriminatedUnion("type", [
14019
+ HiveAuthFrameSchema,
14020
+ object({ type: literal("hello"), session_id: IdSchema, title: string2().max(REMOTE_MAX_TITLE_CHARS) }),
14021
+ object({ type: literal("entry"), entry: HiveEntrySchema }),
14022
+ object({ type: literal("busy"), settled: boolean2() }),
14023
+ object({ type: literal("prompt_result"), prompt_id: IdSchema, accepted: boolean2(), reason: string2().max(100).optional() }),
14024
+ object({ type: literal("question"), id: IdSchema, text: string2().max(500), command: string2().max(REMOTE_MAX_COMMAND_CHARS) }),
14025
+ object({ type: literal("question_closed"), id: IdSchema })
14026
+ ]);
14027
+ var RemoteServerFrameSchema = discriminatedUnion("type", [
14028
+ object({ type: literal("ready"), linked: boolean2(), link_url: string2().optional() }),
14029
+ object({ type: literal("prompt"), prompt_id: IdSchema, text: string2().min(1).max(HIVE_MAX_PROMPT_CHARS) }),
14030
+ object({ type: literal("answer"), question_id: IdSchema, choice: RemoteChoiceSchema }),
14031
+ object({ type: literal("stop") }),
14032
+ object({ type: literal("error"), code: string2() }),
14033
+ object({ type: literal("closed"), reason: string2() })
14034
+ ]);
14035
+ var RemoteLinkCodeResponseSchema = object({ url: string2(), expires_at: string2() });
14036
+ var RemoteLinkStatusSchema = object({ linked: boolean2() });
14037
+
14038
+ // src/remote.ts
14039
+ var OPEN2 = 1;
14040
+ var MCP_RESOURCE_TOOLS3 = ["list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"];
14041
+ var HARD_BLOCKED = new Set([SUBAGENT_TOOL, SEND_MESSAGE_TOOL, BUY_TOOL_NAME]);
14042
+ function wsUrl2(baseUrl) {
14043
+ const url = new URL("/remote/ws", baseUrl);
14044
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
14045
+ return url.toString();
14046
+ }
14047
+ function createRemoteExtension(opts) {
14048
+ return {
14049
+ name: "free-pi-remote",
14050
+ factory(pi) {
14051
+ let ws;
14052
+ let ctxRef;
14053
+ let attachedCwd = "";
14054
+ let title = "";
14055
+ let settled = true;
14056
+ let compacting = false;
14057
+ let allowTurn = false;
14058
+ let localDetach = false;
14059
+ let reconnectAttempt = 0;
14060
+ let reconnectTimer;
14061
+ let injectTimer;
14062
+ let questionChain = Promise.resolve();
14063
+ let openQuestion;
14064
+ const notify = (message, type = "info") => ctxRef?.ui.notify(message, type);
14065
+ const send = (frame) => {
14066
+ if (ws?.readyState === OPEN2)
14067
+ ws.send(JSON.stringify(frame));
14068
+ };
14069
+ const sendEntry = (entry) => send({ type: "entry", entry });
14070
+ function setSettled(next) {
14071
+ if (settled === next)
14072
+ return;
14073
+ settled = next;
14074
+ send({ type: "busy", settled });
14075
+ }
14076
+ function finishQuestion(choice, reason) {
14077
+ const question = openQuestion;
14078
+ if (!question)
14079
+ return;
14080
+ openQuestion = undefined;
14081
+ question.finish(choice, reason);
14082
+ if (choice === undefined)
14083
+ send({ type: "question_closed", id: question.id });
14084
+ }
14085
+ function cancelQuestion(reason) {
14086
+ finishQuestion(undefined, reason);
14087
+ }
14088
+ function clearRemoteState(reason = "turn ended") {
14089
+ opts.state.remoteTurn = false;
14090
+ allowTurn = false;
14091
+ cancelQuestion(reason);
14092
+ }
14093
+ function endApprovals(reason) {
14094
+ allowTurn = false;
14095
+ cancelQuestion(reason);
14096
+ }
14097
+ function recoverRejectedPrompt() {
14098
+ setSettled(true);
14099
+ clearRemoteState();
14100
+ }
14101
+ function clearSocket() {
14102
+ const old = ws;
14103
+ ws = undefined;
14104
+ clearTimeout(reconnectTimer);
14105
+ reconnectTimer = undefined;
14106
+ ctxRef?.ui.setStatus("remote", undefined);
14107
+ cancelQuestion("disconnected");
14108
+ try {
14109
+ old?.close();
14110
+ } catch {}
14111
+ }
14112
+ function detach(notice) {
14113
+ localDetach = true;
14114
+ opts.state.attached = false;
14115
+ endApprovals("detached");
14116
+ clearSocket();
14117
+ attachedCwd = "";
14118
+ reconnectAttempt = 0;
14119
+ if (notice)
14120
+ notify(notice);
14121
+ }
14122
+ function questionFor(event, ctx) {
14123
+ const toolName = String(event.toolName);
14124
+ const input = event.input ?? {};
14125
+ const summary = toolName === "bash" ? String(input.command ?? "") : toolName + " " + JSON.stringify(input);
14126
+ const command = summary.slice(0, 2000);
14127
+ const run = async () => {
14128
+ if (!opts.state.remoteTurn)
14129
+ return { block: true, reason: "Remote: turn ended before approval" };
14130
+ if (allowTurn)
14131
+ return;
14132
+ if (ws?.readyState !== OPEN2)
14133
+ return { block: true, reason: "Remote: disconnected before approval" };
14134
+ const id = randomUUID2();
14135
+ let timer;
14136
+ let abortListener;
14137
+ const answer = await new Promise((resolve) => {
14138
+ openQuestion = {
14139
+ id,
14140
+ finish: (choice, reason) => {
14141
+ clearTimeout(timer);
14142
+ if (abortListener && ctx.signal)
14143
+ ctx.signal.removeEventListener("abort", abortListener);
14144
+ resolve({ choice, reason });
14145
+ }
14146
+ };
14147
+ const timeoutMs = opts.questionTimeoutMs ?? 5 * 60000;
14148
+ timer = setTimeout(() => finishQuestion(undefined, "timed out"), timeoutMs);
14149
+ timer.unref?.();
14150
+ abortListener = () => finishQuestion(undefined, "turn aborted");
14151
+ if (ctx.signal?.aborted)
14152
+ abortListener();
14153
+ else
14154
+ ctx.signal?.addEventListener("abort", abortListener, { once: true });
14155
+ send({ type: "question", id, text: "The agent wants to run this:", command });
14156
+ });
14157
+ if (answer.choice === "deny")
14158
+ return { block: true, reason: "Remote: denied from Telegram" };
14159
+ if (answer.choice === "turn")
14160
+ allowTurn = true;
14161
+ if (answer.choice === "once" || answer.choice === "turn")
14162
+ return;
14163
+ const why = answer.reason ?? "approval ended";
14164
+ return { block: true, reason: "Remote: " + why };
14165
+ };
14166
+ const current = questionChain.then(run, run);
14167
+ questionChain = current.catch(() => {
14168
+ return;
14169
+ });
14170
+ return current;
14171
+ }
14172
+ function handle(frame) {
14173
+ switch (frame.type) {
14174
+ case "ready":
14175
+ if (!frame.linked && frame.link_url) {
14176
+ notify("Remote: open this link in Telegram to connect your account: " + frame.link_url);
14177
+ } else if (frame.linked) {
14178
+ notify("Remote is on. Messages you send to the free-pi bot in Telegram go to this session.");
14179
+ }
14180
+ ctxRef?.ui.setStatus("remote", "remote: on");
14181
+ reconnectAttempt = 0;
14182
+ if (!settled)
14183
+ send({ type: "busy", settled: false });
14184
+ break;
14185
+ case "prompt": {
14186
+ if (!settled || compacting) {
14187
+ send({ type: "prompt_result", prompt_id: frame.prompt_id, accepted: false, reason: "busy" });
14188
+ break;
14189
+ }
14190
+ opts.state.remoteTurn = true;
14191
+ allowTurn = false;
14192
+ setSettled(false);
14193
+ clearTimeout(injectTimer);
14194
+ injectTimer = setTimeout(() => {
14195
+ injectTimer = undefined;
14196
+ if (ctxRef?.isIdle() !== false)
14197
+ recoverRejectedPrompt();
14198
+ }, opts.injectGraceMs ?? 2000);
14199
+ injectTimer.unref?.();
14200
+ try {
14201
+ Promise.resolve(pi.sendUserMessage(frame.text, { expandPromptTemplates: false })).catch(recoverRejectedPrompt);
14202
+ send({ type: "prompt_result", prompt_id: frame.prompt_id, accepted: true });
14203
+ } catch {
14204
+ recoverRejectedPrompt();
14205
+ send({ type: "prompt_result", prompt_id: frame.prompt_id, accepted: false, reason: "error" });
14206
+ }
14207
+ break;
14208
+ }
14209
+ case "answer":
14210
+ if (openQuestion?.id === frame.question_id)
14211
+ finishQuestion(frame.choice);
14212
+ break;
14213
+ case "stop":
14214
+ cancelQuestion("stopped");
14215
+ if (!settled)
14216
+ ctxRef?.abort();
14217
+ break;
14218
+ case "closed":
14219
+ if (frame.reason === "shutdown")
14220
+ break;
14221
+ detach(frame.reason === "replaced" ? "Remote moved to another session." : "Remote closed.");
14222
+ break;
14223
+ case "error":
14224
+ notify("Remote: the server could not process the connection.", "warning");
14225
+ break;
14226
+ }
14227
+ }
14228
+ function connect() {
14229
+ let socket;
14230
+ try {
14231
+ const Impl = opts.WebSocketImpl ?? WebSocket;
14232
+ socket = new Impl(wsUrl2(opts.baseUrl));
14233
+ } catch {
14234
+ retryConnect();
14235
+ return;
14236
+ }
14237
+ ws = socket;
14238
+ localDetach = false;
14239
+ socket.onopen = async () => {
14240
+ if (ws !== socket)
14241
+ return;
14242
+ try {
14243
+ const token = await opts.getToken();
14244
+ if (ws !== socket)
14245
+ return;
14246
+ socket.send(JSON.stringify({ type: "auth", token }));
14247
+ socket.send(JSON.stringify({ type: "hello", session_id: opts.sessionId, title }));
14248
+ } catch {
14249
+ socket.close();
14250
+ }
14251
+ };
14252
+ socket.onerror = () => {
14253
+ if (ws === socket)
14254
+ socket.close();
14255
+ };
14256
+ socket.onclose = () => {
14257
+ if (ws !== socket)
14258
+ return;
14259
+ ws = undefined;
14260
+ ctxRef?.ui.setStatus("remote", undefined);
14261
+ cancelQuestion("disconnected");
14262
+ if (!localDetach)
14263
+ retryConnect();
14264
+ };
14265
+ socket.onmessage = (event) => {
14266
+ if (ws !== socket || typeof event.data !== "string")
14267
+ return;
14268
+ let json;
14269
+ try {
14270
+ json = JSON.parse(event.data);
14271
+ } catch {
14272
+ return;
14273
+ }
14274
+ const parsed = RemoteServerFrameSchema.safeParse(json);
14275
+ if (parsed.success)
14276
+ handle(parsed.data);
14277
+ };
14278
+ }
14279
+ function retryConnect() {
14280
+ if (localDetach)
14281
+ return;
14282
+ const delays = opts.reconnectDelaysMs ?? [1000, 2000, 5000, 15000, 30000];
14283
+ if (reconnectAttempt >= delays.length) {
14284
+ localDetach = true;
14285
+ opts.state.attached = false;
14286
+ endApprovals("disconnected");
14287
+ notify("Remote: lost the connection. Run /remote to attach again.", "warning");
14288
+ return;
14289
+ }
14290
+ const delay = delays[reconnectAttempt++];
14291
+ reconnectTimer = setTimeout(() => {
14292
+ reconnectTimer = undefined;
14293
+ connect();
14294
+ }, delay);
14295
+ reconnectTimer.unref?.();
14296
+ }
14297
+ function attach(ctx) {
14298
+ if (ws || reconnectTimer) {
14299
+ notify("Remote is already attached.");
14300
+ return;
14301
+ }
14302
+ ctxRef = ctx;
14303
+ if (opts.isHiveOpen?.()) {
14304
+ notify("Remote: end the Hive room first (/hive end).");
14305
+ return;
14306
+ }
14307
+ opts.state.attached = true;
14308
+ attachedCwd = ctx.cwd;
14309
+ title = basename5(ctx.cwd).slice(0, 80);
14310
+ settled = ctx.isIdle();
14311
+ compacting = false;
14312
+ localDetach = false;
14313
+ reconnectAttempt = 0;
14314
+ connect();
14315
+ }
14316
+ pi.on("tool_call", (event, ctx) => {
14317
+ if (!opts.state.remoteTurn)
14318
+ return;
14319
+ ctxRef = ctx;
14320
+ if (HARD_BLOCKED.has(event.toolName)) {
14321
+ return { block: true, reason: "Remote: this tool is not run for a prompt sent from Telegram" };
14322
+ }
14323
+ const needsApproval = event.toolName === "bash" || event.toolName.startsWith("mcp__") || MCP_RESOURCE_TOOLS3.includes(event.toolName) || Boolean(event.parentToolCallId);
14324
+ return needsApproval ? questionFor(event, ctx) : undefined;
14325
+ });
14326
+ pi.on("agent_start", () => {
14327
+ clearTimeout(injectTimer);
14328
+ injectTimer = undefined;
14329
+ setSettled(false);
14330
+ });
14331
+ pi.on("agent_settled", () => {
14332
+ setSettled(true);
14333
+ clearRemoteState();
14334
+ sendEntry({ kind: "settled" });
14335
+ });
14336
+ pi.on("session_before_compact", (_event, ctx) => {
14337
+ ctxRef = ctx;
14338
+ compacting = true;
14339
+ setSettled(false);
14340
+ });
14341
+ const compactionEnded = (_event, ctx) => {
14342
+ ctxRef = ctx;
14343
+ compacting = false;
14344
+ if (ctx.isIdle())
14345
+ setSettled(true);
14346
+ };
14347
+ pi.on("session_compact", compactionEnded);
14348
+ pi.on("session_compact_failed", compactionEnded);
14349
+ pi.on("session_start", (_event, ctx) => {
14350
+ ctxRef = ctx;
14351
+ if (attachedCwd && ctx.cwd !== attachedCwd)
14352
+ detach();
14353
+ });
14354
+ pi.on("session_before_switch", () => detach());
14355
+ pi.on("session_shutdown", () => detach());
14356
+ pi.on("input", (event) => {
14357
+ if (event.source !== "extension")
14358
+ sendEntry({ kind: "prompt", text: event.text.slice(0, HIVE_MAX_PROMPT_CHARS) });
14359
+ return { action: "continue" };
14360
+ });
14361
+ pi.on("message_end", (event) => {
14362
+ const message = event.message;
14363
+ if (message.role !== "assistant" || !Array.isArray(message.content))
14364
+ return;
14365
+ const text = message.content.map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").join("");
14366
+ for (let i = 0;i < text.length; i += HIVE_MAX_ASSISTANT_CHARS) {
14367
+ sendEntry({ kind: "assistant", text: text.slice(i, i + HIVE_MAX_ASSISTANT_CHARS) });
14368
+ }
14369
+ });
14370
+ pi.on("tool_execution_end", (event) => {
14371
+ sendEntry({ kind: "tool", name: event.toolName, is_error: event.isError });
14372
+ });
14373
+ if (process.env.FREEPI_REMOTE === "1") {
14374
+ pi.registerCommand("remote", {
14375
+ description: "Attach this session to Telegram: /remote, /remote off, /remote unlink",
14376
+ handler: async (args, ctx) => {
14377
+ ctxRef = ctx;
14378
+ const sub = args.trim().toLowerCase();
14379
+ if (sub === "")
14380
+ return attach(ctx);
14381
+ if (sub === "off") {
14382
+ detach();
14383
+ notify("Remote is off.");
14384
+ return;
14385
+ }
14386
+ if (sub === "unlink") {
14387
+ allowTurn = false;
14388
+ cancelQuestion("unlinked");
14389
+ const token = await opts.getToken().catch(() => {
14390
+ return;
14391
+ });
14392
+ if (!token) {
14393
+ notify("Remote: couldn't read your free-pi login.", "error");
14394
+ return;
14395
+ }
14396
+ try {
14397
+ const response = await (opts.fetchImpl ?? fetch)(opts.baseUrl + "/remote/link", {
14398
+ method: "DELETE",
14399
+ headers: { authorization: "Bearer " + token }
14400
+ });
14401
+ if (response.ok)
14402
+ detach();
14403
+ notify(response.ok ? "Remote account unlinked." : "Remote: couldn't unlink the account.", response.ok ? "info" : "warning");
14404
+ } catch {
14405
+ notify("Remote: couldn't unlink the account.", "warning");
14406
+ }
14407
+ return;
14408
+ }
14409
+ notify("Usage: /remote, /remote off, or /remote unlink.");
14410
+ }
14411
+ });
14412
+ }
14413
+ }
14414
+ };
14415
+ }
14416
+
13808
14417
  // src/shell-sandbox.ts
13809
14418
  import { spawnSync } from "node:child_process";
13810
14419
  import {
@@ -14720,7 +15329,8 @@ function resolveModelName(opts) {
14720
15329
  return opts.models?.[0]?.name || opts.model || MODEL_ID;
14721
15330
  }
14722
15331
  function buildSettingsManager(opts, models, projectTrusted) {
14723
- const saved = readDefaultModel(opts.agentDir);
15332
+ const raw = readDefaultModel(opts.agentDir);
15333
+ const saved = raw?.model === ULTRAFAST_LEGACY_PICKER_ID ? { ...raw, model: ULTRAFAST_PICKER_ID } : raw;
14724
15334
  const known = new Map([
14725
15335
  [PROVIDER_NAME, new Set([...models.map((m) => m.id), FUSION_PICKER_ID, ...models.some((m) => m.id === ULTRAFAST_CATALOG_ID) ? [ULTRAFAST_PICKER_ID] : []])],
14726
15336
  [DEFENSE_PROVIDER_NAME, new Set(defenseModelsFor(opts).map((m) => m.id))]
@@ -14765,7 +15375,6 @@ function buildRuntimeOptions(opts, sessionId) {
14765
15375
  name: "free-pi-provider",
14766
15376
  factory: (pi) => registerProviders(pi, sessionId)
14767
15377
  };
14768
- let advertiserUrl;
14769
15378
  const adsExtension = createAdsExtension({
14770
15379
  registerMeterRefresh: (refresh) => auth.setOnSignedIn(() => void refresh().catch(() => {})),
14771
15380
  baseUrl: opts.baseUrl,
@@ -14773,9 +15382,7 @@ function buildRuntimeOptions(opts, sessionId) {
14773
15382
  sessionId,
14774
15383
  clientVersion: CLI_VERSION,
14775
15384
  getGravityContext: opts.getGravityContext,
14776
- onBannerAd: (u) => {
14777
- advertiserUrl = u;
14778
- },
15385
+ adContext: opts.adContext,
14779
15386
  onMe: (me) => {
14780
15387
  updateAllowanceCache(me);
14781
15388
  }
@@ -14829,7 +15436,6 @@ function buildRuntimeOptions(opts, sessionId) {
14829
15436
  const commandsExtension = createFreePiCommandsExtension({
14830
15437
  baseUrl: opts.baseUrl,
14831
15438
  getToken: () => auth.getJwt(),
14832
- getAdvertiserUrl: () => advertiserUrl,
14833
15439
  credentialsPath: opts.credentialsPath,
14834
15440
  agentDir: opts.agentDir,
14835
15441
  isGuest: () => auth.isGuest(),
@@ -14860,12 +15466,21 @@ function buildRuntimeOptions(opts, sessionId) {
14860
15466
  const codemodeExtension = { name: "pi-codemode", factory: createCodemodeExtension({ models: false }) };
14861
15467
  const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
14862
15468
  const hiveRoom = { open: false };
15469
+ const remoteState = { remoteTurn: false, attached: false };
14863
15470
  const hiveExtension = createHiveExtension({
14864
15471
  baseUrl: opts.baseUrl,
14865
15472
  getToken: () => auth.getJwt(),
14866
15473
  knownTools: allowedTools,
14867
15474
  sandbox: shellSandbox,
14868
- roomState: hiveRoom
15475
+ roomState: hiveRoom,
15476
+ isRemoteAttached: () => remoteState.attached
15477
+ });
15478
+ const remoteExtension = createRemoteExtension({
15479
+ baseUrl: opts.baseUrl,
15480
+ getToken: async () => auth.getJwt(),
15481
+ sessionId,
15482
+ state: remoteState,
15483
+ isHiveOpen: () => hiveRoom.open
14869
15484
  });
14870
15485
  const choiceExtension = choiceOn ? createChoiceToolExtension({
14871
15486
  baseUrl: opts.baseUrl,
@@ -14873,7 +15488,7 @@ function buildRuntimeOptions(opts, sessionId) {
14873
15488
  sessionId,
14874
15489
  arm: () => opts.choiceArm ?? null,
14875
15490
  headless: !(tty.stdin && tty.stdout),
14876
- isRoomOpen: () => hiveRoom.open
15491
+ isRoomOpen: () => hiveRoom.open || remoteState.remoteTurn
14877
15492
  }) : undefined;
14878
15493
  const subagentsExtension = createSubagentsExtension({
14879
15494
  sessionId,
@@ -14894,7 +15509,7 @@ function buildRuntimeOptions(opts, sessionId) {
14894
15509
  const extensionFactories = [
14895
15510
  providerExtension,
14896
15511
  ...models.some((m) => m.id === ULTRAFAST_CATALOG_ID) ? [createUltrafastExtension(models.find((m) => m.id === ULTRAFAST_CATALOG_ID))] : [],
14897
- createFusionExtension(models[0].id, resolveFusionIds(models.map((m) => m.id), opts.fusion)),
15512
+ createFusionExtension(models[0].id, resolveFusionIds(models.map((m) => m.id), opts.fusion), () => fetchHasPremium({ baseUrl: opts.baseUrl, getToken: () => auth.getJwt() })),
14898
15513
  adsExtension,
14899
15514
  usageToolExtension,
14900
15515
  buyToolExtension,
@@ -14918,6 +15533,7 @@ function buildRuntimeOptions(opts, sessionId) {
14918
15533
  toolSearchExtension,
14919
15534
  subagentsExtension,
14920
15535
  hiveExtension,
15536
+ remoteExtension,
14921
15537
  ...guestLoginExtension ? [guestLoginExtension] : [],
14922
15538
  ...opts.guest ? [createGuestNotesExtension({ isGuest: () => auth.isGuest() })] : []
14923
15539
  ];
@@ -15743,6 +16359,7 @@ Couldn't finish GitHub sign-in. Continuing as a guest.
15743
16359
  getGravityContext = deps.startGravityPixel?.(jwt);
15744
16360
  } catch {}
15745
16361
  }
16362
+ const adContext = consentCurrent && process.env.FREEPI_NO_GRAVITY_PIXEL !== "1" ? { redact: redactSecrets } : undefined;
15746
16363
  if (me)
15747
16364
  updateAllowanceCache(me);
15748
16365
  guestHandoffVariant = me?.experiments?.guest_handoff ?? guestHandoffVariant;
@@ -15868,6 +16485,7 @@ ${r.text}
15868
16485
  costArm: versionCheck.costArm,
15869
16486
  updateLatest,
15870
16487
  getGravityContext,
16488
+ adContext,
15871
16489
  guest: Boolean(me?.guest),
15872
16490
  refillCard: refillSession,
15873
16491
  allowanceLine: allowanceLine(),