@aion0/forge 0.10.81 → 0.10.83

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,11 +1,11 @@
1
- # Forge v0.10.81
1
+ # Forge v0.10.83
2
2
 
3
- Released: 2026-06-14
3
+ Released: 2026-06-15
4
4
 
5
- ## Changes since v0.10.80
5
+ ## Changes since v0.10.82
6
6
 
7
7
  ### Other
8
- - feat(ui): URL deep-linking + Automation/History default + Pipeline History page
8
+ - feat(settings): disable admin password change in container mode
9
9
 
10
10
 
11
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.80...v0.10.81
11
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.82...v0.10.83
@@ -1,11 +1,20 @@
1
1
  import { NextResponse } from 'next/server';
2
+ import { existsSync } from 'node:fs';
2
3
  import { loadSettings, loadSettingsMasked, saveSettings, type Settings } from '@/lib/settings';
3
4
  import { restartTelegramBot } from '@/lib/init';
4
5
  import { SECRET_FIELDS } from '@/lib/crypto';
5
6
  import { verifyAdmin } from '@/lib/password';
6
7
 
8
+ // Container deployments manage the admin password out-of-band (control plane /
9
+ // `make change-password`), which also re-pairs the browser-extension bridge
10
+ // token. Same detection signal as app/api/onboarding/route.ts.
11
+ function inContainer(): boolean {
12
+ if (process.env.FORGE_CONTAINER === '1') return true;
13
+ try { return existsSync('/.dockerenv'); } catch { return false; }
14
+ }
15
+
7
16
  export async function GET() {
8
- return NextResponse.json(loadSettingsMasked());
17
+ return NextResponse.json({ ...loadSettingsMasked(), _inContainer: inContainer() });
9
18
  }
10
19
 
11
20
  export async function PUT(req: Request) {
@@ -34,6 +43,21 @@ export async function PUT(req: Request) {
34
43
  && !existingSettings.telegramTunnelPassword
35
44
  && !!newValue;
36
45
 
46
+ // In container mode the admin password is managed out-of-band (the
47
+ // platform rotation also re-pairs the browser extension's bridge token).
48
+ // Changing it here would desync that token and break the agent's browser,
49
+ // with no UI re-pair path. Block rotations — but still allow the very first
50
+ // set (isFirstAdminSet), which happens before any extension pairing.
51
+ if (field === 'telegramTunnelPassword' && !isFirstAdminSet && inContainer()) {
52
+ return NextResponse.json({
53
+ ok: false,
54
+ error:
55
+ 'Password is managed by the platform in container mode. Change it from ' +
56
+ 'the admin console / self-service portal (it also re-pairs the browser ' +
57
+ 'extension), or on the host run: make change-password U=<user>.',
58
+ }, { status: 403 });
59
+ }
60
+
37
61
  if (!isFirstAdminSet && !verifyAdmin(adminPassword)) {
38
62
  return NextResponse.json({ ok: false, error: 'Wrong password' }, { status: 403 });
39
63
  }
@@ -367,6 +367,9 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
367
367
  maxConcurrentPipelines: 5,
368
368
  });
369
369
  const [secretStatus, setSecretStatus] = useState<Record<string, boolean>>({});
370
+ // Container deployments manage the admin password out-of-band (it also
371
+ // re-pairs the browser extension); UI change is disabled when set.
372
+ const [inContainer, setInContainer] = useState(false);
370
373
  const [newRoot, setNewRoot] = useState('');
371
374
  const [newDocRoot, setNewDocRoot] = useState('');
372
375
  const [pickerFor, setPickerFor] = useState<null | 'project' | 'doc'>(null);
@@ -390,7 +393,9 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
390
393
  const fetchSettings = useCallback(() => {
391
394
  fetch('/api/settings').then(r => r.json()).then((data: Settings) => {
392
395
  const status = data._secretStatus || {};
396
+ setInContainer(!!(data as any)._inContainer);
393
397
  delete data._secretStatus;
398
+ delete (data as any)._inContainer;
394
399
  setSettings(data);
395
400
  origSettingsRef.current = JSON.stringify(data);
396
401
  setSecretStatus(status);
@@ -983,14 +988,30 @@ export default function SettingsModal({ onClose }: { onClose: () => void }) {
983
988
  <p className="text-[10px] text-[var(--text-secondary)]">
984
989
  Used for local login, tunnel start, secret changes, and Telegram commands. Remote login requires admin password + session code (generated on tunnel start).
985
990
  </p>
986
- <SecretField
987
- label="Admin Password"
988
- isSet={!!secretStatus.telegramTunnelPassword}
989
- onEdit={() => setEditingSecret({ field: 'telegramTunnelPassword', label: 'Admin Password' })}
990
- />
991
- <p className="text-[9px] text-[var(--text-secondary)]">
992
- Forgot? Run: <code className="text-[var(--accent)]">forge --reset-password</code>
993
- </p>
991
+ {inContainer && secretStatus.telegramTunnelPassword ? (
992
+ <>
993
+ <div className="flex items-center justify-between gap-2 px-2 py-1.5 bg-[var(--bg-tertiary)] border border-[var(--border)] rounded">
994
+ <span className="text-xs font-mono text-[var(--text-secondary)]">••••••••</span>
995
+ <span className="text-[9px] text-[var(--text-secondary)] italic">Managed by platform</span>
996
+ </div>
997
+ <p className="text-[9px] text-[var(--text-secondary)]">
998
+ Password is managed by the platform. Change it from the admin console
999
+ or your self-service portal — it also re-pairs the browser extension.
1000
+ (Host CLI: <code className="text-[var(--accent)]">make change-password U=&lt;user&gt;</code>.)
1001
+ </p>
1002
+ </>
1003
+ ) : (
1004
+ <>
1005
+ <SecretField
1006
+ label="Admin Password"
1007
+ isSet={!!secretStatus.telegramTunnelPassword}
1008
+ onEdit={() => setEditingSecret({ field: 'telegramTunnelPassword', label: 'Admin Password' })}
1009
+ />
1010
+ <p className="text-[9px] text-[var(--text-secondary)]">
1011
+ Forgot? Run: <code className="text-[var(--accent)]">forge --reset-password</code>
1012
+ </p>
1013
+ </>
1014
+ )}
994
1015
  </div>
995
1016
 
996
1017
  {/* API Key */}
@@ -130,7 +130,16 @@ async function runHttpProbe(
130
130
  const t0 = Date.now();
131
131
  let res: Response;
132
132
  try {
133
- res = await fetch(url, { method, headers, body, signal: ctrl.signal });
133
+ // Honour connector-level http.verify_tls self-signed appliances (Jenkins,
134
+ // NAC, ESXi …) need undici with rejectUnauthorized:false, same as http.ts.
135
+ const fetchInit = { method, headers, body, signal: ctrl.signal };
136
+ if (def.http?.verify_tls === false) {
137
+ const { fetch: undiciFetch, Agent } = await import('undici');
138
+ const dispatcher = new Agent({ connect: { rejectUnauthorized: false } });
139
+ res = await undiciFetch(url, { ...fetchInit, dispatcher } as any) as unknown as Response;
140
+ } else {
141
+ res = await fetch(url, fetchInit);
142
+ }
134
143
  } catch (e) {
135
144
  clearTimeout(timer);
136
145
  const err = e as Error & { cause?: unknown };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.10.81",
3
+ "version": "0.10.83",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {