forge-memory 0.2.113 → 0.2.115

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 CHANGED
@@ -38,11 +38,12 @@ tools). It also exposes `forge_memory_mcp_diagnostics` so adapter startup issues
38
38
  show up as a tool result instead of a closed MCP transport.
39
39
 
40
40
  `pair-ios` prefers the Iroh QR. Forge starts a Rust Iroh host, prints a QR payload
41
- with the desktop node id, pairing token, optional relay hint, and ALPN
42
- `forge-companion/1`, and the iPhone app connects through its native Rust bridge. The
43
- CLI renders a short-schema QR to keep the terminal code scannable and saves the
44
- full manual payload under `~/.forge/pairing/` so you can paste it into the iPhone
45
- app if the camera cannot scan.
41
+ with the desktop node id, pairing token, optional relay hint, ALPN
42
+ `forge-companion/1`, and the request URL as a direct fallback when it is
43
+ phone-reachable. The iPhone app connects through its native Rust bridge first and can
44
+ retry through URLSession when that bridge times out. The CLI renders a short-schema QR
45
+ to keep the terminal code scannable and saves the full manual payload under
46
+ `~/.forge/pairing/` so you can paste it into the iPhone app if the camera cannot scan.
46
47
  Use `--manual-http` only when you intentionally want a LAN, Tailscale, or direct
47
48
  HTTP/TCP route. For a real iPhone, pass a phone-reachable URL:
48
49
 
@@ -606,6 +606,59 @@ function findForgeRepo(start = process.cwd()) {
606
606
  }
607
607
  }
608
608
 
609
+ function resolveRuntimeStorageRoot(healthResult) {
610
+ const storageRoot = healthResult?.payload?.runtime?.storageRoot;
611
+ return typeof storageRoot === "string" && storageRoot.trim()
612
+ ? path.resolve(storageRoot)
613
+ : null;
614
+ }
615
+
616
+ function pathsMatch(left, right) {
617
+ return path.resolve(left) === path.resolve(right);
618
+ }
619
+
620
+ async function resolveInstallRuntimeTarget({
621
+ origin,
622
+ requestedPort,
623
+ requestedWebPort,
624
+ dataRoot,
625
+ dataRootWasExplicit
626
+ }) {
627
+ if (requestedPort !== 0) {
628
+ const desiredConfig = { origin, port: requestedPort };
629
+ const desiredHealth = await health(desiredConfig);
630
+ if (isHealthyForgeRuntime(desiredHealth)) {
631
+ const liveDataRoot = resolveRuntimeStorageRoot(desiredHealth);
632
+ if (liveDataRoot && !pathsMatch(liveDataRoot, dataRoot)) {
633
+ if (dataRootWasExplicit) {
634
+ throw new Error(
635
+ [
636
+ `A healthy Forge runtime is already running at ${baseUrl(desiredConfig)}, but it uses a different data folder.`,
637
+ `Live data folder: ${liveDataRoot}.`,
638
+ `Requested data folder: ${path.resolve(dataRoot)}.`,
639
+ "Stop or restart that runtime before switching data folders. Your data folder is unchanged."
640
+ ].join(" ")
641
+ );
642
+ }
643
+ dataRoot = liveDataRoot;
644
+ }
645
+ return {
646
+ port: requestedPort,
647
+ webPort: requestedWebPort,
648
+ dataRoot: path.resolve(dataRoot),
649
+ adoptedExistingRuntime: true
650
+ };
651
+ }
652
+ }
653
+
654
+ return {
655
+ port: await findFreePort(requestedPort),
656
+ webPort: await findFreePort(requestedWebPort),
657
+ dataRoot: path.resolve(dataRoot),
658
+ adoptedExistingRuntime: false
659
+ };
660
+ }
661
+
609
662
  async function buildInstallConfig(parsed, currentConfig, discovery, command) {
610
663
  const repo = parsed.values.repo
611
664
  ? path.resolve(parsed.values.repo)
@@ -634,22 +687,25 @@ async function buildInstallConfig(parsed, currentConfig, discovery, command) {
634
687
  const dataRoot = parsed.flags.yes
635
688
  ? dataRootDefault
636
689
  : await promptLine("Forge data folder", dataRootDefault);
637
- const portInput = parsed.values.port ?? currentConfig.port;
638
- const port = await findFreePort(normalizePort(portInput, DEFAULT_PORT));
639
- const webPort = await findFreePort(
640
- normalizePort(
690
+ const origin = parsed.values.origin ?? currentConfig.origin ?? DEFAULT_ORIGIN;
691
+ const runtimeTarget = await resolveInstallRuntimeTarget({
692
+ origin,
693
+ requestedPort: normalizePort(parsed.values.port ?? currentConfig.port, DEFAULT_PORT),
694
+ requestedWebPort: normalizePort(
641
695
  parsed.values.webPort ?? currentConfig.webPort,
642
696
  DEFAULT_WEB_PORT
643
- )
644
- );
697
+ ),
698
+ dataRoot,
699
+ dataRootWasExplicit: typeof parsed.values.dataRoot === "string"
700
+ });
645
701
 
646
702
  return {
647
703
  version: VERSION,
648
704
  mode: parsed.flags.dev ? "dev" : mode,
649
- origin: parsed.values.origin ?? currentConfig.origin ?? DEFAULT_ORIGIN,
650
- port,
651
- webPort,
652
- dataRoot: path.resolve(dataRoot),
705
+ origin,
706
+ port: runtimeTarget.port,
707
+ webPort: runtimeTarget.webPort,
708
+ dataRoot: runtimeTarget.dataRoot,
653
709
  adapters,
654
710
  repo,
655
711
  command
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-memory",
3
- "version": "0.2.113",
3
+ "version": "0.2.115",
4
4
  "description": "Guided Forge installer and local runtime manager for the Forge UI, OpenClaw, Hermes, Codex, and iOS pairing.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",