| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <pstm-uri>/tasks/{task_id}/terminal</pstm-uri>
- <pstm-template>main</pstm-template>
- <script>
- if (window.statum) {
- (function () {
- 'use strict';
- var connected = false;
- statum.addStateListener('terminal', function () {
- var state = statum.read('terminal');
- if (!connected && state && state.exists && state.session_id > 0) {
- if (window.initQuoteComments && !state.has_raw_log) {
- window.initQuoteComments({
- matches: function (node) {
- return node.closest && node.closest('#term-log') != null;
- },
- action: function () { return state.comment; }
- });
- }
- connect(state.session_id, !!state.has_raw_log);
- }
- });
- function connect(sessionId, rawMode) {
- connected = true;
- var log = document.getElementById(rawMode ? 'term-raw' : 'term-log');
- var source = new EventSource('/terminal-stream/' + sessionId);
- var lastId = 0;
- var lastSeq = 0;
- source.onmessage = function (event) {
- if (event.data === 'ping') return;
- var entry = null;
- try { entry = JSON.parse(event.data); } catch (e) { return; }
- if (!entry) return;
- if (rawMode) {
- // Raw PTY log: one unstyled <pre> of concatenated
- // chunks, byte-exact as the daemon produced them.
- if (entry.type !== 'raw' || entry.seq <= lastSeq) return;
- lastSeq = entry.seq;
- var ph = log.querySelector('.term-placeholder');
- if (ph) ph.remove();
- var near = log.scrollHeight - log.scrollTop - log.clientHeight < 48;
- log.appendChild(document.createTextNode(entry.text));
- if (near) log.scrollTop = log.scrollHeight;
- return;
- }
- if (entry.id <= lastId) return;
- lastId = entry.id;
- var placeholder = log.querySelector('.term-placeholder');
- if (placeholder) placeholder.remove();
- var nearBottom = log.scrollHeight - log.scrollTop - log.clientHeight < 48;
- appendEntry(log, entry);
- if (nearBottom) log.scrollTop = log.scrollHeight;
- };
- }
- function appendEntry(log, entry) {
- // Only render bash entries — llm-comment entries duplicate
- // the # lines already present in the bash command_text
- var type = entry.type || 'command';
- // Recoverable rejections (bash -n / max_tokens) — a
- // distinct non-fatal style; the agent retries on its own
- if (type === 'rejected') {
- var ph = log.querySelector('.term-placeholder');
- if (ph) ph.remove();
- if (entry.command && entry.command.length > 0) {
- var script = document.createElement('span');
- script.className = 'term-stdin';
- script.textContent = '$ ' + entry.command + '\n';
- log.appendChild(script);
- }
- var reason = document.createElement('span');
- reason.className = 'term-rejected';
- reason.textContent = '[rejected] ' + (entry.output || 'recoverable rejection') + '\n';
- log.appendChild(reason);
- log.appendChild(document.createTextNode('\n'));
- return;
- }
- if (type !== 'bash' && type !== 'command') return;
- // stdin: the LLM's response sent to bash — render per-line
- if (entry.command && entry.command.length > 0) {
- var lines = entry.command.split('\n');
- for (var i = 0; i < lines.length; i++) {
- var line = lines[i];
- if (line.length === 0) continue;
- var span = document.createElement('span');
- if (line.charAt(0) === '#') {
- span.className = 'term-think';
- span.textContent = line + '\n';
- } else {
- span.className = 'term-stdin';
- span.textContent = '$ ' + line + '\n';
- }
- log.appendChild(span);
- }
- }
- // piped input (saddle query)
- if (entry.input && entry.input.length > 0) {
- var inp = document.createElement('span');
- inp.className = 'term-stdin';
- inp.textContent = entry.input + '\n';
- log.appendChild(inp);
- }
- // stdout/stderr
- if (entry.output && entry.output.length > 0) {
- var out = document.createElement('span');
- out.className = (entry.exit_code !== 0 && entry.exit_code !== -1)
- ? 'term-stderr' : 'term-stdout';
- out.textContent = entry.output + '\n';
- log.appendChild(out);
- }
- log.appendChild(document.createTextNode('\n'));
- }
- })();
- }
- </script>
- </head>
- <body>
- <section>
- <h2>Terminal — <span stm-text="terminal.task_title">task</span></h2>
- <p stm-if="terminal != null && !terminal.exists" class="muted">Task not found.</p>
- <div stm-if="terminal != null && terminal.exists">
- <p><a stm-attribute.href="'/tasks/' + terminal.task_id" stm-text="'Back to task #' + terminal.task_id">task</a>
- · session <strong stm-text="terminal.session_id">id</strong></p>
- <p stm-if="terminal.sessions.length > 1">Sessions:
- <span stm-for-session-in="terminal.sessions">
- <a stm-if="session.selected"
- stm-attribute.href="'/tasks/' + terminal.task_id + '/terminal?session_id=' + session.id"
- stm-text="session.label">session</a>
- <a stm-if="!session.selected" class="muted"
- stm-attribute.href="'/tasks/' + terminal.task_id + '/terminal?session_id=' + session.id"
- stm-text="session.label">session</a>
- </span>
- </p>
- <pre class="terminal-plain terminal-raw" stm-if="terminal.has_raw_log" id="term-raw"><span class="muted term-placeholder">Connecting…</span></pre>
- <div class="terminal-plain" stm-if="terminal.sessions.length > 0 && !terminal.has_raw_log" id="term-log">
- <span class="muted term-placeholder">Connecting…</span>
- </div>
- <p stm-if="terminal.sessions.length == 0" class="muted">No sessions recorded for this task yet.</p>
- <dialog id="quote-comment-dialog">
- <h3>Comment on selected text</h3>
- <blockquote id="quote-text" class="term-comment-quote"></blockquote>
- <form stm-action="terminal.comment">
- <input type="hidden" name="quote">
- <input type="hidden" name="return_to">
- <textarea name="body" rows="4" placeholder="Your comment…" style="width:100%; margin:0.5em 0;"></textarea>
- <button type="submit" class="primary">Send comment</button>
- <button type="button" onclick="document.getElementById('quote-comment-dialog').close()">Cancel</button>
- </form>
- </dialog>
- </div>
- </section>
- </body>
- </html>
|