@agent-link/server 0.1.186 → 0.1.188

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.
Files changed (54) hide show
  1. package/dist/index.js +13 -15
  2. package/dist/index.js.map +1 -1
  3. package/package.json +3 -3
  4. package/web/dist/assets/index-C9bIrYkZ.js +320 -0
  5. package/web/dist/assets/index-C9bIrYkZ.js.map +1 -0
  6. package/web/dist/assets/index-Y1FN_mFe.css +1 -0
  7. package/web/{index.html → dist/index.html} +2 -19
  8. package/web/app.js +0 -2881
  9. package/web/css/ask-question.css +0 -333
  10. package/web/css/base.css +0 -270
  11. package/web/css/btw.css +0 -148
  12. package/web/css/chat.css +0 -176
  13. package/web/css/file-browser.css +0 -499
  14. package/web/css/input.css +0 -671
  15. package/web/css/loop.css +0 -674
  16. package/web/css/markdown.css +0 -169
  17. package/web/css/responsive.css +0 -314
  18. package/web/css/sidebar.css +0 -593
  19. package/web/css/team.css +0 -1277
  20. package/web/css/tools.css +0 -327
  21. package/web/encryption.js +0 -56
  22. package/web/modules/appHelpers.js +0 -100
  23. package/web/modules/askQuestion.js +0 -63
  24. package/web/modules/backgroundRouting.js +0 -269
  25. package/web/modules/connection.js +0 -731
  26. package/web/modules/fileAttachments.js +0 -125
  27. package/web/modules/fileBrowser.js +0 -379
  28. package/web/modules/filePreview.js +0 -213
  29. package/web/modules/i18n.js +0 -101
  30. package/web/modules/loop.js +0 -338
  31. package/web/modules/loopTemplates.js +0 -110
  32. package/web/modules/markdown.js +0 -83
  33. package/web/modules/messageHelpers.js +0 -206
  34. package/web/modules/sidebar.js +0 -402
  35. package/web/modules/streaming.js +0 -116
  36. package/web/modules/team.js +0 -396
  37. package/web/modules/teamTemplates.js +0 -360
  38. package/web/vendor/highlight.min.js +0 -1213
  39. package/web/vendor/marked.min.js +0 -6
  40. package/web/vendor/nacl-fast.min.js +0 -1
  41. package/web/vendor/nacl-util.min.js +0 -1
  42. package/web/vendor/pako.min.js +0 -2
  43. package/web/vendor/vue.global.prod.js +0 -13
  44. /package/web/{favicon.svg → dist/favicon.svg} +0 -0
  45. /package/web/{images → dist/images}/chat-iPad.webp +0 -0
  46. /package/web/{images → dist/images}/chat-iPhone.webp +0 -0
  47. /package/web/{images → dist/images}/loop-iPad.webp +0 -0
  48. /package/web/{images → dist/images}/team-iPad.webp +0 -0
  49. /package/web/{landing.html → dist/landing.html} +0 -0
  50. /package/web/{landing.zh.html → dist/landing.zh.html} +0 -0
  51. /package/web/{locales → dist/locales}/en.json +0 -0
  52. /package/web/{locales → dist/locales}/zh.json +0 -0
  53. /package/web/{vendor → dist/vendor}/github-dark.min.css +0 -0
  54. /package/web/{vendor → dist/vendor}/github.min.css +0 -0
