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.
- package/README.md +27 -1
- package/dist/{index-xkn61g24.js → index-vz2s5sjf.js} +1054 -287
- package/dist/index.js +1 -1
- package/dist/{main-qswbbh2q.js → main-7j7r4bhj.js} +666 -48
- package/package.json +2 -2
|
@@ -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.
|
|
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.
|
|
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({
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
9293
|
-
|
|
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 = "
|
|
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
|
|
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
|
-
|
|
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(),
|