pikiloop 0.4.0
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/LICENSE +21 -0
- package/README.md +353 -0
- package/README.v2.md +287 -0
- package/README.zh-CN.md +352 -0
- package/dashboard/dist/assets/AgentTab-UZPIhlkr.js +1 -0
- package/dashboard/dist/assets/DirBrowser-Ckcmi-Pi.js +1 -0
- package/dashboard/dist/assets/ExtensionsTab-KZhEDrdu.js +1 -0
- package/dashboard/dist/assets/IMAccessTab-Bd_IY1GQ.js +1 -0
- package/dashboard/dist/assets/Modal-CTeL0y7P.js +1 -0
- package/dashboard/dist/assets/Modals-axftHasy.js +1 -0
- package/dashboard/dist/assets/Select-C8tOdPhe.js +1 -0
- package/dashboard/dist/assets/SessionPanel-C1geSRxw.js +1 -0
- package/dashboard/dist/assets/SystemTab-DBDkaPiO.js +1 -0
- package/dashboard/dist/assets/anthropic-BAdojD7P.ico +0 -0
- package/dashboard/dist/assets/codex-DYadqqp0.png +0 -0
- package/dashboard/dist/assets/deepseek-BeYNZEk0.ico +0 -0
- package/dashboard/dist/assets/doubao-DloFDuFR.png +0 -0
- package/dashboard/dist/assets/feishu-C4OMrjCW.ico +0 -0
- package/dashboard/dist/assets/gemini-BYkEpiWr.svg +1 -0
- package/dashboard/dist/assets/hermes-BAarh-tH.png +0 -0
- package/dashboard/dist/assets/index-CpM4CqZJ.js +23 -0
- package/dashboard/dist/assets/index-DXSohzrE.js +3 -0
- package/dashboard/dist/assets/index-reSbuley.css +1 -0
- package/dashboard/dist/assets/markdown-DxQYQFeH.js +29 -0
- package/dashboard/dist/assets/minimax-PuEGTfrF.ico +0 -0
- package/dashboard/dist/assets/mlx-DhWwjtMw.png +0 -0
- package/dashboard/dist/assets/ollama-Bt9O-2K_.png +0 -0
- package/dashboard/dist/assets/openrouter-CsJ_bD5Q.ico +0 -0
- package/dashboard/dist/assets/playwright-BldPFZgC.ico +0 -0
- package/dashboard/dist/assets/qwen-xykkX0_y.png +0 -0
- package/dashboard/dist/assets/react-vendor-C7Sl8SE7.js +9 -0
- package/dashboard/dist/assets/router-DHISdpPk.js +3 -0
- package/dashboard/dist/assets/shared-BIP_4k4I.js +1 -0
- package/dashboard/dist/favicon.svg +28 -0
- package/dashboard/dist/index.html +17 -0
- package/dist/agent/acp-client.js +261 -0
- package/dist/agent/auto-update.js +432 -0
- package/dist/agent/await-resume.js +50 -0
- package/dist/agent/cli/auth.js +325 -0
- package/dist/agent/cli/catalog.js +40 -0
- package/dist/agent/cli/detector.js +136 -0
- package/dist/agent/cli/index.js +7 -0
- package/dist/agent/cli/registry.js +33 -0
- package/dist/agent/driver.js +39 -0
- package/dist/agent/drivers/claude-tui.js +2297 -0
- package/dist/agent/drivers/claude.js +2689 -0
- package/dist/agent/drivers/codex.js +2210 -0
- package/dist/agent/drivers/gemini.js +1059 -0
- package/dist/agent/drivers/hermes.js +795 -0
- package/dist/agent/goal.js +274 -0
- package/dist/agent/handover.js +130 -0
- package/dist/agent/images.js +355 -0
- package/dist/agent/index.js +50 -0
- package/dist/agent/mcp/bridge.js +791 -0
- package/dist/agent/mcp/extensions.js +637 -0
- package/dist/agent/mcp/oauth.js +353 -0
- package/dist/agent/mcp/registry.js +119 -0
- package/dist/agent/mcp/session-server.js +229 -0
- package/dist/agent/mcp/tools/ask-user.js +113 -0
- package/dist/agent/mcp/tools/await-resume.js +77 -0
- package/dist/agent/mcp/tools/goal.js +144 -0
- package/dist/agent/mcp/tools/types.js +12 -0
- package/dist/agent/mcp/tools/workspace.js +212 -0
- package/dist/agent/npm.js +31 -0
- package/dist/agent/session.js +1206 -0
- package/dist/agent/skill-installer.js +160 -0
- package/dist/agent/skills.js +257 -0
- package/dist/agent/stream.js +743 -0
- package/dist/agent/types.js +13 -0
- package/dist/agent/utils.js +687 -0
- package/dist/bot/bot.js +2499 -0
- package/dist/bot/command-ui.js +633 -0
- package/dist/bot/commands.js +513 -0
- package/dist/bot/headless-bot.js +36 -0
- package/dist/bot/host.js +192 -0
- package/dist/bot/human-loop.js +168 -0
- package/dist/bot/menu.js +48 -0
- package/dist/bot/orchestration.js +79 -0
- package/dist/bot/render-shared.js +309 -0
- package/dist/bot/session-hub.js +361 -0
- package/dist/bot/session-status.js +55 -0
- package/dist/bot/streaming.js +309 -0
- package/dist/browser-profile.js +579 -0
- package/dist/browser-supervisor.js +249 -0
- package/dist/catalog/cli-tools.js +421 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/local-models.js +94 -0
- package/dist/catalog/mcp-servers.js +315 -0
- package/dist/catalog/skill-repos.js +173 -0
- package/dist/channels/base.js +55 -0
- package/dist/channels/dingtalk/bot.js +549 -0
- package/dist/channels/dingtalk/channel.js +268 -0
- package/dist/channels/discord/bot.js +552 -0
- package/dist/channels/discord/channel.js +245 -0
- package/dist/channels/feishu/bot.js +1275 -0
- package/dist/channels/feishu/channel.js +911 -0
- package/dist/channels/feishu/markdown.js +91 -0
- package/dist/channels/feishu/render.js +619 -0
- package/dist/channels/health.js +109 -0
- package/dist/channels/slack/bot.js +554 -0
- package/dist/channels/slack/channel.js +283 -0
- package/dist/channels/states.js +6 -0
- package/dist/channels/telegram/bot.js +1310 -0
- package/dist/channels/telegram/channel.js +820 -0
- package/dist/channels/telegram/directory.js +111 -0
- package/dist/channels/telegram/live-preview.js +220 -0
- package/dist/channels/telegram/render.js +384 -0
- package/dist/channels/wecom/bot.js +558 -0
- package/dist/channels/wecom/channel.js +479 -0
- package/dist/channels/weixin/api.js +520 -0
- package/dist/channels/weixin/bot.js +1000 -0
- package/dist/channels/weixin/channel.js +222 -0
- package/dist/cli/autostart.js +262 -0
- package/dist/cli/channel-supervisor.js +313 -0
- package/dist/cli/channels.js +54 -0
- package/dist/cli/main.js +726 -0
- package/dist/cli/onboarding.js +227 -0
- package/dist/cli/run.js +308 -0
- package/dist/cli/setup-wizard.js +235 -0
- package/dist/core/config/runtime-config.js +201 -0
- package/dist/core/config/user-config.js +510 -0
- package/dist/core/config/validation.js +521 -0
- package/dist/core/constants.js +400 -0
- package/dist/core/git.js +145 -0
- package/dist/core/legacy-compat.js +60 -0
- package/dist/core/logging.js +101 -0
- package/dist/core/platform.js +59 -0
- package/dist/core/process-control.js +315 -0
- package/dist/core/secrets/index.js +42 -0
- package/dist/core/secrets/inline-seal.js +60 -0
- package/dist/core/secrets/ref.js +33 -0
- package/dist/core/secrets/resolver.js +65 -0
- package/dist/core/secrets/store.js +63 -0
- package/dist/core/utils.js +233 -0
- package/dist/core/version.js +15 -0
- package/dist/dashboard/platform.js +219 -0
- package/dist/dashboard/routes/agents.js +450 -0
- package/dist/dashboard/routes/cli.js +174 -0
- package/dist/dashboard/routes/config.js +523 -0
- package/dist/dashboard/routes/extensions.js +745 -0
- package/dist/dashboard/routes/local-models.js +290 -0
- package/dist/dashboard/routes/models.js +324 -0
- package/dist/dashboard/routes/sessions.js +838 -0
- package/dist/dashboard/runtime.js +410 -0
- package/dist/dashboard/server.js +237 -0
- package/dist/dashboard/session-control.js +347 -0
- package/dist/model/catalog.js +104 -0
- package/dist/model/index.js +20 -0
- package/dist/model/injector.js +272 -0
- package/dist/model/provider-models.js +112 -0
- package/dist/model/store.js +212 -0
- package/dist/model/types.js +13 -0
- package/dist/model/validation.js +203 -0
- package/package.json +82 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram-specific message rendering and formatting.
|
|
3
|
+
*/
|
|
4
|
+
import { encodeCommandAction } from '../../bot/command-ui.js';
|
|
5
|
+
import { currentHumanLoopQuestion, humanLoopAnsweredCount, isHumanLoopAwaitingText, isHumanLoopQuestionAnswered, summarizeHumanLoopAnswer, } from '../../bot/human-loop.js';
|
|
6
|
+
import { footerStatusSymbol, formatFooterParts, trimActivityForPreview, buildProviderUsageLines, extractFinalReplyData, extractStreamPreviewData, parseGfmTable, } from '../../bot/render-shared.js';
|
|
7
|
+
export { dispatchImageBlocks } from '../../bot/render-shared.js';
|
|
8
|
+
export function escapeHtml(t) {
|
|
9
|
+
return t.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
10
|
+
}
|
|
11
|
+
export function truncateMiddle(text, maxChars = 36) {
|
|
12
|
+
const value = String(text || '');
|
|
13
|
+
if (value.length <= maxChars)
|
|
14
|
+
return value;
|
|
15
|
+
if (maxChars <= 3)
|
|
16
|
+
return value.slice(0, maxChars);
|
|
17
|
+
const visible = maxChars - 3;
|
|
18
|
+
const head = Math.ceil(visible / 2);
|
|
19
|
+
const tail = Math.floor(visible / 2);
|
|
20
|
+
return `${value.slice(0, head)}...${value.slice(-tail)}`;
|
|
21
|
+
}
|
|
22
|
+
export function compactCode(text, maxChars = 36) {
|
|
23
|
+
return `<code>${escapeHtml(truncateMiddle(text, maxChars))}</code>`;
|
|
24
|
+
}
|
|
25
|
+
export function buildCompactSelectionTitle(title, detail) {
|
|
26
|
+
const cleanDetail = String(detail || '').trim();
|
|
27
|
+
if (!cleanDetail)
|
|
28
|
+
return `<b>${escapeHtml(title)}</b>`;
|
|
29
|
+
return `<b>${escapeHtml(title)}</b> · ${compactCode(cleanDetail, 20)}`;
|
|
30
|
+
}
|
|
31
|
+
export function buildCompactSelectionNotice(title, value, detail, codeMaxChars = 40) {
|
|
32
|
+
const lines = [`<b>${escapeHtml(title)}</b>`, compactCode(value, codeMaxChars)];
|
|
33
|
+
const cleanDetail = String(detail || '').trim();
|
|
34
|
+
if (cleanDetail)
|
|
35
|
+
lines.push(`<i>${escapeHtml(cleanDetail)}</i>`);
|
|
36
|
+
return lines.join('\n');
|
|
37
|
+
}
|
|
38
|
+
function selectionStateSymbol(state) {
|
|
39
|
+
switch (state) {
|
|
40
|
+
case 'current': return '●';
|
|
41
|
+
case 'running': return '◐';
|
|
42
|
+
case 'unavailable': return '✕';
|
|
43
|
+
default: return '○';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function formatCommandItemHtml(item, index) {
|
|
47
|
+
const parts = [
|
|
48
|
+
selectionStateSymbol(item.state),
|
|
49
|
+
`<b>${index + 1}.</b>`,
|
|
50
|
+
escapeHtml(item.label),
|
|
51
|
+
];
|
|
52
|
+
if (item.detail)
|
|
53
|
+
parts.push(escapeHtml(item.detail));
|
|
54
|
+
return parts.join(' ');
|
|
55
|
+
}
|
|
56
|
+
function formatCommandButtonLabel(button, maxChars = 24) {
|
|
57
|
+
const prefix = button.state && button.state !== 'default'
|
|
58
|
+
? `${selectionStateSymbol(button.state)} `
|
|
59
|
+
: '';
|
|
60
|
+
return truncateMiddle(`${prefix}${button.label}`.trim(), maxChars);
|
|
61
|
+
}
|
|
62
|
+
export function renderCommandNoticeHtml(notice) {
|
|
63
|
+
const lines = [`<b>${escapeHtml(notice.title)}</b>`];
|
|
64
|
+
if (notice.value) {
|
|
65
|
+
if (notice.valueMode === 'plain')
|
|
66
|
+
lines.push(escapeHtml(notice.value));
|
|
67
|
+
else
|
|
68
|
+
lines.push(compactCode(notice.value, 40));
|
|
69
|
+
}
|
|
70
|
+
if (notice.detail)
|
|
71
|
+
lines.push(`<i>${escapeHtml(notice.detail)}</i>`);
|
|
72
|
+
return lines.join('\n');
|
|
73
|
+
}
|
|
74
|
+
export function renderCommandSelectionHtml(view) {
|
|
75
|
+
const lines = [buildCompactSelectionTitle(view.title, view.detail)];
|
|
76
|
+
if (view.metaLines.length)
|
|
77
|
+
lines.push(...view.metaLines.map(line => `<i>${escapeHtml(line)}</i>`));
|
|
78
|
+
if (view.items.length) {
|
|
79
|
+
lines.push('', ...view.items.map((item, index) => formatCommandItemHtml(item, index)));
|
|
80
|
+
}
|
|
81
|
+
else if (view.emptyText) {
|
|
82
|
+
lines.push('', `<i>${escapeHtml(view.emptyText)}</i>`);
|
|
83
|
+
}
|
|
84
|
+
if (view.helperText)
|
|
85
|
+
lines.push('', `<i>${escapeHtml(view.helperText)}</i>`);
|
|
86
|
+
return lines.join('\n');
|
|
87
|
+
}
|
|
88
|
+
export function renderCommandSelectionKeyboard(view) {
|
|
89
|
+
if (!view.rows.length)
|
|
90
|
+
return undefined;
|
|
91
|
+
return {
|
|
92
|
+
inline_keyboard: view.rows.map(row => row.map(button => ({
|
|
93
|
+
text: formatCommandButtonLabel(button),
|
|
94
|
+
callback_data: encodeCommandAction(button.action),
|
|
95
|
+
}))),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function buildHumanLoopPromptHtml(prompt) {
|
|
99
|
+
const question = currentHumanLoopQuestion(prompt);
|
|
100
|
+
const lines = [`<b>${escapeHtml(prompt.title)}</b>`];
|
|
101
|
+
if (prompt.detail)
|
|
102
|
+
lines.push(compactCode(prompt.detail, 40));
|
|
103
|
+
lines.push(`<i>${humanLoopAnsweredCount(prompt)}/${prompt.questions.length} answered</i>`);
|
|
104
|
+
if (!question)
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
lines.push('');
|
|
107
|
+
if (question.header.trim())
|
|
108
|
+
lines.push(`<b>${escapeHtml(question.header)}</b>`);
|
|
109
|
+
lines.push(escapeHtml(question.prompt));
|
|
110
|
+
const options = question.options || [];
|
|
111
|
+
if (options.length) {
|
|
112
|
+
lines.push('');
|
|
113
|
+
for (let i = 0; i < options.length; i++) {
|
|
114
|
+
const option = options[i];
|
|
115
|
+
lines.push(`${i + 1}. ${escapeHtml(option.label)}`);
|
|
116
|
+
if (option.description)
|
|
117
|
+
lines.push(`<i>${escapeHtml(option.description)}</i>`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (isHumanLoopAwaitingText(prompt)) {
|
|
121
|
+
lines.push('', `<i>${question.secret ? 'Reply with the secret value in chat.' : 'Reply with text in chat to answer.'}</i>`);
|
|
122
|
+
}
|
|
123
|
+
if (prompt.hint)
|
|
124
|
+
lines.push('', `<i>${escapeHtml(prompt.hint)}</i>`);
|
|
125
|
+
if (prompt.questions.length > 1) {
|
|
126
|
+
lines.push('');
|
|
127
|
+
for (let i = 0; i < prompt.questions.length; i++) {
|
|
128
|
+
const item = prompt.questions[i];
|
|
129
|
+
const summary = summarizeHumanLoopAnswer(prompt, item);
|
|
130
|
+
const answered = isHumanLoopQuestionAnswered(prompt, i);
|
|
131
|
+
lines.push(`${answered ? '●' : '○'} ${escapeHtml(item.header || item.prompt)}: ${escapeHtml(summary.display)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return lines.join('\n');
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Closed-state rendering for a resolved human-loop card. The original prompt
|
|
138
|
+
* collapses to a single header line plus one line per question showing the
|
|
139
|
+
* answer — no buttons, no "submit hint", just the decision frozen in place.
|
|
140
|
+
*/
|
|
141
|
+
export function buildAnsweredHumanLoopPromptHtml(prompt, summary) {
|
|
142
|
+
const symbol = summary.status === 'cancelled' ? '⊘' : '✓';
|
|
143
|
+
const lines = [`<b>${symbol} ${escapeHtml(prompt.title)}</b>`];
|
|
144
|
+
if (summary.status === 'cancelled')
|
|
145
|
+
lines.push('<i>Cancelled.</i>');
|
|
146
|
+
for (const row of summary.rows) {
|
|
147
|
+
lines.push(`${escapeHtml(row.label)}: <b>${escapeHtml(row.display)}</b>`);
|
|
148
|
+
}
|
|
149
|
+
return lines.join('\n');
|
|
150
|
+
}
|
|
151
|
+
function mdInline(line) {
|
|
152
|
+
// Strip inline backtick code — IM channels render them with heavy styling
|
|
153
|
+
const stripped = line.replace(/`([^`\n]+)`/g, '$1');
|
|
154
|
+
return formatMarkdownSegment(stripped);
|
|
155
|
+
}
|
|
156
|
+
function formatMarkdownSegment(text) {
|
|
157
|
+
let value = escapeHtml(text);
|
|
158
|
+
value = value.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>');
|
|
159
|
+
value = value.replace(/__(.+?)__/g, '<b>$1</b>');
|
|
160
|
+
value = value.replace(/(?<!\w)\*([^*]+?)\*(?!\w)/g, '<i>$1</i>');
|
|
161
|
+
value = value.replace(/(?<!\w)_([^_]+?)_(?!\w)/g, '<i>$1</i>');
|
|
162
|
+
value = value.replace(/~~(.+?)~~/g, '<s>$1</s>');
|
|
163
|
+
value = value.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
export function mdToTgHtml(text) {
|
|
167
|
+
const result = [];
|
|
168
|
+
const lines = text.split('\n');
|
|
169
|
+
let i = 0;
|
|
170
|
+
let inCode = false;
|
|
171
|
+
let codeLang = '';
|
|
172
|
+
let codeLines = [];
|
|
173
|
+
while (i < lines.length) {
|
|
174
|
+
const line = lines[i];
|
|
175
|
+
const stripped = line.trim();
|
|
176
|
+
if (stripped.startsWith('```')) {
|
|
177
|
+
if (!inCode) {
|
|
178
|
+
inCode = true;
|
|
179
|
+
codeLang = stripped.slice(3).trim().split(/\s/)[0] || '';
|
|
180
|
+
codeLines = [];
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
inCode = false;
|
|
184
|
+
const content = escapeHtml(codeLines.join('\n'));
|
|
185
|
+
result.push(codeLang
|
|
186
|
+
? `<pre><code class="language-${escapeHtml(codeLang)}">${content}</code></pre>`
|
|
187
|
+
: `<pre>${content}</pre>`);
|
|
188
|
+
}
|
|
189
|
+
i++;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (inCode) {
|
|
193
|
+
codeLines.push(line);
|
|
194
|
+
i++;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (stripped.startsWith('|') && stripped.endsWith('|')) {
|
|
198
|
+
const tableLines = [];
|
|
199
|
+
while (i < lines.length) {
|
|
200
|
+
const tableLine = lines[i].trim();
|
|
201
|
+
if (!tableLine.startsWith('|'))
|
|
202
|
+
break;
|
|
203
|
+
tableLines.push(tableLine);
|
|
204
|
+
i++;
|
|
205
|
+
}
|
|
206
|
+
const parsed = parseGfmTable(tableLines);
|
|
207
|
+
if (parsed && parsed.headers.length >= 2) {
|
|
208
|
+
const parts = [];
|
|
209
|
+
for (let r = 0; r < parsed.rows.length; r++) {
|
|
210
|
+
if (r > 0)
|
|
211
|
+
parts.push('');
|
|
212
|
+
const cells = parsed.rows[r];
|
|
213
|
+
parts.push(`<b>${escapeHtml(cells[0] || '')}</b>`);
|
|
214
|
+
for (let c = 1; c < parsed.headers.length; c++) {
|
|
215
|
+
parts.push(`${escapeHtml(parsed.headers[c])}: ${escapeHtml(cells[c] || '')}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
result.push(parts.join('\n'));
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const plain = tableLines.filter(l => !/^\|[\s\-:|]+\|$/.test(l.trim()));
|
|
222
|
+
if (plain.length)
|
|
223
|
+
result.push(`<pre>${escapeHtml(plain.join('\n'))}</pre>`);
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
228
|
+
if (heading) {
|
|
229
|
+
result.push(`<b>${mdInline(heading[2])}</b>`);
|
|
230
|
+
i++;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
result.push(mdInline(line));
|
|
234
|
+
i++;
|
|
235
|
+
}
|
|
236
|
+
if (inCode && codeLines.length)
|
|
237
|
+
result.push(`<pre>${escapeHtml(codeLines.join('\n'))}</pre>`);
|
|
238
|
+
return result.join('\n');
|
|
239
|
+
}
|
|
240
|
+
export function renderSessionTurnHtml(userText, assistantText) {
|
|
241
|
+
const parts = [];
|
|
242
|
+
const user = String(userText || '').trim();
|
|
243
|
+
const assistant = String(assistantText || '').trim();
|
|
244
|
+
if (user || assistant)
|
|
245
|
+
parts.push('<b>Recent Context</b>');
|
|
246
|
+
if (user)
|
|
247
|
+
parts.push(`<blockquote expandable>${escapeHtml(user)}</blockquote>`);
|
|
248
|
+
if (assistant)
|
|
249
|
+
parts.push(mdToTgHtml(assistant));
|
|
250
|
+
return parts.join('\n\n');
|
|
251
|
+
}
|
|
252
|
+
export function formatMenuLines(commands) {
|
|
253
|
+
return commands.map(cmd => `/${cmd.command} — ${escapeHtml(cmd.description)}`);
|
|
254
|
+
}
|
|
255
|
+
export function renderSkillsListHtml(d) {
|
|
256
|
+
const lines = [
|
|
257
|
+
`<b>Project Skills</b> (${d.skills.length})`,
|
|
258
|
+
'',
|
|
259
|
+
`<b>Agent:</b> ${escapeHtml(d.agent)}`,
|
|
260
|
+
`<b>Workdir:</b> <code>${escapeHtml(d.workdir)}</code>`,
|
|
261
|
+
];
|
|
262
|
+
if (!d.skills.length) {
|
|
263
|
+
lines.push('', '<i>No project skills found in .pikiloop/skills/ or .claude/commands/.</i>');
|
|
264
|
+
return lines.join('\n');
|
|
265
|
+
}
|
|
266
|
+
lines.push('');
|
|
267
|
+
for (const skill of d.skills) {
|
|
268
|
+
lines.push(`<b>/${escapeHtml(skill.command)}</b> — ${escapeHtml(skill.label)}`);
|
|
269
|
+
if (skill.description)
|
|
270
|
+
lines.push(escapeHtml(skill.description));
|
|
271
|
+
}
|
|
272
|
+
return lines.join('\n');
|
|
273
|
+
}
|
|
274
|
+
export function formatPreviewFooterHtml(agent, elapsedMs, meta, decorations) {
|
|
275
|
+
const parts = formatFooterParts(agent, elapsedMs, meta, null, decorations);
|
|
276
|
+
const primary = escapeHtml(`${footerStatusSymbol('running')} ${parts.identity}`);
|
|
277
|
+
return `${primary}\n<i>${escapeHtml(parts.runtime)}</i>`;
|
|
278
|
+
}
|
|
279
|
+
function formatFinalFooterHtml(status, agent, elapsedMs, contextPercent, decorations) {
|
|
280
|
+
const parts = formatFooterParts(agent, elapsedMs, null, contextPercent ?? null, decorations);
|
|
281
|
+
const primary = escapeHtml(`${footerStatusSymbol(status)} ${parts.identity}`);
|
|
282
|
+
return `${primary}\n<i>${escapeHtml(parts.runtime)}</i>`;
|
|
283
|
+
}
|
|
284
|
+
export function formatProviderUsageLines(usage) {
|
|
285
|
+
return buildProviderUsageLines(usage).map(line => line.bold ? `<b>${escapeHtml(line.text)}</b>` : escapeHtml(line.text));
|
|
286
|
+
}
|
|
287
|
+
export function buildInitialPreviewHtml(agent, modelOrWaiting, effortOrQueuePosition, waitingArg = false, queuePositionArg = 0) {
|
|
288
|
+
// Backwards-compat shim: legacy calls pass `(agent, waiting, queuePosition)`,
|
|
289
|
+
// new calls pass `(agent, model, effort, waiting, queuePosition)`. Normalise
|
|
290
|
+
// both shapes here so existing tests + callsites keep working.
|
|
291
|
+
let model = null;
|
|
292
|
+
let effort = null;
|
|
293
|
+
let waiting = waitingArg;
|
|
294
|
+
let queuePosition = queuePositionArg;
|
|
295
|
+
if (typeof modelOrWaiting === 'boolean') {
|
|
296
|
+
waiting = modelOrWaiting;
|
|
297
|
+
queuePosition = typeof effortOrQueuePosition === 'number' ? effortOrQueuePosition : 0;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
model = (typeof modelOrWaiting === 'string' && modelOrWaiting) ? modelOrWaiting : null;
|
|
301
|
+
effort = (typeof effortOrQueuePosition === 'string' && effortOrQueuePosition) ? effortOrQueuePosition : null;
|
|
302
|
+
}
|
|
303
|
+
const decorations = { model, effort };
|
|
304
|
+
if (waiting) {
|
|
305
|
+
const queueLabel = queuePosition > 0
|
|
306
|
+
? `Queued · ${queuePosition} ${queuePosition === 1 ? 'task' : 'tasks'} ahead`
|
|
307
|
+
: 'Waiting in queue...';
|
|
308
|
+
return `<i>${escapeHtml(queueLabel)}</i>\n\n${formatPreviewFooterHtml(agent, 0, null, decorations)}`;
|
|
309
|
+
}
|
|
310
|
+
return formatPreviewFooterHtml(agent, 0, null, decorations);
|
|
311
|
+
}
|
|
312
|
+
export function buildStreamPreviewHtml(input) {
|
|
313
|
+
const data = extractStreamPreviewData(input);
|
|
314
|
+
const parts = [];
|
|
315
|
+
if (data.planDisplay) {
|
|
316
|
+
// The first line of planDisplay is already "Plan N/M" (rendered by
|
|
317
|
+
// renderPlanForPreview). Promote it to the bold heading and inline the
|
|
318
|
+
// rest, so the card doesn't show a redundant "Plan" line above "Plan N/M".
|
|
319
|
+
const planText = data.planDisplay;
|
|
320
|
+
const nl = planText.indexOf('\n');
|
|
321
|
+
const head = nl >= 0 ? planText.slice(0, nl) : planText;
|
|
322
|
+
const body = nl >= 0 ? planText.slice(nl + 1) : '';
|
|
323
|
+
parts.push(`<blockquote><b>${escapeHtml(head)}</b>${body ? `\n${escapeHtml(body)}` : ''}</blockquote>`);
|
|
324
|
+
}
|
|
325
|
+
if (data.activityDisplay) {
|
|
326
|
+
parts.push(`<blockquote><b>Activity</b>\n${escapeHtml(trimActivityForPreview(data.activityDisplay, data.maxActivity))}</blockquote>`);
|
|
327
|
+
}
|
|
328
|
+
if (data.subAgentsDisplay) {
|
|
329
|
+
parts.push(`<blockquote><b>Sub-agent</b>\n${escapeHtml(data.subAgentsDisplay)}</blockquote>`);
|
|
330
|
+
}
|
|
331
|
+
if (data.thinkDisplay && !data.display) {
|
|
332
|
+
// Elapsed lives in the footer only (single timer). Header is the bare label.
|
|
333
|
+
parts.push(`<blockquote><b>${escapeHtml(data.label)}</b>\n${escapeHtml(data.thinkDisplay)}</blockquote>`);
|
|
334
|
+
}
|
|
335
|
+
else if (data.display) {
|
|
336
|
+
if (data.rawThinking) {
|
|
337
|
+
parts.push(`<blockquote><b>${escapeHtml(data.label)}</b>\n${escapeHtml(data.thinkSnippet)}</blockquote>`);
|
|
338
|
+
}
|
|
339
|
+
parts.push(mdToTgHtml(data.preview));
|
|
340
|
+
}
|
|
341
|
+
else if (data.thinkingProgressText) {
|
|
342
|
+
// Thinking phase with no streamed thinking/body text yet — show the bare
|
|
343
|
+
// "{thinkLabel}" so the card isn't blank. The elapsed tick lives in the
|
|
344
|
+
// footer only (one timer, not two); the footer re-renders on the channel
|
|
345
|
+
// heartbeat, so the card still visibly advances.
|
|
346
|
+
parts.push(`<blockquote><b>${escapeHtml(data.label)}</b></blockquote>`);
|
|
347
|
+
}
|
|
348
|
+
parts.push(formatPreviewFooterHtml(input.agent, input.elapsedMs, input.meta ?? null, {
|
|
349
|
+
model: input.model,
|
|
350
|
+
effort: input.effort,
|
|
351
|
+
}));
|
|
352
|
+
return parts.join('\n\n');
|
|
353
|
+
}
|
|
354
|
+
export function buildFinalReplyRender(agent, result) {
|
|
355
|
+
const data = extractFinalReplyData(agent, result);
|
|
356
|
+
const footerHtml = `\n\n${formatFinalFooterHtml(data.footerStatus, agent, data.elapsedMs, result.contextPercent ?? null, {
|
|
357
|
+
model: result.model,
|
|
358
|
+
effort: result.thinkingEffort,
|
|
359
|
+
})}`;
|
|
360
|
+
let activityHtml = '';
|
|
361
|
+
let activityNoteHtml = '';
|
|
362
|
+
if (data.activityNarrative) {
|
|
363
|
+
activityHtml = `<blockquote><b>Activity</b>\n${escapeHtml(data.activityNarrative)}</blockquote>\n\n`;
|
|
364
|
+
}
|
|
365
|
+
if (data.activityCommandSummary) {
|
|
366
|
+
activityNoteHtml = `<i>${escapeHtml(data.activityCommandSummary)}</i>\n\n`;
|
|
367
|
+
}
|
|
368
|
+
let thinkingHtml = '';
|
|
369
|
+
if (data.thinkingDisplay) {
|
|
370
|
+
thinkingHtml = `<blockquote><b>${escapeHtml(data.thinkLabel)}</b>\n${escapeHtml(data.thinkingDisplay)}</blockquote>\n\n`;
|
|
371
|
+
}
|
|
372
|
+
let statusHtml = '';
|
|
373
|
+
if (data.statusLines) {
|
|
374
|
+
statusHtml = `<blockquote expandable><b>Incomplete Response</b>\n${data.statusLines.map(escapeHtml).join('\n')}</blockquote>\n\n`;
|
|
375
|
+
}
|
|
376
|
+
const headerHtml = `${activityHtml}${activityNoteHtml}${statusHtml}${thinkingHtml}`;
|
|
377
|
+
const bodyHtml = mdToTgHtml(data.bodyMessage);
|
|
378
|
+
return {
|
|
379
|
+
fullHtml: `${headerHtml}${bodyHtml}${footerHtml}`,
|
|
380
|
+
headerHtml,
|
|
381
|
+
bodyHtml,
|
|
382
|
+
footerHtml,
|
|
383
|
+
};
|
|
384
|
+
}
|