pi-freeflow 1.26.0 → 1.26.1
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/commands.ts +76 -8
- package/src/deploy.ts +655 -576
- package/src/proxy.ts +113 -47
- package/src/responses.ts +425 -264
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.26.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix `/freeflow deploy vercel` failing at the Vercel build step with `Unterminated string literal`. The generated relay file now passes syntax checks on all three deploy targets, and newly deployed relays come with a built-in password by default instead of being openly usable by anyone with the URL.
|
|
8
|
+
- Recover from expired conversation reasoning references instead of failing every turn. When the provider reports a reasoning item as expired, the request is retried without the unresolvable history on the same relay, so the session continues with one turn of lost reasoning context rather than dying.
|
|
9
|
+
|
|
3
10
|
## 1.26.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-freeflow",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.26.
|
|
4
|
+
"version": "1.26.1",
|
|
5
5
|
"description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
|
|
6
6
|
"main": "extensions/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
package/src/commands.ts
CHANGED
|
@@ -383,6 +383,39 @@ function clineErrorMessage(e: unknown): string {
|
|
|
383
383
|
if (typeof e === "string" && e) return e;
|
|
384
384
|
return "unknown error";
|
|
385
385
|
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Validate a user-supplied deploy project name before any network call.
|
|
389
|
+
* Mirrors the cleanup the deploy builders apply (lowercase, runs of
|
|
390
|
+
* non-[a-z0-9-] become one dash, edge dashes trimmed): a name with no usable
|
|
391
|
+
* characters would silently fall back to a generic worker name and likely
|
|
392
|
+
* collide, and an over-long Cloudflare/Deno name would silently truncate to
|
|
393
|
+
* something the user did not ask for. Pure: never touches disk or network.
|
|
394
|
+
*/
|
|
395
|
+
export function validateDeployProjectName(
|
|
396
|
+
raw: string,
|
|
397
|
+
platform: DeployPlatform,
|
|
398
|
+
): { ok: true; name: string } | { ok: false; reason: string } {
|
|
399
|
+
const clean = (raw || "")
|
|
400
|
+
.toLowerCase()
|
|
401
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
402
|
+
.replace(/-{2,}/g, "-")
|
|
403
|
+
.replace(/^-+|-+$/g, "");
|
|
404
|
+
if (!clean) {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
reason: `Project name '${(raw || "").trim()}' has no usable letters or numbers — use characters a-z, 0-9, '-'`,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const max = platform === "cloudflare" ? 58 : platform === "deno" ? 32 : 0;
|
|
411
|
+
if (max > 0 && clean.length > max) {
|
|
412
|
+
return {
|
|
413
|
+
ok: false,
|
|
414
|
+
reason: `Project name '${clean}' is ${clean.length} chars — ${platform === "cloudflare" ? "Cloudflare" : "Deno Deploy"} allows max ${max}. Shorten it and retry`,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return { ok: true, name: clean };
|
|
418
|
+
}
|
|
386
419
|
export function createCommandSpec(
|
|
387
420
|
_pi: ExtensionAPI,
|
|
388
421
|
onCatalogRefreshed?: (models: RegisteredModel[]) => void,
|
|
@@ -576,18 +609,29 @@ export function createCommandSpec(
|
|
|
576
609
|
ctx.ui.notify("Deploy cancelled: no token", "warning");
|
|
577
610
|
return;
|
|
578
611
|
}
|
|
579
|
-
const
|
|
612
|
+
const entered =
|
|
580
613
|
(
|
|
581
614
|
await ctx.ui.input(
|
|
582
615
|
"Project name (empty = auto):",
|
|
583
616
|
defaultName,
|
|
584
617
|
)
|
|
585
618
|
)?.trim() || defaultName;
|
|
619
|
+
const nameCheck = validateDeployProjectName(entered, platform);
|
|
620
|
+
if (!nameCheck.ok) {
|
|
621
|
+
ctx.ui.notify(`Deploy cancelled: ${nameCheck.reason}`, "warning");
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const name = entered;
|
|
625
|
+
const cleanName = nameCheck.name;
|
|
626
|
+
// Never display a short token in full: its "last 4" would be the secret.
|
|
627
|
+
const tokenTail = token.length > 8 ? token.slice(-4) : "***";
|
|
586
628
|
|
|
587
629
|
if (typeof ctx.ui.confirm === "function") {
|
|
588
630
|
const ok = await ctx.ui.confirm(
|
|
589
631
|
"Deploy relay",
|
|
590
|
-
|
|
632
|
+
cleanName === name
|
|
633
|
+
? `Deploy ${label} relay '${name}' using token ending ${tokenTail}?`
|
|
634
|
+
: `Deploy ${label} relay '${name}' (as '${cleanName}') using token ending ${tokenTail}?`,
|
|
591
635
|
);
|
|
592
636
|
if (!ok) {
|
|
593
637
|
ctx.ui.notify("Deploy cancelled", "warning");
|
|
@@ -606,29 +650,53 @@ export function createCommandSpec(
|
|
|
606
650
|
const { url, auth } = await deployer(token, name, (m) =>
|
|
607
651
|
ctx.ui.notify(m, "info"),
|
|
608
652
|
);
|
|
653
|
+
const finalUrl = url.trim().replace(/\/+$/, "");
|
|
609
654
|
relayState = withRelayState((s) => {
|
|
610
|
-
const
|
|
655
|
+
const prevLabel = s.relays.find((r) => r.url === finalUrl)?.label;
|
|
656
|
+
const r = ensureRelay(s, finalUrl, `deployed ${cleanName}`);
|
|
611
657
|
if (auth) r.auth = auth;
|
|
612
658
|
else delete r.auth;
|
|
659
|
+
// A re-deploy at an existing address refreshes the secret but keeps a
|
|
660
|
+
// user-chosen short name; only stock `deployed …` labels roll forward.
|
|
661
|
+
if (prevLabel && !prevLabel.startsWith("deployed ")) r.label = prevLabel;
|
|
613
662
|
s.enabled = true;
|
|
614
|
-
s.url =
|
|
663
|
+
s.url = finalUrl;
|
|
615
664
|
return s;
|
|
616
665
|
});
|
|
617
666
|
persist();
|
|
667
|
+
// saveRelayState swallows disk errors, so confirm the pool entry landed:
|
|
668
|
+
// otherwise a live deployment would silently vanish on the next restart.
|
|
669
|
+
let poolSaved = false;
|
|
670
|
+
try {
|
|
671
|
+
poolSaved = loadRelayState().relays.some((r) => r.url === finalUrl);
|
|
672
|
+
} catch {
|
|
673
|
+
poolSaved = false;
|
|
674
|
+
}
|
|
618
675
|
let probeNote = "";
|
|
619
676
|
try {
|
|
620
|
-
const probe = await probeRelay(
|
|
677
|
+
const probe = await probeRelay(finalUrl, auth);
|
|
621
678
|
probeNote = probe.ok
|
|
622
679
|
? ` ✓ reachable (HTTP ${probe.status}, ${probe.latencyMs}ms)`
|
|
623
|
-
: ` ⚠ deployed but unreachable (${probe.error || `HTTP ${probe.status}`}) —
|
|
680
|
+
: ` ⚠ deployed but unreachable (${probe.error || `HTTP ${probe.status}`}) — run /freeflow test ${finalUrl} to retry, or /freeflow remove ${finalUrl} to drop it`;
|
|
624
681
|
} catch {
|
|
625
682
|
// probeRelay never throws, but keep the notify safe regardless
|
|
626
683
|
}
|
|
627
|
-
|
|
684
|
+
if (!poolSaved) {
|
|
685
|
+
const secretNote = auth
|
|
686
|
+
? " Note: this relay uses a shared secret, so prefer redeploying if the re-added copy fails its probe."
|
|
687
|
+
: "";
|
|
688
|
+
ctx.ui.notify(`⚠ Relay is live at ${finalUrl} but NOT saved to the pool (disk write failed)${probeNote} — restore it with: /freeflow add ${finalUrl}.${secretNote}`, "error");
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
ctx.ui.notify(`✓ Deployed & active: ${finalUrl}${probeNote}`, "info");
|
|
628
692
|
} catch (e) {
|
|
629
693
|
updateStatusBar(ctx.ui);
|
|
694
|
+
// Deploy errors echo upstream detail; never let the platform API token
|
|
695
|
+
// leak through a message that repeats what the server (or network) said.
|
|
696
|
+
const rawMsg = (e as Error).message;
|
|
697
|
+
const safeMsg = token.length >= 8 ? rawMsg.split(token).join("[redacted]") : rawMsg;
|
|
630
698
|
ctx.ui.notify(
|
|
631
|
-
`Deploy failed: ${
|
|
699
|
+
`Deploy failed: ${safeMsg}`,
|
|
632
700
|
"error",
|
|
633
701
|
);
|
|
634
702
|
}
|