@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,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
|
-
}
|
package/web/modules/markdown.js
DELETED
|
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
44
|
-
}
|
|
45
|
-
} catch {
|
|
46
|
-
html = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
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; }
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
// ── Message helpers: formatting, tool summaries, diff display ─────────────────
|
|
2
|
-
import { renderMarkdown } from './markdown.js';
|
|
3
|
-
|
|
4
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
5
|
-
const CONTEXT_SUMMARY_PREFIX = 'This session is being continued from a previous conversation';
|
|
6
|
-
|
|
7
|
-
function parseToolInput(msg) {
|
|
8
|
-
if (msg._parsedInput !== undefined) return msg._parsedInput;
|
|
9
|
-
try { msg._parsedInput = JSON.parse(msg.toolInput); }
|
|
10
|
-
catch { msg._parsedInput = null; }
|
|
11
|
-
return msg._parsedInput;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function isContextSummary(text) {
|
|
15
|
-
return typeof text === 'string' && text.trimStart().startsWith(CONTEXT_SUMMARY_PREFIX);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function formatRelativeTime(ts, t) {
|
|
19
|
-
const diff = Date.now() - ts;
|
|
20
|
-
const mins = Math.floor(diff / 60000);
|
|
21
|
-
if (mins < 1) return t ? t('time.justNow') : 'just now';
|
|
22
|
-
if (mins < 60) return t ? t('time.minutesAgo', { n: mins }) : `${mins}m ago`;
|
|
23
|
-
const hours = Math.floor(mins / 60);
|
|
24
|
-
if (hours < 24) return t ? t('time.hoursAgo', { n: hours }) : `${hours}h ago`;
|
|
25
|
-
const days = Math.floor(hours / 24);
|
|
26
|
-
if (days < 30) return t ? t('time.daysAgo', { n: days }) : `${days}d ago`;
|
|
27
|
-
return new Date(ts).toLocaleDateString();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function formatTimestamp(ts) {
|
|
31
|
-
if (!ts) return '';
|
|
32
|
-
const d = ts instanceof Date ? ts : new Date(ts);
|
|
33
|
-
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + ' · ' + d.toLocaleDateString();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function getRenderedContent(msg) {
|
|
37
|
-
if (msg.role !== 'assistant' && !msg.isCommandOutput) return msg.content;
|
|
38
|
-
if (msg.isStreaming) {
|
|
39
|
-
const t = msg.content || '';
|
|
40
|
-
return t.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>');
|
|
41
|
-
}
|
|
42
|
-
return renderMarkdown(msg.content);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export async function copyMessage(msg) {
|
|
46
|
-
try {
|
|
47
|
-
await navigator.clipboard.writeText(msg.content);
|
|
48
|
-
msg.copied = true;
|
|
49
|
-
setTimeout(() => { msg.copied = false; }, 2000);
|
|
50
|
-
} catch {}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function isPrevAssistant(visibleMessages, idx) {
|
|
54
|
-
if (idx <= 0) return false;
|
|
55
|
-
const prev = visibleMessages[idx - 1];
|
|
56
|
-
return prev && (prev.role === 'assistant' || prev.role === 'tool');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function toggleContextSummary(msg) {
|
|
60
|
-
msg.contextExpanded = !msg.contextExpanded;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function toggleTool(msg) {
|
|
64
|
-
msg.expanded = !msg.expanded;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function getToolSummary(msg, t) {
|
|
68
|
-
const name = msg.toolName;
|
|
69
|
-
const obj = parseToolInput(msg);
|
|
70
|
-
if (!obj) return '';
|
|
71
|
-
try {
|
|
72
|
-
if (name === 'Read' && obj.file_path) return obj.file_path;
|
|
73
|
-
if (name === 'Edit' && obj.file_path) return obj.file_path;
|
|
74
|
-
if (name === 'Write' && obj.file_path) return obj.file_path;
|
|
75
|
-
if (name === 'Bash' && obj.command) return obj.command.length > 60 ? obj.command.slice(0, 60) + '...' : obj.command;
|
|
76
|
-
if (name === 'Glob' && obj.pattern) return obj.pattern;
|
|
77
|
-
if (name === 'Grep' && obj.pattern) return obj.pattern;
|
|
78
|
-
if (name === 'TodoWrite' && obj.todos) {
|
|
79
|
-
const doneCount = obj.todos.filter(td => td.status === 'completed').length;
|
|
80
|
-
return t ? t('tool.done', { done: doneCount, total: obj.todos.length }) : `${doneCount}/${obj.todos.length} done`;
|
|
81
|
-
}
|
|
82
|
-
if (name === 'Task' && obj.description) return obj.description;
|
|
83
|
-
if (name === 'Agent' && obj.description) return obj.description;
|
|
84
|
-
if (name === 'Agent' && obj.prompt) return obj.prompt.length > 80 ? obj.prompt.slice(0, 80) + '...' : obj.prompt;
|
|
85
|
-
if (name === 'WebSearch' && obj.query) return obj.query;
|
|
86
|
-
if (name === 'WebFetch' && obj.url) return obj.url.length > 60 ? obj.url.slice(0, 60) + '...' : obj.url;
|
|
87
|
-
} catch {}
|
|
88
|
-
return '';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function isEditTool(msg) {
|
|
92
|
-
return msg.role === 'tool' && msg.toolName === 'Edit' && msg.toolInput;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function getFormattedToolInput(msg, t) {
|
|
96
|
-
if (!msg.toolInput) return null;
|
|
97
|
-
const obj = parseToolInput(msg);
|
|
98
|
-
if (!obj) return null;
|
|
99
|
-
try {
|
|
100
|
-
const esc = s => s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
101
|
-
const name = msg.toolName;
|
|
102
|
-
|
|
103
|
-
if (name === 'Read' && obj.file_path) {
|
|
104
|
-
let detail = esc(obj.file_path);
|
|
105
|
-
if (obj.offset && obj.limit) {
|
|
106
|
-
const meta = t ? t('tool.lines', { start: obj.offset, end: obj.offset + obj.limit - 1 }) : `lines ${obj.offset}\u2013${obj.offset + obj.limit - 1}`;
|
|
107
|
-
detail += ` <span class="tool-input-meta">${meta}</span>`;
|
|
108
|
-
} else if (obj.offset) {
|
|
109
|
-
const meta = t ? t('tool.fromLine', { offset: obj.offset }) : `from line ${obj.offset}`;
|
|
110
|
-
detail += ` <span class="tool-input-meta">${meta}</span>`;
|
|
111
|
-
} else if (obj.limit) {
|
|
112
|
-
const meta = t ? t('tool.firstLines', { limit: obj.limit }) : `first ${obj.limit} lines`;
|
|
113
|
-
detail += ` <span class="tool-input-meta">${meta}</span>`;
|
|
114
|
-
}
|
|
115
|
-
return detail;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (name === 'Write' && obj.file_path) {
|
|
119
|
-
const lines = (obj.content || '').split('\n').length;
|
|
120
|
-
const lineCount = t ? t('tool.lineCount', { n: lines }) : `${lines} lines`;
|
|
121
|
-
return esc(obj.file_path) + ` <span class="tool-input-meta">${lineCount}</span>`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (name === 'Bash' && obj.command) {
|
|
125
|
-
let html = '<code class="tool-input-cmd">' + esc(obj.command) + '</code>';
|
|
126
|
-
if (obj.description) html = '<span class="tool-input-meta">' + esc(obj.description) + '</span> ' + html;
|
|
127
|
-
return html;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (name === 'Glob' && obj.pattern) {
|
|
131
|
-
let html = '<code class="tool-input-cmd">' + esc(obj.pattern) + '</code>';
|
|
132
|
-
if (obj.path) html += ' <span class="tool-input-meta">' + (t ? t('tool.inPath', { path: esc(obj.path) }) : 'in ' + esc(obj.path)) + '</span>';
|
|
133
|
-
return html;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (name === 'Grep' && obj.pattern) {
|
|
137
|
-
let html = '<code class="tool-input-cmd">' + esc(obj.pattern) + '</code>';
|
|
138
|
-
if (obj.path) html += ' <span class="tool-input-meta">' + (t ? t('tool.inPath', { path: esc(obj.path) }) : 'in ' + esc(obj.path)) + '</span>';
|
|
139
|
-
return html;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (name === 'TodoWrite' && Array.isArray(obj.todos)) {
|
|
143
|
-
let html = '<div class="todo-list">';
|
|
144
|
-
for (const t of obj.todos) {
|
|
145
|
-
const s = t.status;
|
|
146
|
-
const icon = s === 'completed' ? '<span class="todo-icon done">\u2713</span>'
|
|
147
|
-
: s === 'in_progress' ? '<span class="todo-icon active">\u25CF</span>'
|
|
148
|
-
: '<span class="todo-icon">\u25CB</span>';
|
|
149
|
-
const cls = s === 'completed' ? ' todo-done' : s === 'in_progress' ? ' todo-active' : '';
|
|
150
|
-
html += '<div class="todo-item' + cls + '">' + icon + '<span class="todo-text">' + esc(t.content || t.activeForm || '') + '</span></div>';
|
|
151
|
-
}
|
|
152
|
-
html += '</div>';
|
|
153
|
-
return html;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (name === 'Task' || name === 'Agent') {
|
|
157
|
-
let html = '';
|
|
158
|
-
const descLabel = t ? t('tool.description') : 'Description';
|
|
159
|
-
const agentLabel = t ? t('tool.agent') : 'Agent';
|
|
160
|
-
const promptLabel = t ? t('tool.prompt') : 'Prompt';
|
|
161
|
-
if (obj.description) html += '<div class="task-field"><span class="tool-input-meta">' + descLabel + '</span> ' + esc(obj.description) + '</div>';
|
|
162
|
-
if (obj.subagent_type) html += '<div class="task-field"><span class="tool-input-meta">' + agentLabel + '</span> <code class="tool-input-cmd">' + esc(obj.subagent_type) + '</code></div>';
|
|
163
|
-
if (obj.prompt) {
|
|
164
|
-
html += '<div class="task-field"><span class="tool-input-meta">' + promptLabel + '</span></div><div class="task-prompt">' + esc(obj.prompt) + '</div>';
|
|
165
|
-
}
|
|
166
|
-
if (html) return html;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (name === 'WebSearch' && obj.query) {
|
|
170
|
-
return '<code class="tool-input-cmd">' + esc(obj.query) + '</code>';
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (name === 'WebFetch' && obj.url) {
|
|
174
|
-
let html = '<a class="tool-link" href="' + esc(obj.url) + '" target="_blank" rel="noopener">' + esc(obj.url) + '</a>';
|
|
175
|
-
if (obj.prompt) html += '<div class="task-field"><span class="tool-input-meta">' + esc(obj.prompt) + '</span></div>';
|
|
176
|
-
return html;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
} catch {}
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function getEditDiffHtml(msg, t) {
|
|
184
|
-
const obj = parseToolInput(msg);
|
|
185
|
-
if (!obj) return null;
|
|
186
|
-
try {
|
|
187
|
-
if (!obj.old_string && !obj.new_string) return null;
|
|
188
|
-
const esc = s => s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
189
|
-
const filePath = obj.file_path || '';
|
|
190
|
-
const oldLines = (obj.old_string || '').split('\n');
|
|
191
|
-
const newLines = (obj.new_string || '').split('\n');
|
|
192
|
-
let html = '';
|
|
193
|
-
if (filePath) {
|
|
194
|
-
html += '<div class="diff-file">' + esc(filePath) + (obj.replace_all ? ' <span class="diff-replace-all">' + (t ? t('tool.replaceAll') : '(replace all)') + '</span>' : '') + '</div>';
|
|
195
|
-
}
|
|
196
|
-
html += '<div class="diff-lines">';
|
|
197
|
-
for (const line of oldLines) {
|
|
198
|
-
html += '<div class="diff-removed">' + '<span class="diff-sign">-</span>' + esc(line) + '</div>';
|
|
199
|
-
}
|
|
200
|
-
for (const line of newLines) {
|
|
201
|
-
html += '<div class="diff-added">' + '<span class="diff-sign">+</span>' + esc(line) + '</div>';
|
|
202
|
-
}
|
|
203
|
-
html += '</div>';
|
|
204
|
-
return html;
|
|
205
|
-
} catch { return null; }
|
|
206
|
-
}
|