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.
@@ -16,57 +16,55 @@
16
16
  // outer one. Plain string concatenation sidesteps that class of bug
17
17
  // entirely rather than relying on getting every escape right.
18
18
  import { devUiCss } from './dev-ui-styles.js';
19
+ import { consoleShellScript, consoleSidebarHtml } from './console-shell.js';
19
20
  export const playgroundHtml = `<!doctype html>
20
21
  <html lang="en">
21
22
  <head>
22
23
  <meta charset="utf-8">
23
24
  <meta name="viewport" content="width=device-width, initial-scale=1">
24
- <title>LoopEngine Playground</title>
25
+ <title>LoopEngine Chat</title>
25
26
  <style>${devUiCss}
26
- body {
27
- display: flex;
28
- flex-direction: column;
29
- height: 100vh;
30
- }
31
- header {
32
- padding: 10px 16px;
33
- border-bottom: 1px solid light-dark(#ddd, #333);
27
+ /* The Chat tab of the agent console (see web/console-shell.ts): a
28
+ toolbar, then History | conversation | Run trace. History and Run
29
+ trace can each be hidden (.no-history / .no-trace on .chat-body). */
30
+ .chat-toolbar {
34
31
  display: flex;
35
32
  align-items: center;
36
- gap: 12px;
33
+ gap: 8px;
37
34
  flex-wrap: wrap;
35
+ padding: 8px 24px;
36
+ border-bottom: 1px solid var(--line);
38
37
  }
39
- header h1 { font-size: 15px; margin: 0; font-weight: 600; }
40
- #agentCaption {
41
- font-size: 12px;
42
- color: light-dark(#666, #999);
43
- max-width: 360px;
44
- overflow: hidden;
45
- text-overflow: ellipsis;
46
- white-space: nowrap;
47
- }
48
- #configLink { font-size: 12px; white-space: nowrap; text-decoration: none; }
38
+ .chat-toolbar button { padding: 4px 10px; font-size: 12.5px; }
39
+ .chat-toolbar button[aria-pressed="true"] { background: var(--accent-soft); border-color: var(--accent-soft); }
49
40
  #sessionLabel {
50
41
  margin-left: auto;
51
42
  font-size: 12px;
52
- font-family: ui-monospace, monospace;
53
- color: light-dark(#666, #999);
43
+ font-family: var(--font-mono);
44
+ color: var(--ink-faint);
54
45
  }
55
46
  #sessionLabel.copyable { cursor: pointer; }
56
47
  #sessionLabel.copyable:hover { text-decoration: underline; }
57
- main {
48
+ .chat-body {
58
49
  flex: 1;
59
- display: flex;
50
+ display: grid;
51
+ grid-template-columns: 220px minmax(0, 1fr) 340px;
60
52
  min-height: 0;
61
53
  }
54
+ .chat-body.no-history { grid-template-columns: minmax(0, 1fr) 340px; }
55
+ .chat-body.no-trace { grid-template-columns: 220px minmax(0, 1fr); }
56
+ .chat-body.no-history.no-trace { grid-template-columns: minmax(0, 1fr); }
57
+ .chat-body.no-history .sessions-pane, .chat-body.no-trace .trace-pane { display: none; }
62
58
  .pane {
63
- flex: 1;
64
59
  display: flex;
65
60
  flex-direction: column;
66
61
  min-width: 0;
67
- border-right: 1px solid light-dark(#ddd, #333);
62
+ min-height: 0;
63
+ border-right: 1px solid var(--line);
68
64
  }
69
65
  .pane:last-child { border-right: none; }
66
+ .sessions-pane, .trace-pane { background: var(--surface); }
67
+ .sessions-pane .pane-body { padding: 6px; gap: 2px; }
70
68
  .sessions-pane { flex: 0 0 200px; }
71
69
  .sessions-pane .pane-body { padding: 6px; gap: 2px; }
72
70
  .session-item {
@@ -77,8 +75,8 @@ export const playgroundHtml = `<!doctype html>
77
75
  padding: 6px 8px;
78
76
  cursor: pointer;
