pikiloop 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +353 -0
- package/README.v2.md +287 -0
- package/README.zh-CN.md +352 -0
- package/dashboard/dist/assets/AgentTab-UZPIhlkr.js +1 -0
- package/dashboard/dist/assets/DirBrowser-Ckcmi-Pi.js +1 -0
- package/dashboard/dist/assets/ExtensionsTab-KZhEDrdu.js +1 -0
- package/dashboard/dist/assets/IMAccessTab-Bd_IY1GQ.js +1 -0
- package/dashboard/dist/assets/Modal-CTeL0y7P.js +1 -0
- package/dashboard/dist/assets/Modals-axftHasy.js +1 -0
- package/dashboard/dist/assets/Select-C8tOdPhe.js +1 -0
- package/dashboard/dist/assets/SessionPanel-C1geSRxw.js +1 -0
- package/dashboard/dist/assets/SystemTab-DBDkaPiO.js +1 -0
- package/dashboard/dist/assets/anthropic-BAdojD7P.ico +0 -0
- package/dashboard/dist/assets/codex-DYadqqp0.png +0 -0
- package/dashboard/dist/assets/deepseek-BeYNZEk0.ico +0 -0
- package/dashboard/dist/assets/doubao-DloFDuFR.png +0 -0
- package/dashboard/dist/assets/feishu-C4OMrjCW.ico +0 -0
- package/dashboard/dist/assets/gemini-BYkEpiWr.svg +1 -0
- package/dashboard/dist/assets/hermes-BAarh-tH.png +0 -0
- package/dashboard/dist/assets/index-CpM4CqZJ.js +23 -0
- package/dashboard/dist/assets/index-DXSohzrE.js +3 -0
- package/dashboard/dist/assets/index-reSbuley.css +1 -0
- package/dashboard/dist/assets/markdown-DxQYQFeH.js +29 -0
- package/dashboard/dist/assets/minimax-PuEGTfrF.ico +0 -0
- package/dashboard/dist/assets/mlx-DhWwjtMw.png +0 -0
- package/dashboard/dist/assets/ollama-Bt9O-2K_.png +0 -0
- package/dashboard/dist/assets/openrouter-CsJ_bD5Q.ico +0 -0
- package/dashboard/dist/assets/playwright-BldPFZgC.ico +0 -0
- package/dashboard/dist/assets/qwen-xykkX0_y.png +0 -0
- package/dashboard/dist/assets/react-vendor-C7Sl8SE7.js +9 -0
- package/dashboard/dist/assets/router-DHISdpPk.js +3 -0
- package/dashboard/dist/assets/shared-BIP_4k4I.js +1 -0
- package/dashboard/dist/favicon.svg +28 -0
- package/dashboard/dist/index.html +17 -0
- package/dist/agent/acp-client.js +261 -0
- package/dist/agent/auto-update.js +432 -0
- package/dist/agent/await-resume.js +50 -0
- package/dist/agent/cli/auth.js +325 -0
- package/dist/agent/cli/catalog.js +40 -0
- package/dist/agent/cli/detector.js +136 -0
- package/dist/agent/cli/index.js +7 -0
- package/dist/agent/cli/registry.js +33 -0
- package/dist/agent/driver.js +39 -0
- package/dist/agent/drivers/claude-tui.js +2297 -0
- package/dist/agent/drivers/claude.js +2689 -0
- package/dist/agent/drivers/codex.js +2210 -0
- package/dist/agent/drivers/gemini.js +1059 -0
- package/dist/agent/drivers/hermes.js +795 -0
- package/dist/agent/goal.js +274 -0
- package/dist/agent/handover.js +130 -0
- package/dist/agent/images.js +355 -0
- package/dist/agent/index.js +50 -0
- package/dist/agent/mcp/bridge.js +791 -0
- package/dist/agent/mcp/extensions.js +637 -0
- package/dist/agent/mcp/oauth.js +353 -0
- package/dist/agent/mcp/registry.js +119 -0
- package/dist/agent/mcp/session-server.js +229 -0
- package/dist/agent/mcp/tools/ask-user.js +113 -0
- package/dist/agent/mcp/tools/await-resume.js +77 -0
- package/dist/agent/mcp/tools/goal.js +144 -0
- package/dist/agent/mcp/tools/types.js +12 -0
- package/dist/agent/mcp/tools/workspace.js +212 -0
- package/dist/agent/npm.js +31 -0
- package/dist/agent/session.js +1206 -0
- package/dist/agent/skill-installer.js +160 -0
- package/dist/agent/skills.js +257 -0
- package/dist/agent/stream.js +743 -0
- package/dist/agent/types.js +13 -0
- package/dist/agent/utils.js +687 -0
- package/dist/bot/bot.js +2499 -0
- package/dist/bot/command-ui.js +633 -0
- package/dist/bot/commands.js +513 -0
- package/dist/bot/headless-bot.js +36 -0
- package/dist/bot/host.js +192 -0
- package/dist/bot/human-loop.js +168 -0
- package/dist/bot/menu.js +48 -0
- package/dist/bot/orchestration.js +79 -0
- package/dist/bot/render-shared.js +309 -0
- package/dist/bot/session-hub.js +361 -0
- package/dist/bot/session-status.js +55 -0
- package/dist/bot/streaming.js +309 -0
- package/dist/browser-profile.js +579 -0
- package/dist/browser-supervisor.js +249 -0
- package/dist/catalog/cli-tools.js +421 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/local-models.js +94 -0
- package/dist/catalog/mcp-servers.js +315 -0
- package/dist/catalog/skill-repos.js +173 -0
- package/dist/channels/base.js +55 -0
- package/dist/channels/dingtalk/bot.js +549 -0
- package/dist/channels/dingtalk/channel.js +268 -0
- package/dist/channels/discord/bot.js +552 -0
- package/dist/channels/discord/channel.js +245 -0
- package/dist/channels/feishu/bot.js +1275 -0
- package/dist/channels/feishu/channel.js +911 -0
- package/dist/channels/feishu/markdown.js +91 -0
- package/dist/channels/feishu/render.js +619 -0
- package/dist/channels/health.js +109 -0
- package/dist/channels/slack/bot.js +554 -0
- package/dist/channels/slack/channel.js +283 -0
- package/dist/channels/states.js +6 -0
- package/dist/channels/telegram/bot.js +1310 -0
- package/dist/channels/telegram/channel.js +820 -0
- package/dist/channels/telegram/directory.js +111 -0
- package/dist/channels/telegram/live-preview.js +220 -0
- package/dist/channels/telegram/render.js +384 -0
- package/dist/channels/wecom/bot.js +558 -0
- package/dist/channels/wecom/channel.js +479 -0
- package/dist/channels/weixin/api.js +520 -0
- package/dist/channels/weixin/bot.js +1000 -0
- package/dist/channels/weixin/channel.js +222 -0
- package/dist/cli/autostart.js +262 -0
- package/dist/cli/channel-supervisor.js +313 -0
- package/dist/cli/channels.js +54 -0
- package/dist/cli/main.js +726 -0
- package/dist/cli/onboarding.js +227 -0
- package/dist/cli/run.js +308 -0
- package/dist/cli/setup-wizard.js +235 -0
- package/dist/core/config/runtime-config.js +201 -0
- package/dist/core/config/user-config.js +510 -0
- package/dist/core/config/validation.js +521 -0
- package/dist/core/constants.js +400 -0
- package/dist/core/git.js +145 -0
- package/dist/core/legacy-compat.js +60 -0
- package/dist/core/logging.js +101 -0
- package/dist/core/platform.js +59 -0
- package/dist/core/process-control.js +315 -0
- package/dist/core/secrets/index.js +42 -0
- package/dist/core/secrets/inline-seal.js +60 -0
- package/dist/core/secrets/ref.js +33 -0
- package/dist/core/secrets/resolver.js +65 -0
- package/dist/core/secrets/store.js +63 -0
- package/dist/core/utils.js +233 -0
- package/dist/core/version.js +15 -0
- package/dist/dashboard/platform.js +219 -0
- package/dist/dashboard/routes/agents.js +450 -0
- package/dist/dashboard/routes/cli.js +174 -0
- package/dist/dashboard/routes/config.js +523 -0
- package/dist/dashboard/routes/extensions.js +745 -0
- package/dist/dashboard/routes/local-models.js +290 -0
- package/dist/dashboard/routes/models.js +324 -0
- package/dist/dashboard/routes/sessions.js +838 -0
- package/dist/dashboard/runtime.js +410 -0
- package/dist/dashboard/server.js +237 -0
- package/dist/dashboard/session-control.js +347 -0
- package/dist/model/catalog.js +104 -0
- package/dist/model/index.js +20 -0
- package/dist/model/injector.js +272 -0
- package/dist/model/provider-models.js +112 -0
- package/dist/model/store.js +212 -0
- package/dist/model/types.js +13 -0
- package/dist/model/validation.js +203 -0
- package/package.json +82 -0
|
@@ -0,0 +1,283 @@
|
|
|
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
|
+
import { SocketModeClient } from '@slack/socket-mode';
|
|
16
|
+
import { WebClient } from '@slack/web-api';
|
|
17
|
+
import { Channel, DEFAULT_CHANNEL_CAPABILITIES, splitText, sleep, } from '../base.js';
|
|
18
|
+
import { SLACK_LIMITS } from '../../core/constants.js';
|
|
19
|
+
import { writeScopedLog } from '../../core/logging.js';
|
|
20
|
+
import { ChannelHealth } from '../health.js';
|
|
21
|
+
const SLACK_MAX = SLACK_LIMITS.maxMessageLength;
|
|
22
|
+
function describeError(err) {
|
|
23
|
+
return err instanceof Error ? err.message : String(err ?? 'unknown error');
|
|
24
|
+
}
|
|
25
|
+
export class SlackChannel extends Channel {
|
|
26
|
+
capabilities = {
|
|
27
|
+
...DEFAULT_CHANNEL_CAPABILITIES,
|
|
28
|
+
editMessages: true,
|
|
29
|
+
typingIndicators: false, // chat.postMessage with thread_ts emulates ack
|
|
30
|
+
};
|
|
31
|
+
knownChats = new Set();
|
|
32
|
+
botToken;
|
|
33
|
+
appToken;
|
|
34
|
+
allowedChatIds;
|
|
35
|
+
requireMention;
|
|
36
|
+
webClient;
|
|
37
|
+
socketClient = null;
|
|
38
|
+
botUserId = null;
|
|
39
|
+
running = false;
|
|
40
|
+
listenResolve = null;
|
|
41
|
+
messageHandlers = new Set();
|
|
42
|
+
errorHandlers = new Set();
|
|
43
|
+
/** Dedup: Slack delivers events at-least-once via Socket Mode acks. */
|
|
44
|
+
seenEventIds = new Set();
|
|
45
|
+
seenEventQueue = [];
|
|
46
|
+
static SEEN_EVENT_CAP = 256;
|
|
47
|
+
constructor(opts) {
|
|
48
|
+
super();
|
|
49
|
+
this.botToken = opts.botToken;
|
|
50
|
+
this.appToken = opts.appToken;
|
|
51
|
+
this.allowedChatIds = opts.allowedChatIds;
|
|
52
|
+
this.requireMention = opts.requireMentionInChannel ?? true;
|
|
53
|
+
}
|
|
54
|
+
onMessage(handler) { this.messageHandlers.add(handler); return this; }
|
|
55
|
+
onError(handler) { this.errorHandlers.add(handler); return this; }
|
|
56
|
+
// ========================================================================
|
|
57
|
+
// Lifecycle
|
|
58
|
+
// ========================================================================
|
|
59
|
+
async connect() {
|
|
60
|
+
this.webClient = new WebClient(this.botToken);
|
|
61
|
+
let lastErr = null;
|
|
62
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
63
|
+
try {
|
|
64
|
+
const auth = await this.webClient.auth.test();
|
|
65
|
+
if (!auth?.ok)
|
|
66
|
+
throw new Error(`auth.test failed: ${auth?.error || 'unknown'}`);
|
|
67
|
+
this.botUserId = String(auth.user_id || '').trim();
|
|
68
|
+
this.bot = {
|
|
69
|
+
id: this.botUserId || '',
|
|
70
|
+
username: String(auth.user || '').trim(),
|
|
71
|
+
displayName: String(auth.user || auth.team || 'slack-bot').trim(),
|
|
72
|
+
};
|
|
73
|
+
return this.bot;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
lastErr = err;
|
|
77
|
+
if (attempt >= 5)
|
|
78
|
+
break;
|
|
79
|
+
await sleep(Math.min(1000 * attempt, 5_000));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Slack connect failed: ${describeError(lastErr)}`);
|
|
83
|
+
}
|
|
84
|
+
async listen() {
|
|
85
|
+
this.running = true;
|
|
86
|
+
this.socketClient = new SocketModeClient({
|
|
87
|
+
appToken: this.appToken,
|
|
88
|
+
// We log via our own scope; suppress noisy SDK info logs.
|
|
89
|
+
logLevel: undefined,
|
|
90
|
+
});
|
|
91
|
+
this.socketClient.on('message', async ({ event, ack }) => {
|
|
92
|
+
try {
|
|
93
|
+
await ack();
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
void this.dispatchMessageEvent(event).catch(error => {
|
|
97
|
+
this.emitError(error instanceof Error ? error : new Error(describeError(error)));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
this.socketClient.on('app_mention', async ({ event, ack }) => {
|
|
101
|
+
try {
|
|
102
|
+
await ack();
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
void this.dispatchMessageEvent(event).catch(error => {
|
|
106
|
+
this.emitError(error instanceof Error ? error : new Error(describeError(error)));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
this.socketClient.on('error', err => {
|
|
110
|
+
this.emitError(err instanceof Error ? err : new Error(describeError(err)));
|
|
111
|
+
});
|
|
112
|
+
this.socketClient.on('disconnect', () => {
|
|
113
|
+
this.debug('[ws] disconnected — SDK will auto-reconnect');
|
|
114
|
+
});
|
|
115
|
+
const health = new ChannelHealth({
|
|
116
|
+
label: 'Slack',
|
|
117
|
+
opAction: 'WS start',
|
|
118
|
+
initialDelayMs: SLACK_LIMITS.initialRetryDelay,
|
|
119
|
+
maxDelayMs: SLACK_LIMITS.maxRetryDelay,
|
|
120
|
+
sustainedFailureHint: 'verify slackBotToken (xoxb-) / slackAppToken (xapp-) in setting.json',
|
|
121
|
+
log: (msg, level) => this.log(msg, level),
|
|
122
|
+
});
|
|
123
|
+
while (this.running) {
|
|
124
|
+
try {
|
|
125
|
+
await this.socketClient.start();
|
|
126
|
+
health.recordSuccess();
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (!this.running)
|
|
131
|
+
return;
|
|
132
|
+
await sleep(health.recordFailure(err));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!this.running) {
|
|
136
|
+
try {
|
|
137
|
+
await this.socketClient.disconnect();
|
|
138
|
+
}
|
|
139
|
+
catch { }
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
await new Promise(resolve => {
|
|
143
|
+
this.listenResolve = resolve;
|
|
144
|
+
if (!this.running)
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
disconnect() {
|
|
149
|
+
this.running = false;
|
|
150
|
+
if (this.socketClient) {
|
|
151
|
+
try {
|
|
152
|
+
void this.socketClient.disconnect();
|
|
153
|
+
}
|
|
154
|
+
catch { }
|
|
155
|
+
this.socketClient = null;
|
|
156
|
+
}
|
|
157
|
+
this.listenResolve?.();
|
|
158
|
+
this.listenResolve = null;
|
|
159
|
+
}
|
|
160
|
+
// ========================================================================
|
|
161
|
+
// Outgoing primitives
|
|
162
|
+
// ========================================================================
|
|
163
|
+
async send(chatId, text, opts = {}) {
|
|
164
|
+
const channelId = String(chatId);
|
|
165
|
+
const chunks = splitText((text || '').trim() || '(empty)', SLACK_MAX);
|
|
166
|
+
let lastTs = null;
|
|
167
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
168
|
+
const chunk = chunks[i];
|
|
169
|
+
const payload = { channel: channelId, text: chunk, mrkdwn: true };
|
|
170
|
+
if (opts.replyTo)
|
|
171
|
+
payload.thread_ts = String(opts.replyTo);
|
|
172
|
+
const resp = await this.webClient.chat.postMessage(payload);
|
|
173
|
+
if (!resp?.ok) {
|
|
174
|
+
throw new Error(`Slack chat.postMessage failed: ${resp?.error || 'unknown'}`);
|
|
175
|
+
}
|
|
176
|
+
const ts = String(resp.ts || resp.message?.ts || '').trim();
|
|
177
|
+
if (ts)
|
|
178
|
+
lastTs = ts;
|
|
179
|
+
}
|
|
180
|
+
return lastTs;
|
|
181
|
+
}
|
|
182
|
+
async editMessage(chatId, msgId, text, _opts) {
|
|
183
|
+
if (!String(text).trim())
|
|
184
|
+
return;
|
|
185
|
+
const channelId = String(chatId);
|
|
186
|
+
const ts = String(msgId);
|
|
187
|
+
const trimmed = text.length > SLACK_MAX ? text.slice(0, SLACK_MAX) + '\n…(truncated)' : text;
|
|
188
|
+
try {
|
|
189
|
+
const resp = await this.webClient.chat.update({ channel: channelId, ts, text: trimmed, mrkdwn: true });
|
|
190
|
+
if (!resp?.ok) {
|
|
191
|
+
const detail = String(resp?.error || '');
|
|
192
|
+
if (detail === 'message_not_found' || detail === 'cant_update_message')
|
|
193
|
+
return;
|
|
194
|
+
throw new Error(`Slack chat.update failed: ${detail || 'unknown'}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
const detail = describeError(err).toLowerCase();
|
|
199
|
+
if (detail.includes('message_not_found') || detail.includes('cant_update_message'))
|
|
200
|
+
return;
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async deleteMessage(chatId, msgId) {
|
|
205
|
+
try {
|
|
206
|
+
await this.webClient.chat.delete({ channel: String(chatId), ts: String(msgId) });
|
|
207
|
+
}
|
|
208
|
+
catch { }
|
|
209
|
+
}
|
|
210
|
+
async sendTyping(_chatId, _opts) {
|
|
211
|
+
// Slack does not expose a typing indicator in the public API.
|
|
212
|
+
}
|
|
213
|
+
// ========================================================================
|
|
214
|
+
// Internal dispatch
|
|
215
|
+
// ========================================================================
|
|
216
|
+
async dispatchMessageEvent(event) {
|
|
217
|
+
if (!event || event.bot_id || event.subtype === 'bot_message')
|
|
218
|
+
return;
|
|
219
|
+
if (this.botUserId && event.user === this.botUserId)
|
|
220
|
+
return;
|
|
221
|
+
const channelId = String(event.channel || '').trim();
|
|
222
|
+
const messageTs = String(event.ts || event.event_ts || '').trim();
|
|
223
|
+
const userId = String(event.user || '').trim();
|
|
224
|
+
if (!channelId || !messageTs || !userId)
|
|
225
|
+
return;
|
|
226
|
+
const eventId = String(event.client_msg_id || `${channelId}:${messageTs}`);
|
|
227
|
+
if (this.seenEventIds.has(eventId))
|
|
228
|
+
return;
|
|
229
|
+
this.seenEventIds.add(eventId);
|
|
230
|
+
this.seenEventQueue.push(eventId);
|
|
231
|
+
while (this.seenEventQueue.length > SlackChannel.SEEN_EVENT_CAP) {
|
|
232
|
+
this.seenEventIds.delete(this.seenEventQueue.shift());
|
|
233
|
+
}
|
|
234
|
+
if (!this.isAllowed(channelId))
|
|
235
|
+
return;
|
|
236
|
+
this.knownChats.add(channelId);
|
|
237
|
+
const channelType = String(event.channel_type || '').trim();
|
|
238
|
+
const isDm = channelType === 'im';
|
|
239
|
+
const text = String(event.text || '').trim();
|
|
240
|
+
const mention = this.botUserId ? `<@${this.botUserId}>` : '';
|
|
241
|
+
const mentionedHere = mention ? text.includes(mention) : false;
|
|
242
|
+
if (!isDm && this.requireMention && !mentionedHere)
|
|
243
|
+
return;
|
|
244
|
+
const cleanedText = mention ? text.replace(new RegExp(`<@${this.botUserId}>`, 'g'), '').trim() : text;
|
|
245
|
+
const ctx = {
|
|
246
|
+
chatId: channelId,
|
|
247
|
+
messageId: messageTs,
|
|
248
|
+
threadTs: typeof event.thread_ts === 'string' && event.thread_ts ? event.thread_ts : null,
|
|
249
|
+
from: { userId, username: event.user_profile?.display_name || event.username || undefined },
|
|
250
|
+
reply: (replyText, opts) => this.send(channelId, replyText, {
|
|
251
|
+
...opts,
|
|
252
|
+
replyTo: opts?.replyTo ?? (event.thread_ts || messageTs),
|
|
253
|
+
}),
|
|
254
|
+
editReply: (msgId, replyText, opts) => this.editMessage(channelId, msgId, replyText, opts),
|
|
255
|
+
channel: this,
|
|
256
|
+
raw: event,
|
|
257
|
+
};
|
|
258
|
+
const payload = { text: cleanedText, files: [] };
|
|
259
|
+
for (const handler of this.messageHandlers) {
|
|
260
|
+
try {
|
|
261
|
+
await handler(payload, ctx);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
this.emitError(error instanceof Error ? error : new Error(describeError(error)));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
isAllowed(chatId) {
|
|
269
|
+
if (!this.allowedChatIds?.size)
|
|
270
|
+
return true;
|
|
271
|
+
return this.allowedChatIds.has(chatId);
|
|
272
|
+
}
|
|
273
|
+
emitError(error) {
|
|
274
|
+
for (const handler of this.errorHandlers) {
|
|
275
|
+
try {
|
|
276
|
+
handler(error);
|
|
277
|
+
}
|
|
278
|
+
catch { }
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
debug(msg) { this.log(msg, 'debug'); }
|
|
282
|
+
log(msg, level = 'info') { writeScopedLog('slack', msg, { level }); }
|
|
283
|
+
}
|