open-agents-ai 0.187.200 → 0.187.202

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 +420 -39
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -315617,17 +315617,32 @@ body {
315617
315617
  <textarea id="system-prompt" placeholder="System prompt (optional)"></textarea>
315618
315618
  </div>
315619
315619
 
315620
- <div id="tabs" style="display:flex;gap:0;background:#1e1e22;border-bottom:1px solid #2a2a30;padding:0 16px;flex-shrink:0">
315621
- <button class="tab active" onclick="switchTab('chat')" id="tab-chat" style="background:none;border:none;border-bottom:2px solid #b2920a;color:#b2920a;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">chat</button>
315622
- <button class="tab" onclick="switchTab('agent')" id="tab-agent" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">agent</button>
315623
- <button class="tab" onclick="switchTab('jobs')" id="tab-jobs" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">dashboard</button>
315624
- <button class="tab" onclick="switchTab('config')" id="tab-config" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">config</button>
315625
- <button class="tab" onclick="switchTab('activity')" id="tab-activity" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">activity</button>
315626
- <select id="session-select" onchange="switchSession(this.value)" style="background:#2a2a30;border:1px solid #3a3a42;color:#b0b0b0;padding:2px 6px;border-radius:3px;font-family:inherit;font-size:0.6rem;max-width:140px" title="Chat sessions">
315627
- <option value="">new session</option>
315628
- </select>
315629
- <span id="sys-metrics" style="margin-left:auto;font-size:0.6rem;color:#555;display:flex;flex-flow:column;justify-content:center"></span>
315630
- <span id="token-counter" style="font-size:0.6rem;color:#555;display:flex;flex-flow:column;justify-content:center;padding:0 6px">0 tokens</span>
315620
+ <!-- #tabs is a flex-wrap row with THREE logical sections that each stay
315621
+ together when wrapping: (1) tab buttons, (2) session selector,
315622
+ (3) sys metrics + token counter. On narrow viewports the sections
315623
+ flow onto multiple rows instead of overflowing the header. -->
315624
+ <div id="tabs" style="display:flex;flex-wrap:wrap;gap:4px 0;background:#1e1e22;border-bottom:1px solid #2a2a30;padding:0 16px;flex-shrink:0">
315625
+ <!-- Section 1: tab buttons -->
315626
+ <div id="tabs-section-buttons" style="display:flex;gap:0;flex-shrink:0">
315627
+ <button class="tab active" onclick="switchTab('chat')" id="tab-chat" style="background:none;border:none;border-bottom:2px solid #b2920a;color:#b2920a;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">chat</button>
315628
+ <button class="tab" onclick="switchTab('agent')" id="tab-agent" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">agent</button>
315629
+ <button class="tab" onclick="switchTab('jobs')" id="tab-jobs" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">dashboard</button>
315630
+ <button class="tab" onclick="switchTab('config')" id="tab-config" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">config</button>
315631
+ <button class="tab" onclick="switchTab('activity')" id="tab-activity" style="background:none;border:none;border-bottom:2px solid transparent;color:#555;padding:6px 16px;font-family:inherit;font-size:0.7rem;cursor:pointer">activity</button>
315632
+ </div>
315633
+ <!-- Section 2: session dropdown -->
315634
+ <div id="tabs-section-sessions" style="display:flex;gap:6px;align-items:center;flex-shrink:0;padding:0 12px">
315635
+ <select id="session-select" onchange="switchSession(this.value)" style="background:#2a2a30;border:1px solid #3a3a42;color:#b0b0b0;padding:2px 6px;border-radius:3px;font-family:inherit;font-size:0.6rem;max-width:160px" title="Chat sessions">
315636
+ <option value="">new session</option>
315637
+ </select>
315638
+ </div>
315639
+ <!-- Section 3: sys metrics + token counter (margin-left: auto pushes to
315640
+ the far right when there's room; on narrow viewports it wraps to
315641
+ its own row instead of overflowing) -->
315642
+ <div id="tabs-section-metrics" style="display:flex;align-items:center;gap:10px;flex-shrink:0;margin-left:auto">
315643
+ <span id="sys-metrics" style="font-size:0.6rem;color:#555;display:flex;flex-flow:column;justify-content:center"></span>
315644
+ <span id="token-counter" style="font-size:0.6rem;color:#555;display:flex;flex-flow:column;justify-content:center;padding:0 6px">0 tokens</span>
315645
+ </div>
315631
315646
  </div>
315632
315647
  <div id="chat-container" style="display:flex;flex:1;overflow:hidden">
315633
315648
  <div id="workspace-sidebar" style="display:none;width:250px;background:#1e1e22;border-right:1px solid #2a2a30;overflow-y:auto;padding:8px;flex-shrink:0;font-size:0.7rem">
@@ -315641,6 +315656,41 @@ body {
315641
315656
  <div id="conversation" style="flex:1;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:4px"></div>
315642
315657
  </div>
315643
315658
  <div id="agent-panel" style="display:none;flex:1;overflow-y:auto;padding:12px 16px">
315659
+ <!-- Parameter form: all the dials for POST /v1/run -->
315660
+ <div id="agent-params" style="background:#17171a;border:1px solid #2a2a30;border-radius:4px;padding:10px 12px;margin-bottom:10px;font-size:0.68rem">
315661
+ <div style="color:#b2920a;font-size:0.65rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:8px">Task Parameters</div>
315662
+ <div style="display:grid;grid-template-columns:120px 1fr;gap:6px 12px;align-items:center">
315663
+ <label style="color:#888">Working dir</label>
315664
+ <div style="display:flex;gap:4px;align-items:center">
315665
+ <input id="agent-working-dir" placeholder="(daemon cwd)" style="flex:1;background:#2a2a30;border:1px solid #3a3a42;border-radius:3px;padding:4px 8px;color:#b0b0b0;font-family:inherit;font-size:0.7rem;outline:none">
315666
+ <button onclick="syncFromWorkspace()" title="Use workspace selection" style="background:#2a2a30;border:1px solid #3a3a42;color:#b2920a;padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.65rem;cursor:pointer">from ws</button>
315667
+ <button onclick="document.getElementById('agent-working-dir').value=''" title="Clear" style="background:#2a2a30;border:1px solid #3a3a42;color:#666;padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.65rem;cursor:pointer">\xD7</button>
315668
+ </div>
315669
+
315670
+ <label style="color:#888">Model override</label>
315671
+ <input id="agent-model-override" placeholder="(use global model)" style="background:#2a2a30;border:1px solid #3a3a42;border-radius:3px;padding:4px 8px;color:#b0b0b0;font-family:inherit;font-size:0.7rem;outline:none">
315672
+
315673
+ <label style="color:#888">Max turns</label>
315674
+ <input id="agent-max-turns" type="number" min="1" max="200" placeholder="25" style="background:#2a2a30;border:1px solid #3a3a42;border-radius:3px;padding:4px 8px;color:#b0b0b0;font-family:inherit;font-size:0.7rem;outline:none;width:120px">
315675
+
315676
+ <label style="color:#888">Timeout (s)</label>
315677
+ <input id="agent-timeout-s" type="number" min="10" max="3600" placeholder="300" style="background:#2a2a30;border:1px solid #3a3a42;border-radius:3px;padding:4px 8px;color:#b0b0b0;font-family:inherit;font-size:0.7rem;outline:none;width:120px">
315678
+
315679
+ <label style="color:#888">Sandbox</label>
315680
+ <select id="agent-sandbox" style="background:#2a2a30;border:1px solid #3a3a42;color:#b0b0b0;padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.7rem">
315681
+ <option value="none">none (host process)</option>
315682
+ <option value="workspace">workspace (ephemeral dir)</option>
315683
+ <option value="container">container (docker)</option>
315684
+ </select>
315685
+
315686
+ <label style="color:#888">Stream events</label>
315687
+ <div><input type="checkbox" id="agent-stream" checked style="accent-color:#b2920a"> <span style="color:#555;font-size:0.6rem">SSE with live tool calls</span></div>
315688
+
315689
+ <label style="color:#888">Isolate workspace</label>
315690
+ <div><input type="checkbox" id="agent-isolate" style="accent-color:#b2920a"> <span style="color:#555;font-size:0.6rem">fresh temp dir per run</span></div>
315691
+ </div>
315692
+ </div>
315693
+
315644
315694
  <textarea id="agent-task" placeholder="Describe the task for the agent..." style="width:100%;min-height:80px;background:#2a2a30;border:1px solid #3a3a42;border-radius:3px;padding:8px 12px;color:#b0b0b0;font-family:inherit;font-size:0.82rem;resize:vertical;outline:none;margin-bottom:8px"></textarea>
315645
315695
  <div style="display:flex;gap:8px;margin-bottom:12px;align-items:center">
315646
315696
  <select id="agent-profile" style="background:#2a2a30;border:1px solid #3a3a42;color:#b0b0b0;padding:4px 8px;border-radius:3px;font-family:inherit;font-size:0.7rem"><option value="">no profile</option></select>
@@ -315846,12 +315896,24 @@ async function sendMessage() {
315846
315896
  msgDiv.appendChild(contentDiv);
315847
315897
 
315848
315898
  try {
315899
+ // Prepend context files as a FILES block so the agent can read them
315900
+ // without having to guess paths. The user picked these via right-click
315901
+ // "Add to context" on the workspace tree.
315902
+ let messageWithContext = text;
315903
+ if (contextFiles.length > 0) {
315904
+ const filesBlock = 'FILES IN CONTEXT:\\n' + contextFiles.map(p => ' - ' + p).join('\\n') + '\\n\\n';
315905
+ messageWithContext = filesBlock + text;
315906
+ }
315907
+
315849
315908
  const body = {
315850
315909
  session_id: chatSessionId,
315851
315910
  model: modelSelect.value,
315852
- message: text,
315911
+ message: messageWithContext,
315853
315912
  stream: true,
315854
315913
  max_tokens: 4096,
315914
+ // Pass the user-selected workspace as working_directory so the
315915
+ // agent subprocess operates in the right cwd.
315916
+ ...(chatWorkingDir ? { working_directory: chatWorkingDir } : {}),
315855
315917
  };
315856
315918
 
315857
315919
  const response = await fetch('/v1/chat', {
@@ -316198,14 +316260,39 @@ async function submitAgentTask() {
316198
316260
  document.getElementById('agent-abort').style.display = 'inline-block';
316199
316261
 
316200
316262
  const profile = document.getElementById('agent-profile').value;
316263
+ // Read the parameter form
316264
+ const workingDirEl = document.getElementById('agent-working-dir');
316265
+ const modelOverrideEl = document.getElementById('agent-model-override');
316266
+ const maxTurnsEl = document.getElementById('agent-max-turns');
316267
+ const timeoutSEl = document.getElementById('agent-timeout-s');
316268
+ const sandboxEl = document.getElementById('agent-sandbox');
316269
+ const streamEl = document.getElementById('agent-stream');
316270
+ const isolateEl = document.getElementById('agent-isolate');
316271
+ const wd = workingDirEl && workingDirEl.value.trim();
316272
+ const modelOverride = modelOverrideEl && modelOverrideEl.value.trim();
316273
+ const maxTurns = maxTurnsEl && maxTurnsEl.value ? parseInt(maxTurnsEl.value, 10) : null;
316274
+ const timeoutS = timeoutSEl && timeoutSEl.value ? parseInt(timeoutSEl.value, 10) : null;
316275
+ const sandbox = sandboxEl ? sandboxEl.value : 'none';
316276
+ const stream = streamEl ? streamEl.checked : true;
316277
+ const isolate = isolateEl ? isolateEl.checked : false;
316278
+
316279
+ const runBody = {
316280
+ task,
316281
+ model: modelOverride || modelSelect.value,
316282
+ stream,
316283
+ ...(profile ? { profile } : {}),
316284
+ ...(wd ? { working_directory: wd } : {}),
316285
+ ...(maxTurns && maxTurns > 0 ? { max_turns: maxTurns } : {}),
316286
+ ...(timeoutS && timeoutS > 0 ? { timeout_s: timeoutS } : {}),
316287
+ ...(sandbox && sandbox !== 'none' ? { sandbox } : {}),
316288
+ ...(isolate ? { isolate: true } : {}),
316289
+ };
316290
+
316201
316291
  try {
316202
316292
  const resp = await fetch('/v1/run', {
316203
316293
  method: 'POST',
316204
316294
  headers: headers(),
316205
- body: JSON.stringify({
316206
- task, model: modelSelect.value, stream: true,
316207
- ...(profile ? { profile } : {}),
316208
- }),
316295
+ body: JSON.stringify(runBody),
316209
316296
  });
316210
316297
 
316211
316298
  const reader = resp.body.getReader();
@@ -316457,30 +316544,302 @@ function stopChat() {
316457
316544
  }
316458
316545
 
316459
316546
  // Workspace sidebar
316547
+ // \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
316548
+ // Workspace sidebar \u2014 real tree with right-click context menu
316549
+ // \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
316550
+ //
316551
+ // - Lazy expand on click of the \u25B8/\u25BE caret (not on the whole row)
316552
+ // - Click on a file name opens file content in a modal preview
316553
+ // - Right-click on a folder \u2192 "Make workspace" / "Copy path" / "Add to context"
316554
+ // - Right-click on a file \u2192 "Add to context" / "Copy path" / "Preview"
316555
+ // - Selected workspace path is stored in chatWorkingDir and passed as
316556
+ // working_directory in all /v1/chat and /v1/run body fields
316557
+ // - Selected files accumulate in contextFiles and are prepended to the
316558
+ // next user message as a "FILES:" block (poor-mans attachment)
316559
+
316560
+ let chatWorkingDir = null; // the cwd the agent uses
316561
+ let contextFiles = []; // list of absolute file paths
316562
+
316563
+ // Expanded state: absolute path \u2192 bool
316564
+ const treeExpanded = new Set();
316565
+ // Cached children: absolute path \u2192 array of {name, type}
316566
+ const treeCache = new Map();
316567
+
316460
316568
  function toggleWorkspace() {
316461
316569
  const sb = document.getElementById('workspace-sidebar');
316462
316570
  sb.style.display = sb.style.display === 'none' ? 'block' : 'none';
316463
- if (sb.style.display === 'block') loadWorkspaceTree();
316571
+ if (sb.style.display === 'block') loadWorkspaceRoot();
316464
316572
  }
316465
- async function loadWorkspaceTree(dir) {
316573
+
316574
+ async function loadWorkspaceRoot() {
316575
+ const cwdEl = document.getElementById('workspace-cwd');
316576
+ // Default: daemon's cwd (from /v1/files with no path)
316577
+ try {
316578
+ const r = await fetch('/v1/files', { headers: headers() });
316579
+ const d = await r.json();
316580
+ const rootPath = d.path || '.';
316581
+ cwdEl.innerHTML = '';
316582
+ const wsSpan = document.createElement('span');
316583
+ wsSpan.style.cssText = 'color:#888';
316584
+ wsSpan.textContent = 'ROOT: ' + rootPath;
316585
+ cwdEl.appendChild(wsSpan);
316586
+ cwdEl.appendChild(document.createElement('br'));
316587
+ const wdSpan = document.createElement('span');
316588
+ wdSpan.id = 'workspace-dir-label';
316589
+ wdSpan.style.cssText = 'color:#b2920a';
316590
+ wdSpan.textContent = 'CWD: ' + (chatWorkingDir || rootPath);
316591
+ cwdEl.appendChild(wdSpan);
316592
+
316593
+ // Cache the root's children and expand it
316594
+ treeCache.set(rootPath, d.entries || []);
316595
+ treeExpanded.add(rootPath);
316596
+ renderWorkspaceTree(rootPath);
316597
+ } catch {
316598
+ document.getElementById('workspace-tree').innerHTML = '<div style="color:#555">Could not load files</div>';
316599
+ }
316600
+ }
316601
+
316602
+ function renderWorkspaceTree(rootPath) {
316466
316603
  const tree = document.getElementById('workspace-tree');
316604
+ tree.innerHTML = '';
316605
+ const rootContainer = document.createElement('div');
316606
+ renderTreeNode(rootContainer, rootPath, 0, true);
316607
+ tree.appendChild(rootContainer);
316608
+ }
316609
+
316610
+ function renderTreeNode(parentEl, absPath, depth, isRoot) {
316611
+ const entries = treeCache.get(absPath) || [];
316612
+ // Sort: dirs first, then files, both alphabetical
316613
+ entries.sort((a, b) => {
316614
+ if (a.type !== b.type) return a.type === 'dir' ? -1 : 1;
316615
+ return a.name.localeCompare(b.name);
316616
+ });
316617
+ for (const e of entries) {
316618
+ const childAbs = absPath.replace(/\\/+$/, '') + '/' + e.name;
316619
+ const row = document.createElement('div');
316620
+ row.className = 'tree-row';
316621
+ row.dataset.path = childAbs;
316622
+ row.dataset.type = e.type;
316623
+ row.style.cssText = 'padding:1px 0 1px ' + (depth * 12 + 4) + 'px; cursor:pointer; display:flex; align-items:center; gap:4px; user-select:none';
316624
+
316625
+ if (e.type === 'dir') {
316626
+ const caret = document.createElement('span');
316627
+ caret.className = 'tree-caret';
316628
+ caret.style.cssText = 'color:#666; font-size:0.55rem; width:10px; display:inline-block; text-align:center';
316629
+ caret.textContent = treeExpanded.has(childAbs) ? '\u25BE' : '\u25B8';
316630
+ row.appendChild(caret);
316631
+ const icon = document.createElement('span');
316632
+ icon.textContent = '\u{1F4C1}';
316633
+ row.appendChild(icon);
316634
+ const name = document.createElement('span');
316635
+ name.textContent = e.name;
316636
+ name.style.cssText = 'color:#b2920a';
316637
+ // Highlight the current workspace
316638
+ if (chatWorkingDir && childAbs === chatWorkingDir) {
316639
+ name.style.cssText += ';font-weight:bold;text-decoration:underline';
316640
+ }
316641
+ row.appendChild(name);
316642
+ row.addEventListener('click', async (ev) => {
316643
+ ev.stopPropagation();
316644
+ if (treeExpanded.has(childAbs)) {
316645
+ treeExpanded.delete(childAbs);
316646
+ } else {
316647
+ treeExpanded.add(childAbs);
316648
+ if (!treeCache.has(childAbs)) {
316649
+ try {
316650
+ const r = await fetch('/v1/files?path=' + encodeURIComponent(childAbs), { headers: headers() });
316651
+ const d = await r.json();
316652
+ treeCache.set(childAbs, d.entries || []);
316653
+ } catch {
316654
+ treeCache.set(childAbs, []);
316655
+ }
316656
+ }
316657
+ }
316658
+ // Find the root (topmost path in cache) and re-render
316659
+ const root = [...treeCache.keys()].find(k => [...treeCache.keys()].every(k2 => !k2.startsWith(k) || k === k2));
316660
+ renderWorkspaceTree(root || absPath);
316661
+ });
316662
+ } else {
316663
+ const pad = document.createElement('span');
316664
+ pad.style.cssText = 'width:10px; display:inline-block';
316665
+ row.appendChild(pad);
316666
+ const icon = document.createElement('span');
316667
+ icon.textContent = contextFiles.includes(childAbs) ? '\u{1F4CE}' : '\u{1F4C4}';
316668
+ row.appendChild(icon);
316669
+ const name = document.createElement('span');
316670
+ name.textContent = e.name;
316671
+ name.style.cssText = contextFiles.includes(childAbs)
316672
+ ? 'color:#4e94c9;font-weight:bold'
316673
+ : 'color:#b0b0b0';
316674
+ row.appendChild(name);
316675
+ row.addEventListener('click', (ev) => {
316676
+ ev.stopPropagation();
316677
+ previewFile(childAbs);
316678
+ });
316679
+ }
316680
+
316681
+ // Right-click context menu (works for both files and dirs)
316682
+ row.addEventListener('contextmenu', (ev) => {
316683
+ ev.preventDefault();
316684
+ ev.stopPropagation();
316685
+ showTreeContextMenu(ev.clientX, ev.clientY, childAbs, e.type);
316686
+ });
316687
+
316688
+ parentEl.appendChild(row);
316689
+
316690
+ if (e.type === 'dir' && treeExpanded.has(childAbs)) {
316691
+ const childContainer = document.createElement('div');
316692
+ renderTreeNode(childContainer, childAbs, depth + 1, false);
316693
+ parentEl.appendChild(childContainer);
316694
+ }
316695
+ }
316696
+ }
316697
+
316698
+ // Context menu element \u2014 created lazily, re-used
316699
+ let treeMenuEl = null;
316700
+ function showTreeContextMenu(x, y, path, type) {
316701
+ if (!treeMenuEl) {
316702
+ treeMenuEl = document.createElement('div');
316703
+ treeMenuEl.id = 'tree-menu';
316704
+ treeMenuEl.style.cssText = 'position:fixed; background:#1e1e22; border:1px solid #b2920a; border-radius:4px; padding:4px 0; box-shadow:0 4px 20px rgba(0,0,0,0.6); z-index:200; font-size:0.7rem; min-width:180px';
316705
+ document.body.appendChild(treeMenuEl);
316706
+ document.addEventListener('click', () => { if (treeMenuEl) treeMenuEl.style.display = 'none'; });
316707
+ }
316708
+ treeMenuEl.innerHTML = '';
316709
+ const items = type === 'dir'
316710
+ ? [
316711
+ { label: '\u25B8 Make workspace', fn: () => setChatWorkingDir(path) },
316712
+ { label: '\u25B8 Clear workspace (use daemon cwd)', fn: () => setChatWorkingDir(null), show: !!chatWorkingDir },
316713
+ { label: '\u{1F4CB} Copy path', fn: () => copyToClipboard(path) },
316714
+ { label: '\u{1F4CE} Add all files to context (top-level only)', fn: () => addDirToContext(path) },
316715
+ ]
316716
+ : [
316717
+ { label: contextFiles.includes(path) ? '\u{1F4CE} Remove from context' : '\u{1F4CE} Add to context', fn: () => toggleFileInContext(path) },
316718
+ { label: '\u{1F441} Preview file', fn: () => previewFile(path) },
316719
+ { label: '\u{1F4CB} Copy path', fn: () => copyToClipboard(path) },
316720
+ ];
316721
+ for (const it of items) {
316722
+ if (it.show === false) continue;
316723
+ const row = document.createElement('div');
316724
+ row.style.cssText = 'padding:6px 16px; cursor:pointer; color:#b0b0b0';
316725
+ row.textContent = it.label;
316726
+ row.addEventListener('mouseenter', () => row.style.background = '#2a2a30');
316727
+ row.addEventListener('mouseleave', () => row.style.background = '');
316728
+ row.addEventListener('click', (ev) => {
316729
+ ev.stopPropagation();
316730
+ try { it.fn(); } catch (e) { alert(e.message); }
316731
+ treeMenuEl.style.display = 'none';
316732
+ });
316733
+ treeMenuEl.appendChild(row);
316734
+ }
316735
+ treeMenuEl.style.display = 'block';
316736
+ treeMenuEl.style.left = x + 'px';
316737
+ treeMenuEl.style.top = y + 'px';
316738
+ }
316739
+
316740
+ function setChatWorkingDir(path) {
316741
+ chatWorkingDir = path;
316742
+ const label = document.getElementById('workspace-dir-label');
316743
+ if (label) label.textContent = 'CWD: ' + (path || '(daemon default)');
316744
+ // Refresh the tree to update the highlight
316745
+ const root = [...treeCache.keys()].find(k => [...treeCache.keys()].every(k2 => !k2.startsWith(k) || k === k2));
316746
+ if (root) renderWorkspaceTree(root);
316747
+ // Flash status
316748
+ const s = document.getElementById('status');
316749
+ if (s) {
316750
+ const old = s.textContent;
316751
+ s.textContent = path ? 'workspace \u2192 ' + path.split('/').slice(-2).join('/') : 'workspace cleared';
316752
+ s.style.color = '#4ec94e';
316753
+ setTimeout(() => { s.textContent = old; s.style.color = ''; }, 3000);
316754
+ }
316755
+ // Also push to the Agent tab input if visible
316756
+ const wdInput = document.getElementById('agent-working-dir');
316757
+ if (wdInput) wdInput.value = path || '';
316758
+ }
316759
+
316760
+ function toggleFileInContext(path) {
316761
+ const i = contextFiles.indexOf(path);
316762
+ if (i >= 0) contextFiles.splice(i, 1);
316763
+ else contextFiles.push(path);
316764
+ const root = [...treeCache.keys()].find(k => [...treeCache.keys()].every(k2 => !k2.startsWith(k) || k === k2));
316765
+ if (root) renderWorkspaceTree(root);
316766
+ const s = document.getElementById('status');
316767
+ if (s) {
316768
+ const old = s.textContent;
316769
+ s.textContent = contextFiles.length + ' file' + (contextFiles.length !== 1 ? 's' : '') + ' in context';
316770
+ s.style.color = '#4e94c9';
316771
+ setTimeout(() => { s.textContent = old; s.style.color = ''; }, 3000);
316772
+ }
316773
+ }
316774
+
316775
+ async function addDirToContext(dirPath) {
316776
+ // Fetch directory children and add non-dotfile files to context
316467
316777
  try {
316468
- const r = await fetch('/v1/files' + (dir ? '?path=' + encodeURIComponent(dir) : ''), { headers: headers() });
316778
+ const r = await fetch('/v1/files?path=' + encodeURIComponent(dirPath), { headers: headers() });
316469
316779
  const d = await r.json();
316470
- document.getElementById('workspace-cwd').textContent = d.path || '.';
316471
- tree.innerHTML = d.entries.map(e => {
316472
- const icon = e.type === 'dir' ? '\u{1F4C1}' : '\u{1F4C4}';
316473
- const color = e.type === 'dir' ? '#b2920a' : '#b0b0b0';
316474
- const click = e.type === 'dir'
316475
- ? ' onclick="loadWorkspaceTree(\\'' + (d.path + '/' + e.name).replace(/'/g, "\\\\'") + '\\')"'
316476
- : '';
316477
- return '<div style="padding:1px 0;cursor:' + (e.type === 'dir' ? 'pointer' : 'default') + ';color:' + color + '"' + click + '>' + icon + ' ' + e.name + '</div>';
316478
- }).join('');
316479
- if (dir) {
316480
- const parent = dir.split('/').slice(0, -1).join('/') || '/';
316481
- tree.innerHTML = '<div style="padding:1px 0;cursor:pointer;color:#555" onclick="loadWorkspaceTree(\\'' + parent.replace(/'/g, "\\\\'") + '\\')">\u2B06 ..</div>' + tree.innerHTML;
316780
+ for (const e of (d.entries || [])) {
316781
+ if (e.type === 'file') {
316782
+ const abs = dirPath.replace(/\\/+$/, '') + '/' + e.name;
316783
+ if (!contextFiles.includes(abs)) contextFiles.push(abs);
316784
+ }
316785
+ }
316786
+ const root = [...treeCache.keys()].find(k => [...treeCache.keys()].every(k2 => !k2.startsWith(k) || k === k2));
316787
+ if (root) renderWorkspaceTree(root);
316788
+ alert('Added ' + contextFiles.length + ' files to context');
316789
+ } catch (e) { alert('Failed: ' + e.message); }
316790
+ }
316791
+
316792
+ // Agent form helper \u2014 copy the currently-selected workspace path into the
316793
+ // Agent tab working_directory field
316794
+ function syncFromWorkspace() {
316795
+ const wdInput = document.getElementById('agent-working-dir');
316796
+ if (wdInput) wdInput.value = chatWorkingDir || '';
316797
+ }
316798
+
316799
+ function copyToClipboard(text) {
316800
+ try {
316801
+ navigator.clipboard.writeText(text);
316802
+ const s = document.getElementById('status');
316803
+ if (s) { const old = s.textContent; s.textContent = 'copied: ' + text.split('/').pop(); setTimeout(() => s.textContent = old, 2000); }
316804
+ } catch {}
316805
+ }
316806
+
316807
+ async function previewFile(path) {
316808
+ try {
316809
+ const r = await fetch('/v1/files/read', {
316810
+ method: 'POST',
316811
+ headers: headers(),
316812
+ body: JSON.stringify({ path, limit: 20000, allow_outside_cwd: true }),
316813
+ });
316814
+ const d = await r.json();
316815
+ if (d && d.content !== undefined) {
316816
+ // Create a simple preview modal
316817
+ const modal = document.createElement('div');
316818
+ modal.style.cssText = 'position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.8); z-index:300; display:flex; align-items:center; justify-content:center; padding:40px';
316819
+ modal.addEventListener('click', (e) => { if (e.target === modal) modal.remove(); });
316820
+ const card = document.createElement('div');
316821
+ card.style.cssText = 'background:#1e1e22; border:1px solid #2a2a30; border-radius:4px; max-width:90vw; max-height:90vh; overflow:hidden; display:flex; flex-direction:column';
316822
+ const title = document.createElement('div');
316823
+ title.style.cssText = 'padding:10px 16px; border-bottom:1px solid #2a2a30; color:#b2920a; font-size:0.75rem; display:flex; justify-content:space-between; align-items:center; gap:16px';
316824
+ title.innerHTML = '<span>' + escHtml(path) + '</span>';
316825
+ const closeBtn = document.createElement('button');
316826
+ closeBtn.textContent = '\xD7';
316827
+ closeBtn.style.cssText = 'background:none; border:none; color:#666; font-size:1.2rem; cursor:pointer';
316828
+ closeBtn.onclick = () => modal.remove();
316829
+ title.appendChild(closeBtn);
316830
+ card.appendChild(title);
316831
+ const pre = document.createElement('pre');
316832
+ pre.style.cssText = 'padding:12px 16px; color:#b0b0b0; overflow:auto; font-size:0.7rem; margin:0; background:#17171a';
316833
+ pre.textContent = d.content || '(empty)';
316834
+ card.appendChild(pre);
316835
+ modal.appendChild(card);
316836
+ document.body.appendChild(modal);
316837
+ } else {
316838
+ alert('Preview failed: ' + JSON.stringify(d).slice(0, 200));
316482
316839
  }
316483
- } catch { tree.innerHTML = '<div style="color:#555">Could not load files</div>'; }
316840
+ } catch (e) {
316841
+ alert('Preview failed: ' + e.message);
316842
+ }
316484
316843
  }
316485
316844
 
316486
316845
  // Docker sandbox toggle
@@ -318385,9 +318744,17 @@ async function directChatBackend(opts) {
318385
318744
  const j = JSON.parse(result.body);
318386
318745
  const content = j?.choices?.[0]?.message?.content || "";
318387
318746
  jsonResponse(res, 200, {
318388
- session_id: sessionId,
318747
+ id: `chatcmpl-${sessionId.slice(0, 12)}`,
318748
+ object: "chat.completion",
318749
+ created: Math.floor(Date.now() / 1e3),
318389
318750
  model: cleanModel,
318390
- message: { role: "assistant", content }
318751
+ choices: [{
318752
+ index: 0,
318753
+ message: { role: "assistant", content },
318754
+ finish_reason: "stop"
318755
+ }],
318756
+ usage: j?.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
318757
+ session_id: sessionId
318391
318758
  });
318392
318759
  return content;
318393
318760
  }
@@ -318454,14 +318821,28 @@ async function directChatBackend(opts) {
318454
318821
  const j = JSON.parse(result.body);
318455
318822
  const content = j?.message?.content || "";
318456
318823
  if (!content) throw new Error("Backend returned empty message content");
318824
+ const promptTokens = j?.prompt_eval_count ?? 0;
318825
+ const completionTokens = j?.eval_count ?? 0;
318457
318826
  jsonResponse(res, 200, {
318458
- session_id: sessionId,
318827
+ id: `chatcmpl-${sessionId.slice(0, 12)}`,
318828
+ object: "chat.completion",
318829
+ created: Math.floor(Date.now() / 1e3),
318459
318830
  model: cleanModel,
318460
- message: { role: "assistant", content },
318831
+ choices: [{
318832
+ index: 0,
318833
+ message: { role: "assistant", content },
318834
+ finish_reason: "stop"
318835
+ }],
318461
318836
  usage: {
318462
- prompt_tokens: j?.prompt_eval_count,
318463
- completion_tokens: j?.eval_count,
318464
- total_duration_ns: j?.total_duration
318837
+ prompt_tokens: promptTokens,
318838
+ completion_tokens: completionTokens,
318839
+ total_tokens: promptTokens + completionTokens
318840
+ },
318841
+ session_id: sessionId,
318842
+ _oa: {
318843
+ mode: "direct",
318844
+ ollama_total_duration_ns: j?.total_duration,
318845
+ ollama_load_duration_ns: j?.load_duration
318465
318846
  }
318466
318847
  });
318467
318848
  return content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.200",
3
+ "version": "0.187.202",
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",