free-pi-cli 0.6.19 → 0.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/{main-qn3gsj3n.js → main-qswbbh2q.js} +559 -126
- package/package.json +2 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/main.ts
|
|
4
4
|
import { spawn as spawn4 } from "node:child_process";
|
|
5
|
-
import { realpathSync as realpathSync8, writeFileSync as
|
|
5
|
+
import { realpathSync as realpathSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6
6
|
import { homedir as homedir10 } from "node:os";
|
|
7
|
-
import { dirname as
|
|
7
|
+
import { dirname as dirname15, join as join16 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/browser.ts
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
@@ -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.20",
|
|
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",
|
|
@@ -554,6 +554,7 @@ var package_default = {
|
|
|
554
554
|
prepublishOnly: "bun run build"
|
|
555
555
|
},
|
|
556
556
|
dependencies: {
|
|
557
|
+
"@earendil-works/pi-ai": "1.2.0",
|
|
557
558
|
"@earendil-works/pi-coding-agent": "1.2.0",
|
|
558
559
|
"@earendil-works/pi-tui": "1.2.0",
|
|
559
560
|
"@gravity-ai/api": "1.2.1"
|
|
@@ -569,7 +570,7 @@ var package_default = {
|
|
|
569
570
|
var CLI_VERSION = package_default.version;
|
|
570
571
|
|
|
571
572
|
// src/output-limit.ts
|
|
572
|
-
var MAX_OUTPUT_TOKENS =
|
|
573
|
+
var MAX_OUTPUT_TOKENS = 32768;
|
|
573
574
|
var OUTPUT_LIMIT_MESSAGE = "This tool call was cut off by the output limit before it finished. It either did not run or ran with incomplete content, " + "so a file may be partly written. Check what was written, then redo the work in smaller parts " + "(for example write the first part, then add the rest with edit).";
|
|
574
575
|
function createOutputLimitExtension() {
|
|
575
576
|
let truncated = new Set;
|
|
@@ -6583,14 +6584,15 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
6583
6584
|
function piMcpCliPath() {
|
|
6584
6585
|
return join4(dirname5(fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"))), "extensions", "mcp", "cli.js");
|
|
6585
6586
|
}
|
|
6587
|
+
var MCP_UNAVAILABLE = "free-pi-cli mcp isn't available in this install. Reinstall with npx free-pi-cli@latest.";
|
|
6586
6588
|
async function loadPiRunner() {
|
|
6587
6589
|
const path = piMcpCliPath();
|
|
6588
6590
|
if (!existsSync2(path)) {
|
|
6589
|
-
throw new Error(
|
|
6591
|
+
throw new Error(MCP_UNAVAILABLE);
|
|
6590
6592
|
}
|
|
6591
6593
|
const mod = await import(pathToFileURL(path).href);
|
|
6592
6594
|
if (typeof mod.runMcpCommand !== "function") {
|
|
6593
|
-
throw new Error(
|
|
6595
|
+
throw new Error(MCP_UNAVAILABLE);
|
|
6594
6596
|
}
|
|
6595
6597
|
return mod.runMcpCommand;
|
|
6596
6598
|
}
|
|
@@ -6608,7 +6610,9 @@ async function runFreePiMcp(args, deps) {
|
|
|
6608
6610
|
error: (line) => deps.error(brandMcpLine(line))
|
|
6609
6611
|
});
|
|
6610
6612
|
} catch (err) {
|
|
6611
|
-
|
|
6613
|
+
const msg = (err instanceof Error ? err.message : String(err)).split(`
|
|
6614
|
+
at `)[0];
|
|
6615
|
+
deps.error(msg === MCP_UNAVAILABLE ? msg : `free-pi-cli mcp failed: ${msg}`);
|
|
6612
6616
|
return 1;
|
|
6613
6617
|
}
|
|
6614
6618
|
}
|
|
@@ -7243,7 +7247,7 @@ function createAdsExtension(opts) {
|
|
|
7243
7247
|
}
|
|
7244
7248
|
|
|
7245
7249
|
// src/pi-launch.ts
|
|
7246
|
-
import { dirname as
|
|
7250
|
+
import { dirname as dirname12, join as join12, resolve as resolve4 } from "node:path";
|
|
7247
7251
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7248
7252
|
|
|
7249
7253
|
// ../../node_modules/.bun/typebox@1.3.27/node_modules/typebox/build/system/memory/metrics.mjs
|
|
@@ -7573,6 +7577,28 @@ var integerKeyPattern = new RegExp("^(?:0|[1-9][0-9]*)$");
|
|
|
7573
7577
|
|
|
7574
7578
|
// ../../node_modules/.bun/typebox@1.3.27/node_modules/typebox/build/type/engine/indexed/from_object.mjs
|
|
7575
7579
|
var NumericKeyPattern = new RegExp(IntegerKey);
|
|
7580
|
+
// src/friendly-error.ts
|
|
7581
|
+
function friendlyThrown(e) {
|
|
7582
|
+
const name = e instanceof Error ? e.name : "";
|
|
7583
|
+
if (name === "TimeoutError")
|
|
7584
|
+
return "free-pi took too long to answer. Try again.";
|
|
7585
|
+
if (name === "AbortError")
|
|
7586
|
+
return "Cancelled.";
|
|
7587
|
+
if (name === "SyntaxError")
|
|
7588
|
+
return "free-pi sent an unexpected reply. Try again.";
|
|
7589
|
+
return "Couldn't reach free-pi — check your connection.";
|
|
7590
|
+
}
|
|
7591
|
+
function friendlyStatus(status, audience = "user", message) {
|
|
7592
|
+
if (status === 401) {
|
|
7593
|
+
return audience === "model" ? "The user's free-pi sign-in has expired; tell them to run /login." : "Your free-pi sign-in has expired. Run /login.";
|
|
7594
|
+
}
|
|
7595
|
+
if (message?.trim())
|
|
7596
|
+
return message.trim();
|
|
7597
|
+
if (status >= 500)
|
|
7598
|
+
return `free-pi is having trouble right now (HTTP ${status}). Try again in a minute.`;
|
|
7599
|
+
return `free-pi could not complete the request (HTTP ${status}).`;
|
|
7600
|
+
}
|
|
7601
|
+
|
|
7576
7602
|
// src/usage-tool.ts
|
|
7577
7603
|
var USAGE_TOOL_NAME = "free_pi_usage";
|
|
7578
7604
|
var PARAMS = _Object_({});
|
|
@@ -7630,11 +7656,11 @@ async function fetchUsageText(opts) {
|
|
|
7630
7656
|
signal: AbortSignal.timeout(5000)
|
|
7631
7657
|
});
|
|
7632
7658
|
if (!res.ok)
|
|
7633
|
-
return { ok: false, text:
|
|
7659
|
+
return { ok: false, text: friendlyStatus(res.status) };
|
|
7634
7660
|
const stats = await res.json();
|
|
7635
7661
|
return { ok: true, text: formatStats(stats), stats };
|
|
7636
7662
|
} catch (err) {
|
|
7637
|
-
return { ok: false, text:
|
|
7663
|
+
return { ok: false, text: friendlyThrown(err) };
|
|
7638
7664
|
}
|
|
7639
7665
|
}
|
|
7640
7666
|
function createUsageToolExtension(opts) {
|
|
@@ -7702,11 +7728,9 @@ async function post(opts, body, signal) {
|
|
|
7702
7728
|
const mapped = ERROR_TEXT[code];
|
|
7703
7729
|
if (mapped)
|
|
7704
7730
|
return { ok: false, text: mapped };
|
|
7705
|
-
return { ok: false, text:
|
|
7731
|
+
return { ok: false, text: friendlyStatus(res.status, "model", j.error?.message ?? j.message) };
|
|
7706
7732
|
} catch (e) {
|
|
7707
|
-
|
|
7708
|
-
return { ok: false, text: "Image generation timed out after 120 seconds." };
|
|
7709
|
-
return { ok: false, text: `Request failed: ${String(e)}` };
|
|
7733
|
+
return { ok: false, text: friendlyThrown(e) };
|
|
7710
7734
|
}
|
|
7711
7735
|
}
|
|
7712
7736
|
function slug(s) {
|
|
@@ -7783,7 +7807,8 @@ function createImageToolExtension(opts) {
|
|
|
7783
7807
|
await fh.close();
|
|
7784
7808
|
}
|
|
7785
7809
|
} catch (e) {
|
|
7786
|
-
|
|
7810
|
+
const why = { EACCES: "permission denied", EPERM: "permission denied", ENOSPC: "the disk is full", ENOENT: "the folder does not exist" }[e.code ?? ""] ?? "it could not be written";
|
|
7811
|
+
return fail(`Couldn't save the image to images: ${why}.`);
|
|
7787
7812
|
}
|
|
7788
7813
|
return done(`Saved image to ${rel} (${ext}, ${buf.length} bytes). You cannot see the image; reference this path in your answer or code, and tell the user you could not check how it looks.`, { ok: true, path: rel, cost_usd: d.cost_usd, model: d.model });
|
|
7789
7814
|
}
|
|
@@ -8392,11 +8417,9 @@ async function post2(opts, path, body, label, signal) {
|
|
|
8392
8417
|
const mapped = ERROR_TEXT3[err.code ?? ""];
|
|
8393
8418
|
if (mapped)
|
|
8394
8419
|
return { ok: false, text: typeof mapped === "function" ? mapped(label) : mapped };
|
|
8395
|
-
return { ok: false, text:
|
|
8420
|
+
return { ok: false, text: friendlyStatus(res.status, "model", err.message) };
|
|
8396
8421
|
} catch (e) {
|
|
8397
|
-
|
|
8398
|
-
return { ok: false, text: "Request timed out after 30 seconds." };
|
|
8399
|
-
return { ok: false, text: `Request failed: ${String(e)}` };
|
|
8422
|
+
return { ok: false, text: friendlyThrown(e) };
|
|
8400
8423
|
}
|
|
8401
8424
|
}
|
|
8402
8425
|
var done3 = (o) => ({ content: [{ type: "text", text: o.text }], details: o.details });
|
|
@@ -8465,11 +8488,11 @@ async function resolveBuyPage(opts) {
|
|
|
8465
8488
|
signal: AbortSignal.timeout(5000)
|
|
8466
8489
|
});
|
|
8467
8490
|
if (!res.ok)
|
|
8468
|
-
return { kind: "error", text:
|
|
8491
|
+
return { kind: "error", text: friendlyStatus(res.status) };
|
|
8469
8492
|
const me = await res.json();
|
|
8470
8493
|
return me.buy_url ? { kind: "url", url: me.buy_url } : { kind: "unavailable" };
|
|
8471
8494
|
} catch (err) {
|
|
8472
|
-
return { kind: "error", text:
|
|
8495
|
+
return { kind: "error", text: friendlyThrown(err) };
|
|
8473
8496
|
}
|
|
8474
8497
|
}
|
|
8475
8498
|
var BUY_NOT_AVAILABLE_TEXT = "Buying credits is not available on this server yet.";
|
|
@@ -8980,6 +9003,142 @@ function createToolAliasesExtension(opts = {}) {
|
|
|
8980
9003
|
};
|
|
8981
9004
|
}
|
|
8982
9005
|
|
|
9006
|
+
// ../../node_modules/.bun/@earendil-works+pi-ai@1.2.0+a410056ef8f430f0/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js
|
|
9007
|
+
var OVERFLOW_PATTERNS = [
|
|
9008
|
+
/prompt (?:is )?too long/i,
|
|
9009
|
+
/prompt exceeds max length/i,
|
|
9010
|
+
/request_too_large/i,
|
|
9011
|
+
/input is too long for requested model/i,
|
|
9012
|
+
/exceeds the context window/i,
|
|
9013
|
+
/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i,
|
|
9014
|
+
/input token count.*exceeds the maximum/i,
|
|
9015
|
+
/maximum prompt length is \d+/i,
|
|
9016
|
+
/reduce the length of the messages/i,
|
|
9017
|
+
/maximum context length is \d+ tokens/i,
|
|
9018
|
+
/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i,
|
|
9019
|
+
/input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i,
|
|
9020
|
+
/exceeds the limit of \d+/i,
|
|
9021
|
+
/exceeds the available context size/i,
|
|
9022
|
+
/greater than the context length/i,
|
|
9023
|
+
/context window exceeds limit/i,
|
|
9024
|
+
/exceeded model token limit/i,
|
|
9025
|
+
/too large for model with \d+ maximum context length/i,
|
|
9026
|
+
/prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i,
|
|
9027
|
+
/model_context_window_exceeded/i,
|
|
9028
|
+
/prompt too long; exceeded (?:max )?context length/i,
|
|
9029
|
+
/range of input length should be/i,
|
|
9030
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
9031
|
+
/too many tokens/i,
|
|
9032
|
+
/token limit exceeded/i
|
|
9033
|
+
];
|
|
9034
|
+
var CEREBRAS_BODYLESS_OVERFLOW_PATTERN = /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i;
|
|
9035
|
+
var NON_OVERFLOW_PATTERNS = [
|
|
9036
|
+
/^(Throttling error|Service unavailable):/i,
|
|
9037
|
+
/rate limit/i,
|
|
9038
|
+
/too many requests/i
|
|
9039
|
+
];
|
|
9040
|
+
function isContextOverflow(message, contextWindow) {
|
|
9041
|
+
if (message.stopReason === "error" && message.errorMessage) {
|
|
9042
|
+
const isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage));
|
|
9043
|
+
if (!isNonOverflow) {
|
|
9044
|
+
if (OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage))) {
|
|
9045
|
+
return true;
|
|
9046
|
+
}
|
|
9047
|
+
if (message.provider === "cerebras" && CEREBRAS_BODYLESS_OVERFLOW_PATTERN.test(message.errorMessage)) {
|
|
9048
|
+
return true;
|
|
9049
|
+
}
|
|
9050
|
+
}
|
|
9051
|
+
}
|
|
9052
|
+
if (contextWindow && message.stopReason === "stop") {
|
|
9053
|
+
const inputTokens = message.usage.input + message.usage.cacheRead;
|
|
9054
|
+
if (inputTokens > contextWindow) {
|
|
9055
|
+
return true;
|
|
9056
|
+
}
|
|
9057
|
+
}
|
|
9058
|
+
if (contextWindow && message.stopReason === "length" && message.usage.output === 0) {
|
|
9059
|
+
const inputTokens = message.usage.input + message.usage.cacheRead;
|
|
9060
|
+
if (inputTokens >= contextWindow * 0.99) {
|
|
9061
|
+
return true;
|
|
9062
|
+
}
|
|
9063
|
+
}
|
|
9064
|
+
return false;
|
|
9065
|
+
}
|
|
9066
|
+
|
|
9067
|
+
// ../../node_modules/.bun/@earendil-works+pi-ai@1.2.0+a410056ef8f430f0/node_modules/@earendil-works/pi-ai/dist/utils/retry.js
|
|
9068
|
+
function buildProviderErrorPattern(patterns) {
|
|
9069
|
+
return new RegExp(patterns.join("|"), "i");
|
|
9070
|
+
}
|
|
9071
|
+
var NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([
|
|
9072
|
+
"GoUsageLimitError",
|
|
9073
|
+
"FreeUsageLimitError",
|
|
9074
|
+
"Monthly usage limit reached",
|
|
9075
|
+
"available balance",
|
|
9076
|
+
"insufficient_quota",
|
|
9077
|
+
"out of budget",
|
|
9078
|
+
"quota exceeded",
|
|
9079
|
+
"billing",
|
|
9080
|
+
"subscription_sharing_usage_limit_exceeded"
|
|
9081
|
+
]);
|
|
9082
|
+
var RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
|
|
9083
|
+
"overloaded",
|
|
9084
|
+
"server_busy",
|
|
9085
|
+
"servers are currently busy",
|
|
9086
|
+
"currently experiencing high demand",
|
|
9087
|
+
"model is at capacity",
|
|
9088
|
+
"rate.?limit",
|
|
9089
|
+
"too many requests",
|
|
9090
|
+
"429",
|
|
9091
|
+
"500",
|
|
9092
|
+
"502",
|
|
9093
|
+
"503",
|
|
9094
|
+
"504",
|
|
9095
|
+
"520",
|
|
9096
|
+
"524",
|
|
9097
|
+
"service.?unavailable",
|
|
9098
|
+
"server.?error",
|
|
9099
|
+
"internal.?error",
|
|
9100
|
+
"provider.?returned.?(?:an.?)?error",
|
|
9101
|
+
"exceeded request buffer limit while retrying upstream",
|
|
9102
|
+
"network.?error",
|
|
9103
|
+
"connection.?error",
|
|
9104
|
+
"connection.?refused",
|
|
9105
|
+
"connection.?lost",
|
|
9106
|
+
"other side closed",
|
|
9107
|
+
"fetch failed",
|
|
9108
|
+
"getaddrinfo",
|
|
9109
|
+
"ENOTFOUND",
|
|
9110
|
+
"EAI_AGAIN",
|
|
9111
|
+
"upstream.?connect",
|
|
9112
|
+
"reset before headers",
|
|
9113
|
+
"socket hang up",
|
|
9114
|
+
"socket connection was closed",
|
|
9115
|
+
"timed? out",
|
|
9116
|
+
"timeout",
|
|
9117
|
+
"terminated",
|
|
9118
|
+
"websocket.?closed",
|
|
9119
|
+
"websocket.?error",
|
|
9120
|
+
"ended without",
|
|
9121
|
+
"stream ended before message_stop",
|
|
9122
|
+
"stream ended before a terminal response event",
|
|
9123
|
+
"http2 request did not get a response",
|
|
9124
|
+
"pending stream has been canceled",
|
|
9125
|
+
"retry delay",
|
|
9126
|
+
"you can retry your request",
|
|
9127
|
+
"try your request again",
|
|
9128
|
+
"please retry your request",
|
|
9129
|
+
"ResourceExhausted",
|
|
9130
|
+
"subscription_sharing_usage_unavailable",
|
|
9131
|
+
"subscription_sharing_user_unavailable"
|
|
9132
|
+
]);
|
|
9133
|
+
function isRetryableAssistantError(message) {
|
|
9134
|
+
if (message.stopReason !== "error" || !message.errorMessage)
|
|
9135
|
+
return false;
|
|
9136
|
+
const errorMessage = message.errorMessage;
|
|
9137
|
+
if (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage))
|
|
9138
|
+
return false;
|
|
9139
|
+
return RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage);
|
|
9140
|
+
}
|
|
9141
|
+
|
|
8983
9142
|
// src/fusion.ts
|
|
8984
9143
|
var FUSION_ID = "fusion";
|
|
8985
9144
|
var FUSION_PICKER_ID = "\uD83E\uDDE8 fusion";
|
|
@@ -9175,7 +9334,11 @@ function createFusionExtension(defaultModelId, ids = resolveFusionIds([])) {
|
|
|
9175
9334
|
|
|
9176
9335
|
// src/error-notice.ts
|
|
9177
9336
|
var UPSTREAM_ERROR_FRAME_MESSAGE = "upstream returned an error frame";
|
|
9337
|
+
var PREMIUM_MODEL_NOTICE = "This model needs premium credits. Get some with /buy-credits, or switch to another model with /model.";
|
|
9178
9338
|
var PROVIDER_FAILING_NOTICE = "This model's provider is failing right now. Try again in a moment, or switch models with /model.";
|
|
9339
|
+
var SIGNIN_EXPIRED_NOTICE = "Your free-pi sign-in has expired. Run /login, or restart free-pi-cli.";
|
|
9340
|
+
var CONNECTION_NOTICE = "Can't reach free-pi right now (connection error). Check your internet connection.";
|
|
9341
|
+
var CONTENT_FILTER_NOTICE = "The model's reply was blocked by a content filter. Try rephrasing your request.";
|
|
9179
9342
|
var FREE_OFFER_TITLE = "Switch to the free model and resend this prompt?";
|
|
9180
9343
|
var FREE_OFFER_ACCEPT = "Switch and resend";
|
|
9181
9344
|
var FREE_OFFER_DECLINE = "Stay on this model";
|
|
@@ -9235,6 +9398,8 @@ function parseErrorEnvelope(errorMessage) {
|
|
|
9235
9398
|
}
|
|
9236
9399
|
}
|
|
9237
9400
|
function formatLimitEnvelope(env, opts) {
|
|
9401
|
+
if (env.code === "model_requires_credits" && !/premium credit/i.test(env.message))
|
|
9402
|
+
return PREMIUM_MODEL_NOTICE;
|
|
9238
9403
|
if (env.code === "concurrent_session" || SERVER_MESSAGE_CODES.has(env.code) || env.prompt === "github_signin" || env.copy_variant === "treatment" || typeof env.remaining_credits_today !== "number") {
|
|
9239
9404
|
return env.message;
|
|
9240
9405
|
}
|
|
@@ -9280,6 +9445,36 @@ function lastUserEntryId(ctx) {
|
|
|
9280
9445
|
}
|
|
9281
9446
|
return "";
|
|
9282
9447
|
}
|
|
9448
|
+
var RAW_ERROR_FIELD = "freePiRawError";
|
|
9449
|
+
function rawErrorMessage(msg) {
|
|
9450
|
+
if (!msg)
|
|
9451
|
+
return;
|
|
9452
|
+
const m = msg;
|
|
9453
|
+
return typeof m[RAW_ERROR_FIELD] === "string" ? m[RAW_ERROR_FIELD] : m.errorMessage;
|
|
9454
|
+
}
|
|
9455
|
+
function envelopeOf(raw) {
|
|
9456
|
+
return raw === UPSTREAM_ERROR_FRAME_MESSAGE ? { status: 502, code: "upstream_error", message: UPSTREAM_ERROR_FRAME_MESSAGE } : parseErrorEnvelope(raw);
|
|
9457
|
+
}
|
|
9458
|
+
var friendlyText = (env, timeZone) => env.code === "upstream_error" ? PROVIDER_FAILING_NOTICE : env.code === "unauthorized" ? SIGNIN_EXPIRED_NOTICE : formatLimitEnvelope(env, { timeZone });
|
|
9459
|
+
var isFreePiProvider = (provider) => provider === PROVIDER_NAME || provider === DEFENSE_PROVIDER_NAME;
|
|
9460
|
+
function friendlyRawText(raw) {
|
|
9461
|
+
const text = raw.trim();
|
|
9462
|
+
if (/^(?:connection error\.?|request timed out\.?|terminated|fetch failed)$/i.test(text))
|
|
9463
|
+
return CONNECTION_NOTICE;
|
|
9464
|
+
const http = /^(5\d\d)(?: status code \(no body\)$|\s*<(?:!doctype html|html)\b)/i.exec(text);
|
|
9465
|
+
if (http)
|
|
9466
|
+
return `${PROVIDER_FAILING_NOTICE} (HTTP ${http[1]})`;
|
|
9467
|
+
const finish = /^Provider finish_reason: (\w+)$/.exec(text);
|
|
9468
|
+
if (finish)
|
|
9469
|
+
return finish[1] === "content_filter" ? CONTENT_FILTER_NOTICE : `${PROVIDER_FAILING_NOTICE} (${finish[1].replace(/_/g, " ")})`;
|
|
9470
|
+
return;
|
|
9471
|
+
}
|
|
9472
|
+
function friendlyError(raw, timeZone) {
|
|
9473
|
+
if (typeof raw !== "string")
|
|
9474
|
+
return;
|
|
9475
|
+
const env = envelopeOf(raw);
|
|
9476
|
+
return env ? friendlyText(env, timeZone) : friendlyRawText(raw);
|
|
9477
|
+
}
|
|
9283
9478
|
function createErrorNoticeExtension(opts = {}) {
|
|
9284
9479
|
const lastAt = new Map;
|
|
9285
9480
|
const now = opts.now ?? Date.now;
|
|
@@ -9312,21 +9507,44 @@ function createErrorNoticeExtension(opts = {}) {
|
|
|
9312
9507
|
return {
|
|
9313
9508
|
name: "free-pi-error-notice",
|
|
9314
9509
|
factory(pi) {
|
|
9510
|
+
pi.on("message_end", (event) => {
|
|
9511
|
+
const msg = event.message;
|
|
9512
|
+
if (msg.role !== "assistant" || !isFreePiProvider(msg.provider) || msg.stopReason !== "error" || typeof msg.errorMessage !== "string")
|
|
9513
|
+
return;
|
|
9514
|
+
const env = envelopeOf(msg.errorMessage);
|
|
9515
|
+
const friendly = env ? friendlyText(env, timeZone) : friendlyRawText(msg.errorMessage);
|
|
9516
|
+
if (friendly === undefined)
|
|
9517
|
+
return;
|
|
9518
|
+
const same = (text) => {
|
|
9519
|
+
const next = { ...msg, errorMessage: text };
|
|
9520
|
+
return isRetryableAssistantError(next) === retryable && isContextOverflow(next, 0) === overflow;
|
|
9521
|
+
};
|
|
9522
|
+
const retryable = isRetryableAssistantError(msg);
|
|
9523
|
+
const overflow = isContextOverflow(msg, 0);
|
|
9524
|
+
let text = friendly;
|
|
9525
|
+
if (env && retryable && !isRetryableAssistantError({ ...msg, errorMessage: text }))
|
|
9526
|
+
text += ` (HTTP ${env.status})`;
|
|
9527
|
+
if (!same(text))
|
|
9528
|
+
return;
|
|
9529
|
+
return { message: { ...msg, errorMessage: text, [RAW_ERROR_FIELD]: msg.errorMessage } };
|
|
9530
|
+
});
|
|
9315
9531
|
pi.on("turn_end", async (event, ctx) => {
|
|
9316
9532
|
lastTurnEntryId = event.messageEntryId;
|
|
9317
9533
|
const msg = event.message;
|
|
9318
9534
|
if (!msg || msg.stopReason !== "error")
|
|
9319
9535
|
return;
|
|
9320
|
-
const env =
|
|
9536
|
+
const env = envelopeOf(rawErrorMessage(msg));
|
|
9321
9537
|
if (!env)
|
|
9322
9538
|
return;
|
|
9323
9539
|
const t = now();
|
|
9324
9540
|
if (t - (lastAt.get(env.code) ?? -Infinity) >= NOTICE_DEDUPE_MS) {
|
|
9325
9541
|
lastAt.set(env.code, t);
|
|
9326
9542
|
const extra = opts.extraNotice?.(env);
|
|
9327
|
-
const
|
|
9543
|
+
const shown = msg[RAW_ERROR_FIELD] !== undefined;
|
|
9544
|
+
const text = shown ? extra : friendlyText(env, timeZone) + (extra ? `
|
|
9328
9545
|
${extra}` : "");
|
|
9329
|
-
|
|
9546
|
+
if (text)
|
|
9547
|
+
ctx.ui.notify(text, LIMIT_CODES.has(env.code) ? "info" : env.status >= 500 ? "error" : "warning");
|
|
9330
9548
|
}
|
|
9331
9549
|
await offerFreeModel(pi, env, event.messageEntryId, ctx);
|
|
9332
9550
|
});
|
|
@@ -9820,6 +10038,20 @@ function installBottomPin(mode) {
|
|
|
9820
10038
|
return [...lines, ...Array(bottomPadding(lines.length, other, rows)).fill("")];
|
|
9821
10039
|
};
|
|
9822
10040
|
}
|
|
10041
|
+
var IMAGE_MARKS = ["\x1B_G", "\x1B]1337;File="];
|
|
10042
|
+
function clampLines(lines, width) {
|
|
10043
|
+
return lines.map((l) => IMAGE_MARKS.some((m) => l.includes(m)) || visibleWidth3(l) <= width ? l : truncateToWidth6(l, Math.max(0, width), ""));
|
|
10044
|
+
}
|
|
10045
|
+
function installWidthGuard(mode) {
|
|
10046
|
+
const m = mode;
|
|
10047
|
+
for (const k of ["headerContainer", "loadedResourcesContainer", "chatContainer", "pendingMessagesContainer", "statusContainer", "widgetContainerAbove", "editorContainer", "widgetContainerBelow", "footerContainer"]) {
|
|
10048
|
+
const c = m[k];
|
|
10049
|
+
if (!c)
|
|
10050
|
+
continue;
|
|
10051
|
+
const render = c.render.bind(c);
|
|
10052
|
+
c.render = (w) => clampLines(render(w), w);
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
9823
10055
|
|
|
9824
10056
|
// src/guest-handoff.ts
|
|
9825
10057
|
var LOGIN_VALUE_LINE = "sign in free with GitHub: full daily allowance, FREE abliterated DeepSeek, history saved";
|
|
@@ -9909,6 +10141,10 @@ function mergeAttribution(a, b) {
|
|
|
9909
10141
|
|
|
9910
10142
|
// src/changelog.ts
|
|
9911
10143
|
var CHANGELOG_HIGHLIGHTS = [
|
|
10144
|
+
{
|
|
10145
|
+
version: "0.6.20",
|
|
10146
|
+
highlights: ["Error messages are now plain sentences instead of raw server text."]
|
|
10147
|
+
},
|
|
9912
10148
|
{
|
|
9913
10149
|
version: "0.6.19",
|
|
9914
10150
|
highlights: ["The ad banner now refreshes when you send a message, not on a timer."]
|
|
@@ -10273,6 +10509,7 @@ function highlightsFor(version) {
|
|
|
10273
10509
|
|
|
10274
10510
|
// src/commands.ts
|
|
10275
10511
|
import { access } from "node:fs/promises";
|
|
10512
|
+
import { basename as basename3 } from "node:path";
|
|
10276
10513
|
|
|
10277
10514
|
// src/auth-flow.ts
|
|
10278
10515
|
class UnauthorizedError extends Error {
|
|
@@ -10287,13 +10524,23 @@ async function postJson(baseUrl, path, body, extraHeaders = {}) {
|
|
|
10287
10524
|
body: JSON.stringify(body)
|
|
10288
10525
|
});
|
|
10289
10526
|
}
|
|
10527
|
+
var NETWORK_TEXT = "Can't reach free-pi. Check your internet connection and try again.";
|
|
10528
|
+
var RATE_LIMIT_TEXT = "Too many sign-in attempts from your network. Wait a minute and try again.";
|
|
10529
|
+
var UNEXPECTED_REPLY_TEXT = "free-pi sent an unexpected reply. If you're on public wifi, sign in to the network first, then try again.";
|
|
10530
|
+
async function parseReply(res, schema) {
|
|
10531
|
+
try {
|
|
10532
|
+
return schema.parse(await res.json());
|
|
10533
|
+
} catch (err) {
|
|
10534
|
+
throw new Error(UNEXPECTED_REPLY_TEXT, { cause: err });
|
|
10535
|
+
}
|
|
10536
|
+
}
|
|
10290
10537
|
async function postJsonRetry(baseUrl, path, body, attempts = 4, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), extraHeaders = {}) {
|
|
10291
10538
|
for (let attempt = 1;; attempt++) {
|
|
10292
10539
|
try {
|
|
10293
10540
|
return await postJson(baseUrl, path, body, extraHeaders);
|
|
10294
10541
|
} catch (err) {
|
|
10295
10542
|
if (attempt >= attempts) {
|
|
10296
|
-
throw new Error(
|
|
10543
|
+
throw new Error(NETWORK_TEXT, { cause: err });
|
|
10297
10544
|
}
|
|
10298
10545
|
await sleep(1500 * attempt);
|
|
10299
10546
|
}
|
|
@@ -10310,6 +10557,16 @@ function refusalMessage(status, body) {
|
|
|
10310
10557
|
}
|
|
10311
10558
|
return;
|
|
10312
10559
|
}
|
|
10560
|
+
function failureText(status, body) {
|
|
10561
|
+
if (status === 429 || body?.code === "rate_limited" && status < 500)
|
|
10562
|
+
return RATE_LIMIT_TEXT;
|
|
10563
|
+
const refusal = refusalMessage(status, body);
|
|
10564
|
+
if (refusal)
|
|
10565
|
+
return refusal;
|
|
10566
|
+
if (status >= 500)
|
|
10567
|
+
return `free-pi is having trouble right now (HTTP ${status}). Try again in a minute.`;
|
|
10568
|
+
return `free-pi could not complete sign-in (HTTP ${status}). Try again in a minute.`;
|
|
10569
|
+
}
|
|
10313
10570
|
async function readRefusal(res) {
|
|
10314
10571
|
return await res.json().catch(() => {
|
|
10315
10572
|
return;
|
|
@@ -10318,9 +10575,9 @@ async function readRefusal(res) {
|
|
|
10318
10575
|
async function startDeviceFlow(baseUrl) {
|
|
10319
10576
|
const res = await postJsonRetry(baseUrl, "/auth/github/device", {});
|
|
10320
10577
|
if (!res.ok) {
|
|
10321
|
-
throw new Error(
|
|
10578
|
+
throw new Error(failureText(res.status, await readRefusal(res)));
|
|
10322
10579
|
}
|
|
10323
|
-
return
|
|
10580
|
+
return parseReply(res, GithubDeviceResponseSchema);
|
|
10324
10581
|
}
|
|
10325
10582
|
async function startConnect(baseUrl, consentVersion, attribution = {}, client = {}) {
|
|
10326
10583
|
const res = await postJsonRetry(baseUrl, "/auth/connect/start", {
|
|
@@ -10330,8 +10587,8 @@ async function startConnect(baseUrl, consentVersion, attribution = {}, client =
|
|
|
10330
10587
|
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
10331
10588
|
}, undefined, undefined, { [SIGNIN_EXP_HEADER]: "1" });
|
|
10332
10589
|
if (!res.ok)
|
|
10333
|
-
throw new Error(
|
|
10334
|
-
return
|
|
10590
|
+
throw new Error(failureText(res.status, await readRefusal(res)));
|
|
10591
|
+
return parseReply(res, ConnectStartResponseSchema);
|
|
10335
10592
|
}
|
|
10336
10593
|
async function submitConsent(baseUrl, sessionId, consentVersion, attribution = {}) {
|
|
10337
10594
|
const res = await postJsonRetry(baseUrl, "/auth/consent", {
|
|
@@ -10341,7 +10598,7 @@ async function submitConsent(baseUrl, sessionId, consentVersion, attribution = {
|
|
|
10341
10598
|
...attribution.referral_code ? { referral_code: attribution.referral_code } : {}
|
|
10342
10599
|
});
|
|
10343
10600
|
if (!res.ok) {
|
|
10344
|
-
throw new Error(
|
|
10601
|
+
throw new Error(failureText(res.status, await readRefusal(res)));
|
|
10345
10602
|
}
|
|
10346
10603
|
}
|
|
10347
10604
|
function deviceReport() {
|
|
@@ -10374,8 +10631,8 @@ async function startGuestSession(baseUrl, consentVersion, attribution = {}) {
|
|
|
10374
10631
|
throw new GuestUnavailableError(body?.reason ?? "disabled");
|
|
10375
10632
|
}
|
|
10376
10633
|
if (!res.ok)
|
|
10377
|
-
throw new Error(
|
|
10378
|
-
const parsed =
|
|
10634
|
+
throw new Error(failureText(res.status, await readRefusal(res)));
|
|
10635
|
+
const parsed = await parseReply(res, GuestStartResponseSchema);
|
|
10379
10636
|
return {
|
|
10380
10637
|
token: parsed.token,
|
|
10381
10638
|
allowance_usd: parsed.allowance_usd,
|
|
@@ -10455,7 +10712,7 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
10455
10712
|
throw new PollAbortedError;
|
|
10456
10713
|
consecutiveNetworkFailures++;
|
|
10457
10714
|
if (consecutiveNetworkFailures >= 6) {
|
|
10458
|
-
throw new Error(
|
|
10715
|
+
throw new Error(NETWORK_TEXT, { cause: err });
|
|
10459
10716
|
}
|
|
10460
10717
|
await sleep(Math.max(waitSeconds, 5) * 1000);
|
|
10461
10718
|
continue;
|
|
@@ -10473,9 +10730,9 @@ async function pollForToken(baseUrl, sessionId, interval, sleep, opts = {}) {
|
|
|
10473
10730
|
throw new LoginTimeoutError;
|
|
10474
10731
|
if (isExpiredTokenPoll(body))
|
|
10475
10732
|
throw new DeviceCodeExpiredError;
|
|
10476
|
-
throw new Error(
|
|
10733
|
+
throw new Error(failureText(res.status, body));
|
|
10477
10734
|
}
|
|
10478
|
-
const parsed =
|
|
10735
|
+
const parsed = await parseReply(res, AuthTokenResponseSchema);
|
|
10479
10736
|
if (opts.signal?.aborted)
|
|
10480
10737
|
throw new PollAbortedError;
|
|
10481
10738
|
if ("token" in parsed)
|
|
@@ -11449,7 +11706,7 @@ async function runLoginUnguarded(opts, ctx, lead) {
|
|
|
11449
11706
|
ctx.ui.notify(LOGIN_DENIED_LINE, "info");
|
|
11450
11707
|
return false;
|
|
11451
11708
|
}
|
|
11452
|
-
const reason = err instanceof Error && err.message ?
|
|
11709
|
+
const reason = err instanceof Error && err.message ? `: ${err.message.replace(/\.$/, "")}` : "";
|
|
11453
11710
|
ctx.ui.notify(`Couldn't finish GitHub sign-in${reason}. Run /login to try again.`, "error");
|
|
11454
11711
|
return false;
|
|
11455
11712
|
}
|
|
@@ -11467,6 +11724,26 @@ function installLoginHandler(mode, opts) {
|
|
|
11467
11724
|
return runLogin(opts, { ui: { notify } });
|
|
11468
11725
|
};
|
|
11469
11726
|
}
|
|
11727
|
+
var IMPORT_NEXT_STEP = "Run /import again, or start a fresh session.";
|
|
11728
|
+
var FS_REASONS = {
|
|
11729
|
+
EACCES: "permission denied",
|
|
11730
|
+
EPERM: "permission denied",
|
|
11731
|
+
ENOENT: "not found",
|
|
11732
|
+
EISDIR: "that is a folder, not a file",
|
|
11733
|
+
EMFILE: "too many files are open",
|
|
11734
|
+
ENOSPC: "the disk is full"
|
|
11735
|
+
};
|
|
11736
|
+
function friendlyImportError(err, action = "import") {
|
|
11737
|
+
const e = err;
|
|
11738
|
+
const code = typeof e?.code === "string" && /^E[A-Z0-9]+$/.test(e.code) ? e.code : undefined;
|
|
11739
|
+
if (code) {
|
|
11740
|
+
const file = typeof e.path === "string" && e.path ? basename3(e.path) : undefined;
|
|
11741
|
+
const reason = FS_REASONS[code] ?? `error ${code}`;
|
|
11742
|
+
return `Couldn't ${action}${file ? ` ${file}` : ""}: ${reason}. ${IMPORT_NEXT_STEP}`;
|
|
11743
|
+
}
|
|
11744
|
+
const msg = (err instanceof Error ? err.message : String(err)).trim().replace(/[.\s]+$/, "");
|
|
11745
|
+
return `Couldn't ${action}: ${msg || "unknown error"}. ${IMPORT_NEXT_STEP}`;
|
|
11746
|
+
}
|
|
11470
11747
|
function installImportHandler(mode, opts) {
|
|
11471
11748
|
const m = mode;
|
|
11472
11749
|
mode.handleImportCommand = async (text) => {
|
|
@@ -11489,11 +11766,11 @@ function installImportHandler(mode, opts) {
|
|
|
11489
11766
|
for (let waited = 0;m.session.isCompacting && waited < 60000; waited += 100)
|
|
11490
11767
|
await new Promise((r) => setTimeout(r, 100));
|
|
11491
11768
|
const options = m.session.isStreaming ? { streamingBehavior: "followUp" } : undefined;
|
|
11492
|
-
m.session.prompt(message, options).catch((err) => m.showError(err
|
|
11769
|
+
m.session.prompt(message, options).catch((err) => m.showError(friendlyImportError(err, "send the import")));
|
|
11493
11770
|
}
|
|
11494
11771
|
});
|
|
11495
11772
|
} catch (err) {
|
|
11496
|
-
m.showError(
|
|
11773
|
+
m.showError(friendlyImportError(err));
|
|
11497
11774
|
}
|
|
11498
11775
|
};
|
|
11499
11776
|
}
|
|
@@ -11641,7 +11918,7 @@ function createGuestLoginExtension(opts) {
|
|
|
11641
11918
|
if (!opts.isGuest?.())
|
|
11642
11919
|
return;
|
|
11643
11920
|
const msg = event.message;
|
|
11644
|
-
const envelope = msg?.stopReason === "error" ? parseErrorEnvelope(msg
|
|
11921
|
+
const envelope = msg?.stopReason === "error" ? parseErrorEnvelope(rawErrorMessage(msg)) : undefined;
|
|
11645
11922
|
if (envelope && (envelope.code === "guest_allowance" || envelope.code === "guest_unavailable" || envelope.prompt === "github_signin")) {
|
|
11646
11923
|
if (!inFlight && !signedIn) {
|
|
11647
11924
|
const busy = envelope.code === "guest_allowance" && (envelope.remaining_credits_today ?? 0) > 0;
|
|
@@ -11751,7 +12028,7 @@ import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync a
|
|
|
11751
12028
|
import { homedir as homedir4 } from "node:os";
|
|
11752
12029
|
import { dirname as dirname8 } from "node:path";
|
|
11753
12030
|
import { ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
|
|
11754
|
-
import { Box, Text as Text2, truncateToWidth as truncateToWidth7 } from "@earendil-works/pi-tui";
|
|
12031
|
+
import { Box, Text as Text2, truncateToWidth as truncateToWidth7, visibleWidth as visibleWidth4 } from "@earendil-works/pi-tui";
|
|
11755
12032
|
var TOOL_MODES = ["compact", "full", "hidden"];
|
|
11756
12033
|
var TOOLS_USAGE = "Usage: /tools compact|full|hidden (compact: one line per tool call; full: every call in full; hidden: just a count)";
|
|
11757
12034
|
function readToolMode(path) {
|
|
@@ -11851,12 +12128,30 @@ function toolLines(i) {
|
|
|
11851
12128
|
}
|
|
11852
12129
|
return done;
|
|
11853
12130
|
}
|
|
12131
|
+
var graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
12132
|
+
function wrapByWidth(text, room) {
|
|
12133
|
+
const clean = text.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)|\x1b\[[0-9;?]*[ -\/]*[@-~]/g, "").replace(/\t/g, " ").replace(/[\x00-\x08\x0a-\x1f\x7f-\x9f]/g, "");
|
|
12134
|
+
const pieces = [];
|
|
12135
|
+
let cur = "";
|
|
12136
|
+
let curW = 0;
|
|
12137
|
+
for (const { segment } of graphemes.segment(clean)) {
|
|
12138
|
+
const w = visibleWidth4(segment);
|
|
12139
|
+
if (curW + w > room && cur) {
|
|
12140
|
+
pieces.push(cur);
|
|
12141
|
+
cur = "";
|
|
12142
|
+
curW = 0;
|
|
12143
|
+
}
|
|
12144
|
+
cur += segment;
|
|
12145
|
+
curW += w;
|
|
12146
|
+
}
|
|
12147
|
+
return [...pieces, cur];
|
|
12148
|
+
}
|
|
11854
12149
|
function component(rows, theme, pad) {
|
|
11855
12150
|
return {
|
|
11856
12151
|
render: (w) => rows.flatMap((r) => {
|
|
11857
12152
|
const room = Math.max(1, w - pad);
|
|
11858
|
-
const pieces = r.raw ?
|
|
11859
|
-
return pieces.map((t) => " ".repeat(pad) + theme.fg(r.error ? "error" : "dim", t));
|
|
12153
|
+
const pieces = r.raw ? wrapByWidth(r.text, room) : [truncateToWidth7(r.text, room, "…")];
|
|
12154
|
+
return pieces.map((t) => truncateToWidth7(" ".repeat(pad) + theme.fg(r.error ? "error" : "dim", t), Math.max(0, w), ""));
|
|
11860
12155
|
}),
|
|
11861
12156
|
invalidate() {}
|
|
11862
12157
|
};
|
|
@@ -12128,16 +12423,63 @@ function resolveFreePiScope(modelRuntime, providerName, modelIds, env = process.
|
|
|
12128
12423
|
return scoped;
|
|
12129
12424
|
}
|
|
12130
12425
|
|
|
12426
|
+
// src/default-model.ts
|
|
12427
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
12428
|
+
import { dirname as dirname9, join as join8 } from "node:path";
|
|
12429
|
+
var getDefaultModelPath = (agentDir) => join8(agentDir, "default-model.json");
|
|
12430
|
+
function readDefaultModel(agentDir) {
|
|
12431
|
+
if (!agentDir)
|
|
12432
|
+
return;
|
|
12433
|
+
try {
|
|
12434
|
+
const v = JSON.parse(readFileSync5(getDefaultModelPath(agentDir), "utf8"));
|
|
12435
|
+
return typeof v.provider === "string" && typeof v.model === "string" && v.provider && v.model ? { provider: v.provider, model: v.model } : undefined;
|
|
12436
|
+
} catch {
|
|
12437
|
+
return;
|
|
12438
|
+
}
|
|
12439
|
+
}
|
|
12440
|
+
|
|
12441
|
+
class DefaultModelStorage {
|
|
12442
|
+
agentDir;
|
|
12443
|
+
global;
|
|
12444
|
+
project;
|
|
12445
|
+
constructor(agentDir) {
|
|
12446
|
+
this.agentDir = agentDir;
|
|
12447
|
+
}
|
|
12448
|
+
withLock(scope, fn) {
|
|
12449
|
+
const next = fn(scope === "global" ? this.global : this.project);
|
|
12450
|
+
if (next === undefined)
|
|
12451
|
+
return;
|
|
12452
|
+
if (scope === "project") {
|
|
12453
|
+
this.project = next;
|
|
12454
|
+
return;
|
|
12455
|
+
}
|
|
12456
|
+
const before = this.global;
|
|
12457
|
+
this.global = next;
|
|
12458
|
+
if (before === undefined || !this.agentDir)
|
|
12459
|
+
return;
|
|
12460
|
+
try {
|
|
12461
|
+
const a = JSON.parse(before);
|
|
12462
|
+
const b = JSON.parse(next);
|
|
12463
|
+
if (a.defaultProvider === b.defaultProvider && a.defaultModel === b.defaultModel)
|
|
12464
|
+
return;
|
|
12465
|
+
if (!b.defaultProvider || !b.defaultModel)
|
|
12466
|
+
return;
|
|
12467
|
+
mkdirSync3(dirname9(getDefaultModelPath(this.agentDir)), { recursive: true });
|
|
12468
|
+
writeFileSync3(getDefaultModelPath(this.agentDir), JSON.stringify({ provider: b.defaultProvider, model: b.defaultModel }));
|
|
12469
|
+
} catch {}
|
|
12470
|
+
}
|
|
12471
|
+
}
|
|
12472
|
+
|
|
12131
12473
|
// src/hive.ts
|
|
12132
12474
|
import { createHash as createHash3 } from "node:crypto";
|
|
12133
12475
|
import { execFileSync } from "node:child_process";
|
|
12134
|
-
import { lstatSync as lstatSync2, readFileSync as
|
|
12135
|
-
import { join as
|
|
12476
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync6, readlinkSync as readlinkSync2 } from "node:fs";
|
|
12477
|
+
import { join as join10 } from "node:path";
|
|
12136
12478
|
|
|
12137
12479
|
// src/hive-guard.ts
|
|
12138
12480
|
import { existsSync as existsSync4, lstatSync, readdirSync, readlinkSync, realpathSync as realpathSync3 } from "node:fs";
|
|
12139
12481
|
import { homedir as homedir5 } from "node:os";
|
|
12140
|
-
import { basename as
|
|
12482
|
+
import { basename as basename4, dirname as dirname10, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve2, sep as sep3 } from "node:path";
|
|
12141
12483
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
12142
12484
|
|
|
12143
12485
|
// src/subagents.ts
|
|
@@ -12254,8 +12596,10 @@ function lastAssistant(session) {
|
|
|
12254
12596
|
continue;
|
|
12255
12597
|
const text = Array.isArray(m.content) ? m.content.filter((p) => p.type === "text").map((p) => p.text ?? "").join(`
|
|
12256
12598
|
`).trim() : "";
|
|
12257
|
-
if (m.stopReason === "error")
|
|
12258
|
-
|
|
12599
|
+
if (m.stopReason === "error") {
|
|
12600
|
+
const raw = rawErrorMessage(m);
|
|
12601
|
+
return { text, error: (raw && isFreePiProvider(m.provider) && !raw.includes("subagents_unavailable") ? friendlyError(raw) : undefined) ?? (raw || "the model request failed") };
|
|
12602
|
+
}
|
|
12259
12603
|
if (m.stopReason === "aborted")
|
|
12260
12604
|
return { text, error: "aborted" };
|
|
12261
12605
|
return { text };
|
|
@@ -12646,7 +12990,7 @@ var SAFE_READ_TOOLS = ["read", "grep", "find", "ls"];
|
|
|
12646
12990
|
var ALWAYS_ASK = ["codemode", "tool_search", FUSION_PLAN_TOOL, WEB_SEARCH_TOOL_NAME, DOCS_LOOKUP_TOOL_NAME];
|
|
12647
12991
|
var CLASSIFIED = ["bash", "write", "edit"];
|
|
12648
12992
|
var SECRET_BASENAME = [/^\.env(\..*)?$/, /\.pem$/, /^id_(rsa|dsa|ecdsa|ed25519)/];
|
|
12649
|
-
var SECRET_HOME_DIRS = [".ssh", ".aws",
|
|
12993
|
+
var SECRET_HOME_DIRS = [".ssh", ".aws", join9(".config", "freepi"), ".free-pi"];
|
|
12650
12994
|
var SECRET_IN_TEXT = /(?:^|[\s"'=:/])\.env(?:\.[\w.-]+)?(?=$|[\s"';|&)])|\.pem\b|\bid_(?:rsa|dsa|ecdsa|ed25519)|\.ssh\b|\.aws\b|\.config\/freepi|\.free-pi\b/;
|
|
12651
12995
|
var BASH_DENY = [
|
|
12652
12996
|
[/\brm\s+(?:-\S+\s+)*(?:\/|~|\$HOME)\*?(?:\s|$)/, "destructive command"],
|
|
@@ -12669,7 +13013,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
12669
13013
|
const note = (link) => {
|
|
12670
13014
|
let target;
|
|
12671
13015
|
try {
|
|
12672
|
-
target = resolve2(
|
|
13016
|
+
target = resolve2(dirname10(link), readlinkSync(link));
|
|
12673
13017
|
} catch {
|
|
12674
13018
|
return;
|
|
12675
13019
|
}
|
|
@@ -12679,7 +13023,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
12679
13023
|
out.push(real);
|
|
12680
13024
|
};
|
|
12681
13025
|
for (const entry of [".git/hooks", ".git/config"]) {
|
|
12682
|
-
const link =
|
|
13026
|
+
const link = join9(root, entry);
|
|
12683
13027
|
try {
|
|
12684
13028
|
if (lstatSync(link).isSymbolicLink())
|
|
12685
13029
|
note(link);
|
|
@@ -12698,7 +13042,7 @@ function protectedLinkTargets(root, limit) {
|
|
|
12698
13042
|
for (const entry of entries) {
|
|
12699
13043
|
if (++seen > limit)
|
|
12700
13044
|
return null;
|
|
12701
|
-
const path =
|
|
13045
|
+
const path = join9(dir, entry.name);
|
|
12702
13046
|
if (entry.isSymbolicLink()) {
|
|
12703
13047
|
if (protectedByName(relative2(root, path)))
|
|
12704
13048
|
note(path);
|
|
@@ -12748,7 +13092,7 @@ function resolveLikePi(raw, cwd, home, platform) {
|
|
|
12748
13092
|
if (p === "~")
|
|
12749
13093
|
p = home;
|
|
12750
13094
|
else if (p.startsWith("~/") || platform === "win32" && p.startsWith("~\\"))
|
|
12751
|
-
p =
|
|
13095
|
+
p = join9(home, p.slice(2));
|
|
12752
13096
|
if (/^file:\/\//.test(p))
|
|
12753
13097
|
p = fileURLToPath3(p);
|
|
12754
13098
|
return resolve2(cwd, p);
|
|
@@ -12758,17 +13102,17 @@ function realpathDeepest(p) {
|
|
|
12758
13102
|
const rest = [];
|
|
12759
13103
|
for (;; ) {
|
|
12760
13104
|
try {
|
|
12761
|
-
return
|
|
13105
|
+
return join9(realpathSync3(current), ...rest.reverse());
|
|
12762
13106
|
} catch {
|
|
12763
13107
|
try {
|
|
12764
13108
|
lstatSync(current);
|
|
12765
13109
|
return null;
|
|
12766
13110
|
} catch {}
|
|
12767
13111
|
}
|
|
12768
|
-
const parent =
|
|
13112
|
+
const parent = dirname10(current);
|
|
12769
13113
|
if (parent === current)
|
|
12770
13114
|
return null;
|
|
12771
|
-
rest.push(
|
|
13115
|
+
rest.push(basename4(current));
|
|
12772
13116
|
current = parent;
|
|
12773
13117
|
}
|
|
12774
13118
|
}
|
|
@@ -12777,10 +13121,10 @@ function isInside(root, target) {
|
|
|
12777
13121
|
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute2(rel);
|
|
12778
13122
|
}
|
|
12779
13123
|
function isSecret(target, home) {
|
|
12780
|
-
if (SECRET_BASENAME.some((re) => re.test(
|
|
13124
|
+
if (SECRET_BASENAME.some((re) => re.test(basename4(target))))
|
|
12781
13125
|
return true;
|
|
12782
13126
|
return SECRET_HOME_DIRS.some((dir) => {
|
|
12783
|
-
const lexical =
|
|
13127
|
+
const lexical = join9(home, dir);
|
|
12784
13128
|
return isInside(lexical, target) || isInside(realpathDeepest(lexical) ?? lexical, target);
|
|
12785
13129
|
});
|
|
12786
13130
|
}
|
|
@@ -12801,7 +13145,7 @@ function scanForSecret(dir) {
|
|
|
12801
13145
|
if (SECRET_BASENAME.some((re) => re.test(entry.name)))
|
|
12802
13146
|
return "yes";
|
|
12803
13147
|
if (entry.isDirectory())
|
|
12804
|
-
queue.push(
|
|
13148
|
+
queue.push(join9(current, entry.name));
|
|
12805
13149
|
}
|
|
12806
13150
|
}
|
|
12807
13151
|
return "no";
|
|
@@ -12925,6 +13269,33 @@ async function decideToolCall(event, opts) {
|
|
|
12925
13269
|
}
|
|
12926
13270
|
|
|
12927
13271
|
// src/hive.ts
|
|
13272
|
+
var HIVE_ERRORS = new Map(Object.entries({
|
|
13273
|
+
rate_limited: "Hive: slow down, too many messages in a short time.",
|
|
13274
|
+
pending: "Hive: another request is still waiting for an answer.",
|
|
13275
|
+
not_driver: "Hive: only the person driving the room can do that.",
|
|
13276
|
+
already_driver: "Hive: you are already driving the room.",
|
|
13277
|
+
unknown_conn: "Hive: that person has left the room.",
|
|
13278
|
+
unknown_prompt: "Hive: that prompt is no longer waiting.",
|
|
13279
|
+
unknown_request: "Hive: that request is no longer waiting.",
|
|
13280
|
+
bad_frame: "Hive: the room got a message it could not read. Try again.",
|
|
13281
|
+
internal: "Hive: something went wrong on the server. Try again."
|
|
13282
|
+
}));
|
|
13283
|
+
var HIVE_CLOSED = new Map(Object.entries({
|
|
13284
|
+
replaced: "Hive room closed: you opened a newer room.",
|
|
13285
|
+
host_left: "Hive room closed: the host left.",
|
|
13286
|
+
ended: "Hive room closed: the host ended it.",
|
|
13287
|
+
expired: "Hive room closed: it expired.",
|
|
13288
|
+
kicked: "Hive room closed: you were removed from it.",
|
|
13289
|
+
shutdown: "Hive room closed: the server is restarting.",
|
|
13290
|
+
no_room: "Hive room closed: the room no longer exists.",
|
|
13291
|
+
unauthorized: "Hive room closed: you are not signed in.",
|
|
13292
|
+
auth_timeout: "Hive room closed: sign-in took too long.",
|
|
13293
|
+
busy: "Hive room closed: the server is busy. Try again soon.",
|
|
13294
|
+
overloaded: "Hive room closed: the server is busy. Try again soon.",
|
|
13295
|
+
slow: "Hive room closed: the connection was too slow."
|
|
13296
|
+
}));
|
|
13297
|
+
var hiveErrorText = (code) => HIVE_ERRORS.get(code) ?? `Hive: ${code}`;
|
|
13298
|
+
var hiveClosedText = (reason) => HIVE_CLOSED.get(reason) ?? `Hive room closed (${reason}).`;
|
|
12928
13299
|
var RUN_IN_SANDBOX = "Run in sandbox";
|
|
12929
13300
|
var RUN_OUTSIDE = "Run outside the sandbox";
|
|
12930
13301
|
var DENY = "Deny";
|
|
@@ -12948,7 +13319,7 @@ function fingerprint(path) {
|
|
|
12948
13319
|
return "";
|
|
12949
13320
|
if (st.size > HASH_MAX_BYTES)
|
|
12950
13321
|
return `${st.size}:${st.mtimeMs}`;
|
|
12951
|
-
return createHash3("sha256").update(
|
|
13322
|
+
return createHash3("sha256").update(readFileSync6(path)).digest("hex");
|
|
12952
13323
|
} catch {
|
|
12953
13324
|
return "";
|
|
12954
13325
|
}
|
|
@@ -12978,7 +13349,7 @@ function worktreeSnapshot(g) {
|
|
|
12978
13349
|
const status = entry.slice(0, 2);
|
|
12979
13350
|
if (status[0] === "R" || status[0] === "C")
|
|
12980
13351
|
i++;
|
|
12981
|
-
files.set(entry.slice(3), `${status}:${fingerprint(
|
|
13352
|
+
files.set(entry.slice(3), `${status}:${fingerprint(join10(root, entry.slice(3)))}`);
|
|
12982
13353
|
}
|
|
12983
13354
|
return { files, head: runGit(g, "rev-parse", "HEAD")?.trim() };
|
|
12984
13355
|
} catch (e) {
|
|
@@ -13140,10 +13511,13 @@ function createHiveExtension(opts) {
|
|
|
13140
13511
|
};
|
|
13141
13512
|
socket.onopen = () => socket.send(JSON.stringify({ type: "auth", token }));
|
|
13142
13513
|
socket.onerror = failed;
|
|
13143
|
-
socket.onclose = () => {
|
|
13514
|
+
socket.onclose = (event) => {
|
|
13144
13515
|
if (ws !== socket)
|
|
13145
13516
|
return;
|
|
13146
|
-
|
|
13517
|
+
const known = event?.reason ? HIVE_CLOSED.get(event.reason) : undefined;
|
|
13518
|
+
if (known)
|
|
13519
|
+
endRoom(known);
|
|
13520
|
+
else if (!roomUrl)
|
|
13147
13521
|
failed();
|
|
13148
13522
|
else
|
|
13149
13523
|
endRoom("Hive room closed.");
|
|
@@ -13217,10 +13591,10 @@ Guests sign in with GitHub to watch and chat. They can ask to drive; you approve
|
|
|
13217
13591
|
break;
|
|
13218
13592
|
case "error":
|
|
13219
13593
|
if (frame.code !== "stale")
|
|
13220
|
-
notify(
|
|
13594
|
+
notify(hiveErrorText(frame.code), "warning");
|
|
13221
13595
|
break;
|
|
13222
13596
|
case "closed":
|
|
13223
|
-
endRoom(
|
|
13597
|
+
endRoom(hiveClosedText(frame.reason));
|
|
13224
13598
|
break;
|
|
13225
13599
|
}
|
|
13226
13600
|
}
|
|
@@ -13437,16 +13811,26 @@ import {
|
|
|
13437
13811
|
chmodSync,
|
|
13438
13812
|
existsSync as existsSync5,
|
|
13439
13813
|
lstatSync as lstatSync3,
|
|
13440
|
-
mkdirSync as
|
|
13814
|
+
mkdirSync as mkdirSync4,
|
|
13441
13815
|
mkdtempSync,
|
|
13442
13816
|
readdirSync as readdirSync2,
|
|
13443
13817
|
realpathSync as realpathSync4,
|
|
13444
13818
|
rmSync,
|
|
13445
13819
|
statSync as statSync3,
|
|
13446
|
-
writeFileSync as
|
|
13820
|
+
writeFileSync as writeFileSync4
|
|
13447
13821
|
} from "node:fs";
|
|
13448
13822
|
import { homedir as homedir6, tmpdir } from "node:os";
|
|
13449
|
-
import { dirname as
|
|
13823
|
+
import { dirname as dirname11, join as join11 } from "node:path";
|
|
13824
|
+
var ERRNO_WORDS = { EACCES: "permission denied", EPERM: "operation not permitted", ENOENT: "not found", ENOSPC: "disk full", EROFS: "read-only file system" };
|
|
13825
|
+
function sandboxCause(err) {
|
|
13826
|
+
const e = err;
|
|
13827
|
+
if (typeof e?.code === "string") {
|
|
13828
|
+
const where = [e.syscall, e.path].filter((x) => typeof x === "string").join(" ");
|
|
13829
|
+
return `${Object.hasOwn(ERRNO_WORDS, e.code) ? ERRNO_WORDS[e.code] : e.code}${where ? ` (${where})` : ""}`;
|
|
13830
|
+
}
|
|
13831
|
+
return (err instanceof Error ? err.message : String(err)).split(`
|
|
13832
|
+
`)[0].replace(/\.$/, "");
|
|
13833
|
+
}
|
|
13450
13834
|
var SECRET_NAME_PATTERNS = [
|
|
13451
13835
|
"\\.env(\\..*)?",
|
|
13452
13836
|
"[^/]*\\.pem",
|
|
@@ -13482,7 +13866,7 @@ var TOOLCHAIN_ROOTS = [
|
|
|
13482
13866
|
"Library/pnpm"
|
|
13483
13867
|
];
|
|
13484
13868
|
function toolchainRoots(home, exists = existsSync5) {
|
|
13485
|
-
return TOOLCHAIN_ROOTS.map((r) =>
|
|
13869
|
+
return TOOLCHAIN_ROOTS.map((r) => join11(home, r)).filter((p) => exists(p));
|
|
13486
13870
|
}
|
|
13487
13871
|
var ENV_ALLOW = ["PATH", "LANG", "LC_ALL", "LC_CTYPE", "TERM", "COLORTERM", "TZ", "USER", "LOGNAME", "SHELL", "PI_PROVIDER", "PI_MODEL"];
|
|
13488
13872
|
function under(path, dir) {
|
|
@@ -13500,10 +13884,10 @@ function sandboxEnv(base, o) {
|
|
|
13500
13884
|
for (const k of ENV_ALLOW)
|
|
13501
13885
|
if (base[k] !== undefined)
|
|
13502
13886
|
env[k] = base[k];
|
|
13503
|
-
env.PATH = [...o.developerDir ? [
|
|
13887
|
+
env.PATH = [...o.developerDir ? [join11(o.developerDir, "usr/bin")] : [], ...o.pathPrefix ?? [], ...(base.PATH ?? "/usr/bin:/bin").split(":")].filter((p) => p.startsWith("/") && !under(p, o.project) && !under(realOrSelf(p), o.project)).join(":");
|
|
13504
13888
|
if (o.developerDir)
|
|
13505
13889
|
env.DEVELOPER_DIR = o.developerDir;
|
|
13506
|
-
env.HOME =
|
|
13890
|
+
env.HOME = join11(o.scratch, "home");
|
|
13507
13891
|
env.TMP = env.TEMP = o.scratch;
|
|
13508
13892
|
env.TMPDIR = `${o.scratch}/`;
|
|
13509
13893
|
env.BUN_OPTIONS = "--no-env-file";
|
|
@@ -13513,7 +13897,7 @@ function sandboxEnv(base, o) {
|
|
|
13513
13897
|
env.GIT_CONFIG_COUNT = "1";
|
|
13514
13898
|
env.GIT_CONFIG_KEY_0 = "credential.helper";
|
|
13515
13899
|
env.GIT_CONFIG_VALUE_0 = "";
|
|
13516
|
-
env.npm_config_cache =
|
|
13900
|
+
env.npm_config_cache = join11(o.scratch, "npm-cache");
|
|
13517
13901
|
env.FREEPI_HIVE_CALL = o.callId;
|
|
13518
13902
|
return env;
|
|
13519
13903
|
}
|
|
@@ -13559,7 +13943,7 @@ function scanProject(project, limit = SCAN_LIMIT2) {
|
|
|
13559
13943
|
scan.overflow = true;
|
|
13560
13944
|
break;
|
|
13561
13945
|
}
|
|
13562
|
-
const path =
|
|
13946
|
+
const path = join11(dir, e.name);
|
|
13563
13947
|
if (e.isSymbolicLink())
|
|
13564
13948
|
continue;
|
|
13565
13949
|
if (e.isDirectory()) {
|
|
@@ -13659,7 +14043,7 @@ function developerDir(o = {}) {
|
|
|
13659
14043
|
continue;
|
|
13660
14044
|
try {
|
|
13661
14045
|
const real = realpath(c);
|
|
13662
|
-
if (exists(
|
|
14046
|
+
if (exists(join11(real, "usr/bin/git")))
|
|
13663
14047
|
return real;
|
|
13664
14048
|
} catch {}
|
|
13665
14049
|
}
|
|
@@ -13677,18 +14061,18 @@ function opensslFiles(o = {}) {
|
|
|
13677
14061
|
const files = [];
|
|
13678
14062
|
const dirs = [];
|
|
13679
14063
|
for (const prefix of o.prefixes ?? ["/opt/homebrew", "/usr/local"]) {
|
|
13680
|
-
const etc =
|
|
14064
|
+
const etc = join11(prefix, "etc");
|
|
13681
14065
|
for (const name of list(etc)) {
|
|
13682
14066
|
if (!/^openssl(@[0-9.]+)?$/.test(name))
|
|
13683
14067
|
continue;
|
|
13684
14068
|
for (const f of ["openssl.cnf", "ct_log_list.cnf", "cert.pem"])
|
|
13685
|
-
if (exists(
|
|
13686
|
-
files.push(
|
|
13687
|
-
if (exists(
|
|
13688
|
-
dirs.push(
|
|
14069
|
+
if (exists(join11(etc, name, f)))
|
|
14070
|
+
files.push(join11(etc, name, f));
|
|
14071
|
+
if (exists(join11(etc, name, "certs")))
|
|
14072
|
+
dirs.push(join11(etc, name, "certs"));
|
|
13689
14073
|
}
|
|
13690
|
-
if (exists(
|
|
13691
|
-
files.push(
|
|
14074
|
+
if (exists(join11(etc, "ca-certificates/cert.pem")))
|
|
14075
|
+
files.push(join11(etc, "ca-certificates/cert.pem"));
|
|
13692
14076
|
}
|
|
13693
14077
|
return { files, dirs };
|
|
13694
14078
|
}
|
|
@@ -13696,7 +14080,7 @@ var MAC_DEV_READ = ["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom", "/d
|
|
|
13696
14080
|
var MAC_DEV_WRITE = ["/dev/null", "/dev/tty", "/dev/zero", "/dev/dtracehelper"];
|
|
13697
14081
|
function ancestors(p) {
|
|
13698
14082
|
const out = [];
|
|
13699
|
-
for (let d =
|
|
14083
|
+
for (let d = dirname11(p);; d = dirname11(d)) {
|
|
13700
14084
|
out.push(d);
|
|
13701
14085
|
if (d === "/")
|
|
13702
14086
|
break;
|
|
@@ -13732,9 +14116,9 @@ function buildMacProfile(i) {
|
|
|
13732
14116
|
L.push(`(deny file-read* file-write* (literal ${sbString(s)}))`);
|
|
13733
14117
|
for (const f of i.links)
|
|
13734
14118
|
L.push(`(deny file-write* (literal ${sbString(f)}))`);
|
|
13735
|
-
const guarded = new Set([i.project,
|
|
13736
|
-
for (const f of [...i.sockets, ...i.links, ...i.nodeModules ?? [],
|
|
13737
|
-
for (let d =
|
|
14119
|
+
const guarded = new Set([i.project, join11(i.project, ".git"), ...i.nodeModules ?? []]);
|
|
14120
|
+
for (const f of [...i.sockets, ...i.links, ...i.nodeModules ?? [], join11(i.project, ".git")]) {
|
|
14121
|
+
for (let d = dirname11(f);d.length >= i.project.length && (d === i.project || d.startsWith(i.project + "/")); d = dirname11(d))
|
|
13738
14122
|
guarded.add(d);
|
|
13739
14123
|
}
|
|
13740
14124
|
for (const d of [...guarded].sort())
|
|
@@ -13803,10 +14187,10 @@ function buildBwrapArgs(i) {
|
|
|
13803
14187
|
a.push("--bind", i.scratch, i.scratch);
|
|
13804
14188
|
for (const d of i.nodeModules)
|
|
13805
14189
|
ro(d);
|
|
13806
|
-
if (exists(
|
|
13807
|
-
ro(
|
|
13808
|
-
if (exists(
|
|
13809
|
-
ro(
|
|
14190
|
+
if (exists(join11(i.project, ".git/hooks")))
|
|
14191
|
+
ro(join11(i.project, ".git/hooks"));
|
|
14192
|
+
if (exists(join11(i.project, ".git/config")))
|
|
14193
|
+
ro(join11(i.project, ".git/config"));
|
|
13810
14194
|
for (const f of i.links)
|
|
13811
14195
|
ro(f);
|
|
13812
14196
|
for (const s of i.sockets)
|
|
@@ -13836,7 +14220,7 @@ function removeTree(path) {
|
|
|
13836
14220
|
return;
|
|
13837
14221
|
chmodSync(p, 448);
|
|
13838
14222
|
for (const e of readdirSync2(p))
|
|
13839
|
-
walk(
|
|
14223
|
+
walk(join11(p, e));
|
|
13840
14224
|
} catch {}
|
|
13841
14225
|
};
|
|
13842
14226
|
walk(path);
|
|
@@ -13874,7 +14258,7 @@ function createShellSandbox(deps = {}) {
|
|
|
13874
14258
|
if (deps.python !== undefined)
|
|
13875
14259
|
return deps.python;
|
|
13876
14260
|
const dev = platform === "darwin" ? macHost().developerDir : null;
|
|
13877
|
-
return dev && existsSync5(
|
|
14261
|
+
return dev && existsSync5(join11(dev, "usr/bin/python3")) ? join11(dev, "usr/bin/python3") : null;
|
|
13878
14262
|
};
|
|
13879
14263
|
let cached;
|
|
13880
14264
|
function probe() {
|
|
@@ -13992,13 +14376,13 @@ function createShellSandbox(deps = {}) {
|
|
|
13992
14376
|
return { ready: false, reason: "a folder in the project cannot be read, so it cannot be checked for sockets and hard links" };
|
|
13993
14377
|
let scratch;
|
|
13994
14378
|
try {
|
|
13995
|
-
scratch = mkdtempSync(
|
|
14379
|
+
scratch = mkdtempSync(join11(realpathSync4(tmpdir()), "freepi-hive-"));
|
|
13996
14380
|
const dir = scratch;
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
const emptyFile =
|
|
14000
|
-
const marker =
|
|
14001
|
-
|
|
14381
|
+
mkdirSync4(join11(dir, "home"));
|
|
14382
|
+
mkdirSync4(join11(dir, "npm-cache"));
|
|
14383
|
+
const emptyFile = join11(dir, "empty");
|
|
14384
|
+
const marker = join11(dir, "call.fd");
|
|
14385
|
+
writeFileSync4(emptyFile, "", { mode: 292 });
|
|
14002
14386
|
const roots = toolchainRoots(home);
|
|
14003
14387
|
const mac = platform === "darwin";
|
|
14004
14388
|
const host = mac ? macHost() : undefined;
|
|
@@ -14050,7 +14434,7 @@ function createShellSandbox(deps = {}) {
|
|
|
14050
14434
|
} catch (err) {
|
|
14051
14435
|
if (scratch)
|
|
14052
14436
|
removeTree(scratch);
|
|
14053
|
-
return { ready: false, reason: `
|
|
14437
|
+
return { ready: false, reason: `Couldn't set up the command sandbox: ${sandboxCause(err)}.` };
|
|
14054
14438
|
}
|
|
14055
14439
|
},
|
|
14056
14440
|
mark(toolCallId, mode) {
|
|
@@ -14147,6 +14531,44 @@ function installSafeUrlOpen(mode, open = openLink) {
|
|
|
14147
14531
|
});
|
|
14148
14532
|
}
|
|
14149
14533
|
|
|
14534
|
+
// src/retry-failed-line.ts
|
|
14535
|
+
var RETRY_FAILED = /^Retry failed after (\d+) attempts?: ([\s\S]*)$/;
|
|
14536
|
+
var CRASH_WARNING = /^\S+ crashed on /;
|
|
14537
|
+
var PRODUCT = "free-pi-cli";
|
|
14538
|
+
function installRetryFailedLine(mode) {
|
|
14539
|
+
const m = mode;
|
|
14540
|
+
const original = m.showError.bind(m);
|
|
14541
|
+
m.showError = (msg) => {
|
|
14542
|
+
const hit = typeof msg === "string" ? RETRY_FAILED.exec(msg) : null;
|
|
14543
|
+
if (!hit)
|
|
14544
|
+
return original(msg);
|
|
14545
|
+
const [, n, cause = ""] = hit;
|
|
14546
|
+
if (cause === "Retry cancelled")
|
|
14547
|
+
return m.showWarning("Retry cancelled.");
|
|
14548
|
+
const tries = `${n} ${n === "1" ? "try" : "tries"}`;
|
|
14549
|
+
m.showWarning(`Gave up after ${tries}: ${cause}`);
|
|
14550
|
+
};
|
|
14551
|
+
const warn = m.showWarning.bind(m);
|
|
14552
|
+
m.showWarning = (msg) => warn(typeof msg === "string" ? msg.replace(CRASH_WARNING, `${PRODUCT} crashed on `) : msg);
|
|
14553
|
+
for (const name of ["renderProjectTrustWarningIfNeeded", "suggestBugReport"]) {
|
|
14554
|
+
const orig = m[name]?.bind(m);
|
|
14555
|
+
if (!orig)
|
|
14556
|
+
continue;
|
|
14557
|
+
m[name] = () => {
|
|
14558
|
+
const before = m.chatContainer?.children?.length ?? 0;
|
|
14559
|
+
orig();
|
|
14560
|
+
for (const child of m.chatContainer?.children?.slice(before) ?? []) {
|
|
14561
|
+
const build = child.build;
|
|
14562
|
+
if (typeof build === "function")
|
|
14563
|
+
child.build = () => reword(build.call(child));
|
|
14564
|
+
}
|
|
14565
|
+
};
|
|
14566
|
+
}
|
|
14567
|
+
}
|
|
14568
|
+
function reword(text) {
|
|
14569
|
+
return text.replace(/restart pi\./, `restart ${PRODUCT}.`).replace(/looks like a pi bug/, "looks like a free-pi bug");
|
|
14570
|
+
}
|
|
14571
|
+
|
|
14150
14572
|
// src/project-trust.ts
|
|
14151
14573
|
import { createInterface } from "node:readline/promises";
|
|
14152
14574
|
import { resolve as resolve3 } from "node:path";
|
|
@@ -14293,14 +14715,20 @@ var SDK_TOOLS = [
|
|
|
14293
14715
|
"list_mcp_resource_templates",
|
|
14294
14716
|
"read_mcp_resource"
|
|
14295
14717
|
];
|
|
14296
|
-
var SKILLS_DIR = resolve4(
|
|
14718
|
+
var SKILLS_DIR = resolve4(dirname12(fileURLToPath4(import.meta.url)), "..", "skills");
|
|
14297
14719
|
function resolveModelName(opts) {
|
|
14298
14720
|
return opts.models?.[0]?.name || opts.model || MODEL_ID;
|
|
14299
14721
|
}
|
|
14300
14722
|
function buildSettingsManager(opts, models, projectTrusted) {
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14723
|
+
const saved = readDefaultModel(opts.agentDir);
|
|
14724
|
+
const known = new Map([
|
|
14725
|
+
[PROVIDER_NAME, new Set([...models.map((m) => m.id), FUSION_PICKER_ID, ...models.some((m) => m.id === ULTRAFAST_CATALOG_ID) ? [ULTRAFAST_PICKER_ID] : []])],
|
|
14726
|
+
[DEFENSE_PROVIDER_NAME, new Set(defenseModelsFor(opts).map((m) => m.id))]
|
|
14727
|
+
]);
|
|
14728
|
+
const start = saved && known.get(saved.provider)?.has(saved.model) ? saved : undefined;
|
|
14729
|
+
const settings = {
|
|
14730
|
+
defaultProvider: start?.provider ?? PROVIDER_NAME,
|
|
14731
|
+
defaultModel: start?.model ?? models[0].id,
|
|
14304
14732
|
defaultTools: [...SAFE_TOOLS, TODO_TOOL_NAME],
|
|
14305
14733
|
packages: [],
|
|
14306
14734
|
quietStartup: true,
|
|
@@ -14310,7 +14738,10 @@ function buildSettingsManager(opts, models, projectTrusted) {
|
|
|
14310
14738
|
theme: "light/dark",
|
|
14311
14739
|
enableInstallTelemetry: false,
|
|
14312
14740
|
...compactionSettingsFor(opts.costArm) ? { compaction: compactionSettingsFor(opts.costArm) } : {}
|
|
14313
|
-
}
|
|
14741
|
+
};
|
|
14742
|
+
const storage = new DefaultModelStorage(opts.agentDir);
|
|
14743
|
+
storage.withLock("global", () => JSON.stringify(settings, null, 2));
|
|
14744
|
+
return SettingsManager2.fromStorage(storage, { projectTrusted });
|
|
14314
14745
|
}
|
|
14315
14746
|
function settingsForRuntime(opts, rtCwd, launch) {
|
|
14316
14747
|
return buildSettingsManager(opts, catalogModelsFor(opts), projectTrustForCwd(rtCwd, launch, opts.agentDir));
|
|
@@ -14506,7 +14937,7 @@ ${CHOICE_RULES}` : agentRulesFor(opts.costArm), ...shellPromptLine(shell) ? [she
|
|
|
14506
14937
|
function getSessionDir(cwd, agentDir) {
|
|
14507
14938
|
const resolvedCwd = resolve4(cwd);
|
|
14508
14939
|
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
14509
|
-
return
|
|
14940
|
+
return join12(resolve4(agentDir), "sessions", safePath);
|
|
14510
14941
|
}
|
|
14511
14942
|
async function findLatestSession(cwd, sessionDir, api = SessionManager2) {
|
|
14512
14943
|
const infos = await api.list(cwd, sessionDir);
|
|
@@ -14608,11 +15039,13 @@ async function launchPi(opts) {
|
|
|
14608
15039
|
installLogoutHandler(mode, { credentialsPath: opts.credentialsPath, isGuest: () => auth.isGuest() });
|
|
14609
15040
|
installModelAllowanceHandler(mode);
|
|
14610
15041
|
installSafeUrlOpen(mode);
|
|
15042
|
+
installRetryFailedLine(mode);
|
|
14611
15043
|
installFooterRightAlign(mode, () => {
|
|
14612
15044
|
const id = runtime.session?.model?.id;
|
|
14613
15045
|
return freeModelBase(pickerModelName(opts, id));
|
|
14614
15046
|
});
|
|
14615
15047
|
installEditorPlaceholder(mode);
|
|
15048
|
+
installWidthGuard(mode);
|
|
14616
15049
|
if (process.stdin.isTTY && process.stdout.isTTY && !process.env.CI) {
|
|
14617
15050
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
14618
15051
|
installBottomPin(mode);
|
|
@@ -14665,7 +15098,7 @@ function createRealSpawn(io = { platform: process.platform, spawnSyncImpl: spawn
|
|
|
14665
15098
|
// src/relaunch.ts
|
|
14666
15099
|
import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, realpathSync as realpathSync5, rmSync as rmSync2, statSync as statSync4 } from "node:fs";
|
|
14667
15100
|
import { constants as osConstants, tmpdir as tmpdir2 } from "node:os";
|
|
14668
|
-
import { delimiter, dirname as
|
|
15101
|
+
import { delimiter, dirname as dirname13, isAbsolute as isAbsolute3, join as join13, resolve as resolve5, sep as sep4, win32 } from "node:path";
|
|
14669
15102
|
function applyRelaunchCwd(env, chdir = (d) => process.chdir(d), isDir = (p) => {
|
|
14670
15103
|
try {
|
|
14671
15104
|
return statSync4(p).isDirectory();
|
|
@@ -14706,7 +15139,7 @@ function hasNodeModulesBin(entry) {
|
|
|
14706
15139
|
}
|
|
14707
15140
|
function sanitizedEnv(env, nodeDir, project, home, win) {
|
|
14708
15141
|
const proj = canon(project, win);
|
|
14709
|
-
const broad = proj === canon(home, win) ||
|
|
15142
|
+
const broad = proj === canon(home, win) || dirname13(proj) === proj;
|
|
14710
15143
|
const out = {};
|
|
14711
15144
|
let pathKey = "PATH";
|
|
14712
15145
|
let oldPath = "";
|
|
@@ -14743,13 +15176,13 @@ async function relaunchViaNpx(latest, deps) {
|
|
|
14743
15176
|
if (!latest || !SEMVER.test(latest))
|
|
14744
15177
|
return;
|
|
14745
15178
|
const target = latest;
|
|
14746
|
-
const nodeDir =
|
|
14747
|
-
const npxCli = win ?
|
|
15179
|
+
const nodeDir = dirname13(deps.execPath);
|
|
15180
|
+
const npxCli = win ? join13(nodeDir, "node_modules", "npm", "bin", "npx-cli.js") : join13(dirname13(nodeDir), "lib", "node_modules", "npm", "bin", "npx-cli.js");
|
|
14748
15181
|
const exists = deps.exists ?? existsSync6;
|
|
14749
15182
|
const distroNpxCli = "/usr/share/nodejs/npm/bin/npx-cli.js";
|
|
14750
15183
|
const healthy = (cli) => {
|
|
14751
|
-
const root =
|
|
14752
|
-
return exists(
|
|
15184
|
+
const root = join13(dirname13(cli), "..");
|
|
15185
|
+
return exists(join13(root, "package.json")) && exists(join13(root, "lib", "cli.js"));
|
|
14753
15186
|
};
|
|
14754
15187
|
let npxCliPath = npxCli;
|
|
14755
15188
|
if (!exists(npxCli)) {
|
|
@@ -14761,8 +15194,8 @@ async function relaunchViaNpx(latest, deps) {
|
|
|
14761
15194
|
if (!healthy(npxCliPath))
|
|
14762
15195
|
return;
|
|
14763
15196
|
const childEnv = sanitizedEnv(env, nodeDir, deps.cwd, deps.homedir, win);
|
|
14764
|
-
const readyDir = mkdtempSync2(
|
|
14765
|
-
const readyFile =
|
|
15197
|
+
const readyDir = mkdtempSync2(join13(tmpdir2(), "free-pi-relaunch-"));
|
|
15198
|
+
const readyFile = join13(readyDir, "started");
|
|
14766
15199
|
try {
|
|
14767
15200
|
deps.log(`Updating free-pi to ${target}…`);
|
|
14768
15201
|
return await new Promise((resolve) => {
|
|
@@ -14804,13 +15237,13 @@ async function relaunchViaNpx(latest, deps) {
|
|
|
14804
15237
|
|
|
14805
15238
|
// src/run.ts
|
|
14806
15239
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
14807
|
-
import { mkdirSync as
|
|
15240
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
14808
15241
|
import { SessionManager as SessionManager3 } from "@earendil-works/pi-coding-agent";
|
|
14809
15242
|
|
|
14810
15243
|
// src/onboarding.ts
|
|
14811
15244
|
import { existsSync as existsSync7, readdirSync as readdirSync3, realpathSync as realpathSync6 } from "node:fs";
|
|
14812
15245
|
import { homedir as homedir8 } from "node:os";
|
|
14813
|
-
import { join as
|
|
15246
|
+
import { join as join14 } from "node:path";
|
|
14814
15247
|
var INTRO_ENV_SKIP = "FREEPI_NO_INTRO";
|
|
14815
15248
|
var INTRO_TEXT = "Tell free-pi what to build, fix, or explain. Resume later with: npx free-pi-cli@latest --continue";
|
|
14816
15249
|
function introSkipped(env = process.env) {
|
|
@@ -14830,7 +15263,7 @@ function looksLikeProject(cwd, home = homedir8()) {
|
|
|
14830
15263
|
try {
|
|
14831
15264
|
if (realpathSync6(cwd) === realpathSync6(home))
|
|
14832
15265
|
return false;
|
|
14833
|
-
if (PROJECT_MARKERS.some((m) => existsSync7(
|
|
15266
|
+
if (PROJECT_MARKERS.some((m) => existsSync7(join14(cwd, m))))
|
|
14834
15267
|
return true;
|
|
14835
15268
|
return readdirSync3(cwd).some((f) => /\.(sln|csproj)$/.test(f));
|
|
14836
15269
|
} catch {
|
|
@@ -14859,7 +15292,7 @@ ${text}
|
|
|
14859
15292
|
// src/starter.ts
|
|
14860
15293
|
import { existsSync as existsSync8, readdirSync as readdirSync4, realpathSync as realpathSync7 } from "node:fs";
|
|
14861
15294
|
import { homedir as homedir9 } from "node:os";
|
|
14862
|
-
import { join as
|
|
15295
|
+
import { join as join15 } from "node:path";
|
|
14863
15296
|
var STARTER_EXPERIMENT = "first_session_starter";
|
|
14864
15297
|
var STARTER_PROJECT = [
|
|
14865
15298
|
{
|
|
@@ -14892,7 +15325,7 @@ var STARTER_EMPTY = [
|
|
|
14892
15325
|
var STARTER_HEADER_PROJECT = "Not sure where to start? Press 1, 2 or 3 to send one of these, or any other key to skip:";
|
|
14893
15326
|
var STARTER_HEADER_EMPTY = "This folder is empty. Press 1, 2 or 3 to start with one of these, or any other key to skip:";
|
|
14894
15327
|
function getStarterMarkerPath(agentDir = getFreePiAgentDir()) {
|
|
14895
|
-
return
|
|
15328
|
+
return join15(agentDir, "starter-shown");
|
|
14896
15329
|
}
|
|
14897
15330
|
function starterShownBefore(agentDir) {
|
|
14898
15331
|
return existsSync8(getStarterMarkerPath(agentDir));
|
|
@@ -14924,12 +15357,12 @@ function parseStarterKey(raw, count) {
|
|
|
14924
15357
|
}
|
|
14925
15358
|
|
|
14926
15359
|
// src/whats-new.ts
|
|
14927
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
14928
|
-
import { dirname as
|
|
15360
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
15361
|
+
import { dirname as dirname14 } from "node:path";
|
|
14929
15362
|
function maybeWhatsNew(cliVersion, agentDir) {
|
|
14930
15363
|
try {
|
|
14931
15364
|
const path = getLastSeenVersionPath(agentDir);
|
|
14932
|
-
const lastSeen = existsSync9(path) ?
|
|
15365
|
+
const lastSeen = existsSync9(path) ? readFileSync7(path, "utf-8").trim() : undefined;
|
|
14933
15366
|
writeLastSeen(path, cliVersion);
|
|
14934
15367
|
if (lastSeen === undefined)
|
|
14935
15368
|
return;
|
|
@@ -14945,8 +15378,8 @@ function maybeWhatsNew(cliVersion, agentDir) {
|
|
|
14945
15378
|
}
|
|
14946
15379
|
}
|
|
14947
15380
|
function writeLastSeen(path, version) {
|
|
14948
|
-
|
|
14949
|
-
|
|
15381
|
+
mkdirSync5(dirname14(path), { recursive: true });
|
|
15382
|
+
writeFileSync5(path, version, "utf-8");
|
|
14950
15383
|
}
|
|
14951
15384
|
|
|
14952
15385
|
// src/run.ts
|
|
@@ -15197,7 +15630,7 @@ Your free-pi sign-in has expired. Signing in with GitHub again.
|
|
|
15197
15630
|
deps.log(SIGNIN_UNFINISHED_LINE);
|
|
15198
15631
|
return 0;
|
|
15199
15632
|
}
|
|
15200
|
-
const reason = err instanceof Error && err.message ? err.message : "unknown error";
|
|
15633
|
+
const reason = (err instanceof Error && err.message ? err.message : "unknown error").replace(/\.$/, "");
|
|
15201
15634
|
deps.log(`Couldn't finish GitHub sign-in: ${reason}. Run npx free-pi-cli again to retry.`);
|
|
15202
15635
|
return 1;
|
|
15203
15636
|
}
|
|
@@ -15393,8 +15826,8 @@ ${r.text}
|
|
|
15393
15826
|
const choices = starterChoices();
|
|
15394
15827
|
if (choices) {
|
|
15395
15828
|
try {
|
|
15396
|
-
|
|
15397
|
-
|
|
15829
|
+
mkdirSync6(deps.agentDir, { recursive: true });
|
|
15830
|
+
writeFileSync6(getStarterMarkerPath(deps.agentDir), "1");
|
|
15398
15831
|
} catch {}
|
|
15399
15832
|
const report = (event) => deps.reportStarterEvent ? deps.reportStarterEvent(event) : reportStarterEvent(deps, jwt, event);
|
|
15400
15833
|
deps.log(formatStarterMenu(choices));
|
|
@@ -15547,7 +15980,7 @@ async function main() {
|
|
|
15547
15980
|
}
|
|
15548
15981
|
if (process.env.FREEPI_RELAUNCH_READY) {
|
|
15549
15982
|
try {
|
|
15550
|
-
|
|
15983
|
+
writeFileSync7(process.env.FREEPI_RELAUNCH_READY, "");
|
|
15551
15984
|
} catch {}
|
|
15552
15985
|
}
|
|
15553
15986
|
const command = parseCliArgs(process.argv.slice(2));
|
|
@@ -15608,7 +16041,7 @@ ${HELP_TEXT}`);
|
|
|
15608
16041
|
launchPi,
|
|
15609
16042
|
log: (message) => console.log(message),
|
|
15610
16043
|
checkClientVersion: (baseUrl, cliVersion, jwt) => checkClientVersion(baseUrl, cliVersion, fetch, jwt),
|
|
15611
|
-
startGravityPixel: (jwt) => startGravityPixel(jwt,
|
|
16044
|
+
startGravityPixel: (jwt) => startGravityPixel(jwt, join16(dirname15(agentDir), "gravity")),
|
|
15612
16045
|
relaunchLatest: (latest) => relaunchViaNpx(latest, {
|
|
15613
16046
|
env: process.env,
|
|
15614
16047
|
argv: process.argv.slice(2),
|