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 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.0",
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 name =
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
- `Deploy ${label} relay '${name}' using token ending ${token.slice(-4)}?`,
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 r = ensureRelay(s, url, `deployed ${name}`);
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 = 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(url, auth);
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}`}) — verify with /freeflow test`;
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
- ctx.ui.notify(`✓ Deployed & active: ${url}${probeNote}`, "info");
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: ${(e as Error).message}`,
699
+ `Deploy failed: ${safeMsg}`,
632
700
  "error",
633
701
  );
634
702
  }