@@ -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
- }
@@ -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
- }
@@ -1,110 +0,0 @@
1
- // ── Loop template definitions ─────────────────────────────────────────────────
2
- // Predefined sample cases for the Loop creation panel ("Try it" cards).
3
- // Each template pre-fills name, prompt, scheduleType, and scheduleConfig.
4
-
5
- export const LOOP_TEMPLATES = {
6
- 'competitive-intel': {
7
- label: 'Competitive Intel Monitor',
8
- description: 'Track competitor products, pricing, and industry trends',
9
- name: 'Competitive Intelligence Monitor',
10
- prompt: `Monitor competitor and industry developments. Scan the working directory for any tracked competitor data, news feeds, or intelligence files.
11
-
12
- 1. Identify new product launches, feature updates, or pricing changes from competitors
13
- 2. Summarize key industry trends, regulatory changes, or market shifts
14
- 3. Highlight strategic threats (competitors gaining ground) and opportunities (gaps in market)
15
- 4. Compare against our current positioning where relevant
16
-
17
- Provide a structured briefing with sections: Key Developments, Threats, Opportunities, Recommended Actions.`,
18
- scheduleType: 'daily',
19
- scheduleConfig: { hour: 8, minute: 0 },
20
- },
21
-
22
- 'knowledge-base': {
23
- label: 'Knowledge Base Maintenance',
24
- description: 'Audit notes and docs for broken links, orphan files, and organization',
25
- name: 'Knowledge Base Maintenance',
26
- prompt: `Perform a maintenance audit on the knowledge base / notes in this directory.
27
-
28
- 1. Find broken internal links (references to files or headings that no longer exist)
29
- 2. Identify orphan files (documents with no inbound links from any other document)
30
- 3. Detect duplicate or near-duplicate content across files
31
- 4. Check for outdated information (files not modified in 90+ days that reference time-sensitive topics)
32
- 5. Suggest tag/folder reorganization for better discoverability
33
-
34
- Provide a structured report with sections: Broken Links, Orphan Files, Duplicates, Stale Content, Reorganization Suggestions.`,
35
- scheduleType: 'weekly',
36
- scheduleConfig: { hour: 20, minute: 0, dayOfWeek: 5 }, // Friday 20:00
37
- },
38
-
39
- 'daily-summary': {
40
- label: '日报/周报生成',
41
- description: '根据 git log 自动总结代码变更和工作进展',
42
- name: '每日工作总结',
43
- prompt: `根据当前工作目录的 git log 生成今日工作总结。
44
-
45
- 1. 列出今天所有 commit,按功能模块分组
46
- 2. 总结主要完成的功能、修复的 bug、重构的代码
47
- 3. 统计变更的文件数量和代码行数(新增/删除)
48
- 4. 标注仍在进行中的工作(未完成的分支、TODO 等)
49
- 5. 列出明日待办事项建议
50
-
51
- 输出格式:结构化的日报,包含:今日完成、进行中、明日计划。`,
52
- scheduleType: 'daily',
53
- scheduleConfig: { hour: 18, minute: 0 },
54
- },
55
- };
56
-
57
- export const LOOP_TEMPLATE_KEYS = ['competitive-intel', 'knowledge-base', 'daily-summary'];
58
-
59
- /**
60
- * Convert scheduleType + scheduleConfig into a cron expression string.
61
- * @param {string} scheduleType - 'hourly' | 'daily' | 'weekly' | 'cron'
62
- * @param {object} scheduleConfig - { hour?, minute?, dayOfWeek?, cronExpression? }
63
- * @returns {string} cron expression
64
- */
65
- export function buildCronExpression(scheduleType, scheduleConfig) {
66
- const min = scheduleConfig.minute ?? 0;
67
- const hr = scheduleConfig.hour ?? 9;
68
- switch (scheduleType) {
69
- case 'manual':
70
- return '';
71
- case 'hourly':
72
- return `${min} * * * *`;
73
- case 'daily':
74
- return `${min} ${hr} * * *`;
75
- case 'weekly':
76
- return `${min} ${hr} * * ${scheduleConfig.dayOfWeek ?? 1}`;
77
- case 'cron':
78
- return scheduleConfig.cronExpression || `${min} ${hr} * * *`;
79
- default:
80
- return `${min} ${hr} * * *`;
81
- }
82
- }
83
-
84
- /**
85
- * Format a cron expression into a human-readable description.
86
- * @param {string} scheduleType - 'hourly' | 'daily' | 'weekly' | 'cron'
87
- * @param {object} scheduleConfig - { hour?, minute?, dayOfWeek? }
88
- * @param {string} cronExpr - raw cron expression (for 'cron' type)
89
- * @returns {string}
90
- */
91
- export function formatSchedule(scheduleType, scheduleConfig, cronExpr) {
92
- const pad = n => String(n).padStart(2, '0');
93
- const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
94
- switch (scheduleType) {
95
- case 'manual':
96
- return 'Manual only';
97
- case 'hourly':
98
- return 'Every hour';
99
- case 'daily':
100
- return `Every day at ${pad(scheduleConfig.hour ?? 9)}:${pad(scheduleConfig.minute ?? 0)}`;
101
- case 'weekly': {
102
- const day = DAYS[scheduleConfig.dayOfWeek ?? 1] || 'Monday';
103
- return `Every ${day} at ${pad(scheduleConfig.hour ?? 9)}:${pad(scheduleConfig.minute ?? 0)}`;
104
- }
105
- case 'cron':
106
- return cronExpr || 'Custom cron';
107
- default:
108
- return cronExpr || 'Unknown schedule';
109
- }
110
- }
@@ -1,83 +0,0 @@
1
- // ── Markdown rendering, code copy, tool icons ────────────────────────────────
2
-
3
- if (typeof marked !== 'undefined') {
4
- marked.setOptions({
5
- breaks: true,
6
- gfm: true,
7
- highlight: function(code, lang) {
8
- if (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) {
9
- try { return hljs.highlight(code, { language: lang }).value; } catch {}
10
- }
11
- return code;
12
- },
13
- });
14
- }
15
-
16
- const _mdCache = new Map();
17
-
18
- export function renderMarkdown(text) {
19
- if (!text) return '';
20
- const cached = _mdCache.get(text);
21
- if (cached) return cached;
22
- let html;
23
- try {
24
- if (typeof marked !== 'undefined') {
25
- html = marked.parse(text);
26
- // Add copy buttons to code blocks
27
- html = html.replace(/<pre><code([^>]*)>([\s\S]*?)<\/code><\/pre>/g,
28
- (match, attrs, code) => {
29
- const langMatch = attrs.match(/class="language-(\w+)"/);
30
- const lang = langMatch ? langMatch[1] : '';
31
- return `<div class="code-block-wrapper">
32
- <div class="code-block-header">
33
- <span class="code-lang">${lang}</span>
34
- <button class="code-copy-btn" onclick="window.__copyCodeBlock(this)" title="Copy">
35
- <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
36
- </button>
37
- </div>
38
- <pre><code${attrs}>${code}</code></pre>
39
- </div>`;
40
- }
41
- );
42
- } else {
43
- html = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
44
- }
45
- } catch {
46
- html = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
47
- }
48
- if (_mdCache.size > 500) _mdCache.clear();
49
- _mdCache.set(text, html);
50
- return html;
51
- }
52
-
53
- // Global code copy handler
54
- window.__copyCodeBlock = async function(btn) {
55
- const wrapper = btn.closest('.code-block-wrapper');
56
- const code = wrapper?.querySelector('code');
57
- if (!code) return;
58
- try {
59
- await navigator.clipboard.writeText(code.textContent);
60
- btn.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>';
61
- setTimeout(() => {
62
- btn.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>';
63
- }, 2000);
64
- } catch {}
65
- };
66
-
67
- // Tool icons (monochrome SVG)
68
- const TOOL_SVG = {
69
- Read: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M1 2.5A2.5 2.5 0 0 1 3.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75H3.5a1 1 0 0 0-1 1h9.25a.75.75 0 0 1 0 1.5H3.5A2.5 2.5 0 0 1 1 14V2.5zm3 0v7l1.5-1.25L7 9.5v-7H4z"/></svg>',
70
- Edit: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25a1.75 1.75 0 0 1 .445-.758l8.61-8.61zM11.524 2.2l-8.61 8.61a.25.25 0 0 0-.064.108l-.57 1.996 1.996-.57a.25.25 0 0 0 .108-.064l8.61-8.61a.25.25 0 0 0 0-.354l-1.086-1.086a.25.25 0 0 0-.354 0z"/></svg>',
71
- Write: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M8.75 1.75a.75.75 0 0 0-1.5 0V6H2.75a.75.75 0 0 0 0 1.5H7.25v4.25a.75.75 0 0 0 1.5 0V7.5h4.25a.75.75 0 0 0 0-1.5H8.75V1.75z"/></svg>',
72
- Bash: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M0 2.75C0 1.784.784 1 1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75zm1.75-.25a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25H1.75zM7 11a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5A.75.75 0 0 1 7 11zm-3.22-4.53a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.75.75 0 0 1-1.06-1.06L5.25 9 3.78 7.53a.75.75 0 0 1 0-1.06z"/></svg>',
73
- Glob: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 1 1-1.06 1.06l-3.04-3.04zM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7z"/></svg>',
74
- Grep: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 1 1-1.06 1.06l-3.04-3.04zM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7z"/></svg>',
75
- Task: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75C0 1.784.784 1 1.75 1zm0 1.5a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25H1.75zM3.5 5h9v1.5h-9V5zm0 3h9v1.5h-9V8zm0 3h5v1.5h-5V11z"/></svg>',
76
- Agent: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M10.5 5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zm.061 3.073a4 4 0 1 0-5.123 0 6.004 6.004 0 0 0-3.431 5.142.75.75 0 0 0 1.498.07 4.5 4.5 0 0 1 8.99 0 .75.75 0 1 0 1.498-.07 6.004 6.004 0 0 0-3.432-5.142z"/></svg>',
77
- WebFetch: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.7 5.3a.75.75 0 0 0-1.06-1.06l-5.5 5.5a.75.75 0 1 0 1.06 1.06l5.5-5.5zM8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13z"/></svg>',
78
- WebSearch: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.7 5.3a.75.75 0 0 0-1.06-1.06l-5.5 5.5a.75.75 0 1 0 1.06 1.06l5.5-5.5zM8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13z"/></svg>',
79
- TodoWrite: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 1.042-1.08L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0z"/></svg>',
80
- };
81
- const TOOL_SVG_DEFAULT = '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M7.429 1.525a3.751 3.751 0 0 1 4.41.899l.04.045a.75.75 0 0 1-.17 1.143l-2.2 1.378a1.25 1.25 0 0 0-.473 1.58l.614 1.341a1.25 1.25 0 0 0 1.412.663l2.476-.542a.75.75 0 0 1 .848.496 3.75 3.75 0 0 1-1.468 4.155 3.751 3.751 0 0 1-4.41-.898l-.04-.046a.75.75 0 0 1 .17-1.142l2.2-1.378a1.25 1.25 0 0 0 .473-1.58l-.614-1.342a1.25 1.25 0 0 0-1.412-.662l-2.476.541a.75.75 0 0 1-.848-.496 3.75 3.75 0 0 1 1.468-4.155z"/></svg>';
82
-
83
- export function getToolIcon(name) { return TOOL_SVG[name] || TOOL_SVG_DEFAULT; }