open-agents-ai 0.187.196 → 0.187.198

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.
Files changed (2) hide show
  1. package/dist/index.js +322 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -316518,15 +316518,161 @@ async function pollMetrics() {
316518
316518
  } catch {}
316519
316519
  }
316520
316520
 
316521
- function doUpdate() {
316522
- const latestVer = document.getElementById('update-btn').title?.match(/Latest: v([d.]+)/)?.[1] || 'latest';
316523
- const cmd = 'npm install -g open-agents-ai@' + latestVer;
316524
- if (confirm('Update available!\\n\\nRun this command in your terminal:\\n\\n' + cmd + '\\n\\nThen restart: oa serve')) {
316525
- navigator.clipboard.writeText(cmd).then(() => {
316526
- document.getElementById('update-btn').textContent = 'copied!';
316527
- setTimeout(() => document.getElementById('update-btn').textContent = 'update v' + latestVer, 2000);
316528
- }).catch(() => {});
316521
+ // Self-update flow \u2014 POST /v1/update, then poll /version, then reconnect
316522
+ //
316523
+ // The button:
316524
+ // 1. POSTs /v1/update with {version: latest} \u2192 daemon detaches
316525
+ // 'npm install -g open-agents-ai@latest' which survives the daemon
316526
+ // restart (systemd/launchd auto-restarts it).
316527
+ // 2. Shows inline progress on the button itself (installing, restarting,
316528
+ // reconnecting, done).
316529
+ // 3. Polls GET /version every 2s. The new version appearing means the
316530
+ // daemon has come back with the upgraded bundle.
316531
+ // 4. Reconnects the SSE event stream (connectEventBus()) and refreshes
316532
+ // model list + health + sessions so the UI picks up any new surface.
316533
+ // 5. Flashes the status bar green on success.
316534
+
316535
+ async function doUpdate() {
316536
+ const btn = document.getElementById('update-btn');
316537
+ if (!btn) return;
316538
+ const latestVer = btn.title?.match(/Latest: v([\\d.]+)/)?.[1] || 'latest';
316539
+ const originalText = btn.textContent;
316540
+ const originalBg = btn.style.background;
316541
+ const originalBorder = btn.style.border;
316542
+ const originalColor = btn.style.color;
316543
+
316544
+ if (!confirm('Install open-agents-ai v' + latestVer + ' now?\\n\\n' +
316545
+ 'The daemon will install the new version in the background ' +
316546
+ 'and restart automatically. This UI will reconnect when the ' +
316547
+ 'update completes (~30-60 seconds).')) {
316548
+ return;
316529
316549
  }
316550
+
316551
+ // Visual: orange "installing..." state
316552
+ btn.style.background = '#3a2a10';
316553
+ btn.style.borderColor = '#c9944e';
316554
+ btn.style.color = '#c9944e';
316555
+ btn.style.pointerEvents = 'none';
316556
+ btn.textContent = 'installing...';
316557
+
316558
+ // Determine the current version BEFORE kicking off the update so we can
316559
+ // detect a successful version bump by comparison.
316560
+ let fromVersion = '0.0.0';
316561
+ try {
316562
+ const v = await fetch('/version').then(r => r.json());
316563
+ fromVersion = v.version || '0.0.0';
316564
+ } catch {}
316565
+
316566
+ // Kick off the install
316567
+ let updateStartOk = false;
316568
+ try {
316569
+ const r = await fetch('/v1/update', {
316570
+ method: 'POST',
316571
+ headers: headers(),
316572
+ body: JSON.stringify({ version: latestVer }),
316573
+ });
316574
+ if (r.ok || r.status === 202) {
316575
+ updateStartOk = true;
316576
+ } else {
316577
+ const txt = await r.text().catch(() => '');
316578
+ btn.textContent = 'error';
316579
+ btn.style.background = '#3a1a1a';
316580
+ btn.style.borderColor = '#c94e4e';
316581
+ btn.style.color = '#c94e4e';
316582
+ alert('Update failed to start: HTTP ' + r.status + '\\n\\n' + txt.slice(0, 300));
316583
+ setTimeout(() => {
316584
+ btn.textContent = originalText;
316585
+ btn.style.background = originalBg;
316586
+ btn.style.borderColor = originalBorder;
316587
+ btn.style.color = originalColor;
316588
+ btn.style.pointerEvents = '';
316589
+ }, 5000);
316590
+ return;
316591
+ }
316592
+ } catch (e) {
316593
+ alert('Update request failed: ' + (e.message || e));
316594
+ btn.textContent = originalText;
316595
+ btn.style.pointerEvents = '';
316596
+ return;
316597
+ }
316598
+
316599
+ if (!updateStartOk) return;
316600
+
316601
+ // Poll /version until the daemon reports a different (newer) version.
316602
+ // Timeouts: up to ~5 minutes total with 2s intervals = 150 attempts.
316603
+ // During the first ~10-15s the daemon usually restarts so fetch() will
316604
+ // throw. That's expected \u2014 we just keep polling.
316605
+ btn.textContent = 'restarting...';
316606
+ const startTs = Date.now();
316607
+ const maxWaitMs = 5 * 60 * 1000;
316608
+ let newVersion = null;
316609
+ let lastError = '';
316610
+ while (Date.now() - startTs < maxWaitMs) {
316611
+ await new Promise(r => setTimeout(r, 2000));
316612
+ try {
316613
+ const r = await fetch('/version', { cache: 'no-store' });
316614
+ const j = await r.json();
316615
+ const v = j.version || '';
316616
+ if (v && v !== fromVersion) {
316617
+ newVersion = v;
316618
+ break;
316619
+ }
316620
+ } catch (e) {
316621
+ lastError = e.message || String(e);
316622
+ // Expected during daemon restart \u2014 keep polling.
316623
+ }
316624
+ }
316625
+
316626
+ if (!newVersion) {
316627
+ btn.textContent = 'timeout';
316628
+ btn.style.background = '#3a1a1a';
316629
+ btn.style.borderColor = '#c94e4e';
316630
+ btn.style.color = '#c94e4e';
316631
+ alert('Update did not complete within 5 minutes.\\n\\n' +
316632
+ 'Last error: ' + lastError + '\\n\\n' +
316633
+ 'Check logs: tail -f ~/.open-agents/update.log\\n' +
316634
+ 'Or check daemon status: systemctl --user status open-agents-daemon');
316635
+ setTimeout(() => {
316636
+ btn.textContent = originalText;
316637
+ btn.style.background = originalBg;
316638
+ btn.style.borderColor = originalBorder;
316639
+ btn.style.color = originalColor;
316640
+ btn.style.pointerEvents = '';
316641
+ }, 10000);
316642
+ return;
316643
+ }
316644
+
316645
+ // Success \u2014 reconnect SSE and refresh UI state
316646
+ btn.textContent = 'reconnecting...';
316647
+ try { if (typeof procEventSource !== 'undefined' && procEventSource) procEventSource.close(); } catch {}
316648
+ try { connectEventBus(); } catch {}
316649
+ try { loadModels(); } catch {}
316650
+ try { checkHealth(); } catch {}
316651
+ try { pollMetrics(); } catch {}
316652
+
316653
+ btn.textContent = 'updated v' + newVersion;
316654
+ btn.style.background = '#1a3a1a';
316655
+ btn.style.borderColor = '#4ec94e';
316656
+ btn.style.color = '#4ec94e';
316657
+
316658
+ // Flash status bar
316659
+ const statusEl = document.getElementById('status');
316660
+ if (statusEl) {
316661
+ const origStatusColor = statusEl.style.color;
316662
+ statusEl.textContent = 'updated \u2192 v' + newVersion;
316663
+ statusEl.style.color = '#4ec94e';
316664
+ setTimeout(() => { statusEl.style.color = origStatusColor; }, 5000);
316665
+ }
316666
+
316667
+ // After 6s, hide the button (update is done, nothing more to do)
316668
+ setTimeout(() => {
316669
+ btn.style.display = 'none';
316670
+ btn.textContent = originalText;
316671
+ btn.style.background = originalBg;
316672
+ btn.style.borderColor = originalBorder;
316673
+ btn.style.color = originalColor;
316674
+ btn.style.pointerEvents = '';
316675
+ }, 6000);
316530
316676
  }
316531
316677
 
316532
316678
  // \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
@@ -319239,6 +319385,158 @@ ${task}` : task;
319239
319385
  _oa: { tool_calls: toolCallCount, finish_reason: "stop", duration_ms: durationMs, request_id: requestId }
319240
319386
  });
319241
319387
  }
319388
+ function updateStateFile() {
319389
+ return join95(homedir32(), ".open-agents", "update-state.json");
319390
+ }
319391
+ function updateLogPath() {
319392
+ return join95(homedir32(), ".open-agents", "update.log");
319393
+ }
319394
+ function readUpdateState() {
319395
+ try {
319396
+ const p2 = updateStateFile();
319397
+ if (!existsSync78(p2)) return null;
319398
+ return JSON.parse(readFileSync61(p2, "utf-8"));
319399
+ } catch {
319400
+ return null;
319401
+ }
319402
+ }
319403
+ function writeUpdateState(state) {
319404
+ try {
319405
+ const dir = join95(homedir32(), ".open-agents");
319406
+ const fs4 = __require("node:fs");
319407
+ fs4.mkdirSync(dir, { recursive: true });
319408
+ const finalPath = updateStateFile();
319409
+ const tmpPath = `${finalPath}.tmp.${process.pid}`;
319410
+ fs4.writeFileSync(tmpPath, JSON.stringify(state, null, 2), "utf-8");
319411
+ fs4.renameSync(tmpPath, finalPath);
319412
+ } catch {
319413
+ }
319414
+ }
319415
+ async function handleV1Update(req2, res, requestId) {
319416
+ try {
319417
+ const body = await parseJsonBody(req2);
319418
+ const targetVersion = body?.version || "latest";
319419
+ const currentVersion = getVersion3();
319420
+ const existing = readUpdateState();
319421
+ if (existing?.status === "running") {
319422
+ if (existing.pid && existing.pid > 0) {
319423
+ try {
319424
+ process.kill(existing.pid, 0);
319425
+ jsonResponse(res, 409, {
319426
+ error: "Update already in progress",
319427
+ state: existing
319428
+ });
319429
+ return;
319430
+ } catch {
319431
+ }
319432
+ }
319433
+ }
319434
+ const logPath2 = updateLogPath();
319435
+ const startedAt2 = (/* @__PURE__ */ new Date()).toISOString();
319436
+ writeUpdateState({
319437
+ status: "running",
319438
+ target_version: targetVersion,
319439
+ from_version: currentVersion,
319440
+ started_at: startedAt2,
319441
+ log_path: logPath2
319442
+ });
319443
+ publishEvent("engine.state_changed", {
319444
+ action: "update_started",
319445
+ from: currentVersion,
319446
+ to: targetVersion
319447
+ }, { subject: req2._authUser ?? "anonymous" });
319448
+ const pkgSpec = `open-agents-ai@${targetVersion}`;
319449
+ const installCmd = `npm install -g ${pkgSpec} --no-audit --no-fund --no-progress > "${logPath2}" 2>&1; echo "__EXIT_CODE=$?" >> "${logPath2}"`;
319450
+ const fs4 = __require("node:fs");
319451
+ const dir = join95(homedir32(), ".open-agents");
319452
+ fs4.mkdirSync(dir, { recursive: true });
319453
+ const isWin2 = process.platform === "win32";
319454
+ let child;
319455
+ if (isWin2) {
319456
+ child = spawn25("cmd.exe", ["/c", installCmd], {
319457
+ detached: true,
319458
+ stdio: "ignore",
319459
+ windowsHide: true,
319460
+ env: { ...process.env, npm_config_yes: "true" }
319461
+ });
319462
+ } else {
319463
+ child = spawn25("bash", ["-lc", `nohup bash -c 'setsid bash -c ${JSON.stringify(installCmd)} &' >/dev/null 2>&1; exit 0`], {
319464
+ detached: true,
319465
+ stdio: "ignore",
319466
+ env: { ...process.env }
319467
+ });
319468
+ }
319469
+ child.unref();
319470
+ const initialPid = child.pid ?? 0;
319471
+ writeUpdateState({
319472
+ status: "running",
319473
+ target_version: targetVersion,
319474
+ from_version: currentVersion,
319475
+ started_at: startedAt2,
319476
+ pid: initialPid,
319477
+ log_path: logPath2
319478
+ });
319479
+ jsonResponse(res, 202, {
319480
+ status: "started",
319481
+ from_version: currentVersion,
319482
+ target_version: targetVersion,
319483
+ pid: initialPid,
319484
+ log_path: logPath2,
319485
+ poll_url: "/v1/update",
319486
+ version_url: "/version",
319487
+ note: "The daemon will restart automatically when the install completes. Poll /version until the new version appears, then reconnect /v1/events.",
319488
+ instance: requestId
319489
+ });
319490
+ } catch (err) {
319491
+ if (!res.headersSent) {
319492
+ res.setHeader("Content-Type", "application/problem+json; charset=utf-8");
319493
+ res.writeHead(500);
319494
+ res.end(JSON.stringify({
319495
+ type: "https://openagents.nexus/problems/internal-error",
319496
+ title: "Update failed to start",
319497
+ status: 500,
319498
+ detail: err instanceof Error ? err.message : String(err),
319499
+ instance: requestId
319500
+ }));
319501
+ }
319502
+ }
319503
+ }
319504
+ function handleV1UpdateStatus(res) {
319505
+ const state = readUpdateState();
319506
+ const logPath2 = updateLogPath();
319507
+ let logTail = "";
319508
+ let exitCode = null;
319509
+ try {
319510
+ if (existsSync78(logPath2)) {
319511
+ const raw = readFileSync61(logPath2, "utf-8");
319512
+ const m2 = raw.match(/__EXIT_CODE=(\d+)/);
319513
+ if (m2) exitCode = parseInt(m2[1], 10);
319514
+ logTail = raw.slice(-2e3);
319515
+ }
319516
+ } catch {
319517
+ }
319518
+ if (state && state.status === "running" && exitCode !== null) {
319519
+ const newState = {
319520
+ ...state,
319521
+ status: exitCode === 0 ? "completed" : "failed",
319522
+ completed_at: (/* @__PURE__ */ new Date()).toISOString(),
319523
+ exit_code: exitCode
319524
+ };
319525
+ writeUpdateState(newState);
319526
+ jsonResponse(res, 200, {
319527
+ state: newState,
319528
+ log_tail: logTail,
319529
+ current_version: getVersion3()
319530
+ });
319531
+ return;
319532
+ }
319533
+ jsonResponse(res, 200, {
319534
+ state: state ?? { status: "idle" },
319535
+ log_tail: logTail,
319536
+ current_version: getVersion3(),
319537
+ exit_code: exitCode
319538
+ });
319539
+ }
319242
319540
  async function handleV1Run(req2, res) {
319243
319541
  const body = await parseJsonBody(req2);
319244
319542
  if (!body || typeof body !== "object") {
@@ -320220,6 +320518,22 @@ ${historyLines}
320220
320518
  await handleV1Generate(req2, res, ollamaUrl, requestId);
320221
320519
  return;
320222
320520
  }
320521
+ if (pathname === "/v1/update" && method === "POST") {
320522
+ if (!checkAuth(req2, res, "admin")) {
320523
+ status = 403;
320524
+ return;
320525
+ }
320526
+ await handleV1Update(req2, res, requestId);
320527
+ return;
320528
+ }
320529
+ if (pathname === "/v1/update" && method === "GET") {
320530
+ if (!checkAuth(req2, res, "read")) {
320531
+ status = 401;
320532
+ return;
320533
+ }
320534
+ handleV1UpdateStatus(res);
320535
+ return;
320536
+ }
320223
320537
  if (pathname === "/v1/run" && method === "POST") {
320224
320538
  await handleV1Run(req2, res);
320225
320539
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.196",
3
+ "version": "0.187.198",
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",