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.
Files changed (156) hide show
  1. package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DtjuI8NU.js} +1 -1
  2. package/dashboard/dist/assets/ConnectionModal-BIx6a5o1.js +1 -0
  3. package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-BINh_7NB.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-CHpd147b.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C4DbGxbG.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-D_PUi-5O.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-BahDEMqf.js} +1 -1
  8. package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-Cp5VXb_2.js} +1 -1
  9. package/dashboard/dist/assets/SessionPanel-DZVNXptx.js +1 -0
  10. package/dashboard/dist/assets/SystemTab-CLznS31f.js +1 -0
  11. package/dashboard/dist/assets/index-CLGovkkw.js +3 -0
  12. package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
  13. package/dashboard/dist/assets/index-DcYOI6rL.js +23 -0
  14. package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C8DCTJ44.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/acp-client.js +0 -59
  17. package/dist/agent/artifacts.js +1 -38
  18. package/dist/agent/auto-update.js +2 -78
  19. package/dist/agent/await-resume.js +0 -20
  20. package/dist/agent/cli/auth.js +3 -51
  21. package/dist/agent/cli/catalog.js +0 -3
  22. package/dist/agent/cli/detector.js +1 -19
  23. package/dist/agent/cli/index.js +0 -3
  24. package/dist/agent/cli/registry.js +0 -25
  25. package/dist/agent/driver.js +0 -14
  26. package/dist/agent/drivers/claude-tui.js +2 -769
  27. package/dist/agent/drivers/claude.js +12 -631
  28. package/dist/agent/drivers/codex.js +11 -162
  29. package/dist/agent/drivers/gemini.js +6 -114
  30. package/dist/agent/drivers/hermes.js +0 -128
  31. package/dist/agent/goal.js +0 -45
  32. package/dist/agent/handover.js +1 -46
  33. package/dist/agent/images.js +7 -133
  34. package/dist/agent/index.js +1 -29
  35. package/dist/agent/mcp/bridge.js +1 -197
  36. package/dist/agent/mcp/extensions.js +5 -84
  37. package/dist/agent/mcp/oauth.js +2 -69
  38. package/dist/agent/mcp/registry.js +2 -33
  39. package/dist/agent/mcp/session-server.js +2 -45
  40. package/dist/agent/mcp/tools/ask-user.js +0 -6
  41. package/dist/agent/mcp/tools/await-resume.js +0 -15
  42. package/dist/agent/mcp/tools/goal.js +0 -25
  43. package/dist/agent/mcp/tools/types.js +0 -5
  44. package/dist/agent/mcp/tools/workspace.js +0 -23
  45. package/dist/agent/npm.js +0 -14
  46. package/dist/agent/session.js +86 -196
  47. package/dist/agent/skill-installer.js +3 -70
  48. package/dist/agent/skills.js +0 -43
  49. package/dist/agent/stream.js +5 -148
  50. package/dist/agent/types.js +0 -12
  51. package/dist/agent/utils.js +2 -120
  52. package/dist/bot/bot.js +3 -468
  53. package/dist/bot/command-ui.js +0 -45
  54. package/dist/bot/commands.js +0 -51
  55. package/dist/bot/headless-bot.js +0 -21
  56. package/dist/bot/host.js +1 -5
  57. package/dist/bot/human-loop.js +0 -8
  58. package/dist/bot/menu.js +0 -3
  59. package/dist/bot/orchestration.js +0 -3
  60. package/dist/bot/render-shared.js +0 -63
  61. package/dist/bot/session-hub.js +14 -92
  62. package/dist/bot/session-status.js +1 -5
  63. package/dist/bot/streaming.js +0 -53
  64. package/dist/browser-profile.js +0 -51
  65. package/dist/browser-supervisor.js +0 -75
  66. package/dist/catalog/cli-tools.js +0 -53
  67. package/dist/catalog/index.js +0 -18
  68. package/dist/catalog/local-models.js +0 -18
  69. package/dist/catalog/mcp-servers.js +0 -35
  70. package/dist/catalog/skill-repos.js +0 -32
  71. package/dist/channels/base.js +0 -19
  72. package/dist/channels/dingtalk/bot.js +0 -3
  73. package/dist/channels/dingtalk/channel.js +0 -33
  74. package/dist/channels/discord/bot.js +0 -7
  75. package/dist/channels/discord/channel.js +0 -22
  76. package/dist/channels/feishu/bot.js +0 -71
  77. package/dist/channels/feishu/channel.js +0 -78
  78. package/dist/channels/feishu/markdown.js +0 -5
  79. package/dist/channels/feishu/render.js +0 -45
  80. package/dist/channels/health.js +0 -46
  81. package/dist/channels/slack/bot.js +0 -7
  82. package/dist/channels/slack/channel.js +1 -27
  83. package/dist/channels/states.js +0 -3
  84. package/dist/channels/telegram/bot.js +0 -60
  85. package/dist/channels/telegram/channel.js +2 -99
  86. package/dist/channels/telegram/directory.js +0 -3
  87. package/dist/channels/telegram/live-preview.js +0 -8
  88. package/dist/channels/telegram/render.js +0 -30
  89. package/dist/channels/wecom/bot.js +0 -3
  90. package/dist/channels/wecom/channel.js +0 -58
  91. package/dist/channels/weixin/api.js +0 -9
  92. package/dist/channels/weixin/bot.js +0 -88
  93. package/dist/channels/weixin/channel.js +0 -6
  94. package/dist/cli/autostart.js +1 -44
  95. package/dist/cli/channel-supervisor.js +1 -59
  96. package/dist/cli/channels.js +0 -6
  97. package/dist/cli/main.js +1 -145
  98. package/dist/cli/onboarding.js +0 -6
  99. package/dist/cli/run.js +0 -11
  100. package/dist/cli/setup-wizard.js +0 -3
  101. package/dist/core/config/runtime-config.js +0 -38
  102. package/dist/core/config/user-config.js +0 -63
  103. package/dist/core/config/validation.js +0 -9
  104. package/dist/core/constants.js +0 -268
  105. package/dist/core/git.js +0 -31
  106. package/dist/core/legacy-compat.js +1 -28
  107. package/dist/core/logging.js +0 -3
  108. package/dist/core/platform.js +0 -28
  109. package/dist/core/process-control.js +0 -8
  110. package/dist/core/secrets/index.js +0 -17
  111. package/dist/core/secrets/inline-seal.js +0 -7
  112. package/dist/core/secrets/ref.js +0 -19
  113. package/dist/core/secrets/resolver.js +0 -8
  114. package/dist/core/secrets/store.js +1 -12
  115. package/dist/core/utils.js +1 -37
  116. package/dist/core/version.js +0 -3
  117. package/dist/dashboard/platform.js +1 -35
  118. package/dist/dashboard/routes/agents.js +2 -71
  119. package/dist/dashboard/routes/cli.js +0 -27
  120. package/dist/dashboard/routes/config.js +0 -54
  121. package/dist/dashboard/routes/extensions.js +0 -132
  122. package/dist/dashboard/routes/local-models.js +0 -68
  123. package/dist/dashboard/routes/models.js +0 -46
  124. package/dist/dashboard/routes/sessions.js +0 -100
  125. package/dist/dashboard/runtime.js +1 -58
  126. package/dist/dashboard/server.js +0 -49
  127. package/dist/dashboard/session-control.js +2 -69
  128. package/dist/model/catalog.js +1 -17
  129. package/dist/model/index.js +0 -15
  130. package/dist/model/injector.js +1 -181
  131. package/dist/model/provider-models.js +1 -48
  132. package/dist/model/responses-bridge.js +3 -45
  133. package/dist/model/store.js +0 -17
  134. package/dist/model/types.js +0 -12
  135. package/dist/model/validation.js +1 -18
  136. package/dist/pikichannel/adapter-pikiloom.js +0 -31
  137. package/dist/pikichannel/code.js +0 -12
  138. package/dist/pikichannel/codec.js +0 -14
  139. package/dist/pikichannel/host.js +4 -33
  140. package/dist/pikichannel/protocol.js +0 -52
  141. package/dist/pikichannel/rendezvous-broker.js +5 -26
  142. package/dist/pikichannel/rendezvous-host.js +3 -16
  143. package/dist/pikichannel/server.js +6 -55
  144. package/dist/pikichannel/transport.js +0 -25
  145. package/dist/pikichannel/transports/webrtc-host.js +0 -18
  146. package/dist/pikichannel/transports/webrtc-shared.js +4 -34
  147. package/dist/pikichannel/transports/websocket-host.js +1 -14
  148. package/dist/pikichannel/turn.js +2 -69
  149. package/dist/pikichannel/web/sdk.js +14 -71
  150. package/package.json +1 -1
  151. package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
  152. package/dashboard/dist/assets/SessionPanel-_0dLWvEb.js +0 -1
  153. package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
  154. package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
  155. package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
  156. 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
  ];
@@ -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, // chat.postMessage with thread_ts emulates ack
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;
@@ -1,6 +1,3 @@
1
- /**
2
- * Channel validation state caching.
3
- */
4
1
  export function shouldCacheChannelStates(channels) {
5
2
  return channels.every(channel => !channel.configured || channel.validated);
6
3
  }
@@ -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);