pikiclaw 0.2.62 → 0.2.63
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/agent-auto-update.js +5 -4
- package/dist/bot-feishu-render.js +27 -100
- package/dist/bot-feishu.js +2 -1
- package/dist/bot-orchestration.js +2 -1
- package/dist/bot-render-shared.js +162 -0
- package/dist/bot-telegram-live-preview.js +4 -3
- package/dist/bot-telegram-render.js +25 -123
- package/dist/bot.js +4 -3
- package/dist/channel-feishu.js +5 -4
- package/dist/channel-telegram.js +5 -4
- package/dist/cli.js +191 -115
- package/dist/code-agent.js +8 -5
- package/dist/config-validation.js +3 -2
- package/dist/constants.js +227 -0
- package/dist/dashboard-platform.js +333 -0
- package/dist/dashboard-routes-agent.js +375 -0
- package/dist/dashboard-routes-config.js +342 -0
- package/dist/dashboard.js +65 -877
- package/dist/driver-claude.js +3 -2
- package/dist/driver-codex.js +234 -198
- package/dist/driver-gemini.js +5 -4
- package/dist/mcp-bridge.js +10 -9
- package/dist/setup-wizard.js +2 -1
- package/dist/user-config.js +2 -1
- package/package.json +1 -1
|
@@ -3,8 +3,9 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { getAgentLabel, getAgentPackage } from './agent-npm.js';
|
|
6
|
-
|
|
7
|
-
const
|
|
6
|
+
import { AGENT_UPDATE_TIMEOUTS } from './constants.js';
|
|
7
|
+
const AGENT_UPDATE_LOCK_STALE_MS = AGENT_UPDATE_TIMEOUTS.lockStale;
|
|
8
|
+
const AGENT_UPDATE_COMMAND_TIMEOUT_MS = AGENT_UPDATE_TIMEOUTS.commandTimeout;
|
|
8
9
|
function updaterLockPath() {
|
|
9
10
|
return path.join(os.homedir(), '.pikiclaw', 'agent-auto-update.lock');
|
|
10
11
|
}
|
|
@@ -95,11 +96,11 @@ async function runCommand(cmd, args, opts = {}) {
|
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
async function getNpmGlobalPrefix() {
|
|
98
|
-
const result = await runCommand('npm', ['prefix', '-g'], { timeoutMs:
|
|
99
|
+
const result = await runCommand('npm', ['prefix', '-g'], { timeoutMs: AGENT_UPDATE_TIMEOUTS.npmPrefix });
|
|
99
100
|
return result.ok ? result.stdout.trim().split('\n')[0] || null : null;
|
|
100
101
|
}
|
|
101
102
|
async function getLatestPackageVersion(pkg) {
|
|
102
|
-
const result = await runCommand('npm', ['view', pkg, 'version', '--json'], { timeoutMs:
|
|
103
|
+
const result = await runCommand('npm', ['view', pkg, 'version', '--json'], { timeoutMs: AGENT_UPDATE_TIMEOUTS.npmView });
|
|
103
104
|
if (!result.ok)
|
|
104
105
|
return null;
|
|
105
106
|
const raw = result.stdout.trim();
|
|
@@ -5,64 +5,21 @@
|
|
|
5
5
|
* Also provides a LivePreviewRenderer for streaming output.
|
|
6
6
|
*/
|
|
7
7
|
import { encodeCommandAction } from './bot-command-ui.js';
|
|
8
|
-
import { fmtUptime, fmtTokens, fmtBytes
|
|
8
|
+
import { fmtUptime, fmtTokens, fmtBytes } from './bot.js';
|
|
9
9
|
import { summarizePromptForStatus } from './bot-commands.js';
|
|
10
|
-
import {
|
|
11
|
-
import { formatActivityCommandSummary, parseActivitySummary, renderPlanForPreview, summarizeActivityForPreview } from './bot-streaming.js';
|
|
10
|
+
import { footerStatusSymbol, formatFooterSummary, trimActivityForPreview, buildProviderUsageLines, extractFinalReplyData, extractStreamPreviewData, } from './bot-render-shared.js';
|
|
12
11
|
import { currentHumanLoopQuestion, humanLoopAnsweredCount, isHumanLoopAwaitingText, isHumanLoopQuestionAnswered, summarizeHumanLoopAnswer, } from './human-loop.js';
|
|
13
12
|
import path from 'node:path';
|
|
14
13
|
import { listSubdirs } from './bot.js';
|
|
15
14
|
// ---------------------------------------------------------------------------
|
|
16
15
|
// Helpers
|
|
17
16
|
// ---------------------------------------------------------------------------
|
|
18
|
-
function fmtCompactUptime(ms) {
|
|
19
|
-
return fmtUptime(ms).replace(/\s+/g, '');
|
|
20
|
-
}
|
|
21
|
-
function footerStatusSymbol(status) {
|
|
22
|
-
switch (status) {
|
|
23
|
-
case 'running': return '●';
|
|
24
|
-
case 'done': return '✓';
|
|
25
|
-
case 'failed': return '✗';
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function formatFooterSummary(agent, elapsedMs, meta, contextPercent) {
|
|
29
|
-
const parts = [agent];
|
|
30
|
-
const ctx = contextPercent ?? meta?.contextPercent ?? null;
|
|
31
|
-
if (ctx != null)
|
|
32
|
-
parts.push(`${ctx}%`);
|
|
33
|
-
parts.push(fmtCompactUptime(Math.max(0, Math.round(elapsedMs))));
|
|
34
|
-
return parts.join(' · ');
|
|
35
|
-
}
|
|
36
17
|
function formatPreviewFooter(agent, elapsedMs, meta) {
|
|
37
18
|
return `${footerStatusSymbol('running')} ${formatFooterSummary(agent, elapsedMs, meta)}`;
|
|
38
19
|
}
|
|
39
20
|
function formatFinalFooter(status, agent, elapsedMs, contextPercent) {
|
|
40
21
|
return `${footerStatusSymbol(status)} ${formatFooterSummary(agent, elapsedMs, null, contextPercent ?? null)}`;
|
|
41
22
|
}
|
|
42
|
-
function trimActivityForPreview(text, maxChars = 900) {
|
|
43
|
-
if (text.length <= maxChars)
|
|
44
|
-
return text;
|
|
45
|
-
const lines = text.split('\n').filter(l => l.trim());
|
|
46
|
-
if (lines.length <= 1)
|
|
47
|
-
return text.slice(0, Math.max(0, maxChars - 3)).trimEnd() + '...';
|
|
48
|
-
const tailCount = Math.min(2, Math.max(1, lines.length - 1));
|
|
49
|
-
const tail = lines.slice(-tailCount);
|
|
50
|
-
const headCandidates = lines.slice(0, Math.max(0, lines.length - tailCount));
|
|
51
|
-
const reserved = tail.join('\n').length + 5;
|
|
52
|
-
const budget = Math.max(0, maxChars - reserved);
|
|
53
|
-
const head = [];
|
|
54
|
-
let used = 0;
|
|
55
|
-
for (const line of headCandidates) {
|
|
56
|
-
const extra = line.length + (head.length ? 1 : 0);
|
|
57
|
-
if (used + extra > budget)
|
|
58
|
-
break;
|
|
59
|
-
head.push(line);
|
|
60
|
-
used += extra;
|
|
61
|
-
}
|
|
62
|
-
if (!head.length)
|
|
63
|
-
return text.slice(0, Math.max(0, maxChars - 3)).trimEnd() + '...';
|
|
64
|
-
return [...head, '...', ...tail].join('\n');
|
|
65
|
-
}
|
|
66
23
|
function truncateLabel(label, maxChars = 24) {
|
|
67
24
|
return label.length > maxChars ? `${label.slice(0, Math.max(1, maxChars - 1))}…` : label;
|
|
68
25
|
}
|
|
@@ -212,31 +169,22 @@ export function buildInitialPreviewMarkdown(agent, model, effort, waiting = fals
|
|
|
212
169
|
return parts.join(' · ');
|
|
213
170
|
}
|
|
214
171
|
function buildPreviewMarkdown(input, options) {
|
|
215
|
-
const
|
|
216
|
-
const display = input.bodyText.trim();
|
|
217
|
-
const rawThinking = input.thinking.trim();
|
|
218
|
-
const thinkDisplay = formatThinkingForDisplay(input.thinking, maxBody);
|
|
219
|
-
const planDisplay = renderPlanForPreview(input.plan ?? null);
|
|
220
|
-
const activityDisplay = summarizeActivityForPreview(input.activity);
|
|
221
|
-
const maxActivity = !display && !thinkDisplay && !planDisplay ? 2400 : 1400;
|
|
172
|
+
const data = extractStreamPreviewData(input);
|
|
222
173
|
const parts = [];
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
parts.push(`**Plan**\n${planDisplay}`);
|
|
174
|
+
if (data.planDisplay) {
|
|
175
|
+
parts.push(`**Plan**\n${data.planDisplay}`);
|
|
226
176
|
}
|
|
227
|
-
if (activityDisplay) {
|
|
228
|
-
parts.push(`**Activity**\n${trimActivityForPreview(activityDisplay, maxActivity)}`);
|
|
177
|
+
if (data.activityDisplay) {
|
|
178
|
+
parts.push(`**Activity**\n${trimActivityForPreview(data.activityDisplay, data.maxActivity)}`);
|
|
229
179
|
}
|
|
230
|
-
if (thinkDisplay && !display) {
|
|
231
|
-
parts.push(`**${label}**\n${thinkDisplay}`);
|
|
180
|
+
if (data.thinkDisplay && !data.display) {
|
|
181
|
+
parts.push(`**${data.label}**\n${data.thinkDisplay}`);
|
|
232
182
|
}
|
|
233
|
-
else if (display) {
|
|
234
|
-
if (rawThinking) {
|
|
235
|
-
|
|
236
|
-
parts.push(`**${label}**\n${thinkSnippet}`);
|
|
183
|
+
else if (data.display) {
|
|
184
|
+
if (data.rawThinking) {
|
|
185
|
+
parts.push(`**${data.label}**\n${data.thinkSnippet}`);
|
|
237
186
|
}
|
|
238
|
-
|
|
239
|
-
parts.push(preview);
|
|
187
|
+
parts.push(data.preview);
|
|
240
188
|
}
|
|
241
189
|
if (options?.includeFooter !== false) {
|
|
242
190
|
parts.push(formatPreviewFooter(input.agent, input.elapsedMs, input.meta ?? null));
|
|
@@ -260,46 +208,26 @@ export const feishuStreamingPreviewRenderer = {
|
|
|
260
208
|
renderStream: buildStreamingBodyMarkdown,
|
|
261
209
|
};
|
|
262
210
|
export function buildFinalReplyRender(agent, result) {
|
|
263
|
-
const
|
|
264
|
-
const footerText = `\n\n${formatFinalFooter(footerStatus, agent,
|
|
211
|
+
const data = extractFinalReplyData(agent, result);
|
|
212
|
+
const footerText = `\n\n${formatFinalFooter(data.footerStatus, agent, data.elapsedMs, result.contextPercent ?? null)}`;
|
|
265
213
|
let activityText = '';
|
|
266
214
|
let activityNoteText = '';
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (display.length > 1600)
|
|
273
|
-
display = '...\n' + display.slice(-1600);
|
|
274
|
-
activityText = `**Activity**\n${display}\n\n`;
|
|
275
|
-
}
|
|
276
|
-
const commandSummary = formatActivityCommandSummary(summary.completedCommands, summary.activeCommands, summary.failedCommands);
|
|
277
|
-
if (commandSummary)
|
|
278
|
-
activityNoteText = `*${commandSummary}*\n\n`;
|
|
215
|
+
if (data.activityNarrative) {
|
|
216
|
+
activityText = `**Activity**\n${data.activityNarrative}\n\n`;
|
|
217
|
+
}
|
|
218
|
+
if (data.activityCommandSummary) {
|
|
219
|
+
activityNoteText = `*${data.activityCommandSummary}*\n\n`;
|
|
279
220
|
}
|
|
280
221
|
let thinkingText = '';
|
|
281
|
-
if (
|
|
282
|
-
thinkingText = `**${thinkLabel
|
|
222
|
+
if (data.thinkingDisplay) {
|
|
223
|
+
thinkingText = `**${data.thinkLabel}**\n${data.thinkingDisplay}\n\n`;
|
|
283
224
|
}
|
|
284
225
|
let statusText = '';
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
if (result.stopReason === 'max_tokens')
|
|
288
|
-
statusLines.push('Output limit reached. Response may be truncated.');
|
|
289
|
-
if (result.stopReason === 'timeout') {
|
|
290
|
-
statusLines.push(`Timed out after ${fmtUptime(Math.max(0, Math.round(result.elapsedS * 1000)))} before the agent reported completion.`);
|
|
291
|
-
}
|
|
292
|
-
if (!result.ok) {
|
|
293
|
-
const detail = result.error?.trim();
|
|
294
|
-
if (detail && detail !== result.message.trim() && !statusLines.includes(detail))
|
|
295
|
-
statusLines.push(detail);
|
|
296
|
-
else if (result.stopReason !== 'timeout')
|
|
297
|
-
statusLines.push('Agent exited before reporting completion.');
|
|
298
|
-
}
|
|
299
|
-
statusText = `**⚠ Incomplete Response**\n${statusLines.join('\n')}\n\n`;
|
|
226
|
+
if (data.statusLines) {
|
|
227
|
+
statusText = `**⚠ Incomplete Response**\n${data.statusLines.join('\n')}\n\n`;
|
|
300
228
|
}
|
|
301
229
|
const headerText = `${activityText}${activityNoteText}${statusText}${thinkingText}`;
|
|
302
|
-
const bodyText =
|
|
230
|
+
const bodyText = data.bodyMessage;
|
|
303
231
|
return {
|
|
304
232
|
fullText: `${headerText}${bodyText}${footerText}`,
|
|
305
233
|
headerText,
|
|
@@ -474,12 +402,11 @@ export function renderStatus(d) {
|
|
|
474
402
|
lines.push(`**Running:** ${fmtUptime(Date.now() - d.running.startedAt)} - ${summarizePromptForStatus(d.running.prompt)}`);
|
|
475
403
|
}
|
|
476
404
|
// Provider usage
|
|
477
|
-
const usageLines =
|
|
405
|
+
const usageLines = buildProviderUsageLines(d.usage);
|
|
478
406
|
if (usageLines.length > 1) {
|
|
479
407
|
lines.push('');
|
|
480
|
-
// Strip HTML tags from usage lines (they're HTML-formatted)
|
|
481
408
|
for (const line of usageLines) {
|
|
482
|
-
lines.push(line.
|
|
409
|
+
lines.push(line.text);
|
|
483
410
|
}
|
|
484
411
|
}
|
|
485
412
|
lines.push('', '**Bot Usage**', ` Turns: ${d.stats.totalTurns}`);
|
package/dist/bot-feishu.js
CHANGED
|
@@ -24,6 +24,7 @@ import { currentHumanLoopQuestion, humanLoopOptionSelected } from './human-loop.
|
|
|
24
24
|
import { FeishuChannel } from './channel-feishu.js';
|
|
25
25
|
import { splitText, supportsChannelCapability } from './channel-base.js';
|
|
26
26
|
import { getActiveUserConfig } from './user-config.js';
|
|
27
|
+
import { FEISHU_BOT_CARD_MAX } from './constants.js';
|
|
27
28
|
const SHUTDOWN_EXIT_CODE = {
|
|
28
29
|
SIGINT: 130,
|
|
29
30
|
SIGTERM: 143,
|
|
@@ -640,7 +641,7 @@ export class FeishuBot extends Bot {
|
|
|
640
641
|
async sendFinalReply(ctx, placeholderId, agent, result) {
|
|
641
642
|
const rendered = buildFinalReplyRender(agent, result);
|
|
642
643
|
const messageIds = [];
|
|
643
|
-
const MAX_CARD =
|
|
644
|
+
const MAX_CARD = FEISHU_BOT_CARD_MAX;
|
|
644
645
|
if (rendered.fullText.length <= MAX_CARD) {
|
|
645
646
|
// Fits in one card — edit the placeholder
|
|
646
647
|
if (placeholderId) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildDefaultMenuCommands } from './bot-menu.js';
|
|
2
|
-
|
|
2
|
+
import { BOT_SHUTDOWN_FORCE_EXIT_MS as _BOT_SHUTDOWN_FORCE_EXIT_MS } from './constants.js';
|
|
3
|
+
export const BOT_SHUTDOWN_FORCE_EXIT_MS = _BOT_SHUTDOWN_FORCE_EXIT_MS;
|
|
3
4
|
export function buildBotMenuState(bot) {
|
|
4
5
|
const agents = bot.fetchAgents().agents;
|
|
5
6
|
const installedCount = agents.filter(agent => agent.installed).length;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bot-render-shared.ts — Shared rendering logic used by both Telegram and Feishu renderers.
|
|
3
|
+
*
|
|
4
|
+
* Contains types, pure-data helpers, and functions that are identical across platforms.
|
|
5
|
+
* Platform-specific formatting (HTML vs Markdown) stays in the respective render files.
|
|
6
|
+
*/
|
|
7
|
+
import { fmtUptime, formatThinkingForDisplay, thinkLabel } from './bot.js';
|
|
8
|
+
import { formatActivityCommandSummary, parseActivitySummary, renderPlanForPreview, summarizeActivityForPreview } from './bot-streaming.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Footer helpers
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
export function fmtCompactUptime(ms) {
|
|
13
|
+
return fmtUptime(ms).replace(/\s+/g, '');
|
|
14
|
+
}
|
|
15
|
+
export function footerStatusSymbol(status) {
|
|
16
|
+
switch (status) {
|
|
17
|
+
case 'running': return '●';
|
|
18
|
+
case 'done': return '✓';
|
|
19
|
+
case 'failed': return '✗';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function formatFooterSummary(agent, elapsedMs, meta, contextPercent) {
|
|
23
|
+
const parts = [agent];
|
|
24
|
+
const ctx = contextPercent ?? meta?.contextPercent ?? null;
|
|
25
|
+
if (ctx != null)
|
|
26
|
+
parts.push(`${ctx}%`);
|
|
27
|
+
parts.push(fmtCompactUptime(Math.max(0, Math.round(elapsedMs))));
|
|
28
|
+
return parts.join(' · ');
|
|
29
|
+
}
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Activity trimming
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
export function trimActivityForPreview(text, maxChars = 900) {
|
|
34
|
+
if (text.length <= maxChars)
|
|
35
|
+
return text;
|
|
36
|
+
const lines = text.split('\n').filter(line => line.trim());
|
|
37
|
+
if (lines.length <= 1)
|
|
38
|
+
return text.slice(0, Math.max(0, maxChars - 3)).trimEnd() + '...';
|
|
39
|
+
const tailCount = Math.min(2, Math.max(1, lines.length - 1));
|
|
40
|
+
const tail = lines.slice(-tailCount);
|
|
41
|
+
const headCandidates = lines.slice(0, Math.max(0, lines.length - tailCount));
|
|
42
|
+
const reserved = tail.join('\n').length + 5;
|
|
43
|
+
const budget = Math.max(0, maxChars - reserved);
|
|
44
|
+
const head = [];
|
|
45
|
+
let used = 0;
|
|
46
|
+
for (const line of headCandidates) {
|
|
47
|
+
const extra = line.length + (head.length ? 1 : 0);
|
|
48
|
+
if (used + extra > budget)
|
|
49
|
+
break;
|
|
50
|
+
head.push(line);
|
|
51
|
+
used += extra;
|
|
52
|
+
}
|
|
53
|
+
if (!head.length)
|
|
54
|
+
return text.slice(0, Math.max(0, maxChars - 3)).trimEnd() + '...';
|
|
55
|
+
return [...head, '...', ...tail].join('\n');
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Provider usage (plain-text builder — caller wraps as needed)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
function rawUsageLine(parts) {
|
|
61
|
+
return parts.filter(part => !!part && String(part).trim()).join(' ');
|
|
62
|
+
}
|
|
63
|
+
export function buildProviderUsageLines(usage) {
|
|
64
|
+
const lines = [
|
|
65
|
+
{ text: '', bold: false },
|
|
66
|
+
{ text: 'Provider Usage', bold: true },
|
|
67
|
+
];
|
|
68
|
+
if (!usage.ok) {
|
|
69
|
+
lines.push({ text: ` Unavailable: ${usage.error || 'No recent usage data found.'}` });
|
|
70
|
+
return lines;
|
|
71
|
+
}
|
|
72
|
+
if (usage.capturedAt) {
|
|
73
|
+
const capturedAtMs = Date.parse(usage.capturedAt);
|
|
74
|
+
if (Number.isFinite(capturedAtMs)) {
|
|
75
|
+
lines.push({ text: ` Updated: ${fmtUptime(Math.max(0, Date.now() - capturedAtMs))} ago` });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!usage.windows.length) {
|
|
79
|
+
lines.push({ text: ` ${usage.status ? `status=${usage.status}` : 'No window data'}` });
|
|
80
|
+
return lines;
|
|
81
|
+
}
|
|
82
|
+
for (const window of usage.windows) {
|
|
83
|
+
const details = rawUsageLine([
|
|
84
|
+
window.usedPercent != null ? `${window.usedPercent}% used` : null,
|
|
85
|
+
window.status ? `status=${window.status}` : null,
|
|
86
|
+
window.resetAfterSeconds != null ? `resetAfterSeconds=${window.resetAfterSeconds}` : null,
|
|
87
|
+
]);
|
|
88
|
+
lines.push({ text: ` ${window.label}: ${details || 'No details'}` });
|
|
89
|
+
}
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
92
|
+
export function extractFinalReplyData(agent, result) {
|
|
93
|
+
const footerStatus = result.incomplete || !result.ok ? 'failed' : 'done';
|
|
94
|
+
const elapsedMs = result.elapsedS * 1000;
|
|
95
|
+
const footerSummary = formatFooterSummary(agent, elapsedMs, null, result.contextPercent ?? null);
|
|
96
|
+
let activityNarrative = null;
|
|
97
|
+
let activityCommandSummary = null;
|
|
98
|
+
if (result.activity) {
|
|
99
|
+
const summary = parseActivitySummary(result.activity);
|
|
100
|
+
const narrative = summary.narrative.join('\n');
|
|
101
|
+
if (narrative) {
|
|
102
|
+
activityNarrative = narrative.length > 1600 ? '...\n' + narrative.slice(-1600) : narrative;
|
|
103
|
+
}
|
|
104
|
+
const cmdSummary = formatActivityCommandSummary(summary.completedCommands, summary.activeCommands, summary.failedCommands);
|
|
105
|
+
if (cmdSummary)
|
|
106
|
+
activityCommandSummary = cmdSummary;
|
|
107
|
+
}
|
|
108
|
+
let thinkingDisplay = null;
|
|
109
|
+
if (result.thinking) {
|
|
110
|
+
thinkingDisplay = formatThinkingForDisplay(result.thinking, 1600);
|
|
111
|
+
}
|
|
112
|
+
let statusLines = null;
|
|
113
|
+
if (result.incomplete) {
|
|
114
|
+
statusLines = [];
|
|
115
|
+
if (result.stopReason === 'max_tokens')
|
|
116
|
+
statusLines.push('Output limit reached. Response may be truncated.');
|
|
117
|
+
if (result.stopReason === 'timeout') {
|
|
118
|
+
statusLines.push(`Timed out after ${fmtUptime(Math.max(0, Math.round(elapsedMs)))} before the agent reported completion.`);
|
|
119
|
+
}
|
|
120
|
+
if (!result.ok) {
|
|
121
|
+
const detail = result.error?.trim();
|
|
122
|
+
if (detail && detail !== result.message.trim() && !statusLines.includes(detail))
|
|
123
|
+
statusLines.push(detail);
|
|
124
|
+
else if (result.stopReason !== 'timeout')
|
|
125
|
+
statusLines.push('Agent exited before reporting completion.');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
footerStatus,
|
|
130
|
+
footerSummary,
|
|
131
|
+
activityNarrative,
|
|
132
|
+
activityCommandSummary,
|
|
133
|
+
thinkingDisplay,
|
|
134
|
+
thinkLabel: thinkLabel(agent),
|
|
135
|
+
statusLines,
|
|
136
|
+
bodyMessage: result.message,
|
|
137
|
+
elapsedMs,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export function extractStreamPreviewData(input) {
|
|
141
|
+
const maxBody = 2400;
|
|
142
|
+
const display = input.bodyText.trim();
|
|
143
|
+
const rawThinking = input.thinking.trim();
|
|
144
|
+
const thinkDisplay = formatThinkingForDisplay(input.thinking, maxBody);
|
|
145
|
+
const planDisplay = renderPlanForPreview(input.plan ?? null);
|
|
146
|
+
const activityDisplay = summarizeActivityForPreview(input.activity);
|
|
147
|
+
const maxActivity = !display && !thinkDisplay && !planDisplay ? 2400 : 1400;
|
|
148
|
+
const label = thinkLabel(input.agent);
|
|
149
|
+
const thinkSnippet = rawThinking ? formatThinkingForDisplay(input.thinking, 600) : '';
|
|
150
|
+
const preview = display.length > maxBody ? '(...truncated)\n' + display.slice(-maxBody) : display;
|
|
151
|
+
return {
|
|
152
|
+
display,
|
|
153
|
+
rawThinking,
|
|
154
|
+
thinkDisplay,
|
|
155
|
+
planDisplay,
|
|
156
|
+
activityDisplay,
|
|
157
|
+
maxActivity,
|
|
158
|
+
label,
|
|
159
|
+
thinkSnippet,
|
|
160
|
+
preview,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { hasPreviewMeta, samePreviewMeta, samePreviewPlan } from './bot-streaming.js';
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const
|
|
2
|
+
import { STREAM_PREVIEW_TIMEOUTS } from './constants.js';
|
|
3
|
+
const STREAM_PREVIEW_HEARTBEAT_MS = STREAM_PREVIEW_TIMEOUTS.heartbeat;
|
|
4
|
+
const STREAM_TYPING_HEARTBEAT_MS = STREAM_PREVIEW_TIMEOUTS.typing;
|
|
5
|
+
const STREAM_STALLED_NOTICE_MS = STREAM_PREVIEW_TIMEOUTS.stalledNotice;
|
|
5
6
|
// ---------------------------------------------------------------------------
|
|
6
7
|
// LivePreview — generic streaming preview controller
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { encodeCommandAction } from './bot-command-ui.js';
|
|
2
|
-
import { fmtUptime, formatThinkingForDisplay, thinkLabel } from './bot.js';
|
|
3
|
-
import { formatActivityCommandSummary, parseActivitySummary, renderPlanForPreview, summarizeActivityForPreview } from './bot-streaming.js';
|
|
4
2
|
import { currentHumanLoopQuestion, humanLoopAnsweredCount, isHumanLoopAwaitingText, isHumanLoopQuestionAnswered, summarizeHumanLoopAnswer, } from './human-loop.js';
|
|
3
|
+
import { footerStatusSymbol, formatFooterSummary, trimActivityForPreview, buildProviderUsageLines, extractFinalReplyData, extractStreamPreviewData, } from './bot-render-shared.js';
|
|
5
4
|
export function escapeHtml(t) {
|
|
6
5
|
return t.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
7
6
|
}
|
|
@@ -250,82 +249,14 @@ export function renderSkillsListHtml(d) {
|
|
|
250
249
|
}
|
|
251
250
|
return lines.join('\n');
|
|
252
251
|
}
|
|
253
|
-
function fmtCompactUptime(ms) {
|
|
254
|
-
return fmtUptime(ms).replace(/\s+/g, '');
|
|
255
|
-
}
|
|
256
|
-
function footerStatusSymbol(status) {
|
|
257
|
-
switch (status) {
|
|
258
|
-
case 'running': return '●';
|
|
259
|
-
case 'done': return '✓';
|
|
260
|
-
case 'failed': return '✗';
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
function formatFooterSummary(agent, elapsedMs, meta, contextPercent) {
|
|
264
|
-
const parts = [agent];
|
|
265
|
-
const ctx = contextPercent ?? meta?.contextPercent ?? null;
|
|
266
|
-
if (ctx != null)
|
|
267
|
-
parts.push(`${ctx}%`);
|
|
268
|
-
parts.push(fmtCompactUptime(Math.max(0, Math.round(elapsedMs))));
|
|
269
|
-
return parts.join(' · ');
|
|
270
|
-
}
|
|
271
252
|
export function formatPreviewFooterHtml(agent, elapsedMs, meta) {
|
|
272
253
|
return escapeHtml(`${footerStatusSymbol('running')} ${formatFooterSummary(agent, elapsedMs, meta)}`);
|
|
273
254
|
}
|
|
274
255
|
function formatFinalFooterHtml(status, agent, elapsedMs, contextPercent) {
|
|
275
256
|
return escapeHtml(`${footerStatusSymbol(status)} ${formatFooterSummary(agent, elapsedMs, null, contextPercent ?? null)}`);
|
|
276
257
|
}
|
|
277
|
-
function rawUsageLine(parts) {
|
|
278
|
-
return parts.filter(part => !!part && String(part).trim()).join(' ');
|
|
279
|
-
}
|
|
280
258
|
export function formatProviderUsageLines(usage) {
|
|
281
|
-
|
|
282
|
-
if (!usage.ok) {
|
|
283
|
-
lines.push(` Unavailable: ${escapeHtml(usage.error || 'No recent usage data found.')}`);
|
|
284
|
-
return lines;
|
|
285
|
-
}
|
|
286
|
-
if (usage.capturedAt) {
|
|
287
|
-
const capturedAtMs = Date.parse(usage.capturedAt);
|
|
288
|
-
if (Number.isFinite(capturedAtMs)) {
|
|
289
|
-
lines.push(` Updated: ${fmtUptime(Math.max(0, Date.now() - capturedAtMs))} ago`);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
if (!usage.windows.length) {
|
|
293
|
-
lines.push(` ${escapeHtml(usage.status ? `status=${usage.status}` : 'No window data')}`);
|
|
294
|
-
return lines;
|
|
295
|
-
}
|
|
296
|
-
for (const window of usage.windows) {
|
|
297
|
-
const details = rawUsageLine([
|
|
298
|
-
window.usedPercent != null ? `${window.usedPercent}% used` : null,
|
|
299
|
-
window.status ? `status=${window.status}` : null,
|
|
300
|
-
window.resetAfterSeconds != null ? `resetAfterSeconds=${window.resetAfterSeconds}` : null,
|
|
301
|
-
]);
|
|
302
|
-
lines.push(` ${escapeHtml(window.label)}: ${escapeHtml(details || 'No details')}`);
|
|
303
|
-
}
|
|
304
|
-
return lines;
|
|
305
|
-
}
|
|
306
|
-
function trimActivityForPreview(text, maxChars = 900) {
|
|
307
|
-
if (text.length <= maxChars)
|
|
308
|
-
return text;
|
|
309
|
-
const lines = text.split('\n').filter(line => line.trim());
|
|
310
|
-
if (lines.length <= 1)
|
|
311
|
-
return text.slice(0, Math.max(0, maxChars - 3)).trimEnd() + '...';
|
|
312
|
-
const tailCount = Math.min(2, Math.max(1, lines.length - 1));
|
|
313
|
-
const tail = lines.slice(-tailCount);
|
|
314
|
-
const headCandidates = lines.slice(0, Math.max(0, lines.length - tailCount));
|
|
315
|
-
const reserved = tail.join('\n').length + 5;
|
|
316
|
-
const budget = Math.max(0, maxChars - reserved);
|
|
317
|
-
const head = [];
|
|
318
|
-
let used = 0;
|
|
319
|
-
for (const line of headCandidates) {
|
|
320
|
-
const extra = line.length + (head.length ? 1 : 0);
|
|
321
|
-
if (used + extra > budget)
|
|
322
|
-
break;
|
|
323
|
-
head.push(line);
|
|
324
|
-
used += extra;
|
|
325
|
-
}
|
|
326
|
-
if (!head.length)
|
|
327
|
-
return text.slice(0, Math.max(0, maxChars - 3)).trimEnd() + '...';
|
|
328
|
-
return [...head, '...', ...tail].join('\n');
|
|
259
|
+
return buildProviderUsageLines(usage).map(line => line.bold ? `<b>${escapeHtml(line.text)}</b>` : escapeHtml(line.text));
|
|
329
260
|
}
|
|
330
261
|
export function buildInitialPreviewHtml(agent, waiting = false) {
|
|
331
262
|
return waiting
|
|
@@ -333,76 +264,47 @@ export function buildInitialPreviewHtml(agent, waiting = false) {
|
|
|
333
264
|
: formatPreviewFooterHtml(agent, 0);
|
|
334
265
|
}
|
|
335
266
|
export function buildStreamPreviewHtml(input) {
|
|
336
|
-
const
|
|
337
|
-
const display = input.bodyText.trim();
|
|
338
|
-
const rawThinking = input.thinking.trim();
|
|
339
|
-
const thinkDisplay = formatThinkingForDisplay(input.thinking, maxBody);
|
|
340
|
-
const planDisplay = renderPlanForPreview(input.plan ?? null);
|
|
341
|
-
const activityDisplay = summarizeActivityForPreview(input.activity);
|
|
342
|
-
const maxActivity = !display && !thinkDisplay && !planDisplay ? 2400 : 1400;
|
|
267
|
+
const data = extractStreamPreviewData(input);
|
|
343
268
|
const parts = [];
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
parts.push(`<blockquote><b>Plan</b>\n${escapeHtml(planDisplay)}</blockquote>`);
|
|
269
|
+
if (data.planDisplay) {
|
|
270
|
+
parts.push(`<blockquote><b>Plan</b>\n${escapeHtml(data.planDisplay)}</blockquote>`);
|
|
347
271
|
}
|
|
348
|
-
if (activityDisplay) {
|
|
349
|
-
parts.push(`<blockquote><b>Activity</b>\n${escapeHtml(trimActivityForPreview(activityDisplay, maxActivity))}</blockquote>`);
|
|
272
|
+
if (data.activityDisplay) {
|
|
273
|
+
parts.push(`<blockquote><b>Activity</b>\n${escapeHtml(trimActivityForPreview(data.activityDisplay, data.maxActivity))}</blockquote>`);
|
|
350
274
|
}
|
|
351
|
-
if (thinkDisplay && !display) {
|
|
352
|
-
parts.push(`<blockquote><b>${escapeHtml(label)}</b>\n${escapeHtml(thinkDisplay)}</blockquote>`);
|
|
275
|
+
if (data.thinkDisplay && !data.display) {
|
|
276
|
+
parts.push(`<blockquote><b>${escapeHtml(data.label)}</b>\n${escapeHtml(data.thinkDisplay)}</blockquote>`);
|
|
353
277
|
}
|
|
354
|
-
else if (display) {
|
|
355
|
-
if (rawThinking) {
|
|
356
|
-
|
|
357
|
-
parts.push(`<blockquote><b>${escapeHtml(label)}</b>\n${escapeHtml(thinkSnippet)}</blockquote>`);
|
|
278
|
+
else if (data.display) {
|
|
279
|
+
if (data.rawThinking) {
|
|
280
|
+
parts.push(`<blockquote><b>${escapeHtml(data.label)}</b>\n${escapeHtml(data.thinkSnippet)}</blockquote>`);
|
|
358
281
|
}
|
|
359
|
-
|
|
360
|
-
parts.push(mdToTgHtml(preview));
|
|
282
|
+
parts.push(mdToTgHtml(data.preview));
|
|
361
283
|
}
|
|
362
284
|
parts.push(formatPreviewFooterHtml(input.agent, input.elapsedMs, input.meta ?? null));
|
|
363
285
|
return parts.join('\n\n');
|
|
364
286
|
}
|
|
365
287
|
export function buildFinalReplyRender(agent, result) {
|
|
366
|
-
const
|
|
367
|
-
const footerHtml = `\n\n${formatFinalFooterHtml(footerStatus, agent,
|
|
288
|
+
const data = extractFinalReplyData(agent, result);
|
|
289
|
+
const footerHtml = `\n\n${formatFinalFooterHtml(data.footerStatus, agent, data.elapsedMs, result.contextPercent ?? null)}`;
|
|
368
290
|
let activityHtml = '';
|
|
369
291
|
let activityNoteHtml = '';
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (display.length > 1600)
|
|
376
|
-
display = '...\n' + display.slice(-1600);
|
|
377
|
-
activityHtml = `<blockquote><b>Activity</b>\n${escapeHtml(display)}</blockquote>\n\n`;
|
|
378
|
-
}
|
|
379
|
-
const commandSummary = formatActivityCommandSummary(summary.completedCommands, summary.activeCommands, summary.failedCommands);
|
|
380
|
-
if (commandSummary)
|
|
381
|
-
activityNoteHtml = `<i>${escapeHtml(commandSummary)}</i>\n\n`;
|
|
292
|
+
if (data.activityNarrative) {
|
|
293
|
+
activityHtml = `<blockquote><b>Activity</b>\n${escapeHtml(data.activityNarrative)}</blockquote>\n\n`;
|
|
294
|
+
}
|
|
295
|
+
if (data.activityCommandSummary) {
|
|
296
|
+
activityNoteHtml = `<i>${escapeHtml(data.activityCommandSummary)}</i>\n\n`;
|
|
382
297
|
}
|
|
383
298
|
let thinkingHtml = '';
|
|
384
|
-
if (
|
|
385
|
-
thinkingHtml = `<blockquote><b>${thinkLabel
|
|
299
|
+
if (data.thinkingDisplay) {
|
|
300
|
+
thinkingHtml = `<blockquote><b>${escapeHtml(data.thinkLabel)}</b>\n${escapeHtml(data.thinkingDisplay)}</blockquote>\n\n`;
|
|
386
301
|
}
|
|
387
302
|
let statusHtml = '';
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
if (result.stopReason === 'max_tokens')
|
|
391
|
-
statusLines.push('Output limit reached. Response may be truncated.');
|
|
392
|
-
if (result.stopReason === 'timeout') {
|
|
393
|
-
statusLines.push(`Timed out after ${fmtUptime(Math.max(0, Math.round(result.elapsedS * 1000)))} before the agent reported completion.`);
|
|
394
|
-
}
|
|
395
|
-
if (!result.ok) {
|
|
396
|
-
const detail = result.error?.trim();
|
|
397
|
-
if (detail && detail !== result.message.trim() && !statusLines.includes(detail))
|
|
398
|
-
statusLines.push(detail);
|
|
399
|
-
else if (result.stopReason !== 'timeout')
|
|
400
|
-
statusLines.push('Agent exited before reporting completion.');
|
|
401
|
-
}
|
|
402
|
-
statusHtml = `<blockquote expandable><b>Incomplete Response</b>\n${statusLines.map(escapeHtml).join('\n')}</blockquote>\n\n`;
|
|
303
|
+
if (data.statusLines) {
|
|
304
|
+
statusHtml = `<blockquote expandable><b>Incomplete Response</b>\n${data.statusLines.map(escapeHtml).join('\n')}</blockquote>\n\n`;
|
|
403
305
|
}
|
|
404
306
|
const headerHtml = `${activityHtml}${activityNoteHtml}${statusHtml}${thinkingHtml}`;
|
|
405
|
-
const bodyHtml = mdToTgHtml(
|
|
307
|
+
const bodyHtml = mdToTgHtml(data.bodyMessage);
|
|
406
308
|
return {
|
|
407
309
|
fullHtml: `${headerHtml}${bodyHtml}${footerHtml}`,
|
|
408
310
|
headerHtml,
|
package/dist/bot.js
CHANGED
|
@@ -13,9 +13,10 @@ import { getDriver, hasDriver, allDriverIds } from './agent-driver.js';
|
|
|
13
13
|
import { terminateProcessTree } from './process-control.js';
|
|
14
14
|
import { VERSION } from './version.js';
|
|
15
15
|
import { buildHumanLoopResponse, createEmptyHumanLoopAnswer, currentHumanLoopQuestion, isHumanLoopAwaitingText, setHumanLoopOption, setHumanLoopText, skipHumanLoopQuestion, } from './human-loop.js';
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
16
|
+
import { BOT_TIMEOUTS } from './constants.js';
|
|
17
|
+
export const DEFAULT_RUN_TIMEOUT_S = BOT_TIMEOUTS.defaultRunTimeoutS;
|
|
18
|
+
const MACOS_USER_ACTIVITY_PULSE_INTERVAL_MS = BOT_TIMEOUTS.macosUserActivityPulseInterval;
|
|
19
|
+
const MACOS_USER_ACTIVITY_PULSE_TIMEOUT_S = BOT_TIMEOUTS.macosUserActivityPulseTimeoutS;
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Helpers
|
|
21
22
|
// ---------------------------------------------------------------------------
|
package/dist/channel-feishu.js
CHANGED
|
@@ -14,11 +14,12 @@ import fs from 'node:fs';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { Channel, DEFAULT_CHANNEL_CAPABILITIES, sleep, } from './channel-base.js';
|
|
16
16
|
import { adaptMarkdownForFeishu } from './bot-feishu-render.js';
|
|
17
|
+
import { FEISHU_LIMITS } from './constants.js';
|
|
17
18
|
export { FeishuChannel };
|
|
18
|
-
const FEISHU_CARD_MAX =
|
|
19
|
-
const FILE_MAX_BYTES =
|
|
19
|
+
const FEISHU_CARD_MAX = FEISHU_LIMITS.cardMax;
|
|
20
|
+
const FILE_MAX_BYTES = FEISHU_LIMITS.fileMaxBytes;
|
|
20
21
|
const PHOTO_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif']);
|
|
21
|
-
const FEISHU_WS_START_RETRY_MAX_DELAY_MS =
|
|
22
|
+
const FEISHU_WS_START_RETRY_MAX_DELAY_MS = FEISHU_LIMITS.wsStartRetryMaxDelay;
|
|
22
23
|
function describeError(err) {
|
|
23
24
|
if (!(err instanceof Error))
|
|
24
25
|
return String(err ?? 'unknown error');
|
|
@@ -299,7 +300,7 @@ class FeishuChannel extends Channel {
|
|
|
299
300
|
}
|
|
300
301
|
async listen() {
|
|
301
302
|
this.running = true;
|
|
302
|
-
let retryDelayMs =
|
|
303
|
+
let retryDelayMs = FEISHU_LIMITS.wsStartRetryInitialDelay;
|
|
303
304
|
while (this.running) {
|
|
304
305
|
const sdkDomain = this.domain.includes('larksuite.com')
|
|
305
306
|
? lark.Domain.Lark
|