pikiloom 0.4.26 → 0.4.28
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/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DtjuI8NU.js} +1 -1
- package/dashboard/dist/assets/ConnectionModal-BIx6a5o1.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-BINh_7NB.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-CHpd147b.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C4DbGxbG.js} +1 -1
- package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-D_PUi-5O.js} +1 -1
- package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-BahDEMqf.js} +1 -1
- package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-Cp5VXb_2.js} +1 -1
- package/dashboard/dist/assets/SessionPanel-DZVNXptx.js +1 -0
- package/dashboard/dist/assets/SystemTab-CLznS31f.js +1 -0
- package/dashboard/dist/assets/index-CLGovkkw.js +3 -0
- package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
- package/dashboard/dist/assets/index-DcYOI6rL.js +23 -0
- package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C8DCTJ44.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/acp-client.js +0 -59
- package/dist/agent/artifacts.js +1 -38
- package/dist/agent/auto-update.js +2 -78
- package/dist/agent/await-resume.js +0 -20
- package/dist/agent/cli/auth.js +3 -51
- package/dist/agent/cli/catalog.js +0 -3
- package/dist/agent/cli/detector.js +1 -19
- package/dist/agent/cli/index.js +0 -3
- package/dist/agent/cli/registry.js +0 -25
- package/dist/agent/driver.js +0 -14
- package/dist/agent/drivers/claude-tui.js +2 -769
- package/dist/agent/drivers/claude.js +12 -631
- package/dist/agent/drivers/codex.js +11 -162
- package/dist/agent/drivers/gemini.js +6 -114
- package/dist/agent/drivers/hermes.js +0 -128
- package/dist/agent/goal.js +0 -45
- package/dist/agent/handover.js +1 -46
- package/dist/agent/images.js +7 -133
- package/dist/agent/index.js +1 -29
- package/dist/agent/mcp/bridge.js +1 -197
- package/dist/agent/mcp/extensions.js +5 -84
- package/dist/agent/mcp/oauth.js +2 -69
- package/dist/agent/mcp/registry.js +2 -33
- package/dist/agent/mcp/session-server.js +2 -45
- package/dist/agent/mcp/tools/ask-user.js +0 -6
- package/dist/agent/mcp/tools/await-resume.js +0 -15
- package/dist/agent/mcp/tools/goal.js +0 -25
- package/dist/agent/mcp/tools/types.js +0 -5
- package/dist/agent/mcp/tools/workspace.js +0 -23
- package/dist/agent/npm.js +0 -14
- package/dist/agent/session.js +86 -196
- package/dist/agent/skill-installer.js +3 -70
- package/dist/agent/skills.js +0 -43
- package/dist/agent/stream.js +5 -148
- package/dist/agent/types.js +0 -12
- package/dist/agent/utils.js +2 -120
- package/dist/bot/bot.js +3 -468
- package/dist/bot/command-ui.js +0 -45
- package/dist/bot/commands.js +0 -51
- package/dist/bot/headless-bot.js +0 -21
- package/dist/bot/host.js +1 -5
- package/dist/bot/human-loop.js +0 -8
- package/dist/bot/menu.js +0 -3
- package/dist/bot/orchestration.js +0 -3
- package/dist/bot/render-shared.js +0 -63
- package/dist/bot/session-hub.js +14 -92
- package/dist/bot/session-status.js +1 -5
- package/dist/bot/streaming.js +0 -53
- package/dist/browser-profile.js +0 -51
- package/dist/browser-supervisor.js +0 -75
- package/dist/catalog/cli-tools.js +0 -53
- package/dist/catalog/index.js +0 -18
- package/dist/catalog/local-models.js +0 -18
- package/dist/catalog/mcp-servers.js +0 -35
- package/dist/catalog/skill-repos.js +0 -32
- package/dist/channels/base.js +0 -19
- package/dist/channels/dingtalk/bot.js +0 -3
- package/dist/channels/dingtalk/channel.js +0 -33
- package/dist/channels/discord/bot.js +0 -7
- package/dist/channels/discord/channel.js +0 -22
- package/dist/channels/feishu/bot.js +0 -71
- package/dist/channels/feishu/channel.js +0 -78
- package/dist/channels/feishu/markdown.js +0 -5
- package/dist/channels/feishu/render.js +0 -45
- package/dist/channels/health.js +0 -46
- package/dist/channels/slack/bot.js +0 -7
- package/dist/channels/slack/channel.js +1 -27
- package/dist/channels/states.js +0 -3
- package/dist/channels/telegram/bot.js +0 -60
- package/dist/channels/telegram/channel.js +2 -99
- package/dist/channels/telegram/directory.js +0 -3
- package/dist/channels/telegram/live-preview.js +0 -8
- package/dist/channels/telegram/render.js +0 -30
- package/dist/channels/wecom/bot.js +0 -3
- package/dist/channels/wecom/channel.js +0 -58
- package/dist/channels/weixin/api.js +0 -9
- package/dist/channels/weixin/bot.js +0 -88
- package/dist/channels/weixin/channel.js +0 -6
- package/dist/cli/autostart.js +1 -44
- package/dist/cli/channel-supervisor.js +1 -59
- package/dist/cli/channels.js +0 -6
- package/dist/cli/main.js +1 -145
- package/dist/cli/onboarding.js +0 -6
- package/dist/cli/run.js +0 -11
- package/dist/cli/setup-wizard.js +0 -3
- package/dist/core/config/runtime-config.js +0 -38
- package/dist/core/config/user-config.js +0 -63
- package/dist/core/config/validation.js +0 -9
- package/dist/core/constants.js +0 -268
- package/dist/core/git.js +0 -31
- package/dist/core/legacy-compat.js +1 -28
- package/dist/core/logging.js +0 -3
- package/dist/core/platform.js +0 -28
- package/dist/core/process-control.js +0 -8
- package/dist/core/secrets/index.js +0 -17
- package/dist/core/secrets/inline-seal.js +0 -7
- package/dist/core/secrets/ref.js +0 -19
- package/dist/core/secrets/resolver.js +0 -8
- package/dist/core/secrets/store.js +1 -12
- package/dist/core/utils.js +1 -37
- package/dist/core/version.js +0 -3
- package/dist/dashboard/platform.js +1 -35
- package/dist/dashboard/routes/agents.js +2 -71
- package/dist/dashboard/routes/cli.js +0 -27
- package/dist/dashboard/routes/config.js +0 -54
- package/dist/dashboard/routes/extensions.js +0 -132
- package/dist/dashboard/routes/local-models.js +0 -68
- package/dist/dashboard/routes/models.js +0 -46
- package/dist/dashboard/routes/sessions.js +0 -100
- package/dist/dashboard/runtime.js +1 -58
- package/dist/dashboard/server.js +0 -49
- package/dist/dashboard/session-control.js +2 -69
- package/dist/model/catalog.js +1 -17
- package/dist/model/index.js +0 -15
- package/dist/model/injector.js +1 -181
- package/dist/model/provider-models.js +1 -48
- package/dist/model/responses-bridge.js +3 -45
- package/dist/model/store.js +0 -17
- package/dist/model/types.js +0 -12
- package/dist/model/validation.js +1 -18
- package/dist/pikichannel/adapter-pikiloom.js +0 -31
- package/dist/pikichannel/code.js +0 -12
- package/dist/pikichannel/codec.js +0 -14
- package/dist/pikichannel/host.js +4 -33
- package/dist/pikichannel/protocol.js +0 -52
- package/dist/pikichannel/rendezvous-broker.js +5 -26
- package/dist/pikichannel/rendezvous-host.js +3 -16
- package/dist/pikichannel/server.js +6 -55
- package/dist/pikichannel/transport.js +0 -25
- package/dist/pikichannel/transports/webrtc-host.js +0 -18
- package/dist/pikichannel/transports/webrtc-shared.js +4 -34
- package/dist/pikichannel/transports/websocket-host.js +1 -14
- package/dist/pikichannel/turn.js +2 -69
- package/dist/pikichannel/web/sdk.js +14 -71
- package/package.json +1 -1
- package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
- package/dashboard/dist/assets/SessionPanel-_0dLWvEb.js +0 -1
- package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
- package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
- package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
- package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Feishu-specific message card rendering.
|
|
3
|
-
*
|
|
4
|
-
* Converts structured data from bot/commands.ts into Feishu Markdown (for interactive cards).
|
|
5
|
-
* Also provides a LivePreviewRenderer for streaming output.
|
|
6
|
-
*/
|
|
7
1
|
import { encodeCommandAction } from '../../bot/command-ui.js';
|
|
8
2
|
import { fmtUptime, fmtTokens, fmtBytes, formatGitStatusLine } from '../../bot/bot.js';
|
|
9
3
|
import { summarizePromptForStatus } from '../../bot/commands.js';
|
|
@@ -12,9 +6,6 @@ export { dispatchImageBlocks } from '../../bot/render-shared.js';
|
|
|
12
6
|
import { currentHumanLoopQuestion, humanLoopAnsweredCount, isHumanLoopAwaitingText, isHumanLoopQuestionAnswered, summarizeHumanLoopAnswer, } from '../../bot/human-loop.js';
|
|
13
7
|
import path from 'node:path';
|
|
14
8
|
import { listSubdirs } from '../../bot/bot.js';
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// Helpers
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
9
|
function formatPreviewFooter(agent, elapsedMs, meta, decorations) {
|
|
19
10
|
const parts = formatFooterParts(agent, elapsedMs, meta, null, decorations);
|
|
20
11
|
return `${footerStatusSymbol('running')} ${parts.identity}\n*${parts.runtime}*`;
|
|
@@ -103,19 +94,9 @@ export function renderCommandSelectionCard(view) {
|
|
|
103
94
|
rows: view.rows.map(row => ({ actions: row.map(actionButton) })),
|
|
104
95
|
};
|
|
105
96
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Strip code-fence markers (``` / ~~~) from text that is not meant to be
|
|
108
|
-
* rendered as full markdown (thinking, activity). Truncation by
|
|
109
|
-
* extractThinkingTail can leave stray fences that open unwanted code blocks.
|
|
110
|
-
*/
|
|
111
97
|
function stripCodeFences(text) {
|
|
112
98
|
return text.replace(/^(`{3,}|~{3,}).*$/gm, '');
|
|
113
99
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Ensure code fences in markdown text are balanced. If an odd number of
|
|
116
|
-
* fence markers is detected, append a closing fence so partial code blocks
|
|
117
|
-
* do not swallow the rest of the card.
|
|
118
|
-
*/
|
|
119
100
|
function ensureBalancedCodeFences(text) {
|
|
120
101
|
let inCode = false;
|
|
121
102
|
for (const line of text.split('\n')) {
|
|
@@ -179,11 +160,6 @@ export function buildHumanLoopPromptMarkdown(prompt) {
|
|
|
179
160
|
}
|
|
180
161
|
return lines.join('\n\n');
|
|
181
162
|
}
|
|
182
|
-
/**
|
|
183
|
-
* Closed-state rendering for a resolved human-loop card. Collapsed to the
|
|
184
|
-
* title + one row per question showing the chosen answer. No buttons, no
|
|
185
|
-
* pending-state copy.
|
|
186
|
-
*/
|
|
187
163
|
export function buildAnsweredHumanLoopPromptMarkdown(prompt, summary) {
|
|
188
164
|
const symbol = summary.status === 'cancelled' ? '⊘' : '✓';
|
|
189
165
|
const lines = [`**${symbol} ${prompt.title}**`];
|
|
@@ -194,9 +170,6 @@ export function buildAnsweredHumanLoopPromptMarkdown(prompt, summary) {
|
|
|
194
170
|
}
|
|
195
171
|
return lines.join('\n');
|
|
196
172
|
}
|
|
197
|
-
// ---------------------------------------------------------------------------
|
|
198
|
-
// LivePreview renderer — produces Markdown for Feishu card elements
|
|
199
|
-
// ---------------------------------------------------------------------------
|
|
200
173
|
export function buildInitialPreviewMarkdown(agent, model, effort, waiting = false, queuePosition = 0) {
|
|
201
174
|
const parts = [];
|
|
202
175
|
if (waiting) {
|
|
@@ -217,8 +190,6 @@ function buildPreviewMarkdown(input, options) {
|
|
|
217
190
|
const data = extractStreamPreviewData(input);
|
|
218
191
|
const parts = [];
|
|
219
192
|
if (data.planDisplay) {
|
|
220
|
-
// First line of planDisplay is already "Plan N/M" — promote it to the
|
|
221
|
-
// bold header instead of stacking a second "**Plan**" line above it.
|
|
222
193
|
const text = stripCodeFences(data.planDisplay);
|
|
223
194
|
const nl = text.indexOf('\n');
|
|
224
195
|
parts.push(nl >= 0 ? `**${text.slice(0, nl)}**${text.slice(nl)}` : `**${text}**`);
|
|
@@ -230,7 +201,6 @@ function buildPreviewMarkdown(input, options) {
|
|
|
230
201
|
parts.push(`**Sub-agent**\n${stripCodeFences(data.subAgentsDisplay)}`);
|
|
231
202
|
}
|
|
232
203
|
if (data.thinkDisplay && !data.display) {
|
|
233
|
-
// Elapsed lives in the footer only (single timer). Header is the bare label.
|
|
234
204
|
parts.push(`**${data.label}**\n${stripCodeFences(data.thinkDisplay)}`);
|
|
235
205
|
}
|
|
236
206
|
else if (data.display) {
|
|
@@ -240,10 +210,6 @@ function buildPreviewMarkdown(input, options) {
|
|
|
240
210
|
parts.push(ensureBalancedCodeFences(data.preview));
|
|
241
211
|
}
|
|
242
212
|
else if (data.thinkingProgressText) {
|
|
243
|
-
// Thinking phase with no streamed thinking/body text yet — show the bare
|
|
244
|
-
// "{thinkLabel}" so the card isn't blank. The elapsed tick lives in the
|
|
245
|
-
// footer only (one timer, not two); the footer re-renders on the channel
|
|
246
|
-
// heartbeat, so the card still visibly advances.
|
|
247
213
|
parts.push(`**${data.label}**`);
|
|
248
214
|
}
|
|
249
215
|
if (options?.includeFooter !== false) {
|
|
@@ -292,9 +258,6 @@ export function buildFinalReplyRender(agent, result) {
|
|
|
292
258
|
footerText,
|
|
293
259
|
};
|
|
294
260
|
}
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
|
-
// Command renderers — produce Markdown for Feishu cards
|
|
297
|
-
// ---------------------------------------------------------------------------
|
|
298
261
|
export function renderStart(d) {
|
|
299
262
|
const lines = [
|
|
300
263
|
`**${d.title}** v${d.version}`,
|
|
@@ -467,7 +430,6 @@ export function renderStatus(d) {
|
|
|
467
430
|
if (d.running) {
|
|
468
431
|
lines.push(`**Running:** ${fmtUptime(Date.now() - d.running.startedAt)} - ${summarizePromptForStatus(d.running.prompt)}`);
|
|
469
432
|
}
|
|
470
|
-
// Provider usage
|
|
471
433
|
const usageLines = buildProviderUsageLines(d.usage);
|
|
472
434
|
if (usageLines.length > 1) {
|
|
473
435
|
lines.push('');
|
|
@@ -483,9 +445,6 @@ export function renderStatus(d) {
|
|
|
483
445
|
}
|
|
484
446
|
return lines.join('\n');
|
|
485
447
|
}
|
|
486
|
-
// ---------------------------------------------------------------------------
|
|
487
|
-
// Directory browser (interactive workdir switcher)
|
|
488
|
-
// ---------------------------------------------------------------------------
|
|
489
448
|
class PathRegistry {
|
|
490
449
|
pathToId = new Map();
|
|
491
450
|
idToPath = new Map();
|
|
@@ -520,7 +479,6 @@ export function buildSwitchWorkdirCard(currentWorkdir, browsePath, page = 0, opt
|
|
|
520
479
|
const totalPages = Math.max(1, Math.ceil(dirs.length / DIR_PAGE_SIZE));
|
|
521
480
|
const currentPage = Math.min(Math.max(0, page), totalPages - 1);
|
|
522
481
|
const slice = dirs.slice(currentPage * DIR_PAGE_SIZE, (currentPage + 1) * DIR_PAGE_SIZE);
|
|
523
|
-
// Text
|
|
524
482
|
const lines = ['**Workdir**'];
|
|
525
483
|
lines.push(`● \`${currentWorkdir}\``);
|
|
526
484
|
if (browsePath !== currentWorkdir)
|
|
@@ -528,7 +486,6 @@ export function buildSwitchWorkdirCard(currentWorkdir, browsePath, page = 0, opt
|
|
|
528
486
|
if (opts.savedWorkspaceCount && opts.savedWorkspaceCount > 0) {
|
|
529
487
|
lines.push('', `_Tip: ${opts.savedWorkspaceCount} saved workspace${opts.savedWorkspaceCount === 1 ? '' : 's'} — use /workspaces for one-tap switching._`);
|
|
530
488
|
}
|
|
531
|
-
// Directory buttons (2 per row)
|
|
532
489
|
const dirRows = [];
|
|
533
490
|
for (let i = 0; i < slice.length; i += 2) {
|
|
534
491
|
const rowActions = [];
|
|
@@ -539,7 +496,6 @@ export function buildSwitchWorkdirCard(currentWorkdir, browsePath, page = 0, opt
|
|
|
539
496
|
}
|
|
540
497
|
dirRows.push({ actions: rowActions });
|
|
541
498
|
}
|
|
542
|
-
// Nav row: parent + pagination
|
|
543
499
|
const navActions = [];
|
|
544
500
|
const parent = path.dirname(browsePath);
|
|
545
501
|
if (parent !== browsePath) {
|
|
@@ -552,7 +508,6 @@ export function buildSwitchWorkdirCard(currentWorkdir, browsePath, page = 0, opt
|
|
|
552
508
|
if (currentPage < totalPages - 1)
|
|
553
509
|
navActions.push(cardButton(`${currentPage + 2}/${totalPages} ▶`, `sw:n:${browseId}:${currentPage + 1}`));
|
|
554
510
|
}
|
|
555
|
-
// Select button
|
|
556
511
|
const selectActions = [
|
|
557
512
|
cardButton('✓ Use This', `sw:s:${feishuPathRegistry.register(browsePath)}`, true),
|
|
558
513
|
];
|
package/dist/channels/health.js
CHANGED
|
@@ -1,38 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared transport health tracker for IM channel listen / connect loops.
|
|
3
|
-
*
|
|
4
|
-
* Solves three problems each channel was reinventing:
|
|
5
|
-
*
|
|
6
|
-
* 1. **Exponential backoff** — initial → max with doubling between attempts,
|
|
7
|
-
* reset on success.
|
|
8
|
-
* 2. **Log throttling** — continuous failures emit a log line only when the
|
|
9
|
-
* retry-delay bucket changes (1s → 2s → 4s → …), so identical failures
|
|
10
|
-
* don't flood the journal once per attempt.
|
|
11
|
-
* 3. **Sustained-failure notice** — after a configurable threshold (5 min
|
|
12
|
-
* by default) of uninterrupted failures, emit a one-shot warn-level
|
|
13
|
-
* line pointing the operator at the relevant config. Retries continue
|
|
14
|
-
* indefinitely — the notice is informational, not a stop signal.
|
|
15
|
-
*
|
|
16
|
-
* Usage (long-poll style):
|
|
17
|
-
*
|
|
18
|
-
* ```ts
|
|
19
|
-
* const health = new ChannelHealth({ label: 'Weixin', opAction: 'polling',
|
|
20
|
-
* initialDelayMs: 1_000, maxDelayMs: 60_000,
|
|
21
|
-
* sustainedFailureHint: 'verify baseUrl / token / accountId',
|
|
22
|
-
* log: (msg, level) => this.log(msg, level) });
|
|
23
|
-
*
|
|
24
|
-
* while (!stopping) {
|
|
25
|
-
* try {
|
|
26
|
-
* await poll();
|
|
27
|
-
* health.recordSuccess();
|
|
28
|
-
* // …process result…
|
|
29
|
-
* } catch (err) {
|
|
30
|
-
* if (stopping || isAbort(err)) break;
|
|
31
|
-
* await sleep(health.recordFailure(err));
|
|
32
|
-
* }
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
1
|
function describeError(error) {
|
|
37
2
|
return error instanceof Error ? error.message : String(error ?? 'unknown error');
|
|
38
3
|
}
|
|
@@ -61,10 +26,6 @@ export class ChannelHealth {
|
|
|
61
26
|
this.transientFailureLevel = opts.transientFailureLevel ?? 'warn';
|
|
62
27
|
this.delayMs = opts.initialDelayMs;
|
|
63
28
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Mark the operation as currently succeeding. Resets backoff state and,
|
|
66
|
-
* if there were prior failures, emits an info-level recovery line.
|
|
67
|
-
*/
|
|
68
29
|
recordSuccess() {
|
|
69
30
|
if (this.consecutiveFailures > 0 && this.firstFailureAt !== null) {
|
|
70
31
|
const downtimeMs = Date.now() - this.firstFailureAt;
|
|
@@ -77,12 +38,6 @@ export class ChannelHealth {
|
|
|
77
38
|
this.lastLoggedDelayMs = 0;
|
|
78
39
|
this.sustainedNoticeFired = false;
|
|
79
40
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Record a failure. Emits the per-bucket failure log (throttled) and the
|
|
82
|
-
* one-shot sustained-failure notice when applicable, then advances the
|
|
83
|
-
* backoff for the *next* failure. Returns the delay (ms) the caller
|
|
84
|
-
* should sleep before its next attempt.
|
|
85
|
-
*/
|
|
86
41
|
recordFailure(error) {
|
|
87
42
|
this.consecutiveFailures += 1;
|
|
88
43
|
if (this.firstFailureAt === null)
|
|
@@ -102,7 +57,6 @@ export class ChannelHealth {
|
|
|
102
57
|
this.delayMs = Math.min(this.delayMs * 2, this.maxDelayMs);
|
|
103
58
|
return delayMs;
|
|
104
59
|
}
|
|
105
|
-
/** Current consecutive failure count. */
|
|
106
60
|
get failureCount() {
|
|
107
61
|
return this.consecutiveFailures;
|
|
108
62
|
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slack bot orchestration.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors WeixinBot's command surface (/help, /status, /agent, /models, …)
|
|
5
|
-
* over Slack channels and DMs. Each Slack channel id is a chat key, threads
|
|
6
|
-
* are reused so the bot's reply lands in the same conversation as the trigger.
|
|
7
|
-
*/
|
|
8
1
|
import fs from 'node:fs';
|
|
9
2
|
import os from 'node:os';
|
|
10
3
|
import path from 'node:path';
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slack channel transport — Socket Mode (no public IP needed).
|
|
3
|
-
*
|
|
4
|
-
* Uses @slack/socket-mode + @slack/web-api directly (avoids the heavier Bolt
|
|
5
|
-
* framework so we keep tight control of the receive loop and stay consistent
|
|
6
|
-
* with the other channels).
|
|
7
|
-
*
|
|
8
|
-
* Two tokens are required:
|
|
9
|
-
* - bot token (xoxb-…) — REST send/edit
|
|
10
|
-
* - app token (xapp-…) — Socket Mode connection
|
|
11
|
-
*
|
|
12
|
-
* Receives DMs and messages where the bot is mentioned in a channel. Messages
|
|
13
|
-
* the bot itself authored are filtered out before dispatch.
|
|
14
|
-
*/
|
|
15
1
|
import { SocketModeClient } from '@slack/socket-mode';
|
|
16
2
|
import { WebClient } from '@slack/web-api';
|
|
17
3
|
import { Channel, DEFAULT_CHANNEL_CAPABILITIES, splitText, sleep, } from '../base.js';
|
|
@@ -26,7 +12,7 @@ export class SlackChannel extends Channel {
|
|
|
26
12
|
capabilities = {
|
|
27
13
|
...DEFAULT_CHANNEL_CAPABILITIES,
|
|
28
14
|
editMessages: true,
|
|
29
|
-
typingIndicators: false,
|
|
15
|
+
typingIndicators: false,
|
|
30
16
|
};
|
|
31
17
|
knownChats = new Set();
|
|
32
18
|
botToken;
|
|
@@ -40,7 +26,6 @@ export class SlackChannel extends Channel {
|
|
|
40
26
|
listenResolve = null;
|
|
41
27
|
messageHandlers = new Set();
|
|
42
28
|
errorHandlers = new Set();
|
|
43
|
-
/** Dedup: Slack delivers events at-least-once via Socket Mode acks. */
|
|
44
29
|
seenEventIds = new Set();
|
|
45
30
|
seenEventQueue = [];
|
|
46
31
|
static SEEN_EVENT_CAP = 256;
|
|
@@ -53,9 +38,6 @@ export class SlackChannel extends Channel {
|
|
|
53
38
|
}
|
|
54
39
|
onMessage(handler) { this.messageHandlers.add(handler); return this; }
|
|
55
40
|
onError(handler) { this.errorHandlers.add(handler); return this; }
|
|
56
|
-
// ========================================================================
|
|
57
|
-
// Lifecycle
|
|
58
|
-
// ========================================================================
|
|
59
41
|
async connect() {
|
|
60
42
|
this.webClient = new WebClient(this.botToken);
|
|
61
43
|
let lastErr = null;
|
|
@@ -85,7 +67,6 @@ export class SlackChannel extends Channel {
|
|
|
85
67
|
this.running = true;
|
|
86
68
|
this.socketClient = new SocketModeClient({
|
|
87
69
|
appToken: this.appToken,
|
|
88
|
-
// We log via our own scope; suppress noisy SDK info logs.
|
|
89
70
|
logLevel: undefined,
|
|
90
71
|
});
|
|
91
72
|
this.socketClient.on('message', async ({ event, ack }) => {
|
|
@@ -157,9 +138,6 @@ export class SlackChannel extends Channel {
|
|
|
157
138
|
this.listenResolve?.();
|
|
158
139
|
this.listenResolve = null;
|
|
159
140
|
}
|
|
160
|
-
// ========================================================================
|
|
161
|
-
// Outgoing primitives
|
|
162
|
-
// ========================================================================
|
|
163
141
|
async send(chatId, text, opts = {}) {
|
|
164
142
|
const channelId = String(chatId);
|
|
165
143
|
const chunks = splitText((text || '').trim() || '(empty)', SLACK_MAX);
|
|
@@ -208,11 +186,7 @@ export class SlackChannel extends Channel {
|
|
|
208
186
|
catch { }
|
|
209
187
|
}
|
|
210
188
|
async sendTyping(_chatId, _opts) {
|
|
211
|
-
// Slack does not expose a typing indicator in the public API.
|
|
212
189
|
}
|
|
213
|
-
// ========================================================================
|
|
214
|
-
// Internal dispatch
|
|
215
|
-
// ========================================================================
|
|
216
190
|
async dispatchMessageEvent(event) {
|
|
217
191
|
if (!event || event.bot_id || event.subtype === 'bot_message')
|
|
218
192
|
return;
|
package/dist/channels/states.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Telegram bot orchestration: commands, callbacks, streaming lifecycle.
|
|
3
|
-
*
|
|
4
|
-
* Rendering, workdir browsing, and live preview state live in dedicated helper modules.
|
|
5
|
-
* New IM integrations should stay parallel to this file and compose shared runtime helpers
|
|
6
|
-
* instead of growing a single multi-platform bot entrypoint.
|
|
7
|
-
*/
|
|
8
1
|
import os from 'node:os';
|
|
9
2
|
import fs from 'node:fs';
|
|
10
3
|
import path from 'node:path';
|
|
@@ -24,17 +17,10 @@ import { currentHumanLoopQuestion, humanLoopOptionSelected } from '../../bot/hum
|
|
|
24
17
|
import { TelegramChannel } from './channel.js';
|
|
25
18
|
import { splitText, supportsChannelCapability } from '../base.js';
|
|
26
19
|
import { getActiveUserConfig, loadKnownChatIds } from '../../core/config/user-config.js';
|
|
27
|
-
/** Telegram HTML renderer for LivePreview. */
|
|
28
20
|
const telegramPreviewRenderer = {
|
|
29
21
|
renderInitial: buildInitialPreviewHtml,
|
|
30
22
|
renderStream: buildStreamPreviewHtml,
|
|
31
23
|
};
|
|
32
|
-
/**
|
|
33
|
-
* Echo message rendered after a human-loop prompt resolves — a small chat
|
|
34
|
-
* message that records the decision in the conversation history (independent
|
|
35
|
-
* of the now-closed card above). Returns null when there's nothing useful to
|
|
36
|
-
* say (e.g. an empty cancellation).
|
|
37
|
-
*/
|
|
38
24
|
function buildInteractionEchoHtml(summary) {
|
|
39
25
|
if (summary.status === 'cancelled') {
|
|
40
26
|
return `<i>⊘ Prompt cancelled.</i>`;
|
|
@@ -54,9 +40,6 @@ const SHUTDOWN_EXIT_CODE = {
|
|
|
54
40
|
SIGINT: 130,
|
|
55
41
|
SIGTERM: 143,
|
|
56
42
|
};
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// TelegramBot
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
43
|
export class TelegramBot extends Bot {
|
|
61
44
|
token;
|
|
62
45
|
channel;
|
|
@@ -70,15 +53,10 @@ export class TelegramBot extends Bot {
|
|
|
70
53
|
constructor() {
|
|
71
54
|
super();
|
|
72
55
|
const config = getActiveUserConfig();
|
|
73
|
-
// merge Telegram-specific allowed IDs into base
|
|
74
56
|
if (config.telegramAllowedChatIds) {
|
|
75
57
|
for (const id of parseAllowedChatIds(config.telegramAllowedChatIds))
|
|
76
58
|
this.allowedChatIds.add(id);
|
|
77
59
|
}
|
|
78
|
-
// NOTE: persisted known chats are restored into channel.knownChats in run()
|
|
79
|
-
// (for the startup greeting / per-chat menu) — deliberately NOT into
|
|
80
|
-
// allowedChatIds. allowedChatIds is the explicit allowlist; folding known
|
|
81
|
-
// chats into it flips _isAllowed() into allowlist-only mode.
|
|
82
60
|
this.token = String(config.telegramBotToken || process.env.TELEGRAM_BOT_TOKEN || '').trim();
|
|
83
61
|
if (!this.token)
|
|
84
62
|
throw new Error('Missing Telegram token. Configure via dashboard or set TELEGRAM_BOT_TOKEN');
|
|
@@ -100,13 +78,9 @@ export class TelegramBot extends Bot {
|
|
|
100
78
|
const mergedAllowed = parseAllowedChatIds(process.env.PIKILOOM_ALLOWED_IDS || '');
|
|
101
79
|
for (const id of parseAllowedChatIds(String(config.telegramAllowedChatIds || '')))
|
|
102
80
|
mergedAllowed.add(id);
|
|
103
|
-
// Known chats are NOT merged here — doing so would re-pollute the allowlist on
|
|
104
|
-
// every config reload. They live in channel.knownChats (restored in run()).
|
|
105
81
|
this.allowedChatIds = mergedAllowed;
|
|
106
82
|
}
|
|
107
|
-
/** Skill command prefix used in Telegram bot commands. */
|
|
108
83
|
static SKILL_CMD_PREFIX = SKILL_CMD_PREFIX;
|
|
109
|
-
/** Register bot menu commands. Called automatically after connect. */
|
|
110
84
|
async setupMenu() {
|
|
111
85
|
if (!supportsChannelCapability(this.channel, 'commandMenu'))
|
|
112
86
|
return;
|
|
@@ -156,9 +130,6 @@ export class TelegramBot extends Bot {
|
|
|
156
130
|
shutdownAllDrivers();
|
|
157
131
|
}
|
|
158
132
|
buildRestartEnv() {
|
|
159
|
-
// Hand off only the explicit allowlist. Known chats persist to setting.json
|
|
160
|
-
// and are restored via loadKnownChatIds, so they must NOT ride along in the
|
|
161
|
-
// allowlist env — that would re-pollute allowedChatIds on the next boot.
|
|
162
133
|
return buildKnownChatEnv(this.allowedChatIds, [], 'TELEGRAM_ALLOWED_CHAT_IDS');
|
|
163
134
|
}
|
|
164
135
|
beginShutdown(sig) {
|
|
@@ -224,7 +195,6 @@ export class TelegramBot extends Bot {
|
|
|
224
195
|
return selected;
|
|
225
196
|
return this.ensureSession(ctx.chatId, text, files);
|
|
226
197
|
}
|
|
227
|
-
// ---- commands -------------------------------------------------------------
|
|
228
198
|
async cmdStart(ctx) {
|
|
229
199
|
const d = getStartData(this, ctx.chatId);
|
|
230
200
|
await ctx.reply(this.renderStartHtml(d), { parseMode: 'HTML' });
|
|
@@ -257,7 +227,6 @@ export class TelegramBot extends Bot {
|
|
|
257
227
|
const { getExtensionSummaryData } = await import('../../bot/commands.js');
|
|
258
228
|
const data = getExtensionSummaryData(this, ctx.chatId);
|
|
259
229
|
const lines = ['<b>Extensions</b>', ''];
|
|
260
|
-
// MCP servers
|
|
261
230
|
lines.push(`<b>MCP Servers</b> (${data.mcpCount})`);
|
|
262
231
|
if (data.mcpExtensions.length === 0) {
|
|
263
232
|
lines.push(' <i>No MCP extensions configured</i>');
|
|
@@ -272,7 +241,6 @@ export class TelegramBot extends Bot {
|
|
|
272
241
|
}
|
|
273
242
|
}
|
|
274
243
|
lines.push('');
|
|
275
|
-
// Skills
|
|
276
244
|
lines.push(`<b>Skills</b> (${data.skillCount})`);
|
|
277
245
|
if (data.skills.length === 0) {
|
|
278
246
|
lines.push(' <i>No skills installed</i>');
|
|
@@ -513,12 +481,6 @@ export class TelegramBot extends Bot {
|
|
|
513
481
|
this.debug(`[human-loop] echo send failed chat=${chatId} prompt=${prompt.promptId}: ${err?.message || err}`);
|
|
514
482
|
}
|
|
515
483
|
}
|
|
516
|
-
/**
|
|
517
|
-
* IM presenter for programmatic submissions (e.g. /goal-driven turns) so
|
|
518
|
-
* they stream to Telegram the same way a typed message does — placeholder
|
|
519
|
-
* card, LivePreview edits, sendFinalReply at the end. Without this, /goal
|
|
520
|
-
* just emits a confirmation reply and the rest of the turn is invisible.
|
|
521
|
-
*/
|
|
522
484
|
async createImTaskPresenter(opts) {
|
|
523
485
|
if (typeof opts.chatId !== 'number')
|
|
524
486
|
return null;
|
|
@@ -608,9 +570,7 @@ export class TelegramBot extends Bot {
|
|
|
608
570
|
if (typeof sent === 'number')
|
|
609
571
|
this.registerHumanLoopMessage(prompt.promptId, sent);
|
|
610
572
|
}
|
|
611
|
-
/** Cache the messageThreadId per task so renderInteractionPrompt can use it. */
|
|
612
573
|
interactionThreadIds = new Map();
|
|
613
|
-
// ---- streaming bridge -----------------------------------------------------
|
|
614
574
|
async handleMessage(msg, ctx) {
|
|
615
575
|
const text = msg.text.trim();
|
|
616
576
|
if (!text && !msg.files.length)
|
|
@@ -622,7 +582,6 @@ export class TelegramBot extends Bot {
|
|
|
622
582
|
await ctx.reply('Please answer the active prompt using the buttons above.');
|
|
623
583
|
return;
|
|
624
584
|
}
|
|
625
|
-
// Completed prompts close themselves via onInteractionAnswered.
|
|
626
585
|
if (!result.completed)
|
|
627
586
|
await this.refreshHumanLoopPrompt(ctx.chatId, result.prompt.promptId);
|
|
628
587
|
return;
|
|
@@ -725,7 +684,6 @@ export class TelegramBot extends Bot {
|
|
|
725
684
|
return;
|
|
726
685
|
}
|
|
727
686
|
this.emitStreamStart(taskId, session);
|
|
728
|
-
// Task is now running — update keyboard from Recall/Steer to Stop
|
|
729
687
|
const runningKeyboard = this.buildStopKeyboard(this.actionIdForTask(taskId));
|
|
730
688
|
if (phId != null && waiting) {
|
|
731
689
|
try {
|
|
@@ -752,7 +710,6 @@ export class TelegramBot extends Bot {
|
|
|
752
710
|
});
|
|
753
711
|
livePreview.start();
|
|
754
712
|
}
|
|
755
|
-
// MCP sendFile callback: sends files to IM in real-time during the stream
|
|
756
713
|
const mcpSendFile = this.createMcpSendFileCallback(ctx, messageThreadId);
|
|
757
714
|
this.interactionThreadIds.set(taskId, messageThreadId);
|
|
758
715
|
const result = await this.runStream(prompt, session, files, (nextText, nextThinking, nextActivity = '', meta, plan) => {
|
|
@@ -850,7 +807,6 @@ export class TelegramBot extends Bot {
|
|
|
850
807
|
return [];
|
|
851
808
|
}
|
|
852
809
|
}
|
|
853
|
-
/** Create an MCP sendFile callback bound to a Telegram chat context. */
|
|
854
810
|
createMcpSendFileCallback(ctx, messageThreadId) {
|
|
855
811
|
return async (filePath, opts) => {
|
|
856
812
|
try {
|
|
@@ -907,7 +863,6 @@ export class TelegramBot extends Bot {
|
|
|
907
863
|
finalMsgId = await replacePreview(rendered.fullHtml);
|
|
908
864
|
}
|
|
909
865
|
else {
|
|
910
|
-
// Split: header on first message, footer on last message
|
|
911
866
|
const maxFirst = 3900 - rendered.headerHtml.length;
|
|
912
867
|
let firstBody;
|
|
913
868
|
let remaining;
|
|
@@ -923,7 +878,6 @@ export class TelegramBot extends Bot {
|
|
|
923
878
|
remaining = rendered.bodyHtml;
|
|
924
879
|
}
|
|
925
880
|
if (remaining.trim()) {
|
|
926
|
-
// Multi-message: header on first, footer on last
|
|
927
881
|
const firstHtml = `${rendered.headerHtml}${firstBody}`;
|
|
928
882
|
finalMsgId = await replacePreview(firstHtml);
|
|
929
883
|
const chunks = splitText(remaining, 3800);
|
|
@@ -932,7 +886,6 @@ export class TelegramBot extends Bot {
|
|
|
932
886
|
const chunkText = isLast ? `${chunks[i]}${rendered.footerHtml}` : chunks[i];
|
|
933
887
|
remember(await sendFinalText(chunkText, finalMsgId ?? phId ?? anchor.replyTo));
|
|
934
888
|
}
|
|
935
|
-
// Safety: re-clear the Stop keyboard on the placeholder in case the first edit silently failed
|
|
936
889
|
if (phId != null) {
|
|
937
890
|
try {
|
|
938
891
|
await this.channel.editMessage(anchor.chatId, phId, firstHtml || '(done)', { parseMode: 'HTML', keyboard: { inline_keyboard: [] } });
|
|
@@ -941,14 +894,10 @@ export class TelegramBot extends Bot {
|
|
|
941
894
|
}
|
|
942
895
|
}
|
|
943
896
|
else {
|
|
944
|
-
// Body fits on first message; only footer pushes it over — keep together
|
|
945
897
|
const firstHtml = `${rendered.headerHtml}${firstBody}${rendered.footerHtml}`;
|
|
946
898
|
finalMsgId = await replacePreview(firstHtml);
|
|
947
899
|
}
|
|
948
900
|
}
|
|
949
|
-
// Dispatch any image MessageBlocks the agent produced this turn (Codex
|
|
950
|
-
// built-in `image_gen`, MCP / Skill tool_result images, …). Each goes out
|
|
951
|
-
// as a separate Telegram photo with the optional caption attached.
|
|
952
901
|
const dispatched = await dispatchImageBlocks(this.channel, result.assistantBlocks, {
|
|
953
902
|
chatId: anchor.chatId,
|
|
954
903
|
replyTo: finalMsgId ?? phId ?? anchor.replyTo,
|
|
@@ -961,7 +910,6 @@ export class TelegramBot extends Bot {
|
|
|
961
910
|
}
|
|
962
911
|
return { primaryMessageId: finalMsgId, messageIds };
|
|
963
912
|
}
|
|
964
|
-
// ---- callbacks ------------------------------------------------------------
|
|
965
913
|
async handleSwitchNavigateCallback(data, ctx) {
|
|
966
914
|
if (!data.startsWith('sw:n:'))
|
|
967
915
|
return false;
|
|
@@ -1158,10 +1106,8 @@ export class TelegramBot extends Bot {
|
|
|
1158
1106
|
}
|
|
1159
1107
|
}
|
|
1160
1108
|
catch {
|
|
1161
|
-
// non-critical
|
|
1162
1109
|
}
|
|
1163
1110
|
}
|
|
1164
|
-
// ---- command router -------------------------------------------------------
|
|
1165
1111
|
async handleCommand(cmd, args, ctx) {
|
|
1166
1112
|
try {
|
|
1167
1113
|
switch (cmd) {
|
|
@@ -1211,7 +1157,6 @@ export class TelegramBot extends Bot {
|
|
|
1211
1157
|
await this.cmdRestart(ctx);
|
|
1212
1158
|
return;
|
|
1213
1159
|
default:
|
|
1214
|
-
// Intercept skill commands (sk_<name>) and route to agent
|
|
1215
1160
|
if (cmd.startsWith(TelegramBot.SKILL_CMD_PREFIX)) {
|
|
1216
1161
|
await this.cmdSkill(cmd, args, ctx);
|
|
1217
1162
|
return;
|
|
@@ -1224,7 +1169,6 @@ export class TelegramBot extends Bot {
|
|
|
1224
1169
|
await ctx.reply(`Error: ${String(e).slice(0, 200)}`);
|
|
1225
1170
|
}
|
|
1226
1171
|
}
|
|
1227
|
-
/** Execute a project-defined skill by routing it to the current agent. */
|
|
1228
1172
|
async cmdSkill(cmd, args, ctx) {
|
|
1229
1173
|
const resolved = resolveSkillPrompt(this, ctx.chatId, cmd, args);
|
|
1230
1174
|
if (!resolved) {
|
|
@@ -1234,7 +1178,6 @@ export class TelegramBot extends Bot {
|
|
|
1234
1178
|
this.log(`skill: ${resolved.skillName} agent=${this.chat(ctx.chatId).agent}${args.trim() ? ` args="${args.trim()}"` : ''}`);
|
|
1235
1179
|
await this.handleMessage({ text: resolved.prompt, files: [] }, ctx);
|
|
1236
1180
|
}
|
|
1237
|
-
// ---- lifecycle ------------------------------------------------------------
|
|
1238
1181
|
async run() {
|
|
1239
1182
|
const tmpDir = path.join(os.tmpdir(), 'pikiloom');
|
|
1240
1183
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -1256,9 +1199,6 @@ export class TelegramBot extends Bot {
|
|
|
1256
1199
|
this.connected = true;
|
|
1257
1200
|
this.log(`bot: @${bot.username} (id=${bot.id})`);
|
|
1258
1201
|
this.channel.skipPendingUpdatesOnNextListen();
|
|
1259
|
-
// Seed knownChats so setupMenu applies per-chat commands and the startup
|
|
1260
|
-
// greeting can reach them: the explicit allowlist + persisted known chats
|
|
1261
|
-
// (restored here instead of via allowedChatIds, which stays explicit-only).
|
|
1262
1202
|
for (const cid of this.allowedChatIds)
|
|
1263
1203
|
if (typeof cid === 'number')
|
|
1264
1204
|
this.channel.knownChats.add(cid);
|