79
77
  }
80
- .session-item:hover { background: light-dark(#f0f0f1, #26262b); }
81
- .session-item.active { background: light-dark(#dbeafe, #1e3a5f); }
78
+ .session-item:hover { background: var(--surface); }
79
+ .session-item.active { background: var(--accent-soft); }
82
80
  .session-item .preview {
83
81
  flex: 1;
84
82
  min-width: 0;
@@ -87,7 +85,7 @@ export const playgroundHtml = `<!doctype html>
87
85
  text-overflow: ellipsis;
88
86
  white-space: nowrap;
89
87
  }
90
- .session-item .session-time { font-size: 10px; color: light-dark(#999, #777); flex-shrink: 0; }
88
+ .session-item .session-time { font-size: 10px; color: var(--ink-faint); flex-shrink: 0; }
91
89
  .session-item .session-remove {
92
90
  flex-shrink: 0;
93
91
  border: none;
@@ -95,61 +93,60 @@ export const playgroundHtml = `<!doctype html>
95
93
  padding: 0 2px;
96
94
  font-size: 13px;
97
95
  line-height: 1;
98
- color: light-dark(#999, #777);
96
+ color: var(--ink-faint);
99
97
  display: none;
100
98
  }
101
99
  .session-item:hover .session-remove { display: block; }
102
- .session-item .session-remove:hover { color: light-dark(#991b1b, #f87171); }
100
+ .session-item .session-remove:hover { color: var(--bad); }
103
101
  .pane h2 {
104
- font-size: 12px;
105
- text-transform: uppercase;
106
- letter-spacing: 0.04em;
107
- color: light-dark(#666, #999);
102
+ font-size: 13px;
103
+ font-weight: 500;
104
+ color: var(--ink);
108
105
  margin: 0;
109
- padding: 8px 12px;
110
- border-bottom: 1px solid light-dark(#eee, #2a2a2e);
106
+ padding: 10px 14px;
107
+ border-bottom: 1px solid var(--line);
111
108
  }
112
109
  .pane-body {
113
110
  flex: 1;
114
111
  overflow-y: auto;
115
- padding: 10px 12px;
112
+ padding: 12px 14px;
116
113
  display: flex;
117
114
  flex-direction: column;
118
- gap: 8px;
115
+ gap: 10px;
119
116
  }
120
- .msg { max-width: 90%; }
117
+ .chat-pane .pane-body { padding: 20px 24px; gap: 14px; }
118
+ .msg { max-width: min(72ch, 92%); }
121
119
  .msg-label {
122
- font-size: 10px;
123
- text-transform: uppercase;
124
- color: light-dark(#999, #777);
125
- margin-bottom: 2px;
120
+ font-size: 12px;
121
+ color: var(--ink-faint);
122
+ margin-bottom: 3px;
126
123
  }
127
124
  .msg-body {
128
- padding: 8px 10px;
129
- border-radius: 8px;
125
+ padding: 10px 14px;
126
+ border-radius: 10px;
130
127
  white-space: pre-wrap;
131
128
  word-break: break-word;
132
129
  }
133
130
  .msg-user { align-self: flex-end; }
134
- .msg-user .msg-body { background: light-dark(#dbeafe, #1e3a5f); }
135
- .msg-assistant .msg-body { background: light-dark(#fff, #2a2a2e); border: 1px solid light-dark(#ddd, #3a3a3e); }
136
- .msg-error .msg-body { background: light-dark(#fee2e2, #4a1f1f); color: light-dark(#991b1b, #f87171); }
137
- .msg-stopped .msg-body { font-style: italic; color: light-dark(#666, #999); background: light-dark(#f3f3f4, #26262b); }
131
+ .msg-user .msg-body { background: var(--accent-soft); }
132
+ .msg-assistant .msg-body { background: var(--surface); }
133
+ .msg-error .msg-body { background: var(--bad-soft); color: var(--bad); }
134
+ .msg-stopped .msg-body { font-style: italic; color: var(--ink-muted); background: var(--surface); }
138
135
  .msg-thinking .msg-body { display: inline-flex; gap: 4px; padding: 11px 10px; }
139
136
  .msg-approval .msg-body {
140
- background: light-dark(#fefce8, #422006);
141
- border: 1px solid light-dark(#eab308, #a16207);
137
+ background: var(--bg);
138
+ border: 1px solid var(--warn);
142
139
  display: flex;
143
140
  flex-direction: column;
144
141
  gap: 6px;
145
142
  }
146
143
  .approval-tool { font-family: ui-monospace, monospace; font-weight: 600; }
147
- .approval-scope { font-size: 11px; color: light-dark(#666, #999); }
144
+ .approval-scope { font-size: 11px; color: var(--ink-muted); }
148
145
  .approval-reason { font-size: 12px; }
149
146
  .approval-args {
150
147
  font-size: 11px;
151
148
  margin: 0;
152
- background: light-dark(#fff, #26262b);
149
+ background: var(--bg);
153
150
  border-radius: 6px;
154
151
  padding: 6px 8px;
155
152
  overflow-x: auto;
@@ -158,12 +155,12 @@ export const playgroundHtml = `<!doctype html>
158
155
  }
159
156
  .approval-actions { display: flex; gap: 8px; }
160
157
  .approval-actions button { font-size: 12px; padding: 5px 12px; }
161
- .approval-actions .approve { background: light-dark(#dcfce7, #14532d); }
162
- .approval-actions .deny { background: light-dark(#fee2e2, #450a0a); }
163
- .approval-status { font-size: 12px; font-style: italic; color: light-dark(#666, #999); }
158
+ .approval-actions .approve { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
159
+ .approval-actions .deny { color: var(--bad); }
160
+ .approval-status { font-size: 12px; font-style: italic; color: var(--ink-muted); }
164
161
  .msg-question .msg-body {
165
- background: light-dark(#eff6ff, #1e293b);
166
- border: 1px solid light-dark(#3b82f6, #60a5fa);
162
+ background: var(--accent-soft);
163
+ border: 1px solid var(--accent);
167
164
  display: flex;
168
165
  flex-direction: column;
169
166
  gap: 6px;
@@ -174,7 +171,7 @@ export const playgroundHtml = `<!doctype html>
174
171
  .question-answer-row { display: flex; gap: 6px; }
175
172
  .question-answer-row input { flex: 1; font-size: 12px; }
176
173
  .question-answer-row button { font-size: 12px; padding: 5px 12px; }
177
- .question-status { font-size: 12px; font-style: italic; color: light-dark(#666, #999); }
174
+ .question-status { font-size: 12px; font-style: italic; color: var(--ink-muted); }
178
175
  /* An assistant message's rendered markdown (renderMessageMarkdown) —
179
176
  tight margins since .msg-body already pads the bubble; a nested
180
177
  paragraph/heading with normal margins would double up on that. */
@@ -182,10 +179,10 @@ export const playgroundHtml = `<!doctype html>
182
179
  .msg-body p:first-child { margin-top: 0; }
183
180
  .msg-body p:last-child { margin-bottom: 0; }
184
181
  .msg-body h1, .msg-body h2, .msg-body h3, .msg-body h4 { margin: 10px 0 4px; font-size: 14px; }
185
- .msg-body code { font-family: ui-monospace, monospace; font-size: 12px; background: light-dark(#f3f3f4, #333); padding: 1px 4px; border-radius: 4px; }
186
- .msg-body pre { background: light-dark(#f3f3f4, #26262b); border-radius: 6px; padding: 8px; overflow-x: auto; }
182
+ .msg-body code { font-family: ui-monospace, monospace; font-size: 12px; background: var(--line); padding: 1px 4px; border-radius: 4px; }
183
+ .msg-body pre { background: var(--surface); border-radius: 6px; padding: 8px; overflow-x: auto; }
187
184
  .msg-body pre code { background: none; padding: 0; }
188
- .msg-body a { color: light-dark(#2563eb, #60a5fa); }
185
+ .msg-body a { color: var(--accent); }
189
186
  /* An image immediately followed by a download link (see
190
187
  renderMessageMarkdown's own doc comment) merges into this — full
191
188
  bubble width, a corner download icon that's a plain <a href> (a real
@@ -269,7 +266,7 @@ export const playgroundHtml = `<!doctype html>
269
266
  width: 6px;
270
267
  height: 6px;
271
268
  border-radius: 50%;
272
- background: light-dark(#999, #777);
269
+ background: var(--ink-faint);
273
270
  animation: dot-blink 1.4s infinite both;
274
271
  }
275
272
  .dot:nth-child(2) { animation-delay: 0.2s; }
@@ -277,7 +274,7 @@ export const playgroundHtml = `<!doctype html>
277
274
  @keyframes dot-blink { 0%, 80%, 100% { opacity: 0.2; } 40% { opacity: 1; } }
278
275
  .empty-hint { padding: 10px 2px; }
279
276
  .event {
280
- border-left: 3px solid light-dark(#ccc, #444);
277
+ border-left: 3px solid var(--line);
281
278
  padding: 4px 8px;
282
279
  font-size: 12px;
283
280
  }
@@ -288,7 +285,7 @@ export const playgroundHtml = `<!doctype html>
288
285
  font-size: 11px;
289
286
  white-space: pre-wrap;
290
287
  word-break: break-word;
291
- color: light-dark(#444, #aaa);
288
+ color: var(--ink-muted);
292
289
  }
293
290
  .event-actauth { border-left-color: #f59e0b; }
294
291
  .event-toollane { border-left-color: #3b82f6; }
@@ -299,54 +296,60 @@ export const playgroundHtml = `<!doctype html>
299
296
  .event-approval { border-left-color: #eab308; }
300
297
  .event-question { border-left-color: #3b82f6; }
301
298
  .composer {
302
- border-top: 1px solid light-dark(#ddd, #333);
303
- padding: 10px 12px;
299
+ border-top: 1px solid var(--line);
300
+ padding: 12px 24px 16px;
304
301
  display: flex;
305
302
  flex-direction: column;
306
303
  gap: 8px;
307
304
  }
308
- .send-row { display: flex; gap: 8px; }
309
- #messageInput { flex: 1; resize: vertical; height: 90px; }
305
+ .send-row { display: flex; gap: 8px; align-items: flex-end; }
306
+ #messageInput { flex: 1; min-width: 0; resize: vertical; height: 64px; border-radius: var(--radius); padding: 10px 12px; }
307
+ #sendButton { background: var(--accent); border-color: var(--accent); color: var(--on-accent); padding: 8px 16px; }
310
308
  #messageInput:disabled, #sendButton:disabled { cursor: not-allowed; }
311
- details summary { cursor: pointer; font-size: 12px; color: light-dark(#666, #999); }
309
+ details summary { cursor: pointer; font-size: 12px; color: var(--ink-muted); }
312
310
  .advanced-hint { margin: 6px 0 0; font-size: 11px; }
313
311
  .advanced-fields { display: flex; gap: 8px; margin-top: 6px; }
314
312
  .advanced-fields > div { flex: 1; display: flex; flex-direction: column; gap: 4px; }
315
- .advanced-fields label { font-size: 11px; color: light-dark(#666, #999); }
313
+ .advanced-fields label { font-size: 11px; color: var(--ink-muted); }
316
314
  .advanced-fields textarea { height: 50px; font-family: ui-monospace, monospace; font-size: 11px; resize: vertical; }
317
- @media (max-width: 720px) {
318
- main { flex-direction: column; }
319
- .pane { border-right: none; border-bottom: 1px solid light-dark(#ddd, #333); min-height: 160px; }
320
- .pane:last-child { border-bottom: none; }
321
- .sessions-pane { flex-basis: auto; }
315
+ @media (max-width: 860px) {
316
+ .chat-toolbar { padding: 8px 16px; }
317
+ .chat-body, .chat-body.no-history, .chat-body.no-trace { grid-template-columns: minmax(0, 1fr); grid-auto-rows: minmax(0, auto); overflow-y: auto; }
318
+ .chat-body .pane { border-right: none; border-bottom: 1px solid var(--line); }
319
+ .chat-pane { min-height: 60vh; }
320
+ .sessions-pane, .trace-pane { max-height: 40vh; }
321
+ .chat-pane .pane-body, .composer { padding-left: 16px; padding-right: 16px; }
322
322
  }
323
323
  </style>
324
324
  </head>
325
- <body>
326
- <nav class="topnav">
327
- <a href="/agents">Agents</a>
328
- <a href="/agents/config">Config</a>
329
- <a href="/playground" class="active">Playground</a>
330
- </nav>
331
- <header>
332
- <h1>LoopEngine Playground</h1>
333
- <select id="agentSelect"></select>
334
- <span id="agentCaption"></span>
335
- <button id="newConversationButton" type="button">New conversation</button>
336
- <a id="configLink" href="/agents/config">Agent config &rarr;</a>
337
- <span id="sessionLabel">session: (new)</span>
338
- </header>
339
- <main>
325
+ <body data-le-page="chat">
326
+ <div class="le-app">
327
+ ${consoleSidebarHtml}
328
+ <main class="le-main">
329
+ <div id="leHead"></div>
330
+ <div class="chat-toolbar">
331
+ <select id="agentSelect" hidden aria-label="Agent"></select>
332
+ <span id="agentCaption" hidden></span>
333
+ <a id="configLink" href="/agents/config" hidden>Agent config</a>
334
+ <button id="newConversationButton" type="button">New conversation</button>
335
+ <button id="historyToggle" type="button" aria-pressed="true">History</button>
336
+ <button id="traceToggle" type="button" aria-pressed="true">Run trace</button>
337
+ <span id="sessionLabel">session: (new)</span>
338
+ </div>
339
+ <div class="chat-body" id="chatBody">
340
340
  <section class="pane sessions-pane">
341
- <h2>Sessions</h2>
341
+ <h2>History</h2>
342
342
  <div class="pane-body" id="sessionsPane"></div>
343
343
  </section>
344
- <section class="pane">
345
- <h2>Chat</h2>
344
+ <section class="pane chat-pane" aria-label="Conversation">
346
345
  <div class="pane-body" id="chatPane"></div>
347
346
  <div class="composer">
347
+ <div class="send-row">
348
+ <textarea id="messageInput" placeholder="Message&hellip; (Enter to send, Shift+Enter for a new line)"></textarea>
349
+ <button id="sendButton" type="button">Send</button>
350
+ </div>
348
351
  <details>
349
- <summary>Advanced</summary>
352
+ <summary>Request options</summary>
350
353
  <p class="advanced-hint muted">For agents whose sessionIdFor/tenantFor need something beyond a plain sessionId — e.g. customer-service reads customerEmail from the body.</p>
351
354
  <div class="advanced-fields">
352
355
  <div>
@@ -359,17 +362,16 @@ export const playgroundHtml = `<!doctype html>
359
362
  </div>
360
363
  </div>
361
364
  </details>
362
- <div class="send-row">
363
- <textarea id="messageInput" placeholder="Message&hellip; (Enter to send, Shift+Enter for a new line)"></textarea>
364
- <button id="sendButton" type="button">Send</button>
365
- </div>
366
365
  </div>
367
366
  </section>
368
- <section class="pane">
369
- <h2>Loop events</h2>
367
+ <section class="pane trace-pane">
368
+ <h2>Run trace</h2>
370
369
  <div class="pane-body" id="timelinePane"></div>
371
370
  </section>
371
+ </div>
372
372
  </main>
373
+ </div>
374
+ ${consoleShellScript}
373
375
  <div class="lightbox-overlay" id="lightboxOverlay">
374
376
  <div class="lightbox-inner">
375
377
  <button type="button" class="lightbox-close" id="lightboxClose" aria-label="Close">&times;</button>
@@ -1133,7 +1135,7 @@ export const playgroundHtml = `<!doctype html>
1133
1135
  var list = loadSavedSessions().filter(function (s) { return s.agent === agent; });
1134
1136
  sessionsPane.textContent = '';
1135
1137
  if (!list.length) {
1136
- setEmptyHint(sessionsPane, 'No saved sessions yet.');
1138
+ setEmptyHint(sessionsPane, 'Past conversations with this agent appear here.');
1137
1139
  return;
1138
1140
  }
1139
1141
  for (var i = 0; i < list.length; i++) {
@@ -1243,7 +1245,7 @@ export const playgroundHtml = `<!doctype html>
1243
1245
  chatPane.textContent = '';
1244
1246
  toolCallCardsById = {};
1245
1247
  knownUrlsByBase = {};
1246
- setEmptyHint(timelinePane, 'Loop events (tool calls, permission checks, budget checks) will appear here as the agent runs.');
1248
+ setEmptyHint(timelinePane, 'Each step of a turn (model calls, tool calls, permission checks) appears here as the agent runs.');
1247
1249
  renderSessionsPane();
1248
1250
 
1249
1251
  fetch('/agents/' + encodeURIComponent(item.agent) + '/sessions/' + encodeURIComponent(item.sessionId))
@@ -1400,10 +1402,61 @@ export const playgroundHtml = `<!doctype html>
1400
1402
  sessionLabel.title = '';
1401
1403
  var name = agentSelect.value;
1402
1404
  setEmptyHint(chatPane, name ? 'No messages yet \\u2014 say hi to ' + name + '.' : 'No messages yet \\u2014 pick an agent above to get started.');
1403
- setEmptyHint(timelinePane, 'Loop events (tool calls, permission checks, budget checks) will appear here as the agent runs.');
1405
+ setEmptyHint(timelinePane, 'Each step of a turn (model calls, tool calls, permission checks) appears here as the agent runs.');
1404
1406
  renderSessionsPane();
1405
1407
  }
1406
1408
 
1409
+ // The console's agent header + tabs (web/console-shell.ts), with this
1410
+ // page as the Chat tab. Rendered from the /agents list right away, then
1411
+ // again once this agent's config arrives with its model and counts.
1412
+ function renderAgentHead() {
1413
+ var head = document.getElementById('leHead');
1414
+ var name = agentSelect.value;
1415
+ if (!name) {
1416
+ head.innerHTML = '<div class="le-head plain"><div class="le-title"><button type="button" class="le-menu-btn" data-le-menu aria-label="Open navigation">&#9776;</button>' +
1417
+ '<div><h1>Chat</h1><p>No agents yet. <a href="/agents?new=1">Create one</a> or add a folder under agents/.</p></div></div></div>';
1418
+ return;
1419
+ }
1420
+ leShell.setActive(name);
1421
+ var agent = currentAgents.filter(function (a) { return a.name === name; })[0] || {};
1422
+ function draw(cfg) {
1423
+ var chips = [];
1424
+ var counts = {};
1425
+ if (cfg) {
1426
+ chips.push({ text: typeof cfg.model === 'string' ? cfg.model : cfg.model.provider + ' \u00b7 ' + (cfg.model.model || 'default model'), mono: true });
1427
+ counts = { tools: cfg.tools.length, skills: cfg.skills.length, actauth: cfg.permissions.rules.length };
1428
+ }
1429
+ head.innerHTML = leShell.renderHead({ name: name, prompt: agent.systemPrompt, chips: chips, tab: 'chat', counts: counts });
1430
+ }
1431
+ draw(null);
1432
+ fetch('/agents/' + encodeURIComponent(name) + '/config')
1433
+ .then(function (r) { return r.ok ? r.json() : null; })
1434
+ .then(function (cfg) { if (cfg && agentSelect.value === name) draw(cfg); })
1435
+ .catch(function () {});
1436
+ }
1437
+
1438
+ // History and Run trace can each be hidden; remembered per browser.
1439
+ // Both start hidden on a narrow screen, where they'd stack above and
1440
+ // below the conversation.
1441
+ var chatBody = document.getElementById('chatBody');
1442
+ var historyToggle = document.getElementById('historyToggle');
1443
+ var traceToggle = document.getElementById('traceToggle');
1444
+ var panes = { history: window.innerWidth > 860, trace: window.innerWidth > 860 };
1445
+ try {
1446
+ var savedPanes = JSON.parse(localStorage.getItem('loopengine.chatPanes') || 'null');
1447
+ if (savedPanes && window.innerWidth > 860) panes = savedPanes;
1448
+ } catch (e) {}
1449
+ function applyPanes() {
1450
+ chatBody.classList.toggle('no-history', !panes.history);
1451
+ chatBody.classList.toggle('no-trace', !panes.trace);
1452
+ historyToggle.setAttribute('aria-pressed', String(panes.history));
1453
+ traceToggle.setAttribute('aria-pressed', String(panes.trace));
1454
+ try { localStorage.setItem('loopengine.chatPanes', JSON.stringify(panes)); } catch (e) {}
1455
+ }
1456
+ historyToggle.addEventListener('click', function () { panes.history = !panes.history; applyPanes(); });
1457
+ traceToggle.addEventListener('click', function () { panes.trace = !panes.trace; applyPanes(); });
1458
+ applyPanes();
1459
+
1407
1460
  function loadAgents() {
1408
1461
  fetch('/agents')
1409
1462
  .then(function (r) { return r.json(); })
@@ -1429,12 +1482,14 @@ export const playgroundHtml = `<!doctype html>
1429
1482
  ? 'Message\\u2026 (Enter to send, Shift+Enter for a new line)'
1430
1483
  : 'No agents registered';
1431
1484
  updateCaption();
1485
+ renderAgentHead();
1432
1486
  resetConversation();
1433
1487
  updateSendButtonState();
1434
1488
  if (hasAgents) messageInput.focus();
1435
1489
  })
1436
1490
  .catch(function (err) {
1437
1491
  agentCaption.textContent = 'Could not load agents: ' + err.message;
1492
+ renderAgentHead();
1438
1493
  resetConversation();
1439
1494
  });
1440
1495
  }
@@ -1622,6 +1677,7 @@ export const playgroundHtml = `<!doctype html>
1622
1677
 
1623
1678
  agentSelect.addEventListener('change', function () {
1624
1679
  updateCaption();
1680
+ renderAgentHead();
1625
1681
  resetConversation();
1626
1682
  updateSendButtonState();
1627
1683
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.62",
3
+ "version": "0.1.64",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",
@@ -38,6 +38,12 @@
38
38
  "dist/core/agent-config.d.ts",
39
39
  "dist/core/agent-env.js",
40
40
  "dist/core/agent-env.d.ts",
41
+ "dist/core/secret-store.js",
42
+ "dist/core/secret-store.d.ts",
43
+ "dist/core/trusted-proxy.js",
44
+ "dist/core/trusted-proxy.d.ts",
45
+ "dist/core/usage.js",
46
+ "dist/core/usage.d.ts",
41
47
  "dist/core/session-store.js",
42
48
  "dist/core/session-store.d.ts",
43
49
  "dist/core/vector-index.js",
@@ -1,25 +0,0 @@
1
- ---
2
- name: composio-large-outputs
3
- description: How to retrieve a gateway tool's real output when its result says storedInFile is true instead of returning the data inline.
4
- ---
5
-
6
- # Gateway tools with a large result
7
-
8
- Some gateway tools (Composio-sourced ones in particular) return a small
9
- pointer object instead of their real output when the actual result is
10
- too large to return inline:
11
-
12
- ```
13
- {
14
- "successful": true,
15
- "storedInFile": true,
16
- "tokenCount": 10234,
17
- "outputFilePath": "/tmp/.../SOME_TOOL_OUTPUT_xxxxx.json"
18
- }
19
- ```
20
-
21
- When you see `storedInFile: true` in a tool result, the real data is
22
- **not** in that object — it's in the file at `outputFilePath`. Call
23
- `system_read_file` with that exact path to retrieve the actual content
24
- before answering. Don't guess at an answer, summarize from the pointer
25
- alone, or claim you don't have the data — read the file first.