free-pi-cli 0.6.20 → 0.6.21
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.js +1 -1
- package/dist/{main-qswbbh2q.js → main-7sxf1v5b.js} +531 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ npm install -g free-pi-cli
|
|
|
19
19
|
free-pi-cli
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Requires Node 22.19 or newer. The command is `free-pi-cli`, not `pi`.
|
|
22
|
+
Requires Node 22.19 or newer. The command is `free-pi-cli`, not `pi`. If the built-in agent suggests `pi --session ...` on exit, run `npx free-pi-cli@latest --session <id>` instead (or `free-pi-cli --session <id>` if you installed it globally).
|
|
23
23
|
|
|
24
24
|
## Usage
|
|
25
25
|
|
|
@@ -63,6 +63,31 @@ free-pi may use your sessions to improve and train models. See the privacy polic
|
|
|
63
63
|
|
|
64
64
|
The agent can read files anywhere your user account can, but it only reads what your request needs. Project settings and extensions in a folder load only after you trust that folder.
|
|
65
65
|
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
bun install
|
|
70
|
+
bun test # runs against an in-process stub server; no real network or pi TUI
|
|
71
|
+
bun run typecheck
|
|
72
|
+
bun run build
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`FREEPI_BASE_URL` overrides the backend base URL (default `https://api.freepi.ai`), for pointing at a local dev server.
|
|
76
|
+
|
|
77
|
+
## Repo map
|
|
78
|
+
|
|
79
|
+
| Path | Purpose |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `src/index.ts`, `src/main.ts` | bin entry and top-level wiring |
|
|
82
|
+
| `src/run.ts` | orchestration: consent, login, token check, launch |
|
|
83
|
+
| `src/auth-flow.ts`, `src/credentials.ts` | sign-in clients; token storage (mode 0600) |
|
|
84
|
+
| `src/consent.ts` | consent screen text and parsing |
|
|
85
|
+
| `src/pi-launch.ts`, `src/provider.ts` | pi SDK integration: registers the free-pi provider and launches the TUI |
|
|
86
|
+
| `src/*-tool.ts`, `src/web-tools.ts`, `src/fusion.ts` | agent tools (images, docs, usage, todo, web) and Fusion model selection |
|
|
87
|
+
| `src/vendor/` | copies of the internal `shared` and `pi-ads` packages |
|
|
88
|
+
| `test/` | tests, one file per source module |
|
|
89
|
+
| `skills/` | bundled agent skills |
|
|
90
|
+
|
|
66
91
|
## Links
|
|
67
92
|
|
|
68
93
|
- Website: https://freepi.ai
|
|
@@ -70,3 +95,4 @@ The agent can read files anywhere your user account can, but it only reads what
|
|
|
70
95
|
- Terms: https://freepi.ai/terms
|
|
71
96
|
- Privacy: https://freepi.ai/privacy
|
|
72
97
|
- Changelog: [CHANGELOG.md](CHANGELOG.md)
|
|
98
|
+
- Contributing: [CONTRIBUTING.md](CONTRIBUTING.md). This repo is a one-way mirror of a private monorepo, and the file explains how pull requests flow back.
|
package/dist/index.js
CHANGED
|
@@ -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.21",
|
|
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",
|
|
@@ -6021,6 +6021,8 @@ var AdNextResponseSchema = object({
|
|
|
6021
6021
|
});
|
|
6022
6022
|
var GRAVITY_CONTEXT_HEADER = "x-gravity-context";
|
|
6023
6023
|
var GRAVITY_CONTEXT_MAX_BYTES = 8192;
|
|
6024
|
+
var AD_CONTEXT_HEADER = "x-ad-context";
|
|
6025
|
+
var AD_CONTEXT_MAX_CHARS = 1000;
|
|
6024
6026
|
var AdImpressionRequestSchema = object({
|
|
6025
6027
|
ad_id: string2(),
|
|
6026
6028
|
click_token: string2(),
|
|
@@ -6767,6 +6769,52 @@ function gravityHeader(deps) {
|
|
|
6767
6769
|
return {};
|
|
6768
6770
|
}
|
|
6769
6771
|
}
|
|
6772
|
+
function textParts(content) {
|
|
6773
|
+
if (typeof content === "string")
|
|
6774
|
+
return content;
|
|
6775
|
+
if (!Array.isArray(content))
|
|
6776
|
+
return "";
|
|
6777
|
+
return content.map((p) => p && p.type === "text" && typeof p.text === "string" ? p.text : "").filter(Boolean).join(`
|
|
6778
|
+
`);
|
|
6779
|
+
}
|
|
6780
|
+
function cut(redact, text) {
|
|
6781
|
+
const full = redact(text.slice(0, 20000));
|
|
6782
|
+
const s = full.slice(0, AD_CONTEXT_MAX_CHARS);
|
|
6783
|
+
return full.length > s.length && /\S/.test(full[s.length]) ? s.replace(/\S+$/, "").trimEnd() : s;
|
|
6784
|
+
}
|
|
6785
|
+
function adContextHeader(deps, ctx, prompt) {
|
|
6786
|
+
if (!deps.adContext || !ctx)
|
|
6787
|
+
return;
|
|
6788
|
+
try {
|
|
6789
|
+
const out = {};
|
|
6790
|
+
const entries = ctx.sessionManager.getBranch();
|
|
6791
|
+
if (prompt?.trim())
|
|
6792
|
+
out.user = cut(deps.adContext.redact, prompt.trim());
|
|
6793
|
+
for (let i = entries.length - 1;i >= 0 && !(out.user && out.assistant); i--) {
|
|
6794
|
+
const m = entries[i].type === "message" ? entries[i].message : undefined;
|
|
6795
|
+
if (m?.role !== "user" && m?.role !== "assistant" || out[m.role])
|
|
6796
|
+
continue;
|
|
6797
|
+
const text = textParts(m.content).trim();
|
|
6798
|
+
if (text)
|
|
6799
|
+
out[m.role] = cut(deps.adContext.redact, text);
|
|
6800
|
+
}
|
|
6801
|
+
if (!out.user && !out.assistant)
|
|
6802
|
+
return;
|
|
6803
|
+
const limit = Math.min(4096, 16 * 1024 - 2500 - (deps.getGravityContext?.()?.length ?? 0));
|
|
6804
|
+
if (limit < 600)
|
|
6805
|
+
return;
|
|
6806
|
+
let encoded = Buffer.from(JSON.stringify(out)).toString("base64url");
|
|
6807
|
+
while (encoded.length > limit && (out.user || out.assistant)) {
|
|
6808
|
+
for (const k of ["user", "assistant"])
|
|
6809
|
+
if (out[k])
|
|
6810
|
+
out[k] = out[k].slice(0, Math.floor(out[k].length / 2));
|
|
6811
|
+
encoded = Buffer.from(JSON.stringify(out)).toString("base64url");
|
|
6812
|
+
}
|
|
6813
|
+
return encoded;
|
|
6814
|
+
} catch {
|
|
6815
|
+
return;
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6770
6818
|
var IMP_TIMEOUT_MS = 3000;
|
|
6771
6819
|
function isGravityImpUrl(raw) {
|
|
6772
6820
|
if (!raw)
|
|
@@ -6786,9 +6834,11 @@ function fireGravityImpression(deps, ad) {
|
|
|
6786
6834
|
} catch {}
|
|
6787
6835
|
return true;
|
|
6788
6836
|
}
|
|
6789
|
-
async function fetchAdNext(deps, slot, trigger) {
|
|
6837
|
+
async function fetchAdNext(deps, slot, trigger, ctx, prompt) {
|
|
6790
6838
|
try {
|
|
6791
|
-
const
|
|
6839
|
+
const pair = adContextHeader(deps, ctx, prompt);
|
|
6840
|
+
const headers = { ...trigger ? { "x-ad-trigger": trigger } : {}, ...pair ? { [AD_CONTEXT_HEADER]: pair } : {} };
|
|
6841
|
+
const res = await authedFetch(deps, `/ads/next?slot=${slot}`, { headers });
|
|
6792
6842
|
if (res.status === 204 || !res.ok)
|
|
6793
6843
|
return;
|
|
6794
6844
|
return await res.json();
|
|
@@ -6888,8 +6938,8 @@ function createBannerRenderer(opts = {}) {
|
|
|
6888
6938
|
let lastPostedToken;
|
|
6889
6939
|
const screen = opts.screen ?? {};
|
|
6890
6940
|
return {
|
|
6891
|
-
async render(deps, ctx, config, trigger) {
|
|
6892
|
-
const ad = await fetchAdNext(deps, "banner", trigger);
|
|
6941
|
+
async render(deps, ctx, config, trigger, prompt) {
|
|
6942
|
+
const ad = await fetchAdNext(deps, "banner", trigger, ctx, prompt);
|
|
6893
6943
|
if (opts.isActive && !opts.isActive())
|
|
6894
6944
|
return;
|
|
6895
6945
|
if (!ad) {
|
|
@@ -6947,7 +6997,7 @@ function createInlineRenderer(screen = {}) {
|
|
|
6947
6997
|
return;
|
|
6948
6998
|
if (screen.banner === "gravity")
|
|
6949
6999
|
return;
|
|
6950
|
-
const ad = await fetchAdNext(deps, "inline");
|
|
7000
|
+
const ad = await fetchAdNext(deps, "inline", undefined, ctx);
|
|
6951
7001
|
if (!ad)
|
|
6952
7002
|
return;
|
|
6953
7003
|
const isGravity = ad.source === "gravity";
|
|
@@ -7165,6 +7215,7 @@ function createAdsExtension(opts) {
|
|
|
7165
7215
|
clientVersion: opts.clientVersion,
|
|
7166
7216
|
fetchImpl: opts.fetchImpl,
|
|
7167
7217
|
getGravityContext: opts.getGravityContext,
|
|
7218
|
+
adContext: opts.adContext,
|
|
7168
7219
|
onMe: opts.onMe
|
|
7169
7220
|
};
|
|
7170
7221
|
const config = loadConfig();
|
|
@@ -7180,14 +7231,14 @@ function createAdsExtension(opts) {
|
|
|
7180
7231
|
let launchAt = 0;
|
|
7181
7232
|
let lastBannerAt = 0;
|
|
7182
7233
|
let bannerInFlight = false;
|
|
7183
|
-
const renderBanner = async (ctx, trigger, force = false) => {
|
|
7234
|
+
const renderBanner = async (ctx, trigger, force = false, prompt) => {
|
|
7184
7235
|
if (stopped)
|
|
7185
7236
|
return;
|
|
7186
7237
|
if (!force && (bannerInFlight || Date.now() - lastBannerAt < BANNER_MIN_REFRESH_MS))
|
|
7187
7238
|
return;
|
|
7188
7239
|
bannerInFlight = true;
|
|
7189
7240
|
try {
|
|
7190
|
-
await banner.render(deps, ctx, config, trigger);
|
|
7241
|
+
await banner.render(deps, ctx, config, trigger, prompt);
|
|
7191
7242
|
} finally {
|
|
7192
7243
|
bannerInFlight = false;
|
|
7193
7244
|
lastBannerAt = Date.now();
|
|
@@ -7211,7 +7262,7 @@ function createAdsExtension(opts) {
|
|
|
7211
7262
|
await Promise.all([renderBanner(ctx, "launch", true), renderMeter(deps, ctx)]);
|
|
7212
7263
|
});
|
|
7213
7264
|
const LAUNCH_AD_MAX_AGE_MS = 60 * 60000;
|
|
7214
|
-
const newMessage = async (ctx) => {
|
|
7265
|
+
const newMessage = async (ctx, prompt) => {
|
|
7215
7266
|
if (launchTurnUnused) {
|
|
7216
7267
|
launchTurnUnused = false;
|
|
7217
7268
|
const token = await Promise.resolve(deps.getToken()).catch(() => {
|
|
@@ -7221,17 +7272,17 @@ function createAdsExtension(opts) {
|
|
|
7221
7272
|
return;
|
|
7222
7273
|
}
|
|
7223
7274
|
startTurn();
|
|
7224
|
-
renderBanner(ctx, "user_message", true);
|
|
7275
|
+
renderBanner(ctx, "user_message", true, prompt);
|
|
7225
7276
|
};
|
|
7226
7277
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
7227
7278
|
if (event.messageType !== "user")
|
|
7228
7279
|
return;
|
|
7229
|
-
await newMessage(ctx);
|
|
7280
|
+
await newMessage(ctx, event.prompt);
|
|
7230
7281
|
});
|
|
7231
7282
|
pi.on("input", (event, ctx) => {
|
|
7232
7283
|
const e = event;
|
|
7233
7284
|
if (e.streamingBehavior && e.source !== "extension")
|
|
7234
|
-
return newMessage(ctx).then(() => ({ action: "continue" }));
|
|
7285
|
+
return newMessage(ctx, e.text).then(() => ({ action: "continue" }));
|
|
7235
7286
|
return { action: "continue" };
|
|
7236
7287
|
});
|
|
7237
7288
|
pi.on("turn_end", async (_event, ctx) => {
|
|
@@ -8572,7 +8623,6 @@ session.
|
|
|
8572
8623
|
|
|
8573
8624
|
## Commands
|
|
8574
8625
|
- \`/usage\` — spend and remaining budget today
|
|
8575
|
-
- \`/support\` — visit today's advertiser
|
|
8576
8626
|
- \`/feedback <text>\` — send feedback to the free-pi team
|
|
8577
8627
|
- \`/tos\` — open the Terms of Service
|
|
8578
8628
|
- \`/privacy-policy\` — open the Privacy Policy
|
|
@@ -8608,9 +8658,6 @@ in to a server that uses OAuth, and \`free-pi-cli mcp logout <server>\`. Add
|
|
|
8608
8658
|
Inside a session, \`/mcp\` shows each server and its tools, and lets you sign
|
|
8609
8659
|
in, reconnect, or turn a server on or off.
|
|
8610
8660
|
|
|
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
8661
|
More at https://freepi.ai
|
|
8615
8662
|
`;
|
|
8616
8663
|
|
|
@@ -9682,9 +9729,16 @@ function createUpgradeNoticeExtension(opts) {
|
|
|
9682
9729
|
|
|
9683
9730
|
// src/tool-outcomes.ts
|
|
9684
9731
|
var MAX_IDS = 50;
|
|
9732
|
+
var TOOL_META_HEADER = "x-freepi-tool-meta";
|
|
9733
|
+
var COMPACTED_HEADER = "x-freepi-compacted";
|
|
9734
|
+
var META_BUDGET = 1500;
|
|
9685
9735
|
function createToolOutcomesExtension() {
|
|
9686
9736
|
const failed = [];
|
|
9687
9737
|
let evicted = false;
|
|
9738
|
+
const meta = [];
|
|
9739
|
+
let metaBytes = 3;
|
|
9740
|
+
let droppedMessages = 0;
|
|
9741
|
+
let pendingDropped = 0;
|
|
9688
9742
|
return {
|
|
9689
9743
|
name: "free-pi-tool-outcomes",
|
|
9690
9744
|
factory(pi) {
|
|
@@ -9698,7 +9752,37 @@ function createToolOutcomesExtension() {
|
|
|
9698
9752
|
evicted = true;
|
|
9699
9753
|
}
|
|
9700
9754
|
});
|
|
9755
|
+
pi.on("tool_execution_end", (event) => {
|
|
9756
|
+
const e = event;
|
|
9757
|
+
if (!e.toolCallId || !/^[\w.:-]{1,100}$/.test(e.toolCallId) || typeof e.durationMs !== "number")
|
|
9758
|
+
return;
|
|
9759
|
+
let exit = "-";
|
|
9760
|
+
if (e.toolName === "bash" || e.toolName === "powershell") {
|
|
9761
|
+
const m = e.isError ? /Command exited with code (\d{1,3})\s*$/.exec(e.result?.content?.map((b) => b.text ?? "").join("") ?? "") : null;
|
|
9762
|
+
exit = m ? m[1] : e.isError ? "-" : "0";
|
|
9763
|
+
}
|
|
9764
|
+
const entry = `${e.toolCallId}~${Math.max(0, Math.round(e.durationMs))}~${exit}~${e.result?.details?.truncation?.truncated ? 1 : 0}`;
|
|
9765
|
+
if (entry.length > 150)
|
|
9766
|
+
return;
|
|
9767
|
+
meta.push(entry);
|
|
9768
|
+
metaBytes += entry.length + 1;
|
|
9769
|
+
while (metaBytes > META_BUDGET && meta.length > 1)
|
|
9770
|
+
metaBytes -= meta.shift().length + 1;
|
|
9771
|
+
});
|
|
9772
|
+
pi.on("session_before_compact", (event) => {
|
|
9773
|
+
const p = event.preparation;
|
|
9774
|
+
pendingDropped = (p?.messagesToSummarize?.length ?? 0) + (p?.turnPrefixMessages?.length ?? 0);
|
|
9775
|
+
});
|
|
9776
|
+
pi.on("session_compact", () => {
|
|
9777
|
+
droppedMessages = pendingDropped;
|
|
9778
|
+
});
|
|
9701
9779
|
pi.on("before_provider_headers", (event) => {
|
|
9780
|
+
if (meta.length)
|
|
9781
|
+
event.headers[TOOL_META_HEADER] = `v1:${meta.join(",")}`;
|
|
9782
|
+
if (droppedMessages) {
|
|
9783
|
+
event.headers[COMPACTED_HEADER] = `v1:${droppedMessages}`;
|
|
9784
|
+
droppedMessages = 0;
|
|
9785
|
+
}
|
|
9702
9786
|
event.headers["x-freepi-tool-errors"] = `${evicted ? "v1x" : "v1"}:${failed.join(",")}`;
|
|
9703
9787
|
});
|
|
9704
9788
|
}
|
|
@@ -10141,6 +10225,10 @@ function mergeAttribution(a, b) {
|
|
|
10141
10225
|
|
|
10142
10226
|
// src/changelog.ts
|
|
10143
10227
|
var CHANGELOG_HIGHLIGHTS = [
|
|
10228
|
+
{
|
|
10229
|
+
version: "0.6.21",
|
|
10230
|
+
highlights: ["The /support command is removed."]
|
|
10231
|
+
},
|
|
10144
10232
|
{
|
|
10145
10233
|
version: "0.6.20",
|
|
10146
10234
|
highlights: ["Error messages are now plain sentences instead of raw server text."]
|
|
@@ -11634,15 +11722,6 @@ async function buyCredits(opts, ctx) {
|
|
|
11634
11722
|
(opts.openBrowserImpl ?? openBrowser)(page.url);
|
|
11635
11723
|
ctx.ui.notify(buyPageText(page.url), "info");
|
|
11636
11724
|
}
|
|
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
11725
|
var TERMS_URL = "https://freepi.ai/terms";
|
|
11647
11726
|
var PRIVACY_URL = "https://freepi.ai/privacy";
|
|
11648
11727
|
async function openTos(opts, ctx) {
|
|
@@ -11988,10 +12067,6 @@ function createFreePiCommandsExtension(opts) {
|
|
|
11988
12067
|
description: FEEDBACK_DESCRIPTION,
|
|
11989
12068
|
handler: (args, ctx) => sendFeedback(opts, args, ctx)
|
|
11990
12069
|
});
|
|
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
12070
|
pi.registerCommand("invite", {
|
|
11996
12071
|
description: "Get your invite code and link: you and a friend both get +5k credits/day.",
|
|
11997
12072
|
handler: (_args, ctx) => showInvite(opts, ctx)
|
|
@@ -12254,7 +12329,6 @@ var ALL_COMMANDS = [
|
|
|
12254
12329
|
{ name: "/usage", description: "credits left today and refill time" },
|
|
12255
12330
|
{ name: "/import", description: "continue a Claude Code or Codex session" },
|
|
12256
12331
|
{ name: "/hive", description: "share this session with friends" },
|
|
12257
|
-
{ name: "/support", description: "visit today's advertiser" },
|
|
12258
12332
|
{ name: "/tos", description: "terms of service" },
|
|
12259
12333
|
{ name: "/privacy-policy", description: "privacy policy" },
|
|
12260
12334
|
{ name: "/buy-credits", description: "get more usage" },
|
|
@@ -13477,6 +13551,10 @@ function createHiveExtension(opts) {
|
|
|
13477
13551
|
roomWaiter = undefined;
|
|
13478
13552
|
}
|
|
13479
13553
|
async function start(ctx) {
|
|
13554
|
+
if (opts.isRemoteAttached?.()) {
|
|
13555
|
+
notify("Hive: turn the Telegram remote off first (/remote off).");
|
|
13556
|
+
return;
|
|
13557
|
+
}
|
|
13480
13558
|
if (ws || starting) {
|
|
13481
13559
|
notify(roomUrl ? `Hive room: ${roomUrl}` : "Hive: still connecting...");
|
|
13482
13560
|
return;
|
|
@@ -13805,6 +13883,414 @@ ${decision.reason}`, ACTION_DIALOG_MS);
|
|
|
13805
13883
|
};
|
|
13806
13884
|
}
|
|
13807
13885
|
|
|
13886
|
+
// src/remote.ts
|
|
13887
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
13888
|
+
import { basename as basename5 } from "node:path";
|
|
13889
|
+
|
|
13890
|
+
// ../shared/src/remote.ts
|
|
13891
|
+
var REMOTE_MAX_TITLE_CHARS = 80;
|
|
13892
|
+
var REMOTE_MAX_COMMAND_CHARS = 2000;
|
|
13893
|
+
var IdSchema = string2().min(1).max(64);
|
|
13894
|
+
var RemoteChoiceSchema = _enum(["once", "turn", "deny"]);
|
|
13895
|
+
var RemoteSessionFrameSchema = discriminatedUnion("type", [
|
|
13896
|
+
HiveAuthFrameSchema,
|
|
13897
|
+
object({ type: literal("hello"), session_id: IdSchema, title: string2().max(REMOTE_MAX_TITLE_CHARS) }),
|
|
13898
|
+
object({ type: literal("entry"), entry: HiveEntrySchema }),
|
|
13899
|
+
object({ type: literal("busy"), settled: boolean2() }),
|
|
13900
|
+
object({ type: literal("prompt_result"), prompt_id: IdSchema, accepted: boolean2(), reason: string2().max(100).optional() }),
|
|
13901
|
+
object({ type: literal("question"), id: IdSchema, text: string2().max(500), command: string2().max(REMOTE_MAX_COMMAND_CHARS) }),
|
|
13902
|
+
object({ type: literal("question_closed"), id: IdSchema })
|
|
13903
|
+
]);
|
|
13904
|
+
var RemoteServerFrameSchema = discriminatedUnion("type", [
|
|
13905
|
+
object({ type: literal("ready"), linked: boolean2(), link_url: string2().optional() }),
|
|
13906
|
+
object({ type: literal("prompt"), prompt_id: IdSchema, text: string2().min(1).max(HIVE_MAX_PROMPT_CHARS) }),
|
|
13907
|
+
object({ type: literal("answer"), question_id: IdSchema, choice: RemoteChoiceSchema }),
|
|
13908
|
+
object({ type: literal("stop") }),
|
|
13909
|
+
object({ type: literal("error"), code: string2() }),
|
|
13910
|
+
object({ type: literal("closed"), reason: string2() })
|
|
13911
|
+
]);
|
|
13912
|
+
var RemoteLinkCodeResponseSchema = object({ url: string2(), expires_at: string2() });
|
|
13913
|
+
var RemoteLinkStatusSchema = object({ linked: boolean2() });
|
|
13914
|
+
|
|
13915
|
+
// src/remote.ts
|
|
13916
|
+
var OPEN2 = 1;
|
|
13917
|
+
var MCP_RESOURCE_TOOLS3 = ["list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"];
|
|
13918
|
+
var HARD_BLOCKED = new Set([SUBAGENT_TOOL, SEND_MESSAGE_TOOL, BUY_TOOL_NAME]);
|
|
13919
|
+
function wsUrl2(baseUrl) {
|
|
13920
|
+
const url = new URL("/remote/ws", baseUrl);
|
|
13921
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
13922
|
+
return url.toString();
|
|
13923
|
+
}
|
|
13924
|
+
function createRemoteExtension(opts) {
|
|
13925
|
+
return {
|
|
13926
|
+
name: "free-pi-remote",
|
|
13927
|
+
factory(pi) {
|
|
13928
|
+
let ws;
|
|
13929
|
+
let ctxRef;
|
|
13930
|
+
let attachedCwd = "";
|
|
13931
|
+
let title = "";
|
|
13932
|
+
let settled = true;
|
|
13933
|
+
let compacting = false;
|
|
13934
|
+
let allowTurn = false;
|
|
13935
|
+
let localDetach = false;
|
|
13936
|
+
let reconnectAttempt = 0;
|
|
13937
|
+
let reconnectTimer;
|
|
13938
|
+
let injectTimer;
|
|
13939
|
+
let questionChain = Promise.resolve();
|
|
13940
|
+
let openQuestion;
|
|
13941
|
+
const notify = (message, type = "info") => ctxRef?.ui.notify(message, type);
|
|
13942
|
+
const send = (frame) => {
|
|
13943
|
+
if (ws?.readyState === OPEN2)
|
|
13944
|
+
ws.send(JSON.stringify(frame));
|
|
13945
|
+
};
|
|
13946
|
+
const sendEntry = (entry) => send({ type: "entry", entry });
|
|
13947
|
+
function setSettled(next) {
|
|
13948
|
+
if (settled === next)
|
|
13949
|
+
return;
|
|
13950
|
+
settled = next;
|
|
13951
|
+
send({ type: "busy", settled });
|
|
13952
|
+
}
|
|
13953
|
+
function finishQuestion(choice, reason) {
|
|
13954
|
+
const question = openQuestion;
|
|
13955
|
+
if (!question)
|
|
13956
|
+
return;
|
|
13957
|
+
openQuestion = undefined;
|
|
13958
|
+
question.finish(choice, reason);
|
|
13959
|
+
if (choice === undefined)
|
|
13960
|
+
send({ type: "question_closed", id: question.id });
|
|
13961
|
+
}
|
|
13962
|
+
function cancelQuestion(reason) {
|
|
13963
|
+
finishQuestion(undefined, reason);
|
|
13964
|
+
}
|
|
13965
|
+
function clearRemoteState(reason = "turn ended") {
|
|
13966
|
+
opts.state.remoteTurn = false;
|
|
13967
|
+
allowTurn = false;
|
|
13968
|
+
cancelQuestion(reason);
|
|
13969
|
+
}
|
|
13970
|
+
function endApprovals(reason) {
|
|
13971
|
+
allowTurn = false;
|
|
13972
|
+
cancelQuestion(reason);
|
|
13973
|
+
}
|
|
13974
|
+
function recoverRejectedPrompt() {
|
|
13975
|
+
setSettled(true);
|
|
13976
|
+
clearRemoteState();
|
|
13977
|
+
}
|
|
13978
|
+
function clearSocket() {
|
|
13979
|
+
const old = ws;
|
|
13980
|
+
ws = undefined;
|
|
13981
|
+
clearTimeout(reconnectTimer);
|
|
13982
|
+
reconnectTimer = undefined;
|
|
13983
|
+
ctxRef?.ui.setStatus("remote", undefined);
|
|
13984
|
+
cancelQuestion("disconnected");
|
|
13985
|
+
try {
|
|
13986
|
+
old?.close();
|
|
13987
|
+
} catch {}
|
|
13988
|
+
}
|
|
13989
|
+
function detach(notice) {
|
|
13990
|
+
localDetach = true;
|
|
13991
|
+
opts.state.attached = false;
|
|
13992
|
+
endApprovals("detached");
|
|
13993
|
+
clearSocket();
|
|
13994
|
+
attachedCwd = "";
|
|
13995
|
+
reconnectAttempt = 0;
|
|
13996
|
+
if (notice)
|
|
13997
|
+
notify(notice);
|
|
13998
|
+
}
|
|
13999
|
+
function questionFor(event, ctx) {
|
|
14000
|
+
const toolName = String(event.toolName);
|
|
14001
|
+
const input = event.input ?? {};
|
|
14002
|
+
const summary = toolName === "bash" ? String(input.command ?? "") : toolName + " " + JSON.stringify(input);
|
|
14003
|
+
const command = summary.slice(0, 2000);
|
|
14004
|
+
const run = async () => {
|
|
14005
|
+
if (!opts.state.remoteTurn)
|
|
14006
|
+
return { block: true, reason: "Remote: turn ended before approval" };
|
|
14007
|
+
if (allowTurn)
|
|
14008
|
+
return;
|
|
14009
|
+
if (ws?.readyState !== OPEN2)
|
|
14010
|
+
return { block: true, reason: "Remote: disconnected before approval" };
|
|
14011
|
+
const id = randomUUID2();
|
|
14012
|
+
let timer;
|
|
14013
|
+
let abortListener;
|
|
14014
|
+
const answer = await new Promise((resolve) => {
|
|
14015
|
+
openQuestion = {
|
|
14016
|
+
id,
|
|
14017
|
+
finish: (choice, reason) => {
|
|
14018
|
+
clearTimeout(timer);
|
|
14019
|
+
if (abortListener && ctx.signal)
|
|
14020
|
+
ctx.signal.removeEventListener("abort", abortListener);
|
|
14021
|
+
resolve({ choice, reason });
|
|
14022
|
+
}
|
|
14023
|
+
};
|
|
14024
|
+
const timeoutMs = opts.questionTimeoutMs ?? 5 * 60000;
|
|
14025
|
+
timer = setTimeout(() => finishQuestion(undefined, "timed out"), timeoutMs);
|
|
14026
|
+
timer.unref?.();
|
|
14027
|
+
abortListener = () => finishQuestion(undefined, "turn aborted");
|
|
14028
|
+
if (ctx.signal?.aborted)
|
|
14029
|
+
abortListener();
|
|
14030
|
+
else
|
|
14031
|
+
ctx.signal?.addEventListener("abort", abortListener, { once: true });
|
|
14032
|
+
send({ type: "question", id, text: "The agent wants to run this:", command });
|
|
14033
|
+
});
|
|
14034
|
+
if (answer.choice === "deny")
|
|
14035
|
+
return { block: true, reason: "Remote: denied from Telegram" };
|
|
14036
|
+
if (answer.choice === "turn")
|
|
14037
|
+
allowTurn = true;
|
|
14038
|
+
if (answer.choice === "once" || answer.choice === "turn")
|
|
14039
|
+
return;
|
|
14040
|
+
const why = answer.reason ?? "approval ended";
|
|
14041
|
+
return { block: true, reason: "Remote: " + why };
|
|
14042
|
+
};
|
|
14043
|
+
const current = questionChain.then(run, run);
|
|
14044
|
+
questionChain = current.catch(() => {
|
|
14045
|
+
return;
|
|
14046
|
+
});
|
|
14047
|
+
return current;
|
|
14048
|
+
}
|
|
14049
|
+
function handle(frame) {
|
|
14050
|
+
switch (frame.type) {
|
|
14051
|
+
case "ready":
|
|
14052
|
+
if (!frame.linked && frame.link_url) {
|
|
14053
|
+
notify("Remote: open this link in Telegram to connect your account: " + frame.link_url);
|
|
14054
|
+
} else if (frame.linked) {
|
|
14055
|
+
notify("Remote is on. Messages you send to the free-pi bot in Telegram go to this session.");
|
|
14056
|
+
}
|
|
14057
|
+
ctxRef?.ui.setStatus("remote", "remote: on");
|
|
14058
|
+
reconnectAttempt = 0;
|
|
14059
|
+
if (!settled)
|
|
14060
|
+
send({ type: "busy", settled: false });
|
|
14061
|
+
break;
|
|
14062
|
+
case "prompt": {
|
|
14063
|
+
if (!settled || compacting) {
|
|
14064
|
+
send({ type: "prompt_result", prompt_id: frame.prompt_id, accepted: false, reason: "busy" });
|
|
14065
|
+
break;
|
|
14066
|
+
}
|
|
14067
|
+
opts.state.remoteTurn = true;
|
|
14068
|
+
allowTurn = false;
|
|
14069
|
+
setSettled(false);
|
|
14070
|
+
clearTimeout(injectTimer);
|
|
14071
|
+
injectTimer = setTimeout(() => {
|
|
14072
|
+
injectTimer = undefined;
|
|
14073
|
+
if (ctxRef?.isIdle() !== false)
|
|
14074
|
+
recoverRejectedPrompt();
|
|
14075
|
+
}, opts.injectGraceMs ?? 2000);
|
|
14076
|
+
injectTimer.unref?.();
|
|
14077
|
+
try {
|
|
14078
|
+
Promise.resolve(pi.sendUserMessage(frame.text, { expandPromptTemplates: false })).catch(recoverRejectedPrompt);
|
|
14079
|
+
send({ type: "prompt_result", prompt_id: frame.prompt_id, accepted: true });
|
|
14080
|
+
} catch {
|
|
14081
|
+
recoverRejectedPrompt();
|
|
14082
|
+
send({ type: "prompt_result", prompt_id: frame.prompt_id, accepted: false, reason: "error" });
|
|
14083
|
+
}
|
|
14084
|
+
break;
|
|
14085
|
+
}
|
|
14086
|
+
case "answer":
|
|
14087
|
+
if (openQuestion?.id === frame.question_id)
|
|
14088
|
+
finishQuestion(frame.choice);
|
|
14089
|
+
break;
|
|
14090
|
+
case "stop":
|
|
14091
|
+
cancelQuestion("stopped");
|
|
14092
|
+
if (!settled)
|
|
14093
|
+
ctxRef?.abort();
|
|
14094
|
+
break;
|
|
14095
|
+
case "closed":
|
|
14096
|
+
if (frame.reason === "shutdown")
|
|
14097
|
+
break;
|
|
14098
|
+
detach(frame.reason === "replaced" ? "Remote moved to another session." : "Remote closed.");
|
|
14099
|
+
break;
|
|
14100
|
+
case "error":
|
|
14101
|
+
notify("Remote: the server could not process the connection.", "warning");
|
|
14102
|
+
break;
|
|
14103
|
+
}
|
|
14104
|
+
}
|
|
14105
|
+
function connect() {
|
|
14106
|
+
let socket;
|
|
14107
|
+
try {
|
|
14108
|
+
const Impl = opts.WebSocketImpl ?? WebSocket;
|
|
14109
|
+
socket = new Impl(wsUrl2(opts.baseUrl));
|
|
14110
|
+
} catch {
|
|
14111
|
+
retryConnect();
|
|
14112
|
+
return;
|
|
14113
|
+
}
|
|
14114
|
+
ws = socket;
|
|
14115
|
+
localDetach = false;
|
|
14116
|
+
socket.onopen = async () => {
|
|
14117
|
+
if (ws !== socket)
|
|
14118
|
+
return;
|
|
14119
|
+
try {
|
|
14120
|
+
const token = await opts.getToken();
|
|
14121
|
+
if (ws !== socket)
|
|
14122
|
+
return;
|
|
14123
|
+
socket.send(JSON.stringify({ type: "auth", token }));
|
|
14124
|
+
socket.send(JSON.stringify({ type: "hello", session_id: opts.sessionId, title }));
|
|
14125
|
+
} catch {
|
|
14126
|
+
socket.close();
|
|
14127
|
+
}
|
|
14128
|
+
};
|
|
14129
|
+
socket.onerror = () => {
|
|
14130
|
+
if (ws === socket)
|
|
14131
|
+
socket.close();
|
|
14132
|
+
};
|
|
14133
|
+
socket.onclose = () => {
|
|
14134
|
+
if (ws !== socket)
|
|
14135
|
+
return;
|
|
14136
|
+
ws = undefined;
|
|
14137
|
+
ctxRef?.ui.setStatus("remote", undefined);
|
|
14138
|
+
cancelQuestion("disconnected");
|
|
14139
|
+
if (!localDetach)
|
|
14140
|
+
retryConnect();
|
|
14141
|
+
};
|
|
14142
|
+
socket.onmessage = (event) => {
|
|
14143
|
+
if (ws !== socket || typeof event.data !== "string")
|
|
14144
|
+
return;
|
|
14145
|
+
let json;
|
|
14146
|
+
try {
|
|
14147
|
+
json = JSON.parse(event.data);
|
|
14148
|
+
} catch {
|
|
14149
|
+
return;
|
|
14150
|
+
}
|
|
14151
|
+
const parsed = RemoteServerFrameSchema.safeParse(json);
|
|
14152
|
+
if (parsed.success)
|
|
14153
|
+
handle(parsed.data);
|
|
14154
|
+
};
|
|
14155
|
+
}
|
|
14156
|
+
function retryConnect() {
|
|
14157
|
+
if (localDetach)
|
|
14158
|
+
return;
|
|
14159
|
+
const delays = opts.reconnectDelaysMs ?? [1000, 2000, 5000, 15000, 30000];
|
|
14160
|
+
if (reconnectAttempt >= delays.length) {
|
|
14161
|
+
localDetach = true;
|
|
14162
|
+
opts.state.attached = false;
|
|
14163
|
+
endApprovals("disconnected");
|
|
14164
|
+
notify("Remote: lost the connection. Run /remote to attach again.", "warning");
|
|
14165
|
+
return;
|
|
14166
|
+
}
|
|
14167
|
+
const delay = delays[reconnectAttempt++];
|
|
14168
|
+
reconnectTimer = setTimeout(() => {
|
|
14169
|
+
reconnectTimer = undefined;
|
|
14170
|
+
connect();
|
|
14171
|
+
}, delay);
|
|
14172
|
+
reconnectTimer.unref?.();
|
|
14173
|
+
}
|
|
14174
|
+
function attach(ctx) {
|
|
14175
|
+
if (ws || reconnectTimer) {
|
|
14176
|
+
notify("Remote is already attached.");
|
|
14177
|
+
return;
|
|
14178
|
+
}
|
|
14179
|
+
ctxRef = ctx;
|
|
14180
|
+
if (opts.isHiveOpen?.()) {
|
|
14181
|
+
notify("Remote: end the Hive room first (/hive end).");
|
|
14182
|
+
return;
|
|
14183
|
+
}
|
|
14184
|
+
opts.state.attached = true;
|
|
14185
|
+
attachedCwd = ctx.cwd;
|
|
14186
|
+
title = basename5(ctx.cwd).slice(0, 80);
|
|
14187
|
+
settled = ctx.isIdle();
|
|
14188
|
+
compacting = false;
|
|
14189
|
+
localDetach = false;
|
|
14190
|
+
reconnectAttempt = 0;
|
|
14191
|
+
connect();
|
|
14192
|
+
}
|
|
14193
|
+
pi.on("tool_call", (event, ctx) => {
|
|
14194
|
+
if (!opts.state.remoteTurn)
|
|
14195
|
+
return;
|
|
14196
|
+
ctxRef = ctx;
|
|
14197
|
+
if (HARD_BLOCKED.has(event.toolName)) {
|
|
14198
|
+
return { block: true, reason: "Remote: this tool is not run for a prompt sent from Telegram" };
|
|
14199
|
+
}
|
|
14200
|
+
const needsApproval = event.toolName === "bash" || event.toolName.startsWith("mcp__") || MCP_RESOURCE_TOOLS3.includes(event.toolName) || Boolean(event.parentToolCallId);
|
|
14201
|
+
return needsApproval ? questionFor(event, ctx) : undefined;
|
|
14202
|
+
});
|
|
14203
|
+
pi.on("agent_start", () => {
|
|
14204
|
+
clearTimeout(injectTimer);
|
|
14205
|
+
injectTimer = undefined;
|
|
14206
|
+
setSettled(false);
|
|
14207
|
+
});
|
|
14208
|
+
pi.on("agent_settled", () => {
|
|
14209
|
+
setSettled(true);
|
|
14210
|
+
clearRemoteState();
|
|
14211
|
+
sendEntry({ kind: "settled" });
|
|
14212
|
+
});
|
|
14213
|
+
pi.on("session_before_compact", (_event, ctx) => {
|
|
14214
|
+
ctxRef = ctx;
|
|
14215
|
+
compacting = true;
|
|
14216
|
+
setSettled(false);
|
|
14217
|
+
});
|
|
14218
|
+
const compactionEnded = (_event, ctx) => {
|
|
14219
|
+
ctxRef = ctx;
|
|
14220
|
+
compacting = false;
|
|
14221
|
+
if (ctx.isIdle())
|
|
14222
|
+
setSettled(true);
|
|
14223
|
+
};
|
|
14224
|
+
pi.on("session_compact", compactionEnded);
|
|
14225
|
+
pi.on("session_compact_failed", compactionEnded);
|
|
14226
|
+
pi.on("session_start", (_event, ctx) => {
|
|
14227
|
+
ctxRef = ctx;
|
|
14228
|
+
if (attachedCwd && ctx.cwd !== attachedCwd)
|
|
14229
|
+
detach();
|
|
14230
|
+
});
|
|
14231
|
+
pi.on("session_before_switch", () => detach());
|
|
14232
|
+
pi.on("session_shutdown", () => detach());
|
|
14233
|
+
pi.on("input", (event) => {
|
|
14234
|
+
if (event.source !== "extension")
|
|
14235
|
+
sendEntry({ kind: "prompt", text: event.text.slice(0, HIVE_MAX_PROMPT_CHARS) });
|
|
14236
|
+
return { action: "continue" };
|
|
14237
|
+
});
|
|
14238
|
+
pi.on("message_end", (event) => {
|
|
14239
|
+
const message = event.message;
|
|
14240
|
+
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
14241
|
+
return;
|
|
14242
|
+
const text = message.content.map((part) => part.type === "text" && typeof part.text === "string" ? part.text : "").join("");
|
|
14243
|
+
for (let i = 0;i < text.length; i += HIVE_MAX_ASSISTANT_CHARS) {
|
|
14244
|
+
sendEntry({ kind: "assistant", text: text.slice(i, i + HIVE_MAX_ASSISTANT_CHARS) });
|
|
14245
|
+
}
|
|
14246
|
+
});
|
|
14247
|
+
pi.on("tool_execution_end", (event) => {
|
|
14248
|
+
sendEntry({ kind: "tool", name: event.toolName, is_error: event.isError });
|
|
14249
|
+
});
|
|
14250
|
+
if (process.env.FREEPI_REMOTE === "1") {
|
|
14251
|
+
pi.registerCommand("remote", {
|
|
14252
|
+
description: "Attach this session to Telegram: /remote, /remote off, /remote unlink",
|
|
14253
|
+
handler: async (args, ctx) => {
|
|
14254
|
+
ctxRef = ctx;
|
|
14255
|
+
const sub = args.trim().toLowerCase();
|
|
14256
|
+
if (sub === "")
|
|
14257
|
+
return attach(ctx);
|
|
14258
|
+
if (sub === "off") {
|
|
14259
|
+
detach();
|
|
14260
|
+
notify("Remote is off.");
|
|
14261
|
+
return;
|
|
14262
|
+
}
|
|
14263
|
+
if (sub === "unlink") {
|
|
14264
|
+
allowTurn = false;
|
|
14265
|
+
cancelQuestion("unlinked");
|
|
14266
|
+
const token = await opts.getToken().catch(() => {
|
|
14267
|
+
return;
|
|
14268
|
+
});
|
|
14269
|
+
if (!token) {
|
|
14270
|
+
notify("Remote: couldn't read your free-pi login.", "error");
|
|
14271
|
+
return;
|
|
14272
|
+
}
|
|
14273
|
+
try {
|
|
14274
|
+
const response = await (opts.fetchImpl ?? fetch)(opts.baseUrl + "/remote/link", {
|
|
14275
|
+
method: "DELETE",
|
|
14276
|
+
headers: { authorization: "Bearer " + token }
|
|
14277
|
+
});
|
|
14278
|
+
if (response.ok)
|
|
14279
|
+
detach();
|
|
14280
|
+
notify(response.ok ? "Remote account unlinked." : "Remote: couldn't unlink the account.", response.ok ? "info" : "warning");
|
|
14281
|
+
} catch {
|
|
14282
|
+
notify("Remote: couldn't unlink the account.", "warning");
|
|
14283
|
+
}
|
|
14284
|
+
return;
|
|
14285
|
+
}
|
|
14286
|
+
notify("Usage: /remote, /remote off, or /remote unlink.");
|
|
14287
|
+
}
|
|
14288
|
+
});
|
|
14289
|
+
}
|
|
14290
|
+
}
|
|
14291
|
+
};
|
|
14292
|
+
}
|
|
14293
|
+
|
|
13808
14294
|
// src/shell-sandbox.ts
|
|
13809
14295
|
import { spawnSync } from "node:child_process";
|
|
13810
14296
|
import {
|
|
@@ -14765,7 +15251,6 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
14765
15251
|
name: "free-pi-provider",
|
|
14766
15252
|
factory: (pi) => registerProviders(pi, sessionId)
|
|
14767
15253
|
};
|
|
14768
|
-
let advertiserUrl;
|
|
14769
15254
|
const adsExtension = createAdsExtension({
|
|
14770
15255
|
registerMeterRefresh: (refresh) => auth.setOnSignedIn(() => void refresh().catch(() => {})),
|
|
14771
15256
|
baseUrl: opts.baseUrl,
|
|
@@ -14773,9 +15258,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
14773
15258
|
sessionId,
|
|
14774
15259
|
clientVersion: CLI_VERSION,
|
|
14775
15260
|
getGravityContext: opts.getGravityContext,
|
|
14776
|
-
|
|
14777
|
-
advertiserUrl = u;
|
|
14778
|
-
},
|
|
15261
|
+
adContext: opts.adContext,
|
|
14779
15262
|
onMe: (me) => {
|
|
14780
15263
|
updateAllowanceCache(me);
|
|
14781
15264
|
}
|
|
@@ -14829,7 +15312,6 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
14829
15312
|
const commandsExtension = createFreePiCommandsExtension({
|
|
14830
15313
|
baseUrl: opts.baseUrl,
|
|
14831
15314
|
getToken: () => auth.getJwt(),
|
|
14832
|
-
getAdvertiserUrl: () => advertiserUrl,
|
|
14833
15315
|
credentialsPath: opts.credentialsPath,
|
|
14834
15316
|
agentDir: opts.agentDir,
|
|
14835
15317
|
isGuest: () => auth.isGuest(),
|
|
@@ -14860,12 +15342,21 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
14860
15342
|
const codemodeExtension = { name: "pi-codemode", factory: createCodemodeExtension({ models: false }) };
|
|
14861
15343
|
const toolSearchExtension = { name: "pi-tool-search", factory: createToolSearchExtension() };
|
|
14862
15344
|
const hiveRoom = { open: false };
|
|
15345
|
+
const remoteState = { remoteTurn: false, attached: false };
|
|
14863
15346
|
const hiveExtension = createHiveExtension({
|
|
14864
15347
|
baseUrl: opts.baseUrl,
|
|
14865
15348
|
getToken: () => auth.getJwt(),
|
|
14866
15349
|
knownTools: allowedTools,
|
|
14867
15350
|
sandbox: shellSandbox,
|
|
14868
|
-
roomState: hiveRoom
|
|
15351
|
+
roomState: hiveRoom,
|
|
15352
|
+
isRemoteAttached: () => remoteState.attached
|
|
15353
|
+
});
|
|
15354
|
+
const remoteExtension = createRemoteExtension({
|
|
15355
|
+
baseUrl: opts.baseUrl,
|
|
15356
|
+
getToken: async () => auth.getJwt(),
|
|
15357
|
+
sessionId,
|
|
15358
|
+
state: remoteState,
|
|
15359
|
+
isHiveOpen: () => hiveRoom.open
|
|
14869
15360
|
});
|
|
14870
15361
|
const choiceExtension = choiceOn ? createChoiceToolExtension({
|
|
14871
15362
|
baseUrl: opts.baseUrl,
|
|
@@ -14873,7 +15364,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
14873
15364
|
sessionId,
|
|
14874
15365
|
arm: () => opts.choiceArm ?? null,
|
|
14875
15366
|
headless: !(tty.stdin && tty.stdout),
|
|
14876
|
-
isRoomOpen: () => hiveRoom.open
|
|
15367
|
+
isRoomOpen: () => hiveRoom.open || remoteState.remoteTurn
|
|
14877
15368
|
}) : undefined;
|
|
14878
15369
|
const subagentsExtension = createSubagentsExtension({
|
|
14879
15370
|
sessionId,
|
|
@@ -14918,6 +15409,7 @@ function buildRuntimeOptions(opts, sessionId) {
|
|
|
14918
15409
|
toolSearchExtension,
|
|
14919
15410
|
subagentsExtension,
|
|
14920
15411
|
hiveExtension,
|
|
15412
|
+
remoteExtension,
|
|
14921
15413
|
...guestLoginExtension ? [guestLoginExtension] : [],
|
|
14922
15414
|
...opts.guest ? [createGuestNotesExtension({ isGuest: () => auth.isGuest() })] : []
|
|
14923
15415
|
];
|
|
@@ -15743,6 +16235,7 @@ Couldn't finish GitHub sign-in. Continuing as a guest.
|
|
|
15743
16235
|
getGravityContext = deps.startGravityPixel?.(jwt);
|
|
15744
16236
|
} catch {}
|
|
15745
16237
|
}
|
|
16238
|
+
const adContext = consentCurrent && process.env.FREEPI_NO_GRAVITY_PIXEL !== "1" ? { redact: redactSecrets } : undefined;
|
|
15746
16239
|
if (me)
|
|
15747
16240
|
updateAllowanceCache(me);
|
|
15748
16241
|
guestHandoffVariant = me?.experiments?.guest_handoff ?? guestHandoffVariant;
|
|
@@ -15868,6 +16361,7 @@ ${r.text}
|
|
|
15868
16361
|
costArm: versionCheck.costArm,
|
|
15869
16362
|
updateLatest,
|
|
15870
16363
|
getGravityContext,
|
|
16364
|
+
adContext,
|
|
15871
16365
|
guest: Boolean(me?.guest),
|
|
15872
16366
|
refillCard: refillSession,
|
|
15873
16367
|
allowanceLine: allowanceLine(),
|
package/package.json
CHANGED