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.
- package/dist/index.js +420 -39
- 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
|
-
|
|
315621
|
-
|
|
315622
|
-
|
|
315623
|
-
|
|
315624
|
-
|
|
315625
|
-
|
|
315626
|
-
<
|
|
315627
|
-
<
|
|
315628
|
-
|
|
315629
|
-
|
|
315630
|
-
|
|
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:
|
|
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')
|
|
316571
|
+
if (sb.style.display === 'block') loadWorkspaceRoot();
|
|
316464
316572
|
}
|
|
316465
|
-
|
|
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
|
|
316778
|
+
const r = await fetch('/v1/files?path=' + encodeURIComponent(dirPath), { headers: headers() });
|
|
316469
316779
|
const d = await r.json();
|
|
316470
|
-
|
|
316471
|
-
|
|
316472
|
-
|
|
316473
|
-
|
|
316474
|
-
|
|
316475
|
-
|
|
316476
|
-
|
|
316477
|
-
|
|
316478
|
-
|
|
316479
|
-
|
|
316480
|
-
|
|
316481
|
-
|
|
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 {
|
|
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
|
-
|
|
318747
|
+
id: `chatcmpl-${sessionId.slice(0, 12)}`,
|
|
318748
|
+
object: "chat.completion",
|
|
318749
|
+
created: Math.floor(Date.now() / 1e3),
|
|
318389
318750
|
model: cleanModel,
|
|
318390
|
-
|
|
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
|
-
|
|
318827
|
+
id: `chatcmpl-${sessionId.slice(0, 12)}`,
|
|
318828
|
+
object: "chat.completion",
|
|
318829
|
+
created: Math.floor(Date.now() / 1e3),
|
|
318459
318830
|
model: cleanModel,
|
|
318460
|
-
|
|
318831
|
+
choices: [{
|
|
318832
|
+
index: 0,
|
|
318833
|
+
message: { role: "assistant", content },
|
|
318834
|
+
finish_reason: "stop"
|
|
318835
|
+
}],
|
|
318461
318836
|
usage: {
|
|
318462
|
-
prompt_tokens:
|
|
318463
|
-
completion_tokens:
|
|
318464
|
-
|
|
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