loopengine 0.1.62 → 0.1.64
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/README.md +37 -11
- package/dist/core/agent-env.d.ts +2 -8
- package/dist/core/agent-env.js +7 -19
- package/dist/core/model-calls/anthropic-model-call.js +2 -1
- package/dist/core/model-calls/deepseek-model-call.js +2 -1
- package/dist/core/model-calls/gemini-model-call.js +2 -1
- package/dist/core/model-calls/glm-model-call.js +2 -1
- package/dist/core/model-calls/kimi-model-call.js +2 -1
- package/dist/core/model-calls/openai-model-call.js +2 -1
- package/dist/core/run-agent.d.ts +6 -0
- package/dist/core/run-agent.js +20 -2
- package/dist/core/secret-store.d.ts +32 -0
- package/dist/core/secret-store.js +116 -0
- package/dist/core/trusted-proxy.d.ts +15 -0
- package/dist/core/trusted-proxy.js +47 -0
- package/dist/core/usage.d.ts +18 -0
- package/dist/core/usage.js +32 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/web/agents-config-page.js +228 -294
- package/dist/web/agents-list-page.js +80 -99
- package/dist/web/console-shell.d.ts +2 -0
- package/dist/web/console-shell.js +156 -0
- package/dist/web/dev-ui-styles.js +146 -40
- package/dist/web/env-admin.d.ts +26 -25
- package/dist/web/env-admin.js +39 -103
- package/dist/web/global-config-page.js +26 -80
- package/dist/web/playground.js +161 -105
- package/package.json +7 -1
- package/dist/core/system-skills/system-skills/composio-large-outputs/SKILL.md +0 -25
|
@@ -26,108 +26,37 @@
|
|
|
26
26
|
// concatenation, not a nested template literal, for the same
|
|
27
27
|
// backtick/${}-escaping reason playground.ts's own header comment explains.
|
|
28
28
|
import { devUiCss } from './dev-ui-styles.js';
|
|
29
|
+
import { consoleShellScript, consoleSidebarHtml } from './console-shell.js';
|
|
29
30
|
export const agentsConfigPageHtml = `<!doctype html>
|
|
30
31
|
<html lang="en">
|
|
31
32
|
<head>
|
|
32
33
|
<meta charset="utf-8">
|
|
33
34
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
34
|
-
<title>LoopEngine
|
|
35
|
+
<title>LoopEngine Console</title>
|
|
35
36
|
<style>${devUiCss}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
it could drift apart. Named .section-sidebar (not .sidebar) to avoid
|
|
49
|
-
colliding with nav.sidebar right below, which is the agent picker,
|
|
50
|
-
not this new outer one. */
|
|
51
|
-
.page-body { flex: 1; display: flex; min-height: 0; }
|
|
52
|
-
nav.section-sidebar {
|
|
53
|
-
width: 150px;
|
|
54
|
-
flex-shrink: 0;
|
|
55
|
-
border-right: 1px solid light-dark(#ddd, #333);
|
|
56
|
-
padding: 6px;
|
|
57
|
-
}
|
|
58
|
-
nav.section-sidebar a {
|
|
59
|
-
display: block;
|
|
60
|
-
border-radius: 6px;
|
|
61
|
-
padding: 8px 10px;
|
|
62
|
-
font-size: 13px;
|
|
63
|
-
text-decoration: none;
|
|
64
|
-
color: inherit;
|
|
65
|
-
}
|
|
66
|
-
nav.section-sidebar a:hover { background: light-dark(#eee, #26262b); }
|
|
67
|
-
nav.section-sidebar a.active { background: light-dark(#dbeafe, #1e3a5f); font-weight: 600; }
|
|
68
|
-
.layout { flex: 1; display: flex; min-height: 0; }
|
|
69
|
-
nav.sidebar {
|
|
70
|
-
width: 220px;
|
|
71
|
-
flex-shrink: 0;
|
|
72
|
-
border-right: 1px solid light-dark(#ddd, #333);
|
|
73
|
-
overflow-y: auto;
|
|
74
|
-
display: flex;
|
|
75
|
-
flex-direction: column;
|
|
76
|
-
}
|
|
77
|
-
#agentList { list-style: none; margin: 0; padding: 6px; flex: 1; }
|
|
78
|
-
#agentList li { border-radius: 6px; }
|
|
79
|
-
#agentList li a {
|
|
80
|
-
display: block;
|
|
81
|
-
padding: 8px 10px;
|
|
82
|
-
border-radius: 6px;
|
|
83
|
-
font-size: 13px;
|
|
84
|
-
font-family: ui-monospace, monospace;
|
|
85
|
-
color: inherit;
|
|
86
|
-
text-decoration: none;
|
|
87
|
-
}
|
|
88
|
-
#agentList li a:hover { background: light-dark(#eee, #26262b); }
|
|
89
|
-
#agentList li.active a { background: light-dark(#dbeafe, #1e3a5f); font-weight: 600; }
|
|
90
|
-
main {
|
|
91
|
-
flex: 1;
|
|
92
|
-
overflow-y: auto;
|
|
93
|
-
padding: 20px 28px;
|
|
94
|
-
}
|
|
95
|
-
#empty { color: light-dark(#666, #999); font-size: 13px; }
|
|
96
|
-
h2 { font-size: 20px; margin: 0 0 4px; font-family: ui-monospace, monospace; }
|
|
97
|
-
.tabs {
|
|
98
|
-
display: flex;
|
|
99
|
-
gap: 18px;
|
|
100
|
-
margin: 14px 0 20px;
|
|
101
|
-
border-bottom: 1px solid light-dark(#ddd, #333);
|
|
102
|
-
}
|
|
103
|
-
.tabs button {
|
|
104
|
-
background: none;
|
|
105
|
-
border: none;
|
|
106
|
-
border-bottom: 2px solid transparent;
|
|
107
|
-
padding: 8px 2px;
|
|
108
|
-
font-size: 13px;
|
|
109
|
-
cursor: pointer;
|
|
110
|
-
color: light-dark(#666, #999);
|
|
111
|
-
}
|
|
112
|
-
.tabs button:hover { color: light-dark(#1a1a1e, #e8e8ea); }
|
|
113
|
-
.tabs button.active {
|
|
114
|
-
color: light-dark(#1a1a1e, #e8e8ea);
|
|
115
|
-
border-bottom-color: light-dark(#1d4ed8, #60a5fa);
|
|
116
|
-
font-weight: 600;
|
|
117
|
-
}
|
|
118
|
-
section { margin-bottom: 22px; }
|
|
37
|
+
/* Each tab panel is a .le-page column inside the shell's scrolling
|
|
38
|
+
main area (see web/console-shell.ts); everything below styles the
|
|
39
|
+
panels' own content. */
|
|
40
|
+
#empty { color: var(--ink-muted); font-size: 13px; padding: 24px; }
|
|
41
|
+
.env-cell { display: grid; gap: 6px; }
|
|
42
|
+
.env-desc { max-width: 340px; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
|
|
43
|
+
form.add-source.env-inline { border: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; max-width: 320px; }
|
|
44
|
+
form.add-source.env-inline input, form.add-source.env-inline select { flex: 1 1 140px; width: auto; min-width: 0; }
|
|
45
|
+
form.add-source.env-inline textarea { flex: 1 1 100%; }
|
|
46
|
+
form.add-source.env-inline button { padding: 4px 10px; }
|
|
47
|
+
.tab-panel .le-table-wrap table { border: 0; border-radius: 0; }
|
|
48
|
+
section { margin-bottom: 28px; }
|
|
119
49
|
section h3 {
|
|
120
|
-
font-size:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
margin: 0 0 8px;
|
|
125
|
-
border-bottom: 1px solid light-dark(#eee, #2a2a2e);
|
|
126
|
-
padding-bottom: 4px;
|
|
50
|
+
font-size: 15px;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
color: var(--ink);
|
|
53
|
+
margin: 0 0 10px;
|
|
127
54
|
display: flex;
|
|
128
55
|
align-items: center;
|
|
129
56
|
justify-content: space-between;
|
|
57
|
+
gap: 12px;
|
|
130
58
|
}
|
|
59
|
+
section > table, .tab-panel table { border: 1px solid var(--line); border-radius: var(--radius); border-collapse: separate; border-spacing: 0; overflow: hidden; }
|
|
131
60
|
.hint-btn {
|
|
132
61
|
font-size: 11px;
|
|
133
62
|
text-transform: none;
|
|
@@ -137,18 +66,18 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
137
66
|
.admin-subsection {
|
|
138
67
|
margin-top: 14px;
|
|
139
68
|
padding-top: 14px;
|
|
140
|
-
border-top: 1px dashed
|
|
69
|
+
border-top: 1px dashed var(--line);
|
|
141
70
|
}
|
|
142
71
|
.admin-subsection h4 {
|
|
143
72
|
font-size: 12px;
|
|
144
73
|
text-transform: uppercase;
|
|
145
74
|
letter-spacing: 0.04em;
|
|
146
|
-
color:
|
|
75
|
+
color: var(--ink-muted);
|
|
147
76
|
margin: 0 0 8px;
|
|
148
77
|
}
|
|
149
78
|
pre {
|
|
150
|
-
background:
|
|
151
|
-
border: 1px solid
|
|
79
|
+
background: var(--bg);
|
|
80
|
+
border: 1px solid var(--line);
|
|
152
81
|
border-radius: 6px;
|
|
153
82
|
padding: 10px 12px;
|
|
154
83
|
font-size: 12px;
|
|
@@ -160,29 +89,29 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
160
89
|
th, td {
|
|
161
90
|
text-align: left;
|
|
162
91
|
padding: 6px 8px;
|
|
163
|
-
border-bottom: 1px solid
|
|
92
|
+
border-bottom: 1px solid var(--line);
|
|
164
93
|
vertical-align: top;
|
|
165
94
|
}
|
|
166
|
-
th { color:
|
|
167
|
-
.decision-allow { color:
|
|
168
|
-
.decision-ask { color:
|
|
169
|
-
.decision-deny { color:
|
|
95
|
+
th { color: var(--ink-muted); font-weight: 600; }
|
|
96
|
+
.decision-allow { color: var(--good); }
|
|
97
|
+
.decision-ask { color: var(--warn); }
|
|
98
|
+
.decision-deny { color: var(--bad); }
|
|
170
99
|
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; font-size: 13px; }
|
|
171
|
-
.kv dt { color:
|
|
100
|
+
.kv dt { color: var(--ink-muted); }
|
|
172
101
|
.kv dd { margin: 0; font-family: ui-monospace, monospace; }
|
|
173
|
-
.error { color:
|
|
174
|
-
.hint { font-size: 11px; color:
|
|
102
|
+
.error { color: var(--bad); font-size: 13px; }
|
|
103
|
+
.hint { font-size: 11px; color: var(--ink-muted); }
|
|
175
104
|
#playgroundLink { font-size: 12px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: normal; text-decoration: none; margin-left: 8px; }
|
|
176
105
|
.source {
|
|
177
|
-
border: 1px solid
|
|
106
|
+
border: 1px solid var(--line);
|
|
178
107
|
border-radius: 8px;
|
|
179
108
|
padding: 12px 14px;
|
|
180
109
|
margin-bottom: 10px;
|
|
181
110
|
}
|
|
182
111
|
.source-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
183
112
|
.source-head h4 { margin: 0; font-size: 14px; font-family: ui-monospace, monospace; }
|
|
184
|
-
.status-ok { color:
|
|
185
|
-
.status-error { color:
|
|
113
|
+
.status-ok { color: var(--good); }
|
|
114
|
+
.status-error { color: var(--bad); }
|
|
186
115
|
.tool-list { margin: 8px 0 0; padding-left: 0; font-size: 12px; }
|
|
187
116
|
.tool-list li {
|
|
188
117
|
margin-bottom: 2px;
|
|
@@ -193,11 +122,11 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
193
122
|
.delete-btn {
|
|
194
123
|
flex-shrink: 0;
|
|
195
124
|
font-size: 12px;
|
|
196
|
-
color:
|
|
197
|
-
background:
|
|
198
|
-
border-color:
|
|
125
|
+
color: var(--bad);
|
|
126
|
+
background: var(--bad-soft);
|
|
127
|
+
border-color: var(--bad);
|
|
199
128
|
}
|
|
200
|
-
.delete-btn:hover { background:
|
|
129
|
+
.delete-btn:hover { background: var(--bad-soft); }
|
|
201
130
|
/* Explicit rather than relying on inherited font-size: .edit-rule-btn
|
|
202
131
|
sits inside a 12px table, but .edit-skill-btn sits in .source-head
|
|
203
132
|
(no font-size of its own, so it'd otherwise inherit the page's
|
|
@@ -218,7 +147,7 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
218
147
|
once there's nowhere left to wrap normally. */
|
|
219
148
|
.tool-picker-label { flex: 1; min-width: 0; overflow-wrap: break-word; }
|
|
220
149
|
form.add-source {
|
|
221
|
-
border: 1px solid
|
|
150
|
+
border: 1px solid var(--line);
|
|
222
151
|
border-radius: 8px;
|
|
223
152
|
padding: 14px;
|
|
224
153
|
display: grid;
|
|
@@ -247,9 +176,9 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
247
176
|
.body-field-head { display: flex; align-items: center; justify-content: space-between; }
|
|
248
177
|
.md-toggle { display: flex; gap: 4px; }
|
|
249
178
|
.md-toggle-btn { padding: 2px 8px; font-size: 11px; background: transparent; }
|
|
250
|
-
.md-toggle-btn.active { background:
|
|
179
|
+
.md-toggle-btn.active { background: var(--surface-2); font-weight: 600; }
|
|
251
180
|
.markdown-body {
|
|
252
|
-
border: 1px solid
|
|
181
|
+
border: 1px solid var(--line);
|
|
253
182
|
border-radius: 6px;
|
|
254
183
|
padding: 10px 14px;
|
|
255
184
|
min-height: 280px;
|
|
@@ -257,17 +186,17 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
257
186
|
overflow-y: auto;
|
|
258
187
|
}
|
|
259
188
|
.markdown-body h1, .markdown-body h2 {
|
|
260
|
-
border-bottom: 1px solid
|
|
189
|
+
border-bottom: 1px solid var(--line);
|
|
261
190
|
padding-bottom: 4px;
|
|
262
191
|
}
|
|
263
192
|
.markdown-body pre {
|
|
264
|
-
background:
|
|
193
|
+
background: var(--surface);
|
|
265
194
|
padding: 10px 12px;
|
|
266
195
|
border-radius: 6px;
|
|
267
196
|
overflow-x: auto;
|
|
268
197
|
}
|
|
269
198
|
.markdown-body code {
|
|
270
|
-
background:
|
|
199
|
+
background: var(--line);
|
|
271
200
|
padding: 1px 5px;
|
|
272
201
|
border-radius: 4px;
|
|
273
202
|
font-family: ui-monospace, monospace;
|
|
@@ -284,7 +213,7 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
284
213
|
.tool-picker-list {
|
|
285
214
|
max-height: 240px;
|
|
286
215
|
overflow-y: auto;
|
|
287
|
-
border: 1px solid
|
|
216
|
+
border: 1px solid var(--line);
|
|
288
217
|
border-radius: 6px;
|
|
289
218
|
padding: 6px 8px;
|
|
290
219
|
}
|
|
@@ -304,7 +233,7 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
304
233
|
gap: 8px;
|
|
305
234
|
padding: 5px 2px;
|
|
306
235
|
font-size: 12px;
|
|
307
|
-
border-bottom: 1px solid
|
|
236
|
+
border-bottom: 1px solid var(--line);
|
|
308
237
|
}
|
|
309
238
|
.tool-picker-item:last-child { border-bottom: none; }
|
|
310
239
|
/* Higher specificity than "form.add-source input { width: 100%; }" —
|
|
@@ -372,31 +301,17 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
372
301
|
}
|
|
373
302
|
</style>
|
|
374
303
|
</head>
|
|
375
|
-
<body>
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
<div class="page-body">
|
|
382
|
-
<nav class="section-sidebar">
|
|
383
|
-
<a href="/agents/config" class="active">Agents</a>
|
|
384
|
-
<a href="/config?section=models">Models</a>
|
|
385
|
-
<a href="/config?section=gateways">Gateways</a>
|
|
386
|
-
</nav>
|
|
387
|
-
<div class="layout">
|
|
388
|
-
<nav class="sidebar">
|
|
389
|
-
<ul id="agentList"></ul>
|
|
390
|
-
</nav>
|
|
391
|
-
<main>
|
|
392
|
-
<div id="empty">Pick an agent to see its config.</div>
|
|
393
|
-
<div id="detail" style="display:none"></div>
|
|
304
|
+
<body data-le-page="agent-config">
|
|
305
|
+
<div class="le-app">
|
|
306
|
+
${consoleSidebarHtml}
|
|
307
|
+
<main class="le-main">
|
|
308
|
+
<div id="empty">Loading agents…</div>
|
|
309
|
+
<div id="detail" style="display:none;flex-direction:column;flex:1;min-height:0"></div>
|
|
394
310
|
</main>
|
|
395
311
|
</div>
|
|
396
|
-
|
|
312
|
+
${consoleShellScript}
|
|
397
313
|
<script>
|
|
398
314
|
(function () {
|
|
399
|
-
var agentList = document.getElementById('agentList');
|
|
400
315
|
var empty = document.getElementById('empty');
|
|
401
316
|
var detail = document.getElementById('detail');
|
|
402
317
|
var currentName = null;
|
|
@@ -574,14 +489,31 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
574
489
|
|
|
575
490
|
// ---- Overview tab ----
|
|
576
491
|
|
|
492
|
+
// Whether a call to this tool runs on its own, waits for a person, or
|
|
493
|
+
// never runs — read off the agent's resolved rules (cfg.permissions).
|
|
494
|
+
// A tool whose rules disagree by tenant/environment, or depend on a
|
|
495
|
+
// "when" condition, shows "depends" rather than picking one.
|
|
496
|
+
function runsPill(toolName) {
|
|
497
|
+
var perms = currentCfg && currentCfg.permissions;
|
|
498
|
+
if (!perms) return '';
|
|
499
|
+
var matching = perms.rules.filter(function (r) { return r.tool === toolName || r.tool === '*'; });
|
|
500
|
+
var decisions = [];
|
|
501
|
+
matching.forEach(function (r) { if (decisions.indexOf(r.decision) === -1) decisions.push(r.decision); });
|
|
502
|
+
var conditional = matching.some(function (r) { return r.when; });
|
|
503
|
+
if (!matching.length) return '<span class="le-pill ' + perms.defaultDecision + '" title="No rule names this tool; the default applies">' + escapeHtml(perms.defaultDecision) + '</span>';
|
|
504
|
+
if (decisions.length === 1 && !conditional) return '<span class="le-pill ' + decisions[0] + '">' + escapeHtml(decisions[0]) + '</span>';
|
|
505
|
+
return '<span class="le-pill neutral" title="' + escapeHtml(decisions.join(' / ')) + ', depending on tenant, environment or condition">depends</span>';
|
|
506
|
+
}
|
|
507
|
+
|
|
577
508
|
function renderTools(tools) {
|
|
578
|
-
if (!tools.length) return '<p class="muted">
|
|
509
|
+
if (!tools.length) return '<p class="muted">None.</p>';
|
|
579
510
|
var rows = tools.map(function (t) {
|
|
580
511
|
return '<tr><td><code>' + escapeHtml(t.name) + '</code></td>' +
|
|
581
512
|
'<td>' + escapeHtml(t.description) + '</td>' +
|
|
513
|
+
'<td>' + runsPill(t.name) + '</td>' +
|
|
582
514
|
'<td>' + (t.safe ? 'yes' : 'no') + '</td></tr>';
|
|
583
515
|
}).join('');
|
|
584
|
-
return '<table><thead><tr><th>Name</th><th>
|
|
516
|
+
return '<table><thead><tr><th>Name</th><th>What it does</th><th>Runs</th><th>Parallel-safe</th></tr></thead><tbody>' + rows + '</tbody></table>';
|
|
585
517
|
}
|
|
586
518
|
|
|
587
519
|
// Local tools' own table, unlike renderTools above (shared by Agent as
|
|
@@ -593,17 +525,18 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
593
525
|
// has no spec to repopulate an edit form from, so it just gets a blank
|
|
594
526
|
// cell — not a button that would 404 the moment it's clicked.
|
|
595
527
|
function renderLocalTools(tools) {
|
|
596
|
-
if (!tools.length) return '<p class="muted">No tools.</p>';
|
|
528
|
+
if (!tools.length) return '<p class="muted">No tools yet. Add one below, install an ability, or connect an integration.</p>';
|
|
597
529
|
var rows = tools.map(function (t) {
|
|
598
530
|
var actions = t.httpToolEditable
|
|
599
531
|
? '<button type="button" class="edit-http-tool-btn" data-name="' + escapeHtml(t.name) + '">Edit</button>'
|
|
600
532
|
: '';
|
|
601
533
|
return '<tr><td><code>' + escapeHtml(t.name) + '</code></td>' +
|
|
602
534
|
'<td>' + escapeHtml(t.description) + '</td>' +
|
|
535
|
+
'<td>' + runsPill(t.name) + '</td>' +
|
|
603
536
|
'<td>' + (t.safe ? 'yes' : 'no') + '</td>' +
|
|
604
537
|
'<td>' + actions + '</td></tr>';
|
|
605
538
|
}).join('');
|
|
606
|
-
return '<table><thead><tr><th>Name</th><th>
|
|
539
|
+
return '<table><thead><tr><th>Name</th><th>What it does</th><th>Runs</th><th>Parallel-safe</th><th></th></tr></thead><tbody>' + rows + '</tbody></table>';
|
|
607
540
|
}
|
|
608
541
|
|
|
609
542
|
// Overview's own tools table, unlike the Tools tab's three separately-
|
|
@@ -648,6 +581,34 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
648
581
|
return '<table><thead><tr><th>Name</th><th>Description</th></tr></thead><tbody>' + rows + '</tbody></table>';
|
|
649
582
|
}
|
|
650
583
|
|
|
584
|
+
// The top of Overview: one card per other tab, each a summary that
|
|
585
|
+
// links to the tab with the detail — Overview itself no longer repeats
|
|
586
|
+
// the full tool/skill/rule tables those tabs already show.
|
|
587
|
+
function renderOverviewCards(cfg) {
|
|
588
|
+
var rules = cfg.permissions.rules;
|
|
589
|
+
var asks = rules.filter(function (r) { return r.decision === 'ask'; }).length;
|
|
590
|
+
var toolNames = cfg.tools.map(function (t) { return t.name; });
|
|
591
|
+
var modelValue = typeof cfg.model === 'string' ? cfg.model : (cfg.model.model || cfg.model.provider);
|
|
592
|
+
var modelDetail = typeof cfg.model === 'string' ? 'custom createModelCall' : cfg.model.provider + (cfg.model.maxTokens != null ? ' \u00b7 max tokens ' + cfg.model.maxTokens : '');
|
|
593
|
+
function card(tab, label, value, detailText) {
|
|
594
|
+
var inner = '<span class="le-card-label"><span>' + label + '</span>' + (tab ? '<span aria-hidden="true">→</span>' : '') + '</span>' +
|
|
595
|
+
'<span class="le-card-value">' + value + '</span>' +
|
|
596
|
+
'<span class="le-card-detail">' + detailText + '</span>';
|
|
597
|
+
return tab
|
|
598
|
+
? '<a class="le-card" data-tab="' + tab + '" href="' + leShell.tabHref(cfg.name, tab) + '">' + inner + '</a>'
|
|
599
|
+
: '<div class="le-card">' + inner + '</div>';
|
|
600
|
+
}
|
|
601
|
+
return '<div class="le-cards" style="margin-bottom:28px">' +
|
|
602
|
+
card(null, 'Model', escapeHtml(modelValue), escapeHtml(modelDetail)) +
|
|
603
|
+
card('tools', 'Tools', String(cfg.tools.length), escapeHtml(toolNames.slice(0, 3).join(', ') + (toolNames.length > 3 ? ', \u2026' : '')) || 'None yet') +
|
|
604
|
+
card('actauth', 'Permissions', rules.length + (rules.length === 1 ? ' rule' : ' rules'),
|
|
605
|
+
(asks ? asks + ' need approval \u00b7 ' : '') + 'unmatched calls: ' + escapeHtml(cfg.permissions.defaultDecision)) +
|
|
606
|
+
card('skills', 'Skills', String(cfg.skills.length), escapeHtml(cfg.skills.map(function (k) { return k.name; }).join(', ')) || 'None yet') +
|
|
607
|
+
card('abilities', 'Abilities', 'Browse', 'Add tools, skills and rules in one install') +
|
|
608
|
+
card('env', 'Environment', 'Settings', '<span data-le-env-count="' + escapeHtml(cfg.name) + '">' + leShell.envCountHtml(cfg.name) + '</span> Values your installed abilities read') +
|
|
609
|
+
'</div>';
|
|
610
|
+
}
|
|
611
|
+
|
|
651
612
|
function renderOverviewHtml(cfg) {
|
|
652
613
|
var systemPromptSection =
|
|
653
614
|
'<h3>System prompt <button type="button" class="edit-btn" id="editSystemPromptBtn">Edit</button></h3>' +
|
|
@@ -703,11 +664,9 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
703
664
|
'</form>';
|
|
704
665
|
}
|
|
705
666
|
|
|
706
|
-
return
|
|
667
|
+
return renderOverviewCards(cfg) +
|
|
668
|
+
'<section>' + systemPromptSection + '</section>' +
|
|
707
669
|
'<section>' + modelSection + '</section>' +
|
|
708
|
-
'<section><h3>Skills (' + cfg.skills.length + ')</h3>' + renderSkills(cfg.skills) + '</section>' +
|
|
709
|
-
'<section><h3>Tools (' + cfg.tools.length + ')</h3>' + renderToolsWithType(cfg) + '</section>' +
|
|
710
|
-
'<section><h3>ActAuth</h3>' + renderRules(cfg.permissions) + '</section>' +
|
|
711
670
|
'<section><h3>Hooks</h3><dl class="kv">' +
|
|
712
671
|
'<dt>sessionIdFor</dt><dd>' + badge(cfg.sessionIdFor.indexOf('custom') === 0 ? 'custom' : 'default') + ' <span class="muted">' + escapeHtml(cfg.sessionIdFor) + '</span></dd>' +
|
|
713
672
|
'<dt>tenantFor</dt><dd>' + badge(cfg.tenantFor.indexOf('custom') === 0 ? 'custom' : 'default') + ' <span class="muted">' + escapeHtml(cfg.tenantFor) + '</span></dd>' +
|
|
@@ -943,20 +902,22 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
943
902
|
'<td><code>' + escapeHtml(r.name || '') + '</code></td>' +
|
|
944
903
|
'<td><code>' + escapeHtml(r.scope) + '</code></td>' +
|
|
945
904
|
'<td><code>' + escapeHtml(r.tool) + '</code></td>' +
|
|
946
|
-
'<td class="' +
|
|
905
|
+
'<td><span class="le-pill ' + escapeHtml(r.decision) + '">' + escapeHtml(r.decision) + '</span></td>' +
|
|
947
906
|
'<td>' + actionsHtml + '</td>' +
|
|
948
907
|
'</tr>';
|
|
949
908
|
}
|
|
950
909
|
|
|
951
910
|
function renderActauthConfigHtml(cfg) {
|
|
952
911
|
var rulesHtml = cfg.rules.length
|
|
953
|
-
? '<table><thead><tr><th>Name</th><th>
|
|
912
|
+
? '<table><thead><tr><th>Name</th><th>Who (tenant / environment)</th><th>Tool</th><th>Decision</th><th></th></tr></thead><tbody>' +
|
|
954
913
|
cfg.rules.map(renderActauthRuleRow).join('') + '</tbody></table>'
|
|
955
914
|
: '<p class="hint">No explicit rules — every tool call falls through to the default decision.</p>';
|
|
956
915
|
|
|
957
|
-
return '<
|
|
958
|
-
|
|
959
|
-
|
|
916
|
+
return '<p class="muted" style="margin:0 0 20px">Decide which tool calls run on their own, which wait for a person, and which never run. Stored in <code>actauth.yml</code> in this agent's folder.</p>' +
|
|
917
|
+
'<section><h3>Rules (' + cfg.rules.length + ')</h3>' + rulesHtml + '</section>' +
|
|
918
|
+
'<section><h3>When no rule matches</h3>' +
|
|
919
|
+
'<form class="add-source" id="defaultDecisionForm" style="max-width:260px">' +
|
|
920
|
+
'<label>Decision' +
|
|
960
921
|
'<select name="defaultDecision" id="defaultDecisionSelect">' +
|
|
961
922
|
'<option value="allow"' + (cfg.defaultDecision === 'allow' ? ' selected' : '') + '>allow</option>' +
|
|
962
923
|
'<option value="ask"' + (cfg.defaultDecision === 'ask' ? ' selected' : '') + '>ask</option>' +
|
|
@@ -966,13 +927,12 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
966
927
|
'<button type="submit">Save</button>' +
|
|
967
928
|
'<div id="defaultDecisionError" class="error"></div>' +
|
|
968
929
|
'</form></section>' +
|
|
969
|
-
'<section><h3>Rules (' + cfg.rules.length + ')</h3>' + rulesHtml + '</section>' +
|
|
970
930
|
'<section><h3 id="ruleFormHeading">Add a rule</h3>' +
|
|
971
931
|
'<form class="add-source" id="ruleForm">' +
|
|
972
932
|
'<label>Name' +
|
|
973
933
|
'<input name="name" id="ruleNameInput" required placeholder="my-rule-name">' +
|
|
974
934
|
'</label>' +
|
|
975
|
-
'<label>
|
|
935
|
+
'<label>Who <span class="hint">(tenant/environment, e.g. "default/production" or "*/*" for everyone)</span>' +
|
|
976
936
|
'<input name="scope" id="ruleScopeInput" required placeholder="default/production">' +
|
|
977
937
|
'</label>' +
|
|
978
938
|
'<label>Tool' +
|
|
@@ -1157,104 +1117,86 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1157
1117
|
return '<section id="envSection"><div id="envContent"><p class="hint">Loading…</p></div></section>';
|
|
1158
1118
|
}
|
|
1159
1119
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
? ' <span class="hint" title="Also declared by: ' + escapeHtml(v.abilityNames.join(', ')) + '. There is only one value for it project-wide, so check whether they actually need the same one.">(shared)</span>'
|
|
1173
|
-
: '';
|
|
1174
|
-
// A secret's value is never sent to the browser at all (see
|
|
1175
|
-
// env-admin.ts) — "set" is the most this tab can ever say about one.
|
|
1176
|
-
// A non-secret var's actual value comes through, so show it instead
|
|
1177
|
-
// of a bare "set", since the whole point of looking here is usually
|
|
1178
|
-
// to check *what* something is currently configured to, not just
|
|
1179
|
-
// whether it has a value.
|
|
1180
|
-
// An agent row (v.slot === 'agent' — this agent's own value, see
|
|
1181
|
-
// env-admin.ts's DeclaredEnvVar.slot) for an overridable var isn't a
|
|
1182
|
-
// problem when unset: it just falls back to the shared row. One for
|
|
1183
|
-
// an agent-scoped var has no fallback, so unset really is missing.
|
|
1184
|
-
var statusHtml = !v.set
|
|
1185
|
-
? (v.slot === 'agent' && v.scope === 'overridable'
|
|
1186
|
-
? '<span class="hint">not set — uses shared value</span>'
|
|
1187
|
-
: '<span class="error">not set</span>')
|
|
1120
|
+
// One slot's cell: its current value (never a secret's), a form to set
|
|
1121
|
+
// it, and Remove once set. Slot "shared" is the project .env, slot
|
|
1122
|
+
// "agent" this agent's own agents/<name>/.env — see env-admin.ts's
|
|
1123
|
+
// DeclaredEnvVar.slot. The form/button classes and data-* attributes
|
|
1124
|
+
// are what wireEnvHandlers below binds to.
|
|
1125
|
+
function renderEnvSlotCell(v) {
|
|
1126
|
+
var status = !v.set
|
|
1127
|
+
? (v.slot === 'agent' && v.scope === 'agent'
|
|
1128
|
+
? '<span class="le-pill bad">missing</span>'
|
|
1129
|
+
: v.slot === 'agent'
|
|
1130
|
+
? '<span class="hint">uses project-wide</span>'
|
|
1131
|
+
: '<span class="hint">not set</span>')
|
|
1188
1132
|
: v.secret
|
|
1189
|
-
? '<span class="
|
|
1133
|
+
? '<span class="le-pill good">set</span>'
|
|
1190
1134
|
: '<code>' + escapeHtml(v.value || '') + '</code>';
|
|
1191
1135
|
if (v.set && v.source === 'legacy') {
|
|
1192
|
-
|
|
1136
|
+
status += ' <span class="hint" title="Read from the older prefixed project variable. Saving a value here moves it into this agent's own .env.">(from ' + escapeHtml(v.legacyName || '') + ')</span>';
|
|
1193
1137
|
}
|
|
1194
|
-
var
|
|
1195
|
-
? ' <span class="hint">' + (v.scope === 'agent' ? 'this agent only' : 'this agent's override') + '</span>'
|
|
1196
|
-
: (v.scope === 'overridable' ? ' <span class="hint">shared</span>' : '');
|
|
1197
|
-
// A var with a closed set of valid values (AbilityEnvDecl.options,
|
|
1198
|
-
// e.g. a provider switch) gets a <select> instead of a free-text
|
|
1199
|
-
// field, so there's nothing to mistype — the same "value" form field
|
|
1200
|
-
// name either element uses means wireEnvHandlers's own submit
|
|
1201
|
-
// handler doesn't need to know which one it's looking at.
|
|
1202
|
-
// A naturally multi-line value (AbilityEnvDecl.multiline, e.g. a
|
|
1203
|
-
// pasted JSON key file) gets a <textarea> — a single-line <input>
|
|
1204
|
-
// (even type="text") strips embedded line breaks from its value
|
|
1205
|
-
// entirely per the HTML spec's own value sanitization, mangling
|
|
1206
|
-
// exactly this kind of paste. No password-style masking for it
|
|
1207
|
-
// either — <textarea> has no such mode, and masking a large blob
|
|
1208
|
-
// secret buys little anyway compared to being able to paste it
|
|
1209
|
-
// correctly at all.
|
|
1210
|
-
var fieldHtml = v.options
|
|
1138
|
+
var field = v.options
|
|
1211
1139
|
? '<select name="value">' + v.options.map(function (opt) {
|
|
1212
1140
|
return '<option value="' + escapeHtml(opt) + '"' + (v.value === opt ? ' selected' : '') + '>' + escapeHtml(opt) + '</option>';
|
|
1213
1141
|
}).join('') + '</select>'
|
|
1214
1142
|
: v.multiline
|
|
1215
|
-
? '<textarea name="value" rows="
|
|
1216
|
-
: '<input type="' + (v.secret ? 'password' : 'text') + '" name="value" placeholder="' + (v.set ? '
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1143
|
+
? '<textarea name="value" rows="4" placeholder="' + (v.set ? 'paste a new value to replace it' : 'paste the full value') + '" required></textarea>'
|
|
1144
|
+
: '<input type="' + (v.secret ? 'password' : 'text') + '" name="value" placeholder="' + (v.set ? 'new value' : 'value') + '" required>';
|
|
1145
|
+
var remove = v.set
|
|
1146
|
+
? '<button type="button" class="env-var-remove" data-name="' + escapeHtml(v.name) + '" data-slot="' + escapeHtml(v.slot) + '">Remove</button>'
|
|
1147
|
+
: '';
|
|
1148
|
+
return '<div class="env-cell"><div>' + status + '</div>' +
|
|
1149
|
+
'<form class="add-source env-var-form env-inline" data-name="' + escapeHtml(v.name) + '" data-slot="' + escapeHtml(v.slot) + '">' +
|
|
1150
|
+
field + '<button type="submit">' + (v.set ? 'Change' : 'Set') + '</button>' + remove +
|
|
1151
|
+
'</form></div>';
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
var ENV_SCOPE_LABELS = {
|
|
1155
|
+
shared: 'Shared by every agent',
|
|
1156
|
+
overridable: 'Shared; an agent can set its own',
|
|
1157
|
+
agent: 'Set per agent; never shared',
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
// One setting per row, its project-wide and per-agent values side by
|
|
1161
|
+
// side, plus which one this agent's tools actually read.
|
|
1162
|
+
function renderEnvSettingRow(shared, own) {
|
|
1163
|
+
var v = own || shared;
|
|
1164
|
+
var description = (shared && shared.description) || (own && own.description) || '';
|
|
1165
|
+
var alsoDeclared = v.abilityNames.length > 1
|
|
1166
|
+
? ' <span class="hint" title="Also declared by: ' + escapeHtml(v.abilityNames.join(', ')) + '">(+' + (v.abilityNames.length - 1) + ' more)</span>'
|
|
1224
1167
|
: '';
|
|
1168
|
+
var used = own && own.set
|
|
1169
|
+
? 'this agent'
|
|
1170
|
+
: v.scope === 'agent'
|
|
1171
|
+
? '<span class="error">nothing — its tools fail</span>'
|
|
1172
|
+
: shared && shared.set ? 'project-wide' : '<span class="hint">not set</span>';
|
|
1225
1173
|
return '<tr>' +
|
|
1226
|
-
'<td><code>' + escapeHtml(v.name) + '</code>' +
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
'<td
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
'</form>' + removeHtml + '</td>' +
|
|
1174
|
+
'<td style="min-width:200px"><code>' + escapeHtml(v.name) + '</code>' + (v.secret ? ' <span class="le-pill neutral">secret</span>' : '') + alsoDeclared +
|
|
1175
|
+
'<div class="hint">' + escapeHtml(ENV_SCOPE_LABELS[v.scope] || '') + '</div>' +
|
|
1176
|
+
(description ? '<div class="hint env-desc" title="' + escapeHtml(description) + '">' + escapeHtml(description) + '</div>' : '') + '</td>' +
|
|
1177
|
+
'<td>' + (shared ? renderEnvSlotCell(shared) : '<span class="hint">not used</span>') + '</td>' +
|
|
1178
|
+
'<td>' + (own ? renderEnvSlotCell(own) : '<span class="hint">—</span>') + '</td>' +
|
|
1179
|
+
'<td>' + used + '</td>' +
|
|
1233
1180
|
'</tr>';
|
|
1234
1181
|
}
|
|
1235
1182
|
|
|
1236
|
-
// One card per ability, matching the same .source/.source-head look
|
|
1237
|
-
// Gateway Tools sources already use — so an operator scanning the tab
|
|
1238
|
-
// sees, per ability, which env vars it declared, instead of hunting
|
|
1239
|
-
// through one long flat table for rows that belong to whichever
|
|
1240
|
-
// ability they're actually trying to set up right now. Deliberately no
|
|
1241
|
-
// "not set" count here (or in renderEnvConfigHtml's own summary) — a
|
|
1242
|
-
// declared var not being set doesn't mean anything is broken, since
|
|
1243
|
-
// plenty of them have working defaults (see each var's own
|
|
1244
|
-
// description) and are only ever declared so an operator *can*
|
|
1245
|
-
// override them, not because every single one is required.
|
|
1246
1183
|
function renderEnvAbilityCard(abilityName, vars) {
|
|
1247
|
-
var
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1184
|
+
var names = [];
|
|
1185
|
+
var bySlot = {};
|
|
1186
|
+
vars.forEach(function (v) {
|
|
1187
|
+
if (!bySlot[v.name]) { bySlot[v.name] = {}; names.push(v.name); }
|
|
1188
|
+
bySlot[v.name][v.slot] = v;
|
|
1189
|
+
});
|
|
1190
|
+
var rows = names.map(function (n) { return renderEnvSettingRow(bySlot[n].shared, bySlot[n].agent); }).join('');
|
|
1191
|
+
return '<section><h3>' + escapeHtml(abilityName) + '</h3>' +
|
|
1192
|
+
'<div class="le-table-wrap"><table><thead><tr><th>Setting</th><th>Project-wide</th><th>This agent</th><th>Used</th></tr></thead><tbody>' + rows + '</tbody></table></div>' +
|
|
1193
|
+
'</section>';
|
|
1252
1194
|
}
|
|
1253
1195
|
|
|
1254
1196
|
function renderEnvConfigHtml(vars) {
|
|
1255
1197
|
if (!vars.length) return '<p class="hint">No installed ability has declared any environment variables for this agent yet.</p>';
|
|
1256
1198
|
|
|
1257
|
-
var summaryHtml = '<p class="
|
|
1199
|
+
var summaryHtml = '<p class="muted" style="margin:0 0 20px">Settings your installed abilities read. A secret is never shown again after it is saved. Project-wide values live in the project <code>.env</code>; this agent's own values in <code>agents/<name>/.env</code>.</p>';
|
|
1258
1200
|
|
|
1259
1201
|
// Grouped by declaring ability, not a single flat list — a var
|
|
1260
1202
|
// declared by more than one ability (rare, but see renderEnvRow's
|
|
@@ -1904,14 +1846,15 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1904
1846
|
// describeGatewayTools), a real cost not worth paying up front. ----
|
|
1905
1847
|
|
|
1906
1848
|
function renderToolsTabHtml(cfg) {
|
|
1907
|
-
return '<
|
|
1849
|
+
return '<p class="muted" style="margin:0 0 20px">Everything this agent can call. <strong>Runs</strong> comes from Permissions: allow runs on its own, ask waits for a person, deny never runs.</p>' +
|
|
1850
|
+
'<section><h3>Your tools (' + cfg.localTools.length + ')</h3>' + renderLocalTools(cfg.localTools) +
|
|
1908
1851
|
'<div class="admin-subsection"><h4 id="httpToolFormHeading">Create an HTTP tool</h4>' + renderHttpToolFormHtml() + '</div>' +
|
|
1909
1852
|
'</section>' +
|
|
1910
1853
|
'<section id="gatewayToolsSection">' +
|
|
1911
|
-
'<h3>
|
|
1854
|
+
'<h3>From integrations <button type="button" id="gatewayToolsRefreshBtn" class="hint-btn">Refresh</button></h3>' +
|
|
1912
1855
|
'<div id="gatewayToolsContent"><p class="hint">Loading…</p></div>' +
|
|
1913
1856
|
'</section>' +
|
|
1914
|
-
'<section><h3>
|
|
1857
|
+
'<section><h3>Subagents (' + cfg.agentAsTools.length + ')</h3>' + renderTools(cfg.agentAsTools) + '</section>';
|
|
1915
1858
|
}
|
|
1916
1859
|
|
|
1917
1860
|
// ---- Create an HTTP tool: generates a real agents/:name/tools/<tool>.ts
|
|
@@ -2480,12 +2423,17 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2480
2423
|
|
|
2481
2424
|
// ---- Tab switching + agent selection ----
|
|
2482
2425
|
|
|
2426
|
+
var CONFIG_TABS = ['overview', 'tools', 'skills', 'abilities', 'actauth', 'env'];
|
|
2427
|
+
|
|
2483
2428
|
function switchTab(tab) {
|
|
2429
|
+
if (CONFIG_TABS.indexOf(tab) === -1) tab = 'overview';
|
|
2484
2430
|
currentTab = tab;
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2431
|
+
leShell.setTab(tab);
|
|
2432
|
+
// Same URL a fresh load of this tab would have, so reload, back/forward
|
|
2433
|
+
// and a copied link all land here — without a navigation per click.
|
|
2434
|
+
var url = new URL(location.href);
|
|
2435
|
+
if (tab === 'overview') url.searchParams.delete('tab'); else url.searchParams.set('tab', tab);
|
|
2436
|
+
history.replaceState(null, '', url.pathname + url.search);
|
|
2489
2437
|
var panels = detail.querySelectorAll('.tab-panel');
|
|
2490
2438
|
for (var i = 0; i < panels.length; i++) {
|
|
2491
2439
|
panels[i].style.display = panels[i].dataset.tabPanel === tab ? 'block' : 'none';
|
|
@@ -2576,30 +2524,29 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2576
2524
|
.catch(function () {});
|
|
2577
2525
|
}
|
|
2578
2526
|
|
|
2527
|
+
function modelChip(cfg) {
|
|
2528
|
+
if (typeof cfg.model === 'string') return { text: cfg.model, mono: true };
|
|
2529
|
+
return { text: cfg.model.provider + ' \u00b7 ' + (cfg.model.model || 'default model'), mono: true };
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2579
2532
|
function renderDetail(cfg) {
|
|
2580
2533
|
currentCfg = cfg;
|
|
2581
2534
|
detail.innerHTML =
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
'
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
'<button class="tab" data-tab="env">Environment</button>' +
|
|
2591
|
-
'</div>' +
|
|
2535
|
+
leShell.renderHead({
|
|
2536
|
+
name: cfg.name,
|
|
2537
|
+
prompt: cfg.systemPrompt,
|
|
2538
|
+
chips: [modelChip(cfg), { text: 'unmatched calls: ' + cfg.permissions.defaultDecision }],
|
|
2539
|
+
tab: currentTab,
|
|
2540
|
+
counts: { tools: cfg.tools.length, skills: cfg.skills.length, actauth: cfg.permissions.rules.length },
|
|
2541
|
+
}) +
|
|
2542
|
+
'<div class="le-scroll"><div class="le-page" style="display:block">' +
|
|
2592
2543
|
'<div class="tab-panel" data-tab-panel="overview">' + renderOverviewHtml(cfg) + '</div>' +
|
|
2593
2544
|
'<div class="tab-panel" data-tab-panel="skills">' + renderSkillsTabHtml(cfg) + '</div>' +
|
|
2594
2545
|
'<div class="tab-panel" data-tab-panel="tools">' + renderToolsTabHtml(cfg) + '</div>' +
|
|
2595
2546
|
'<div class="tab-panel" data-tab-panel="actauth">' + renderActauthTabPlaceholder() + '</div>' +
|
|
2596
2547
|
'<div class="tab-panel" data-tab-panel="env">' + renderEnvTabPlaceholder() + '</div>' +
|
|
2597
|
-
'<div class="tab-panel" data-tab-panel="abilities">' + renderAbilitiesTabPlaceholder() + '</div>'
|
|
2598
|
-
|
|
2599
|
-
var buttons = detail.querySelectorAll('.tabs button');
|
|
2600
|
-
for (var i = 0; i < buttons.length; i++) {
|
|
2601
|
-
buttons[i].addEventListener('click', function (ev) { switchTab(ev.currentTarget.dataset.tab); });
|
|
2602
|
-
}
|
|
2548
|
+
'<div class="tab-panel" data-tab-panel="abilities">' + renderAbilitiesTabPlaceholder() + '</div>' +
|
|
2549
|
+
'</div></div>';
|
|
2603
2550
|
|
|
2604
2551
|
wireSkillsHandlers(cfg.name);
|
|
2605
2552
|
wireOverviewHandlers(cfg.name);
|
|
@@ -2610,7 +2557,7 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2610
2557
|
}
|
|
2611
2558
|
|
|
2612
2559
|
empty.style.display = 'none';
|
|
2613
|
-
detail.style.display = '
|
|
2560
|
+
detail.style.display = 'flex';
|
|
2614
2561
|
switchTab(currentTab);
|
|
2615
2562
|
}
|
|
2616
2563
|
|
|
@@ -2620,13 +2567,10 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2620
2567
|
actauthLoadedFor = null;
|
|
2621
2568
|
envLoadedFor = null;
|
|
2622
2569
|
abilitiesLoadedFor = null;
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
items[i].classList.toggle('active', items[i].dataset.name === name);
|
|
2626
|
-
}
|
|
2627
|
-
detail.innerHTML = '<p class="muted">Loading…</p>';
|
|
2570
|
+
leShell.setActive(name);
|
|
2571
|
+
detail.innerHTML = '<p class="muted" style="padding:24px">Loading…</p>';
|
|
2628
2572
|
empty.style.display = 'none';
|
|
2629
|
-
detail.style.display = '
|
|
2573
|
+
detail.style.display = 'flex';
|
|
2630
2574
|
fetch('/agents/' + encodeURIComponent(name) + '/config')
|
|
2631
2575
|
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
2632
2576
|
.then(function (result) {
|
|
@@ -2647,37 +2591,27 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
2647
2591
|
});
|
|
2648
2592
|
}
|
|
2649
2593
|
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
}
|
|
2672
|
-
if (agents.length) {
|
|
2673
|
-
var requested = new URLSearchParams(location.search).get('agent');
|
|
2674
|
-
var initial = agents.some(function (a) { return a.name === requested; }) ? requested : agents[0].name;
|
|
2675
|
-
selectAgent(initial);
|
|
2676
|
-
}
|
|
2677
|
-
})
|
|
2678
|
-
.catch(function (err) {
|
|
2679
|
-
empty.textContent = 'Could not load agents: ' + err.message;
|
|
2680
|
-
});
|
|
2594
|
+
// Chat is a real link to the playground; every other tab, and every
|
|
2595
|
+
// Overview card pointing at one, switches in place. Delegated once
|
|
2596
|
+
// here because Overview's cards are re-rendered after every edit.
|
|
2597
|
+
detail.addEventListener('click', function (ev) {
|
|
2598
|
+
var link = ev.target.closest('a[data-tab]');
|
|
2599
|
+
if (!link || link.dataset.tab === 'chat' || ev.metaKey || ev.ctrlKey || ev.shiftKey) return;
|
|
2600
|
+
ev.preventDefault();
|
|
2601
|
+
switchTab(link.dataset.tab);
|
|
2602
|
+
});
|
|
2603
|
+
|
|
2604
|
+
var initialParams = new URLSearchParams(location.search);
|
|
2605
|
+
currentTab = CONFIG_TABS.indexOf(initialParams.get('tab')) === -1 ? 'overview' : initialParams.get('tab');
|
|
2606
|
+
leShell.ready.then(function (agents) {
|
|
2607
|
+
if (!agents.length) {
|
|
2608
|
+
empty.innerHTML = 'No agents yet. <a href="/agents?new=1">Create one</a> or add a folder under agents/.';
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
var requested = initialParams.get('agent');
|
|
2612
|
+
var initial = agents.some(function (a) { return a.name === requested; }) ? requested : agents[0].name;
|
|
2613
|
+
selectAgent(initial);
|
|
2614
|
+
});
|
|
2681
2615
|
})();
|
|
2682
2616
|
</script>
|
|
2683
2617
|
</body>
|