@aion0/forge 0.10.76 → 0.10.77

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,12 +1,12 @@
1
- # Forge v0.10.76
1
+ # Forge v0.10.77
2
2
 
3
3
  Released: 2026-06-12
4
4
 
5
- ## Changes since v0.10.75
5
+ ## Changes since v0.10.76
6
6
 
7
7
  ### Other
8
- - feat(memory): align Temper client with documented API contract
9
- - fix(chat): drain leftover notes at turn end late-merged input no longer lost
8
+ - fix(ui): route idpManualUrls open through openPortal too
9
+ - feat(ui): Open-portal buttons route through container Chromium when present
10
10
 
11
11
 
12
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.75...v0.10.76
12
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.10.76...v0.10.77
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
20
+ import { openPortal } from '@/lib/ui/openPortal';
20
21
 
21
22
  interface MarketEntry {
22
23
  id: string;
@@ -929,9 +930,8 @@ function TemplateImportModal({
929
930
  {p.url && (
930
931
  <a
931
932
  href={p.url}
932
- target="_blank"
933
- rel="noopener noreferrer"
934
- className="ml-auto text-[10px] text-[var(--accent)] hover:underline"
933
+ onClick={(e) => { e.preventDefault(); void openPortal(p.url!); }}
934
+ className="ml-auto text-[10px] text-[var(--accent)] hover:underline cursor-pointer"
935
935
  title={p.url}
936
936
  >
937
937
  ↗ {p.url_label || 'Get token'}
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { useEffect, useRef, useState } from 'react';
19
+ import { openPortal } from '@/lib/ui/openPortal';
19
20
 
20
21
  interface SourceView {
21
22
  tenant_id: string;
@@ -171,7 +172,7 @@ export default function EnterpriseBadge({ onOpenSettings: _onOpenSettings }: { o
171
172
  r.source.refresh.url,
172
173
  );
173
174
  failed.forEach((r, i) => setTimeout(() => {
174
- window.open(r.source.refresh.url!, '_blank', 'noopener');
175
+ void openPortal(r.source.refresh.url!);
175
176
  }, i * 250));
176
177
  }
177
178
  } catch (e) {
@@ -183,7 +184,7 @@ export default function EnterpriseBadge({ onOpenSettings: _onOpenSettings }: { o
183
184
 
184
185
  const handleOpenWeb = (row: { refresh: { kind: string; url?: string } }) => {
185
186
  if (row.refresh.kind === 'open-url' && row.refresh.url) {
186
- window.open(row.refresh.url, '_blank', 'noopener');
187
+ void openPortal(row.refresh.url);
187
188
  }
188
189
  };
189
190
 
@@ -533,10 +534,9 @@ export default function EnterpriseBadge({ onOpenSettings: _onOpenSettings }: { o
533
534
  <div key={i} className="flex items-center gap-1">
534
535
  <a
535
536
  href={u.url}
536
- target="_blank"
537
- rel="noopener"
537
+ onClick={(e) => { e.preventDefault(); void openPortal(u.url); }}
538
538
  title={u.error || u.host}
539
- className="flex-1 bg-black/30 px-1.5 py-0.5 rounded font-mono text-[10px] break-all hover:bg-black/40 underline"
539
+ className="flex-1 bg-black/30 px-1.5 py-0.5 rounded font-mono text-[10px] break-all hover:bg-black/40 underline cursor-pointer"
540
540
  >
541
541
  {u.url}
542
542
  </a>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
+ import { openPortal } from '@/lib/ui/openPortal';
4
5
 
5
6
  type LoginCategory = 'browser' | 'token' | 'external';
6
7
 
@@ -104,7 +105,7 @@ export default function LoginStatusPanel({ onClose }: { onClose: () => void }) {
104
105
  const refresh = (source: LoginSource) => {
105
106
  const r = source.refresh;
106
107
  if (r.kind === 'open-url') {
107
- window.open(r.url, '_blank', 'noopener');
108
+ void openPortal(r.url);
108
109
  } else if (r.kind === 'show-command') {
109
110
  setShowCmd({ command: r.command, description: r.description });
110
111
  } else if (r.kind === 'open-settings') {
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import { useEffect, useRef, useState } from 'react';
17
+ import { openPortal } from '@/lib/ui/openPortal';
17
18
 
18
19
  // ─── Types echoing the API surface ───────────────────────────
19
20
 
@@ -746,9 +747,8 @@ export function OnboardingDrawer({ onClose, onComplete, initialSourceId }: { onC
746
747
  {!ok && ref?.kind === 'open-url' && ref.url && (
747
748
  <a
748
749
  href={ref.url}
749
- target="_blank"
750
- rel="noopener noreferrer"
751
- className="text-[var(--accent)] hover:underline ml-auto"
750
+ onClick={(e) => { e.preventDefault(); void openPortal(ref.url!); }}
751
+ className="text-[var(--accent)] hover:underline ml-auto cursor-pointer"
752
752
  title={ref.description || ref.url}
753
753
  >
754
754
  ↗ login
@@ -1242,7 +1242,7 @@ export function OnboardingDrawer({ onClose, onComplete, initialSourceId }: { onC
1242
1242
  {!p.required && !isSet && <span className="text-[9px] text-[var(--text-secondary)]">(optional)</span>}
1243
1243
  {isSet && <span className="text-[9px] text-emerald-500">● currently set</span>}
1244
1244
  {p.url && (
1245
- <a href={p.url} target="_blank" rel="noopener noreferrer" className="ml-auto text-[10px] text-[var(--accent)] hover:underline" title={p.url}>
1245
+ <a href={p.url} onClick={(e) => { e.preventDefault(); void openPortal(p.url!); }} className="ml-auto text-[10px] text-[var(--accent)] hover:underline cursor-pointer" title={p.url}>
1246
1246
  ↗ {p.url_label || 'Get token'}
1247
1247
  </a>
1248
1248
  )}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * openPortal — open a vendor login / portal URL in the RIGHT browser.
3
+ *
4
+ * Mac-native Forge: the UI runs in the user's own browser, which holds
5
+ * their SSO cookies → window.open is correct.
6
+ *
7
+ * Container deploy: the UI renders in the user's browser but the agent
8
+ * drives a Chromium INSIDE the container, where all SSO cookies live.
9
+ * window.open would land in the host browser (no corp session, useless).
10
+ * So when an extension is connected — the same signal the Login-status
11
+ * probe already relies on — route the open through the bridge so the
12
+ * extension runs chrome.tabs.create in the container Chromium.
13
+ *
14
+ * Pure client helper (fetch + DOM). Reference/help links should keep
15
+ * using a plain window.open / <a target="_blank"> — they genuinely
16
+ * belong in the user's own browser.
17
+ */
18
+
19
+ function portalToast(msg: string): void {
20
+ if (typeof document === 'undefined') return;
21
+ const el = document.createElement('div');
22
+ el.textContent = msg;
23
+ el.style.cssText =
24
+ 'position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:99999;' +
25
+ 'background:#1e293b;color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;' +
26
+ 'box-shadow:0 4px 12px rgba(0,0,0,.3);opacity:0;transition:opacity .2s';
27
+ document.body.appendChild(el);
28
+ requestAnimationFrame(() => { el.style.opacity = '1'; });
29
+ setTimeout(() => { el.style.opacity = '0'; setTimeout(() => el.remove(), 300); }, 3000);
30
+ }
31
+
32
+ export async function openPortal(url: string): Promise<void> {
33
+ if (!url) return;
34
+ try {
35
+ const status = await fetch('/api/browser-bridge?action=status')
36
+ .then((r) => (r.ok ? r.json() : null))
37
+ .catch(() => null);
38
+ const connected = Number(status?.connected_extensions || 0) > 0;
39
+ if (connected) {
40
+ const r = await fetch('/api/browser-bridge', {
41
+ method: 'POST',
42
+ headers: { 'content-type': 'application/json' },
43
+ body: JSON.stringify({
44
+ action: 'rpc',
45
+ method: 'browser.open_tab',
46
+ params: { url, active: true },
47
+ }),
48
+ });
49
+ // proxy returns the bridge rpc envelope: {ok, value} | {ok:false, error}
50
+ const j = await r.json().catch(() => null);
51
+ if (r.ok && j && j.ok !== false) {
52
+ portalToast('Opened in workspace browser (visible via stream)');
53
+ return;
54
+ }
55
+ }
56
+ } catch {
57
+ /* fall through to host-browser open */
58
+ }
59
+ window.open(url, '_blank', 'noopener');
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.10.76",
3
+ "version": "0.10.77",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {