@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 +4 -6
- package/bin/forge-server.mjs +35 -0
- package/lib/pipeline.ts +4 -0
- package/package.json +1 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
# Forge v0.10.
|
|
1
|
+
# Forge v0.10.71
|
|
2
2
|
|
|
3
3
|
Released: 2026-06-11
|
|
4
4
|
|
|
5
|
-
## Changes since v0.10.
|
|
5
|
+
## Changes since v0.10.70
|
|
6
6
|
|
|
7
7
|
### Other
|
|
8
|
-
- fix(
|
|
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.
|
|
11
|
+
**Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.70...v0.10.71
|
package/bin/forge-server.mjs
CHANGED
|
@@ -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