open-agents-ai 0.187.533 → 0.187.534

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/dist/index.js CHANGED
@@ -528897,6 +528897,8 @@ TASK: ${task}` : task;
528897
528897
  try {
528898
528898
  if (this.options.subAgent)
528899
528899
  throw "skip-handoff-subagent";
528900
+ if (process.env["OA_FRESH_SESSION"] === "1")
528901
+ throw "skip-handoff-fresh";
528900
528902
  const oaDir = this._workingDirectory ? _pathJoin(this._workingDirectory, ".oa") : _pathJoin(process.cwd(), ".oa");
528901
528903
  const chainPairs = loadMessagePairsFromLog(oaDir, { currentTask: cleanedTask });
528902
528904
  if (chainPairs.length > 0) {
@@ -585554,6 +585556,7 @@ async function tryRouteV1(ctx3) {
585554
585556
  const m2 = /^\/v1\/keys\/([^/]+)$/.exec(pathname);
585555
585557
  if (m2 && method === "DELETE") return handleRevokeKey(ctx3, decodeURIComponent(m2[1]));
585556
585558
  }
585559
+ if (pathname === "/v1/share/generate" && method === "POST") return handleGenerateShare(ctx3);
585557
585560
  if (pathname === "/v1/tools" && method === "GET") {
585558
585561
  return handleListTools(ctx3);
585559
585562
  }
@@ -586695,6 +586698,75 @@ async function handleMintKey(ctx3) {
586695
586698
  }
586696
586699
  return true;
586697
586700
  }
586701
+ async function handleGenerateShare(ctx3) {
586702
+ const { req: req2, res, requestId } = ctx3;
586703
+ const reqAuth = req2;
586704
+ if (reqAuth._authScope !== "admin") {
586705
+ sendProblem(res, problemDetails({
586706
+ type: P.forbidden,
586707
+ status: 403,
586708
+ title: "Admin scope required",
586709
+ detail: "Generating a share URL mints a runtime key, which requires 'admin' scope.",
586710
+ instance: requestId
586711
+ }));
586712
+ return true;
586713
+ }
586714
+ try {
586715
+ const body = await parseJsonBodyStrict(req2).catch(() => null) ?? {};
586716
+ const label = typeof body["label"] === "string" && body["label"] ? String(body["label"]).slice(0, 100) : `share-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
586717
+ const scope = typeof body["scope"] === "string" && ["read", "run", "admin"].includes(body["scope"]) ? body["scope"] : "run";
586718
+ const owner = `share:${label}`;
586719
+ const { mintKey: mintKey2 } = await Promise.resolve().then(() => (init_runtime_keys(), runtime_keys_exports));
586720
+ const rec = mintKey2({
586721
+ scope,
586722
+ owner,
586723
+ profile: null
586724
+ });
586725
+ const hostHeader = String(req2.headers["host"] || "").trim();
586726
+ const fallbackHost = process.env["OA_HOST"] || "127.0.0.1:11435";
586727
+ let hostPort = hostHeader || fallbackHost;
586728
+ hostPort = hostPort.replace(/^https?:\/\//i, "");
586729
+ const colonIdx = hostPort.lastIndexOf(":");
586730
+ const host = colonIdx > 0 ? hostPort.slice(0, colonIdx) : hostPort;
586731
+ const portStr = colonIdx > 0 ? hostPort.slice(colonIdx + 1) : "11435";
586732
+ const port = parseInt(portStr, 10) || 11435;
586733
+ const fullKey = rec.key || "";
586734
+ const keyPrefix2 = fullKey.slice(0, 12);
586735
+ if (!fullKey) {
586736
+ sendProblem(res, problemDetails({
586737
+ type: P.internalError,
586738
+ status: 500,
586739
+ title: "Key generation succeeded but no secret returned",
586740
+ detail: "mintKey() did not include the full secret in its response.",
586741
+ instance: requestId
586742
+ }));
586743
+ return true;
586744
+ }
586745
+ const scheme = String(req2.headers["x-forwarded-proto"] || (req2.socket?.encrypted ? "https" : "http"));
586746
+ const shareUrl = `oa-share://${hostPort}#${fullKey}`;
586747
+ const plainUrl = `${scheme}://${hostPort}/?oa-key=${encodeURIComponent(fullKey)}&oa-share-label=${encodeURIComponent(label)}`;
586748
+ sendJson(res, 201, {
586749
+ shareUrl,
586750
+ plainUrl,
586751
+ host,
586752
+ port,
586753
+ key: fullKey,
586754
+ keyPrefix: keyPrefix2,
586755
+ label,
586756
+ issuedAt: rec.created || (/* @__PURE__ */ new Date()).toISOString(),
586757
+ _note: "This is the ONLY response that contains the full key. Hand off the URL now."
586758
+ });
586759
+ } catch (err) {
586760
+ sendProblem(res, problemDetails({
586761
+ type: P.internalError,
586762
+ status: 500,
586763
+ title: "Share URL generation failed",
586764
+ detail: err instanceof Error ? err.message : String(err),
586765
+ instance: requestId
586766
+ }));
586767
+ }
586768
+ return true;
586769
+ }
586698
586770
  async function handleRevokeKey(ctx3, prefix) {
586699
586771
  const { req: req2, res, requestId } = ctx3;
586700
586772
  const reqAuth = req2;
@@ -589011,7 +589083,7 @@ body { display:flex; flex-direction:column; height:100vh; margin:0; overflow:hid
589011
589083
  <span id="sidebar-status-dot" style="width:8px;height:8px;border-radius:50%;background:var(--color-fg-faint);flex-shrink:0" title="Backend connection"></span>
589012
589084
  <span id="sidebar-status-text" style="flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">connecting...</span>
589013
589085
  <button id="sidebar-update-btn" onclick="doUpdate()" style="display:none;background:var(--color-warning);border:none;color:#000;padding:2px 6px;border-radius:var(--radius-sm);font-size:0.6rem;cursor:pointer;font-weight:600">update</button>
589014
- <button id="sidebar-key-btn" onclick="document.getElementById('key-modal').style.display='flex'" title="Set API key" style="background:transparent;border:1px solid var(--color-border);color:var(--color-fg-muted);padding:2px 6px;border-radius:var(--radius-sm);font-size:0.6rem;cursor:pointer">key</button>
589086
+ <button id="sidebar-key-btn" onclick="openKeyModal()" title="Set API key / share access" style="background:transparent;border:1px solid var(--color-border);color:var(--color-fg-muted);padding:2px 6px;border-radius:var(--radius-sm);font-size:0.6rem;cursor:pointer">key</button>
589015
589087
  </div>
589016
589088
 
589017
589089
  <!-- Resize handle -->
@@ -589343,14 +589415,21 @@ body { display:flex; flex-direction:column; height:100vh; margin:0; overflow:hid
589343
589415
  </div>
589344
589416
 
589345
589417
  <div id="key-modal">
589346
- <form class="modal" onsubmit="event.preventDefault(); saveKey();" autocomplete="off">
589347
- <h3>API Key</h3>
589348
- <input id="key-input" type="password" placeholder="Bearer token (leave empty if auth disabled)" autocomplete="new-password">
589349
- <div>
589418
+ <form class="modal" onsubmit="event.preventDefault(); saveKey();" autocomplete="off" style="min-width:480px">
589419
+ <h3>API Key &amp; Sharing</h3>
589420
+ <div style="position:relative">
589421
+ <input id="key-input" type="password" placeholder="Bearer token (leave empty if auth disabled, or paste an oa-share:// URL to connect remote)" autocomplete="new-password" oninput="onKeyInputChange(this.value)" onfocus="showRecentKeysDropdown()" onblur="setTimeout(hideRecentKeysDropdown, 150)">
589422
+ <div id="key-recent-dropdown" style="display:none;position:absolute;top:100%;left:0;right:0;background:var(--color-bg-elevated);border:1px solid var(--color-border);border-radius:var(--radius-sm);max-height:180px;overflow-y:auto;z-index:10;font-size:0.78rem;margin-top:2px"></div>
589423
+ </div>
589424
+ <p id="key-input-hint" style="font-size:0.7rem;color:var(--color-fg-muted);margin:4px 0 8px">Tip: paste an <code>oa-share://host:port#key</code> URL or <code>http://host:port/?oa-key=…</code> to connect to a remote OA instance.</p>
589425
+ <div style="display:flex;gap:6px;flex-wrap:wrap">
589350
589426
  <button type="submit">save</button>
589351
589427
  <button type="button" onclick="clearKey()">clear</button>
589428
+ <button type="button" onclick="generateShareUrl()" title="Generate a share URL that lets a remote OA instance connect to this one">share access</button>
589352
589429
  <button type="button" onclick="closeKeyModal()">cancel</button>
589353
589430
  </div>
589431
+ <div id="share-result" style="display:none;margin-top:14px;padding:10px;background:var(--color-bg-elevated);border:1px solid var(--color-border);border-radius:var(--radius-sm);font-size:0.78rem"></div>
589432
+ <div id="remote-state" style="display:none;margin-top:14px;padding:10px;background:var(--color-bg-elevated);border:1px solid var(--color-accent);border-radius:var(--radius-sm);font-size:0.78rem"></div>
589354
589433
  </form>
589355
589434
  </div>
589356
589435
 
@@ -590472,6 +590551,15 @@ async function sendMessage() {
590472
590551
  messageWithContext = filesBlock + text;
590473
590552
  }
590474
590553
 
590554
+ // FRESH-SESSION: pull the one-shot fresh flag the newChatSession()
590555
+ // handler set. Consumed (cleared) here so subsequent sends within
590556
+ // this same session don't keep claiming "fresh" and miss legitimate
590557
+ // mid-session handoff.
590558
+ let _freshPending = false;
590559
+ try {
590560
+ _freshPending = localStorage.getItem('oa.freshSessionPending') === '1';
590561
+ if (_freshPending) localStorage.removeItem('oa.freshSessionPending');
590562
+ } catch {}
590475
590563
  const body = {
590476
590564
  session_id: chatSessionId,
590477
590565
  model: modelSelect.value,
@@ -590481,6 +590569,10 @@ async function sendMessage() {
590481
590569
  // Pass the user-selected workspace as working_directory so the
590482
590570
  // agent subprocess operates in the right cwd.
590483
590571
  ...(chatWorkingDir ? { working_directory: chatWorkingDir } : {}),
590572
+ // FRESH-SESSION: tell the daemon to skip cross-task handoff
590573
+ // injection on this turn. Daemon plumbs it to OA_FRESH_SESSION=1
590574
+ // for the agent subprocess; runner's handoff block respects it.
590575
+ ...(_freshPending ? { fresh: true } : {}),
590484
590576
  };
590485
590577
 
590486
590578
  const response = await fetch('/v1/chat', {
@@ -590788,8 +590880,33 @@ document.getElementById('key-btn').onclick = () => {
590788
590880
  document.getElementById('key-input').value = apiKey;
590789
590881
  };
590790
590882
  function saveKey() {
590791
- apiKey = document.getElementById('key-input').value;
590883
+ const raw = document.getElementById('key-input').value || '';
590884
+ // SHARE: detect oa-share://host:port#key OR http(s)://host:port/?oa-key=...
590885
+ const parsed = parseShareInput(raw);
590886
+ if (parsed) {
590887
+ // Pasting a share URL → switch into REMOTE mode and reload page
590888
+ // against the remote origin with the key in localStorage.
590889
+ saveRecentKey({ key: parsed.key, host: parsed.host, label: parsed.label || ('remote ' + parsed.host) });
590890
+ try {
590891
+ localStorage.setItem('oa.remoteHost', parsed.host);
590892
+ localStorage.setItem('oa.remoteScheme', parsed.scheme || 'http');
590893
+ localStorage.setItem('oa-api-key', parsed.key);
590894
+ } catch {}
590895
+ // Redirect to the remote host's UI with the key carried in localStorage
590896
+ // (the remote origin uses its own localStorage so we set on first
590897
+ // load there). We open the remote UI in a new tab to preserve the
590898
+ // local session.
590899
+ const remoteUrl = (parsed.scheme || 'http') + '://' + parsed.host + '/?oa-key=' + encodeURIComponent(parsed.key) + '&oa-share-label=' + encodeURIComponent(parsed.label || '');
590900
+ window.open(remoteUrl, '_blank');
590901
+ closeKeyModal();
590902
+ return;
590903
+ }
590904
+ apiKey = raw;
590792
590905
  localStorage.setItem('oa-api-key', apiKey);
590906
+ // Track this key in the recent-keys list so future paste autocompletes it.
590907
+ if (apiKey) {
590908
+ saveRecentKey({ key: apiKey, host: location.host, label: 'local ' + location.host });
590909
+ }
590793
590910
  closeKeyModal();
590794
590911
  loadModels();
590795
590912
  }
@@ -590802,8 +590919,296 @@ function clearKey() {
590802
590919
  }
590803
590920
  function closeKeyModal() {
590804
590921
  document.getElementById('key-modal').classList.remove('visible');
590922
+ const sr = document.getElementById('share-result'); if (sr) sr.style.display = 'none';
590923
+ }
590924
+ function openKeyModal() {
590925
+ document.getElementById('key-modal').classList.add('visible');
590926
+ document.getElementById('key-input').value = apiKey;
590927
+ // Refresh the remote-state hint based on current localStorage.
590928
+ refreshKeyModalRemoteState();
590929
+ }
590930
+ window.openKeyModal = openKeyModal;
590931
+
590932
+ // ─── Share URL parsing ─────────────────────────────────────────────────
590933
+ // Accepts BOTH:
590934
+ // oa-share://[peerId@]host:port#key
590935
+ // http(s)://host:port/?oa-key=KEY[&oa-share-label=LABEL]
590936
+ // Returns { host, key, scheme?, label? } or null when the input is a plain key.
590937
+ function parseShareInput(raw) {
590938
+ const v = String(raw || '').trim();
590939
+ if (!v) return null;
590940
+ // oa-share scheme — use a custom parser since URL() doesn't always honor it.
590941
+ if (v.toLowerCase().startsWith('oa-share://')) {
590942
+ const after = v.slice('oa-share://'.length);
590943
+ const hashIdx = after.indexOf('#');
590944
+ if (hashIdx < 0) return null;
590945
+ const hostPart = after.slice(0, hashIdx);
590946
+ const key = after.slice(hashIdx + 1);
590947
+ if (!hostPart || !key) return null;
590948
+ // Strip optional peerId@ prefix (forward-compat for libp2p tunneling).
590949
+ const atIdx = hostPart.indexOf('@');
590950
+ const host = atIdx >= 0 ? hostPart.slice(atIdx + 1) : hostPart;
590951
+ return { host, key, scheme: 'http' };
590952
+ }
590953
+ // http(s) URL with ?oa-key=...
590954
+ if (/^https?:\\/\\//i.test(v)) {
590955
+ try {
590956
+ const u = new URL(v);
590957
+ const k = u.searchParams.get('oa-key');
590958
+ if (!k) return null;
590959
+ const label = u.searchParams.get('oa-share-label') || '';
590960
+ return { host: u.host, key: k, scheme: u.protocol.replace(':', ''), label };
590961
+ } catch { return null; }
590962
+ }
590963
+ return null;
590964
+ }
590965
+
590966
+ function onKeyInputChange(v) {
590967
+ const hint = document.getElementById('key-input-hint');
590968
+ if (!hint) return;
590969
+ const parsed = parseShareInput(v);
590970
+ if (parsed) {
590971
+ hint.innerHTML = '✓ detected share URL — host <code>' + escapeHtml(parsed.host) + '</code>; clicking save will open the remote UI with this key';
590972
+ hint.style.color = 'var(--color-success)';
590973
+ } else {
590974
+ hint.innerHTML = 'Tip: paste an <code>oa-share://host:port#key</code> URL or <code>http://host:port/?oa-key=…</code> to connect to a remote OA instance.';
590975
+ hint.style.color = 'var(--color-fg-muted)';
590976
+ }
590977
+ showRecentKeysDropdown();
590805
590978
  }
590806
590979
 
590980
+ // Tiny HTML escape — same as the one used in connections settings.
590981
+ function escapeHtml(s) {
590982
+ return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
590983
+ }
590984
+
590985
+ // ─── Recent keys (localStorage history with autocomplete dropdown) ──
590986
+ const _OA_RECENT_KEYS_LS = 'oa.recentKeys';
590987
+ const _OA_RECENT_KEYS_MAX = 10;
590988
+ function loadRecentKeys() {
590989
+ try {
590990
+ const raw = localStorage.getItem(_OA_RECENT_KEYS_LS);
590991
+ if (!raw) return [];
590992
+ const parsed = JSON.parse(raw);
590993
+ return Array.isArray(parsed) ? parsed : [];
590994
+ } catch { return []; }
590995
+ }
590996
+ function saveRecentKey(rec) {
590997
+ if (!rec || !rec.key) return;
590998
+ let list = loadRecentKeys();
590999
+ // Move existing entry with same key to top; otherwise prepend.
591000
+ list = list.filter(r => r.key !== rec.key);
591001
+ list.unshift({ ...rec, lastUsed: new Date().toISOString() });
591002
+ if (list.length > _OA_RECENT_KEYS_MAX) list = list.slice(0, _OA_RECENT_KEYS_MAX);
591003
+ try { localStorage.setItem(_OA_RECENT_KEYS_LS, JSON.stringify(list)); } catch {}
591004
+ }
591005
+ function deleteRecentKey(key) {
591006
+ let list = loadRecentKeys().filter(r => r.key !== key);
591007
+ try { localStorage.setItem(_OA_RECENT_KEYS_LS, JSON.stringify(list)); } catch {}
591008
+ showRecentKeysDropdown();
591009
+ }
591010
+ window.deleteRecentKey = deleteRecentKey;
591011
+
591012
+ function showRecentKeysDropdown() {
591013
+ const dd = document.getElementById('key-recent-dropdown');
591014
+ if (!dd) return;
591015
+ const recents = loadRecentKeys();
591016
+ const inp = document.getElementById('key-input');
591017
+ const filter = (inp && inp.value || '').toLowerCase();
591018
+ const matches = recents.filter(r => {
591019
+ if (!filter) return true;
591020
+ return (r.key || '').toLowerCase().includes(filter)
591021
+ || (r.host || '').toLowerCase().includes(filter)
591022
+ || (r.label || '').toLowerCase().includes(filter);
591023
+ });
591024
+ if (matches.length === 0) { dd.style.display = 'none'; return; }
591025
+ dd.innerHTML = matches.map(r => {
591026
+ const k = String(r.key || '');
591027
+ const masked = k.length > 12 ? k.slice(0, 4) + '…' + k.slice(-4) : k;
591028
+ const host = escapeHtml(r.host || '');
591029
+ const label = escapeHtml(r.label || '');
591030
+ const keySafe = k.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, "\\\\'");
591031
+ return '<div class="oa-rec-key" style="display:flex;align-items:center;gap:8px;padding:6px 10px;cursor:pointer;border-bottom:1px solid var(--color-border)" '
591032
+ + 'onclick="useRecentKey(\\'' + keySafe + '\\')" '
591033
+ + 'onmouseover="this.style.background=\\'var(--color-bg-hover)\\'" '
591034
+ + 'onmouseout="this.style.background=\\'transparent\\'">'
591035
+ + '<span style="flex:1;font-family:var(--font-mono);font-size:0.78rem"><span style="color:var(--color-fg-muted)">' + (host || 'local') + '</span> · <code>' + masked + '</code></span>'
591036
+ + '<span style="font-size:0.7rem;color:var(--color-fg-muted)">' + label + '</span>'
591037
+ + '<button type="button" onclick="event.stopPropagation();deleteRecentKey(\\'' + keySafe + '\\')" '
591038
+ + 'title="Forget this key" '
591039
+ + 'style="background:transparent;border:none;color:var(--color-fg-muted);cursor:pointer;font-size:1rem;padding:0 4px">&times;</button>'
591040
+ + '</div>';
591041
+ }).join('');
591042
+ dd.style.display = 'block';
591043
+ }
591044
+ function hideRecentKeysDropdown() {
591045
+ const dd = document.getElementById('key-recent-dropdown');
591046
+ if (dd) dd.style.display = 'none';
591047
+ }
591048
+ function useRecentKey(k) {
591049
+ const inp = document.getElementById('key-input');
591050
+ if (inp) {
591051
+ inp.value = k;
591052
+ inp.focus();
591053
+ onKeyInputChange(k);
591054
+ }
591055
+ hideRecentKeysDropdown();
591056
+ }
591057
+ window.useRecentKey = useRecentKey;
591058
+
591059
+ // ─── Generate share URL ────────────────────────────────────────────────
591060
+ async function generateShareUrl() {
591061
+ const out = document.getElementById('share-result');
591062
+ if (!out) return;
591063
+ out.style.display = 'block';
591064
+ out.innerHTML = '<div style="color:var(--color-fg-muted)">generating…</div>';
591065
+ try {
591066
+ const r = await fetch('/v1/share/generate', {
591067
+ method: 'POST',
591068
+ headers: { ...headers(), 'Content-Type': 'application/json' },
591069
+ body: JSON.stringify({ scope: 'run' }),
591070
+ });
591071
+ if (r.status === 403) {
591072
+ out.innerHTML = '<div style="color:var(--color-error)">✗ admin scope required — your current key does not have permission to mint share URLs. Set an admin-scope key first.</div>';
591073
+ return;
591074
+ }
591075
+ if (r.status >= 400) {
591076
+ const err = await r.json().catch(() => ({}));
591077
+ out.innerHTML = '<div style="color:var(--color-error)">✗ HTTP ' + r.status + (err.detail ? ' — ' + escapeHtml(err.detail) : '') + '</div>';
591078
+ return;
591079
+ }
591080
+ const j = await r.json();
591081
+ const safeShare = escapeHtml(j.shareUrl || '');
591082
+ const safePlain = escapeHtml(j.plainUrl || '');
591083
+ out.innerHTML =
591084
+ '<div style="font-weight:500;margin-bottom:6px;color:var(--color-success)">✓ Share URL generated — hand off to remote</div>' +
591085
+ '<div style="margin:6px 0">' +
591086
+ '<label style="display:block;font-size:0.7rem;color:var(--color-fg-muted);margin-bottom:2px">oa-share URL (compact, recommended for OA-to-OA)</label>' +
591087
+ '<div style="display:flex;gap:6px;align-items:center">' +
591088
+ '<input readonly value="' + safeShare + '" style="flex:1;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:5px 8px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:0.74rem">' +
591089
+ '<button type="button" onclick="copyShareUrl(\\'oa-share\\')" style="font-size:0.74rem">copy</button>' +
591090
+ '</div>' +
591091
+ '</div>' +
591092
+ '<div style="margin:6px 0">' +
591093
+ '<label style="display:block;font-size:0.7rem;color:var(--color-fg-muted);margin-bottom:2px">plain URL (works in any browser, no scheme handler needed)</label>' +
591094
+ '<div style="display:flex;gap:6px;align-items:center">' +
591095
+ '<input readonly value="' + safePlain + '" style="flex:1;background:var(--color-bg-input);border:1px solid var(--color-border);color:var(--color-fg);padding:5px 8px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:0.74rem">' +
591096
+ '<button type="button" onclick="copyShareUrl(\\'plain\\')" style="font-size:0.74rem">copy</button>' +
591097
+ '</div>' +
591098
+ '</div>' +
591099
+ '<p style="font-size:0.68rem;color:var(--color-fg-muted);margin:6px 0 0">Note: this URL contains the full secret. Anyone with it can use this OA. To revoke: open Advanced settings → keys → revoke the prefix <code>' + escapeHtml(j.keyPrefix || '') + '</code>.</p>';
591100
+ // Stash for the copy buttons + add to recent.
591101
+ window.__oaLastShareUrl = j.shareUrl;
591102
+ window.__oaLastPlainUrl = j.plainUrl;
591103
+ saveRecentKey({ key: j.key, host: j.host + ':' + j.port, label: j.label || ('shared ' + j.host) });
591104
+ } catch (e) {
591105
+ out.innerHTML = '<div style="color:var(--color-error)">✗ ' + escapeHtml(e && e.message ? e.message : String(e)) + '</div>';
591106
+ }
591107
+ }
591108
+ function copyShareUrl(which) {
591109
+ const v = which === 'plain' ? window.__oaLastPlainUrl : window.__oaLastShareUrl;
591110
+ if (!v) return;
591111
+ if (navigator.clipboard && navigator.clipboard.writeText) {
591112
+ navigator.clipboard.writeText(v).then(
591113
+ () => { /* ok */ },
591114
+ () => { fallbackCopy(v); }
591115
+ );
591116
+ } else {
591117
+ fallbackCopy(v);
591118
+ }
591119
+ }
591120
+ function fallbackCopy(v) {
591121
+ const ta = document.createElement('textarea');
591122
+ ta.value = v; document.body.appendChild(ta); ta.select();
591123
+ try { document.execCommand('copy'); } catch {}
591124
+ document.body.removeChild(ta);
591125
+ }
591126
+ window.generateShareUrl = generateShareUrl;
591127
+ window.copyShareUrl = copyShareUrl;
591128
+
591129
+ // ─── Remote-state helpers (visual indicator on the key button) ───────
591130
+ // When localStorage carries oa.remoteHost we are in REMOTE mode: the
591131
+ // key was loaded from an oa-share URL or via on-load ?oa-key=. The key
591132
+ // button shows a "remote" badge and clicking expands to show host+key
591133
+ // inline with a "close connection" button that severs and shuffles the
591134
+ // key into recents.
591135
+ function refreshKeyModalRemoteState() {
591136
+ const remote = (function() {
591137
+ try { return localStorage.getItem('oa.remoteHost') || ''; } catch { return ''; }
591138
+ })();
591139
+ const stateBox = document.getElementById('remote-state');
591140
+ const btn = document.getElementById('sidebar-key-btn');
591141
+ if (remote && btn) {
591142
+ btn.textContent = 'remote';
591143
+ btn.style.background = 'var(--color-accent)';
591144
+ btn.style.color = '#fff';
591145
+ btn.style.borderColor = 'var(--color-accent)';
591146
+ btn.title = 'connected to remote ' + remote + ' — click to view / disconnect';
591147
+ } else if (btn) {
591148
+ btn.textContent = 'key';
591149
+ btn.style.background = 'transparent';
591150
+ btn.style.color = 'var(--color-fg-muted)';
591151
+ btn.style.borderColor = 'var(--color-border)';
591152
+ btn.title = 'set API key / share access';
591153
+ }
591154
+ if (!stateBox) return;
591155
+ if (remote) {
591156
+ const safeRemote = escapeHtml(remote);
591157
+ stateBox.style.display = 'block';
591158
+ stateBox.innerHTML =
591159
+ '<div style="font-weight:500;color:var(--color-accent)">REMOTE connection active</div>' +
591160
+ '<div style="margin-top:4px;font-size:0.74rem">host <code>' + safeRemote + '</code></div>' +
591161
+ '<div style="margin-top:8px"><button type="button" onclick="closeRemoteConnection()" style="background:var(--color-error);color:#fff;border:none;padding:4px 10px;border-radius:var(--radius-sm);cursor:pointer;font-size:0.74rem">close connection</button></div>';
591162
+ } else {
591163
+ stateBox.style.display = 'none';
591164
+ }
591165
+ }
591166
+ function closeRemoteConnection() {
591167
+ // Move current remote key into recents BEFORE clearing.
591168
+ let savedKey = '';
591169
+ let savedHost = '';
591170
+ try {
591171
+ savedKey = localStorage.getItem('oa-api-key') || '';
591172
+ savedHost = localStorage.getItem('oa.remoteHost') || '';
591173
+ } catch {}
591174
+ if (savedKey) {
591175
+ saveRecentKey({ key: savedKey, host: savedHost, label: 'recent remote ' + savedHost });
591176
+ }
591177
+ try {
591178
+ localStorage.removeItem('oa.remoteHost');
591179
+ localStorage.removeItem('oa.remoteScheme');
591180
+ localStorage.removeItem('oa-api-key');
591181
+ } catch {}
591182
+ apiKey = '';
591183
+ refreshKeyModalRemoteState();
591184
+ // Reload to drop any cached state from the remote target.
591185
+ location.reload();
591186
+ }
591187
+ window.closeRemoteConnection = closeRemoteConnection;
591188
+
591189
+ // ─── On-load: ?oa-key=... pickup ───────────────────────────────────────
591190
+ // When the page loads with an oa-key query param (i.e. someone clicked
591191
+ // a share URL), capture the key into localStorage, mark this as a
591192
+ // remote session (host = this origin), and clean the URL.
591193
+ (function pickupShareKeyFromUrl() {
591194
+ try {
591195
+ const u = new URL(location.href);
591196
+ const k = u.searchParams.get('oa-key');
591197
+ if (!k) return;
591198
+ const label = u.searchParams.get('oa-share-label') || '';
591199
+ localStorage.setItem('oa-api-key', k);
591200
+ // Origin-based remote: when the page loaded from a different host
591201
+ // than the user's "home" OA, that's a remote session by definition.
591202
+ localStorage.setItem('oa.remoteHost', location.host);
591203
+ localStorage.setItem('oa.remoteScheme', location.protocol.replace(':', ''));
591204
+ saveRecentKey({ key: k, host: location.host, label: label || ('remote ' + location.host) });
591205
+ // Strip the key from the URL so it isn't visible / browser-history'd.
591206
+ u.searchParams.delete('oa-key');
591207
+ u.searchParams.delete('oa-share-label');
591208
+ history.replaceState({}, '', u.pathname + (u.search || '') + (u.hash || ''));
591209
+ } catch {}
591210
+ })();
591211
+
590807
591212
  // Tab switching
590808
591213
  const allPanels = ['chat-container','agent-panel','jobs-panel','config-panel','activity-panel','projects-panel','voice-panel'];
590809
591214
  function switchTab(tab) {
@@ -591536,6 +591941,11 @@ function newChatSession() {
591536
591941
  switchSession('');
591537
591942
  const sel = document.getElementById('chat-session-select');
591538
591943
  if (sel) sel.value = '';
591944
+ // FRESH-SESSION: mark the next chat send as fresh so the daemon skips
591945
+ // cross-task handoff injection (which previously bled prior session
591946
+ // context into the new chat regardless of the browser's reset).
591947
+ // Cleared inside the chat send handler after one use.
591948
+ try { localStorage.setItem('oa.freshSessionPending', '1'); } catch {}
591539
591949
  }
591540
591950
  function deleteChatSession() {
591541
591951
  if (!chatSessionId) return;
@@ -592399,6 +592809,7 @@ async function doUpdate() {
592399
592809
  try { pollMetrics(); } catch {}
592400
592810
  try { loadScheduled(); } catch {}
592401
592811
  try { loadServices(); } catch {}
592812
+ try { refreshKeyModalRemoteState(); } catch {}
592402
592813
 
592403
592814
  btn.textContent = 'updated v' + newVersion;
592404
592815
  btn.style.background = '#1a3a1a';
@@ -593383,6 +593794,7 @@ $currentProject.subscribe((proj) => {
593383
593794
  try { if (typeof updateSessionSelect === 'function') updateSessionSelect(); } catch {}
593384
593795
  try { if (typeof updateAgentRunSelect === 'function') updateAgentRunSelect(); } catch {}
593385
593796
  try { if (typeof restoreChatSession === 'function') restoreChatSession(); } catch {}
593797
+ try { if (typeof refreshKeyModalRemoteState === 'function') refreshKeyModalRemoteState(); } catch {}
593386
593798
  });
593387
593799
 
593388
593800
  // $selectedModel changes update the visible <select> if it's not
@@ -600994,6 +601406,9 @@ ${historyLines}
600994
601406
  runEnv["OA_RUN_USER"] = req2._authUser || "anonymous";
600995
601407
  runEnv["OA_RUN_SCOPE"] = req2._authScope || "admin";
600996
601408
  runEnv["OA_SESSION_ID"] = session.id;
601409
+ if (chatBody["fresh"] === true) {
601410
+ runEnv["OA_FRESH_SESSION"] = "1";
601411
+ }
600997
601412
  runEnv["OLLAMA_HOST"] = currentCfg.backendUrl || process.env["OLLAMA_HOST"] || "http://127.0.0.1:11434";
600998
601413
  if (currentCfg.apiKey) runEnv["OA_API_KEY_INHERIT"] = currentCfg.apiKey;
600999
601414
  const child = spawn25(process.execPath, [oaBin, ...args], {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.533",
3
+ "version": "0.187.534",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.533",
9
+ "version": "0.187.534",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
@@ -2192,9 +2192,9 @@
2192
2192
  }
2193
2193
  },
2194
2194
  "node_modules/bare-url": {
2195
- "version": "2.4.2",
2196
- "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.2.tgz",
2197
- "integrity": "sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==",
2195
+ "version": "2.4.3",
2196
+ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz",
2197
+ "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==",
2198
2198
  "license": "Apache-2.0",
2199
2199
  "optional": true,
2200
2200
  "dependencies": {
@@ -3179,9 +3179,9 @@
3179
3179
  }
3180
3180
  },
3181
3181
  "node_modules/fast-uri": {
3182
- "version": "3.1.1",
3183
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.1.tgz",
3184
- "integrity": "sha512-h2r7rcm6Ee/J8o0LD5djLuFVcfbZxhvho4vvsbeV0aMvXjUgqv4YpxpkEx0d68l6+IleVfLAdVEfhR7QNMkGHQ==",
3182
+ "version": "3.1.2",
3183
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
3184
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
3185
3185
  "funding": [
3186
3186
  {
3187
3187
  "type": "github",
@@ -3638,9 +3638,9 @@
3638
3638
  }
3639
3639
  },
3640
3640
  "node_modules/hono": {
3641
- "version": "4.12.16",
3642
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.16.tgz",
3643
- "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==",
3641
+ "version": "4.12.17",
3642
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.17.tgz",
3643
+ "integrity": "sha512-FbJJNb/XgX7YW0hX/V8w5oYLztKEsRLykCMZWt1WdLtsfjzMvmoqWBA4H4t5norinq8/rh20oiZYr+WSl4UzAQ==",
3644
3644
  "license": "MIT",
3645
3645
  "engines": {
3646
3646
  "node": ">=16.9.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.533",
3
+ "version": "0.187.534",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",