@agent-link/server 0.1.187 → 0.1.189
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/auth-manager.d.ts +36 -0
- package/dist/auth-manager.js +96 -0
- package/dist/auth-manager.js.map +1 -0
- package/dist/http.d.ts +4 -0
- package/dist/http.js +85 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +5 -84
- package/dist/index.js.map +1 -1
- package/dist/message-relay.d.ts +17 -0
- package/dist/message-relay.js +23 -0
- package/dist/message-relay.js.map +1 -0
- package/dist/session-manager.d.ts +44 -0
- package/dist/session-manager.js +83 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/ws-agent.js +19 -27
- package/dist/ws-agent.js.map +1 -1
- package/dist/ws-client.js +31 -37
- package/dist/ws-client.js.map +1 -1
- package/package.json +3 -3
- package/web/dist/assets/index-DIO7Hox0.js +320 -0
- package/web/dist/assets/index-DIO7Hox0.js.map +1 -0
- package/web/dist/assets/index-Y1FN_mFe.css +1 -0
- package/web/{index.html → dist/index.html} +2 -19
- package/dist/auth.d.ts +0 -13
- package/dist/auth.js +0 -65
- package/dist/auth.js.map +0 -1
- package/dist/context.d.ts +0 -52
- package/dist/context.js +0 -60
- package/dist/context.js.map +0 -1
- package/web/app.js +0 -2881
- package/web/css/ask-question.css +0 -333
- package/web/css/base.css +0 -270
- package/web/css/btw.css +0 -148
- package/web/css/chat.css +0 -176
- package/web/css/file-browser.css +0 -499
- package/web/css/input.css +0 -671
- package/web/css/loop.css +0 -674
- package/web/css/markdown.css +0 -169
- package/web/css/responsive.css +0 -314
- package/web/css/sidebar.css +0 -593
- package/web/css/team.css +0 -1277
- package/web/css/tools.css +0 -327
- package/web/encryption.js +0 -56
- package/web/modules/appHelpers.js +0 -100
- package/web/modules/askQuestion.js +0 -63
- package/web/modules/backgroundRouting.js +0 -269
- package/web/modules/connection.js +0 -731
- package/web/modules/fileAttachments.js +0 -125
- package/web/modules/fileBrowser.js +0 -398
- package/web/modules/filePreview.js +0 -213
- package/web/modules/i18n.js +0 -101
- package/web/modules/loop.js +0 -338
- package/web/modules/loopTemplates.js +0 -110
- package/web/modules/markdown.js +0 -83
- package/web/modules/messageHelpers.js +0 -206
- package/web/modules/sidebar.js +0 -402
- package/web/modules/streaming.js +0 -116
- package/web/modules/team.js +0 -396
- package/web/modules/teamTemplates.js +0 -360
- package/web/vendor/highlight.min.js +0 -1213
- package/web/vendor/marked.min.js +0 -6
- package/web/vendor/nacl-fast.min.js +0 -1
- package/web/vendor/nacl-util.min.js +0 -1
- package/web/vendor/pako.min.js +0 -2
- package/web/vendor/vue.global.prod.js +0 -13
- /package/web/{favicon.svg → dist/favicon.svg} +0 -0
- /package/web/{images → dist/images}/chat-iPad.webp +0 -0
- /package/web/{images → dist/images}/chat-iPhone.webp +0 -0
- /package/web/{images → dist/images}/loop-iPad.webp +0 -0
- /package/web/{images → dist/images}/team-iPad.webp +0 -0
- /package/web/{landing.html → dist/landing.html} +0 -0
- /package/web/{landing.zh.html → dist/landing.zh.html} +0 -0
- /package/web/{locales → dist/locales}/en.json +0 -0
- /package/web/{locales → dist/locales}/zh.json +0 -0
- /package/web/{vendor → dist/vendor}/github-dark.min.css +0 -0
- /package/web/{vendor → dist/vendor}/github.min.css +0 -0
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
// ── File Preview: panel state, content rendering, resize handle ────────
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Creates the file preview controller.
|
|
5
|
-
* @param {object} deps - Reactive state and callbacks
|
|
6
|
-
*/
|
|
7
|
-
export function createFilePreview(deps) {
|
|
8
|
-
const {
|
|
9
|
-
wsSend,
|
|
10
|
-
previewPanelOpen,
|
|
11
|
-
previewPanelWidth,
|
|
12
|
-
previewFile,
|
|
13
|
-
previewLoading,
|
|
14
|
-
previewMarkdownRendered,
|
|
15
|
-
sidebarView,
|
|
16
|
-
sidebarOpen,
|
|
17
|
-
isMobile,
|
|
18
|
-
renderMarkdown,
|
|
19
|
-
} = deps;
|
|
20
|
-
|
|
21
|
-
// ── Open / Close ──
|
|
22
|
-
|
|
23
|
-
function openPreview(filePath) {
|
|
24
|
-
// Skip re-fetch if same file already loaded
|
|
25
|
-
if (previewFile.value && previewFile.value.filePath === filePath && !previewFile.value.error) {
|
|
26
|
-
if (isMobile.value) {
|
|
27
|
-
sidebarView.value = 'preview';
|
|
28
|
-
sidebarOpen.value = true;
|
|
29
|
-
} else {
|
|
30
|
-
previewPanelOpen.value = true;
|
|
31
|
-
}
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (isMobile.value) {
|
|
35
|
-
sidebarView.value = 'preview';
|
|
36
|
-
sidebarOpen.value = true;
|
|
37
|
-
} else {
|
|
38
|
-
previewPanelOpen.value = true;
|
|
39
|
-
}
|
|
40
|
-
previewLoading.value = true;
|
|
41
|
-
previewFile.value = null;
|
|
42
|
-
wsSend({ type: 'read_file', filePath });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function closePreview() {
|
|
46
|
-
if (isMobile.value) {
|
|
47
|
-
sidebarView.value = 'files';
|
|
48
|
-
} else {
|
|
49
|
-
previewPanelOpen.value = false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ── Handle file_content response ──
|
|
54
|
-
|
|
55
|
-
function handleFileContent(msg) {
|
|
56
|
-
previewLoading.value = false;
|
|
57
|
-
previewMarkdownRendered.value = false;
|
|
58
|
-
previewFile.value = {
|
|
59
|
-
filePath: msg.filePath,
|
|
60
|
-
fileName: msg.fileName,
|
|
61
|
-
content: msg.content,
|
|
62
|
-
encoding: msg.encoding,
|
|
63
|
-
mimeType: msg.mimeType,
|
|
64
|
-
truncated: msg.truncated,
|
|
65
|
-
totalSize: msg.totalSize,
|
|
66
|
-
error: msg.error || null,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ── Workdir changed → close preview ──
|
|
71
|
-
|
|
72
|
-
function onWorkdirChanged() {
|
|
73
|
-
previewPanelOpen.value = false;
|
|
74
|
-
previewFile.value = null;
|
|
75
|
-
previewLoading.value = false;
|
|
76
|
-
if (sidebarView.value === 'preview') {
|
|
77
|
-
sidebarView.value = 'sessions';
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ── Syntax highlighting helpers ──
|
|
82
|
-
|
|
83
|
-
const LANG_MAP = {
|
|
84
|
-
ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
|
|
85
|
-
mjs: 'javascript', cjs: 'javascript', py: 'python', rb: 'ruby',
|
|
86
|
-
rs: 'rust', go: 'go', java: 'java', c: 'c', h: 'c',
|
|
87
|
-
cpp: 'cpp', hpp: 'cpp', cs: 'csharp', swift: 'swift', kt: 'kotlin',
|
|
88
|
-
lua: 'lua', r: 'r', sql: 'sql', sh: 'bash', bash: 'bash', zsh: 'bash',
|
|
89
|
-
fish: 'bash', ps1: 'powershell', bat: 'dos', cmd: 'dos',
|
|
90
|
-
json: 'json', json5: 'json', yaml: 'yaml', yml: 'yaml', toml: 'ini',
|
|
91
|
-
xml: 'xml', html: 'xml', htm: 'xml', css: 'css', scss: 'scss', less: 'less',
|
|
92
|
-
md: 'markdown', txt: 'plaintext', log: 'plaintext', graphql: 'graphql',
|
|
93
|
-
proto: 'protobuf', vue: 'xml', svelte: 'xml', ini: 'ini', cfg: 'ini',
|
|
94
|
-
conf: 'ini', env: 'bash',
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
function detectLanguage(fileName) {
|
|
98
|
-
const ext = (fileName || '').split('.').pop()?.toLowerCase();
|
|
99
|
-
return LANG_MAP[ext] || ext || 'plaintext';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function highlightCode(code, fileName) {
|
|
103
|
-
if (!code) return '';
|
|
104
|
-
if (!window.hljs) return escapeHtml(code);
|
|
105
|
-
const lang = detectLanguage(fileName);
|
|
106
|
-
try {
|
|
107
|
-
return window.hljs.highlight(code, { language: lang }).value;
|
|
108
|
-
} catch {
|
|
109
|
-
try {
|
|
110
|
-
return window.hljs.highlightAuto(code).value;
|
|
111
|
-
} catch {
|
|
112
|
-
return escapeHtml(code);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function escapeHtml(str) {
|
|
118
|
-
return str
|
|
119
|
-
.replace(/&/g, '&')
|
|
120
|
-
.replace(/</g, '<')
|
|
121
|
-
.replace(/>/g, '>')
|
|
122
|
-
.replace(/"/g, '"');
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ── File size formatting ──
|
|
126
|
-
|
|
127
|
-
function formatFileSize(bytes) {
|
|
128
|
-
if (bytes == null) return '';
|
|
129
|
-
if (bytes < 1024) return bytes + ' B';
|
|
130
|
-
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
131
|
-
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ── Resize handle (mouse + touch) ──
|
|
135
|
-
|
|
136
|
-
let _resizing = false;
|
|
137
|
-
let _startX = 0;
|
|
138
|
-
let _startWidth = 0;
|
|
139
|
-
const MIN_WIDTH = 200;
|
|
140
|
-
const MAX_WIDTH = 800;
|
|
141
|
-
|
|
142
|
-
function onResizeStart(e) {
|
|
143
|
-
e.preventDefault();
|
|
144
|
-
_resizing = true;
|
|
145
|
-
_startX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
|
|
146
|
-
_startWidth = previewPanelWidth.value;
|
|
147
|
-
document.body.style.cursor = 'col-resize';
|
|
148
|
-
document.body.style.userSelect = 'none';
|
|
149
|
-
if (e.type === 'touchstart') {
|
|
150
|
-
document.addEventListener('touchmove', onResizeMove, { passive: false });
|
|
151
|
-
document.addEventListener('touchend', onResizeEnd);
|
|
152
|
-
} else {
|
|
153
|
-
document.addEventListener('mousemove', onResizeMove);
|
|
154
|
-
document.addEventListener('mouseup', onResizeEnd);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function onResizeMove(e) {
|
|
159
|
-
if (!_resizing) return;
|
|
160
|
-
if (e.type === 'touchmove') e.preventDefault();
|
|
161
|
-
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
|
|
162
|
-
// Left edge resize: dragging left = wider, dragging right = narrower
|
|
163
|
-
const delta = _startX - clientX;
|
|
164
|
-
const newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, _startWidth + delta));
|
|
165
|
-
previewPanelWidth.value = newWidth;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function onResizeEnd() {
|
|
169
|
-
if (!_resizing) return;
|
|
170
|
-
_resizing = false;
|
|
171
|
-
document.body.style.cursor = '';
|
|
172
|
-
document.body.style.userSelect = '';
|
|
173
|
-
document.removeEventListener('mousemove', onResizeMove);
|
|
174
|
-
document.removeEventListener('mouseup', onResizeEnd);
|
|
175
|
-
document.removeEventListener('touchmove', onResizeMove);
|
|
176
|
-
document.removeEventListener('touchend', onResizeEnd);
|
|
177
|
-
localStorage.setItem('agentlink-preview-panel-width', String(previewPanelWidth.value));
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ── Markdown preview ──
|
|
181
|
-
|
|
182
|
-
function isMarkdownFile(fileName) {
|
|
183
|
-
const ext = (fileName || '').split('.').pop()?.toLowerCase();
|
|
184
|
-
return ext === 'md' || ext === 'mdx';
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function renderedMarkdownHtml(content) {
|
|
188
|
-
return renderMarkdown(content || '');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** Force re-fetch the currently open preview file (e.g. after editing) */
|
|
192
|
-
function refreshPreview() {
|
|
193
|
-
if (!previewFile.value?.filePath) return;
|
|
194
|
-
const filePath = previewFile.value.filePath;
|
|
195
|
-
previewFile.value = null;
|
|
196
|
-
previewLoading.value = true;
|
|
197
|
-
wsSend({ type: 'read_file', filePath });
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
openPreview,
|
|
202
|
-
closePreview,
|
|
203
|
-
refreshPreview,
|
|
204
|
-
handleFileContent,
|
|
205
|
-
onWorkdirChanged,
|
|
206
|
-
detectLanguage,
|
|
207
|
-
highlightCode,
|
|
208
|
-
formatFileSize,
|
|
209
|
-
onResizeStart,
|
|
210
|
-
isMarkdownFile,
|
|
211
|
-
renderedMarkdownHtml,
|
|
212
|
-
};
|
|
213
|
-
}
|
package/web/modules/i18n.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
// ── Lightweight i18n module ─────────────────────────────────────────────────
|
|
2
|
-
const { ref, computed } = Vue;
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Creates i18n functionality: t() translator, locale switching, persistence.
|
|
6
|
-
* Locale data is loaded dynamically from /locales/<lang>.json.
|
|
7
|
-
*
|
|
8
|
-
* @returns {{ t: Function, locale: import('vue').Ref<string>, setLocale: Function }}
|
|
9
|
-
*/
|
|
10
|
-
export function createI18n() {
|
|
11
|
-
const STORAGE_KEY = 'agentlink-language';
|
|
12
|
-
const SUPPORTED = ['en', 'zh'];
|
|
13
|
-
const DEFAULT_LOCALE = 'en';
|
|
14
|
-
|
|
15
|
-
// Detect initial locale
|
|
16
|
-
function detectLocale() {
|
|
17
|
-
// 1. Explicit user choice
|
|
18
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
19
|
-
if (stored && SUPPORTED.includes(stored)) return stored;
|
|
20
|
-
|
|
21
|
-
// 2. Browser preference
|
|
22
|
-
const nav = (navigator.language || '').toLowerCase();
|
|
23
|
-
if (nav.startsWith('zh')) return 'zh';
|
|
24
|
-
|
|
25
|
-
// 3. Default
|
|
26
|
-
return DEFAULT_LOCALE;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const locale = ref(detectLocale());
|
|
30
|
-
const _messages = ref({});
|
|
31
|
-
let _loadedLocale = null;
|
|
32
|
-
|
|
33
|
-
// Load locale JSON
|
|
34
|
-
async function loadMessages(lang) {
|
|
35
|
-
if (_loadedLocale === lang && Object.keys(_messages.value).length > 0) return;
|
|
36
|
-
try {
|
|
37
|
-
const resp = await fetch(`/locales/${lang}.json`);
|
|
38
|
-
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
39
|
-
_messages.value = await resp.json();
|
|
40
|
-
_loadedLocale = lang;
|
|
41
|
-
} catch (e) {
|
|
42
|
-
console.warn(`[i18n] Failed to load locale "${lang}":`, e);
|
|
43
|
-
// Fallback: try loading English
|
|
44
|
-
if (lang !== DEFAULT_LOCALE) {
|
|
45
|
-
try {
|
|
46
|
-
const resp = await fetch(`/locales/${DEFAULT_LOCALE}.json`);
|
|
47
|
-
if (resp.ok) {
|
|
48
|
-
_messages.value = await resp.json();
|
|
49
|
-
_loadedLocale = DEFAULT_LOCALE;
|
|
50
|
-
}
|
|
51
|
-
} catch { /* give up */ }
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Translate a key, with optional parameter substitution.
|
|
58
|
-
* Returns the key itself if no translation is found (fallback).
|
|
59
|
-
*
|
|
60
|
-
* @param {string} key - Dot-notation key, e.g. "button.send"
|
|
61
|
-
* @param {object} [params] - Substitution params, e.g. { n: 5 }
|
|
62
|
-
* @returns {string}
|
|
63
|
-
*/
|
|
64
|
-
function t(key, params) {
|
|
65
|
-
let str = _messages.value[key];
|
|
66
|
-
if (str === undefined) return key;
|
|
67
|
-
if (params) {
|
|
68
|
-
for (const [k, v] of Object.entries(params)) {
|
|
69
|
-
str = str.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return str;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Switch locale, persist choice, and reload strings.
|
|
77
|
-
* @param {string} lang
|
|
78
|
-
*/
|
|
79
|
-
async function setLocale(lang) {
|
|
80
|
-
if (!SUPPORTED.includes(lang)) return;
|
|
81
|
-
locale.value = lang;
|
|
82
|
-
localStorage.setItem(STORAGE_KEY, lang);
|
|
83
|
-
await loadMessages(lang);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Toggle between supported locales (EN ↔ 中).
|
|
88
|
-
*/
|
|
89
|
-
async function toggleLocale() {
|
|
90
|
-
const next = locale.value === 'en' ? 'zh' : 'en';
|
|
91
|
-
await setLocale(next);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// The display label for the language switcher button
|
|
95
|
-
const localeLabel = computed(() => locale.value === 'en' ? 'EN' : '中');
|
|
96
|
-
|
|
97
|
-
// Load initial messages
|
|
98
|
-
loadMessages(locale.value);
|
|
99
|
-
|
|
100
|
-
return { t, locale, setLocale, toggleLocale, localeLabel };
|
|
101
|
-
}
|
package/web/modules/loop.js
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
// ── Loop mode: state management and message routing ───────────────────────────
|
|
2
|
-
const { ref, computed } = Vue;
|
|
3
|
-
|
|
4
|
-
import { buildHistoryBatch } from './backgroundRouting.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Creates the Loop mode controller.
|
|
8
|
-
* @param {object} deps
|
|
9
|
-
* @param {Function} deps.wsSend
|
|
10
|
-
* @param {Function} deps.scrollToBottom
|
|
11
|
-
*/
|
|
12
|
-
export function createLoop(deps) {
|
|
13
|
-
const { wsSend, scrollToBottom, loadingLoops } = deps;
|
|
14
|
-
|
|
15
|
-
// ── Reactive state ──────────────────────────────────
|
|
16
|
-
|
|
17
|
-
/** @type {import('vue').Ref<Array>} All Loop definitions from agent */
|
|
18
|
-
const loopsList = ref([]);
|
|
19
|
-
|
|
20
|
-
/** @type {import('vue').Ref<object|null>} Loop selected for detail view */
|
|
21
|
-
const selectedLoop = ref(null);
|
|
22
|
-
|
|
23
|
-
/** @type {import('vue').Ref<string|null>} Execution ID selected for replay */
|
|
24
|
-
const selectedExecution = ref(null);
|
|
25
|
-
|
|
26
|
-
/** @type {import('vue').Ref<Array>} Execution history for selectedLoop */
|
|
27
|
-
const executionHistory = ref([]);
|
|
28
|
-
|
|
29
|
-
/** @type {import('vue').Ref<Array>} Messages for selectedExecution replay */
|
|
30
|
-
const executionMessages = ref([]);
|
|
31
|
-
|
|
32
|
-
/** @type {import('vue').Ref<object>} loopId -> LoopExecution for currently running */
|
|
33
|
-
const runningLoops = ref({});
|
|
34
|
-
|
|
35
|
-
/** @type {import('vue').Ref<boolean>} Loading execution list */
|
|
36
|
-
const loadingExecutions = ref(false);
|
|
37
|
-
|
|
38
|
-
/** @type {import('vue').Ref<boolean>} Loading single execution detail */
|
|
39
|
-
const loadingExecution = ref(false);
|
|
40
|
-
|
|
41
|
-
/** @type {import('vue').Ref<string|null>} Loop being edited (loopId) or null for new */
|
|
42
|
-
const editingLoopId = ref(null);
|
|
43
|
-
|
|
44
|
-
/** @type {import('vue').Ref<string>} Error message from last loop operation (create/update) */
|
|
45
|
-
const loopError = ref('');
|
|
46
|
-
|
|
47
|
-
/** @type {number} Current execution history page limit */
|
|
48
|
-
let execPageLimit = 20;
|
|
49
|
-
|
|
50
|
-
/** @type {import('vue').Ref<boolean>} Whether more execution history may be available */
|
|
51
|
-
const hasMoreExecutions = ref(false);
|
|
52
|
-
|
|
53
|
-
/** @type {import('vue').Ref<boolean>} Loading more executions via pagination */
|
|
54
|
-
const loadingMoreExecutions = ref(false);
|
|
55
|
-
|
|
56
|
-
// ── Computed ──────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
/** Whether any Loop execution is currently running */
|
|
59
|
-
const hasRunningLoop = computed(() => Object.keys(runningLoops.value).length > 0);
|
|
60
|
-
|
|
61
|
-
/** Get the first running loop for notification banner */
|
|
62
|
-
const firstRunningLoop = computed(() => {
|
|
63
|
-
const entries = Object.entries(runningLoops.value);
|
|
64
|
-
if (entries.length === 0) return null;
|
|
65
|
-
const [loopId, execution] = entries[0];
|
|
66
|
-
const loop = loopsList.value.find(l => l.id === loopId);
|
|
67
|
-
return { loopId, execution, name: loop?.name || 'Unknown' };
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// ── Loop CRUD ─────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
function createNewLoop(config) {
|
|
73
|
-
wsSend({ type: 'create_loop', ...config });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function updateExistingLoop(loopId, updates) {
|
|
77
|
-
wsSend({ type: 'update_loop', loopId, updates });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function deleteExistingLoop(loopId) {
|
|
81
|
-
wsSend({ type: 'delete_loop', loopId });
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function toggleLoop(loopId) {
|
|
85
|
-
const loop = loopsList.value.find(l => l.id === loopId);
|
|
86
|
-
if (!loop) return;
|
|
87
|
-
wsSend({ type: 'update_loop', loopId, updates: { enabled: !loop.enabled } });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function runNow(loopId) {
|
|
91
|
-
wsSend({ type: 'run_loop', loopId });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function cancelExecution(loopId) {
|
|
95
|
-
wsSend({ type: 'cancel_loop_execution', loopId });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function requestLoopsList() {
|
|
99
|
-
if (loadingLoops) loadingLoops.value = true;
|
|
100
|
-
wsSend({ type: 'list_loops' });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ── Navigation ────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
function viewLoopDetail(loopId) {
|
|
106
|
-
const loop = loopsList.value.find(l => l.id === loopId);
|
|
107
|
-
if (!loop) return;
|
|
108
|
-
selectedLoop.value = { ...loop };
|
|
109
|
-
selectedExecution.value = null;
|
|
110
|
-
executionMessages.value = [];
|
|
111
|
-
executionHistory.value = [];
|
|
112
|
-
loadingExecutions.value = true;
|
|
113
|
-
editingLoopId.value = null;
|
|
114
|
-
execPageLimit = 20;
|
|
115
|
-
hasMoreExecutions.value = false;
|
|
116
|
-
wsSend({ type: 'list_loop_executions', loopId, limit: execPageLimit });
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function viewExecution(loopId, executionId) {
|
|
120
|
-
selectedExecution.value = executionId;
|
|
121
|
-
loadingExecution.value = true;
|
|
122
|
-
executionMessages.value = [];
|
|
123
|
-
wsSend({ type: 'get_loop_execution_messages', loopId, executionId });
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function backToLoopsList() {
|
|
127
|
-
selectedLoop.value = null;
|
|
128
|
-
selectedExecution.value = null;
|
|
129
|
-
executionHistory.value = [];
|
|
130
|
-
executionMessages.value = [];
|
|
131
|
-
editingLoopId.value = null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function backToLoopDetail() {
|
|
135
|
-
selectedExecution.value = null;
|
|
136
|
-
executionMessages.value = [];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function startEditing(loopId) {
|
|
140
|
-
editingLoopId.value = loopId;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function cancelEditing() {
|
|
144
|
-
editingLoopId.value = null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function loadMoreExecutions() {
|
|
148
|
-
if (!selectedLoop.value || loadingMoreExecutions.value) return;
|
|
149
|
-
loadingMoreExecutions.value = true;
|
|
150
|
-
execPageLimit *= 2;
|
|
151
|
-
wsSend({ type: 'list_loop_executions', loopId: selectedLoop.value.id, limit: execPageLimit });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function clearLoopError() {
|
|
155
|
-
loopError.value = '';
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ── Live output accumulation ─────────────────────
|
|
159
|
-
|
|
160
|
-
/** Message ID counter for live execution messages */
|
|
161
|
-
let liveMsgIdCounter = 0;
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Append a Claude output message to the live execution display.
|
|
165
|
-
* Mirrors the team.js handleTeamAgentOutput accumulation logic.
|
|
166
|
-
*/
|
|
167
|
-
function appendOutputToDisplay(data) {
|
|
168
|
-
if (!data) return;
|
|
169
|
-
const msgs = executionMessages.value;
|
|
170
|
-
|
|
171
|
-
if (data.type === 'content_block_delta' && data.delta) {
|
|
172
|
-
const last = msgs.length > 0 ? msgs[msgs.length - 1] : null;
|
|
173
|
-
if (last && last.role === 'assistant' && last.isStreaming) {
|
|
174
|
-
last.content += data.delta;
|
|
175
|
-
} else {
|
|
176
|
-
msgs.push({
|
|
177
|
-
id: ++liveMsgIdCounter, role: 'assistant',
|
|
178
|
-
content: data.delta, isStreaming: true, timestamp: Date.now(),
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
} else if (data.type === 'tool_use' && data.tools) {
|
|
182
|
-
const last = msgs.length > 0 ? msgs[msgs.length - 1] : null;
|
|
183
|
-
if (last && last.role === 'assistant' && last.isStreaming) {
|
|
184
|
-
last.isStreaming = false;
|
|
185
|
-
}
|
|
186
|
-
for (const tool of data.tools) {
|
|
187
|
-
msgs.push({
|
|
188
|
-
id: ++liveMsgIdCounter, role: 'tool',
|
|
189
|
-
toolId: tool.id, toolName: tool.name || 'unknown',
|
|
190
|
-
toolInput: tool.input ? JSON.stringify(tool.input, null, 2) : '',
|
|
191
|
-
hasResult: false, expanded: true, timestamp: Date.now(),
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
} else if (data.type === 'user' && data.tool_use_result) {
|
|
195
|
-
const result = data.tool_use_result;
|
|
196
|
-
const results = Array.isArray(result) ? result : [result];
|
|
197
|
-
for (const r of results) {
|
|
198
|
-
const toolMsg = msgs.find(m => m.role === 'tool' && m.toolId === r.tool_use_id);
|
|
199
|
-
if (toolMsg) {
|
|
200
|
-
toolMsg.toolOutput = typeof r.content === 'string'
|
|
201
|
-
? r.content : JSON.stringify(r.content, null, 2);
|
|
202
|
-
toolMsg.hasResult = true;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
scrollToBottom();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ── Message routing ───────────────────────────────
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Handle incoming Loop-related messages from the WebSocket.
|
|
214
|
-
* Returns true if the message was consumed.
|
|
215
|
-
*/
|
|
216
|
-
function handleLoopMessage(msg) {
|
|
217
|
-
switch (msg.type) {
|
|
218
|
-
case 'loops_list':
|
|
219
|
-
loopsList.value = msg.loops || [];
|
|
220
|
-
return true;
|
|
221
|
-
|
|
222
|
-
case 'loop_created':
|
|
223
|
-
loopsList.value.push(msg.loop);
|
|
224
|
-
loopError.value = '';
|
|
225
|
-
return true;
|
|
226
|
-
|
|
227
|
-
case 'loop_updated': {
|
|
228
|
-
const idx = loopsList.value.findIndex(l => l.id === msg.loop.id);
|
|
229
|
-
if (idx >= 0) loopsList.value[idx] = msg.loop;
|
|
230
|
-
if (selectedLoop.value?.id === msg.loop.id) {
|
|
231
|
-
selectedLoop.value = { ...msg.loop };
|
|
232
|
-
}
|
|
233
|
-
editingLoopId.value = null;
|
|
234
|
-
loopError.value = '';
|
|
235
|
-
return true;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
case 'loop_deleted':
|
|
239
|
-
loopsList.value = loopsList.value.filter(l => l.id !== msg.loopId);
|
|
240
|
-
if (selectedLoop.value?.id === msg.loopId) backToLoopsList();
|
|
241
|
-
return true;
|
|
242
|
-
|
|
243
|
-
case 'loop_execution_started':
|
|
244
|
-
runningLoops.value = { ...runningLoops.value, [msg.loopId]: msg.execution };
|
|
245
|
-
// If viewing this loop's detail, prepend to history
|
|
246
|
-
if (selectedLoop.value?.id === msg.loopId) {
|
|
247
|
-
executionHistory.value.unshift(msg.execution);
|
|
248
|
-
}
|
|
249
|
-
return true;
|
|
250
|
-
|
|
251
|
-
case 'loop_execution_output':
|
|
252
|
-
// If user is viewing this execution live, append to display
|
|
253
|
-
if (selectedExecution.value === msg.executionId) {
|
|
254
|
-
appendOutputToDisplay(msg.data);
|
|
255
|
-
}
|
|
256
|
-
return true;
|
|
257
|
-
|
|
258
|
-
case 'loop_execution_completed': {
|
|
259
|
-
const newRunning = { ...runningLoops.value };
|
|
260
|
-
delete newRunning[msg.loopId];
|
|
261
|
-
runningLoops.value = newRunning;
|
|
262
|
-
// Update execution in history list
|
|
263
|
-
if (selectedLoop.value?.id === msg.loopId) {
|
|
264
|
-
const idx = executionHistory.value.findIndex(e => e.id === msg.execution.id);
|
|
265
|
-
if (idx >= 0) executionHistory.value[idx] = msg.execution;
|
|
266
|
-
}
|
|
267
|
-
// Finalize streaming message
|
|
268
|
-
const msgs = executionMessages.value;
|
|
269
|
-
if (msgs.length > 0) {
|
|
270
|
-
const last = msgs[msgs.length - 1];
|
|
271
|
-
if (last.role === 'assistant' && last.isStreaming) {
|
|
272
|
-
last.isStreaming = false;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// Update Loop's lastExecution in sidebar list
|
|
276
|
-
const loop = loopsList.value.find(l => l.id === msg.loopId);
|
|
277
|
-
if (loop) {
|
|
278
|
-
loop.lastExecution = {
|
|
279
|
-
id: msg.execution.id,
|
|
280
|
-
status: msg.execution.status,
|
|
281
|
-
startedAt: msg.execution.startedAt,
|
|
282
|
-
durationMs: msg.execution.durationMs,
|
|
283
|
-
trigger: msg.execution.trigger,
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
return true;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
case 'loop_executions_list':
|
|
290
|
-
if (selectedLoop.value?.id === msg.loopId) {
|
|
291
|
-
const execs = msg.executions || [];
|
|
292
|
-
executionHistory.value = execs;
|
|
293
|
-
loadingExecutions.value = false;
|
|
294
|
-
loadingMoreExecutions.value = false;
|
|
295
|
-
hasMoreExecutions.value = execs.length >= execPageLimit;
|
|
296
|
-
}
|
|
297
|
-
return true;
|
|
298
|
-
|
|
299
|
-
case 'loop_execution_messages':
|
|
300
|
-
if (selectedExecution.value === msg.executionId) {
|
|
301
|
-
if (msg.messages && msg.messages.length > 0) {
|
|
302
|
-
let idCounter = 0;
|
|
303
|
-
executionMessages.value = buildHistoryBatch(msg.messages, () => ++idCounter);
|
|
304
|
-
liveMsgIdCounter = idCounter;
|
|
305
|
-
} else {
|
|
306
|
-
executionMessages.value = [];
|
|
307
|
-
}
|
|
308
|
-
loadingExecution.value = false;
|
|
309
|
-
scrollToBottom();
|
|
310
|
-
}
|
|
311
|
-
return true;
|
|
312
|
-
|
|
313
|
-
default:
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return {
|
|
319
|
-
// State
|
|
320
|
-
loopsList, selectedLoop, selectedExecution,
|
|
321
|
-
executionHistory, executionMessages, runningLoops,
|
|
322
|
-
loadingExecutions, loadingExecution, editingLoopId,
|
|
323
|
-
loopError, hasMoreExecutions, loadingMoreExecutions,
|
|
324
|
-
// Computed
|
|
325
|
-
hasRunningLoop, firstRunningLoop,
|
|
326
|
-
// CRUD
|
|
327
|
-
createNewLoop, updateExistingLoop, deleteExistingLoop,
|
|
328
|
-
toggleLoop, runNow, cancelExecution, requestLoopsList,
|
|
329
|
-
// Navigation
|
|
330
|
-
viewLoopDetail, viewExecution,
|
|
331
|
-
backToLoopsList, backToLoopDetail,
|
|
332
|
-
startEditing, cancelEditing,
|
|
333
|
-
// Pagination & errors
|
|
334
|
-
loadMoreExecutions, clearLoopError,
|
|
335
|
-
// Message routing
|
|
336
|
-
handleLoopMessage,
|
|
337
|
-
};
|
|
338
|
-
}
|