@aion0/forge 0.10.69 → 0.10.71

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/RELEASE_NOTES.md CHANGED
@@ -1,13 +1,11 @@
1
- # Forge v0.10.69
1
+ # Forge v0.10.71
2
2
 
3
3
  Released: 2026-06-11
4
4
 
5
- ## Changes since v0.10.68
5
+ ## Changes since v0.10.70
6
6
 
7
7
  ### Other
8
- - fix(idp): decouple probe host from IdP login host (probe_host)
9
- - fix(terminal): resolve absolute claude binary on tab restore
10
- - feat(pipeline): git OTP/2FA preflight + auth-blocked alerts
8
+ - fix(auth): persist AUTH_SECRET so sessions survive refresh/restart
11
9
 
12
10
 
13
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.68...v0.10.69
11
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.70...v0.10.71
@@ -123,6 +123,19 @@ const workspacePort = parseInt(getArg('--workspace-port')) || (webPort + 2);
123
123
  // second instance (dev-test on a different webPort) collided with the
124
124
  // main instance. Offset from webPort like every other service.
125
125
  const mcpPort = parseInt(getArg('--mcp-port')) || (webPort + 3);
126
+ // Browser-bridge standalone. Was hardcoded to 8407 everywhere (standalone,
127
+ // bridge-info route, extension fallback), so a second instance (e.g. --port
128
+ // 4000) reused the 8403 instance's bridge → the extension connected to the
129
+ // wrong instance and its token was rejected. Offset from webPort like the
130
+ // others; default 8403+4 = 8407 keeps existing single-instance pairings.
131
+ const bridgePort = parseInt(getArg('--bridge-port')) || (webPort + 4);
132
+ // Chat standalone. Was hardcoded to 8408 everywhere — a second instance
133
+ // (--port 4000) failed to bind it, then its next-server fell back to the
134
+ // 8408 default and hit the FIRST instance's chat standalone with a token
135
+ // signed for the wrong instance → "Sync failed: unauthorized" on
136
+ // settings/marketplace. Offset like bridge; default 8403+5 = 8408 keeps
137
+ // existing single-instance behaviour.
138
+ const chatPort = parseInt(getArg('--chat-port')) || (webPort + 5);
126
139
  const DATA_DIR = getArg('--dir')?.replace(/^~/, homedir()) || join(homedir(), '.forge', 'data');
127
140
 
128
141
  const PID_FILE = join(DATA_DIR, 'forge.pid');
@@ -245,8 +258,29 @@ process.env.PORT = String(webPort);
245
258
  process.env.TERMINAL_PORT = String(terminalPort);
246
259
  process.env.WORKSPACE_PORT = String(workspacePort);
247
260
  process.env.MCP_PORT = String(mcpPort);
261
+ process.env.BRIDGE_PORT = String(bridgePort);
262
+ process.env.CHAT_PORT = String(chatPort);
248
263
  process.env.FORGE_DATA_DIR = DATA_DIR;
249
264
 
265
+ // Persist AUTH_SECRET so every next-server worker uses the same key.
266
+ // lib/auth.ts generates a random one per process when unset, and Next prod
267
+ // mode spawns multiple workers — without a stable secret, a cookie signed
268
+ // by worker A is rejected by worker B, so the user randomly gets bounced
269
+ // to /login every few requests. Forge-server.mjs is the only common parent
270
+ // of all workers; lib/auth.ts is intentionally untouched.
271
+ if (!process.env.AUTH_SECRET) {
272
+ const SECRET_FILE = join(DATA_DIR, '.auth-secret');
273
+ let secret = '';
274
+ try { if (existsSync(SECRET_FILE)) secret = readFileSync(SECRET_FILE, 'utf-8').trim(); } catch {}
275
+ if (!secret) {
276
+ const { randomBytes } = await import('node:crypto');
277
+ secret = randomBytes(32).toString('hex');
278
+ if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
279
+ writeFileSync(SECRET_FILE, secret, { mode: 0o600 });
280
+ }
281
+ process.env.AUTH_SECRET = secret;
282
+ }
283
+
250
284
  // ── Password setup (first run or --reset-password) ──
251
285
  // Skipped in --add-enterprise-key persist-and-exit mode: registering a
252
286
  // key from install.sh must not block on an interactive prompt.
@@ -318,6 +352,7 @@ if (!isStop && !addEnterpriseKeyOnly) {
318
352
  }
319
353
  }
320
354
 
355
+
321
356
  // ── Enterprise keys (--enterprise-key=…) ──
322
357
  //
323
358
  // Each key is encrypted with AES-256-GCM via the same `.encrypt-key`
package/lib/pipeline.ts CHANGED
@@ -1068,6 +1068,10 @@ export function startPipeline(
1068
1068
  pipeline.error = `${blocked.message}${blocked.action ? `\nRun: ${blocked.action}` : ''}`;
1069
1069
  pipeline.completedAt = new Date().toISOString();
1070
1070
  savePipeline(pipeline);
1071
+ // Same settle housekeeping every other failure path gets — without it
1072
+ // schedule/binding-fired runs linger as "running" in pipeline_runs
1073
+ // until zombie reconciliation catches them.
1074
+ finalizePipeline(pipeline);
1071
1075
  void notifyAuthBlocked({
1072
1076
  title: `Pipeline aborted — git auth required (${workflowName})`,
1073
1077
  body: blocked.message,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.10.69",
3
+ "version": "0.10.71",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {