pikiclaw 0.2.35
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 +315 -0
- package/dist/agent-driver.js +24 -0
- package/dist/bot-command-ui.js +299 -0
- package/dist/bot-commands.js +236 -0
- package/dist/bot-feishu-render.js +527 -0
- package/dist/bot-feishu.js +752 -0
- package/dist/bot-handler.js +115 -0
- package/dist/bot-menu.js +44 -0
- package/dist/bot-streaming.js +165 -0
- package/dist/bot-telegram-directory.js +74 -0
- package/dist/bot-telegram-live-preview.js +192 -0
- package/dist/bot-telegram-render.js +369 -0
- package/dist/bot-telegram.js +789 -0
- package/dist/bot.js +897 -0
- package/dist/channel-base.js +46 -0
- package/dist/channel-feishu.js +873 -0
- package/dist/channel-states.js +3 -0
- package/dist/channel-telegram.js +773 -0
- package/dist/cli-channels.js +24 -0
- package/dist/cli.js +484 -0
- package/dist/code-agent.js +1080 -0
- package/dist/config-validation.js +244 -0
- package/dist/dashboard-ui.js +31 -0
- package/dist/dashboard.js +840 -0
- package/dist/driver-claude.js +520 -0
- package/dist/driver-codex.js +1055 -0
- package/dist/driver-gemini.js +230 -0
- package/dist/mcp-bridge.js +192 -0
- package/dist/mcp-session-server.js +321 -0
- package/dist/onboarding.js +138 -0
- package/dist/process-control.js +259 -0
- package/dist/run.js +275 -0
- package/dist/session-status.js +43 -0
- package/dist/setup-wizard.js +231 -0
- package/dist/user-config.js +195 -0
- package/package.json +60 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import * as lark from '@larksuiteoapi/node-sdk';
|
|
2
|
+
import { validateTelegramToken } from './setup-wizard.js';
|
|
3
|
+
const DEFAULT_FEISHU_VALIDATION_TIMEOUT_MS = 15_000;
|
|
4
|
+
function feishuValidationLog(appId, message) {
|
|
5
|
+
const ts = new Date().toISOString().slice(11, 19);
|
|
6
|
+
process.stdout.write(`[feishu-validate ${ts}] app=${appId} ${message}\n`);
|
|
7
|
+
}
|
|
8
|
+
function maskAppId(appId) {
|
|
9
|
+
if (!appId)
|
|
10
|
+
return '(missing)';
|
|
11
|
+
if (appId.length <= 10)
|
|
12
|
+
return appId;
|
|
13
|
+
return `${appId.slice(0, 6)}...${appId.slice(-4)}`;
|
|
14
|
+
}
|
|
15
|
+
class ValidationTimeoutError extends Error {
|
|
16
|
+
constructor(service, timeoutMs) {
|
|
17
|
+
super(`${service} request timed out after ${timeoutMs}ms.`);
|
|
18
|
+
this.name = 'ValidationTimeoutError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function withTimeout(promise, timeoutMs, service, onTimeout) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
let settled = false;
|
|
24
|
+
const timer = setTimeout(() => {
|
|
25
|
+
if (settled)
|
|
26
|
+
return;
|
|
27
|
+
settled = true;
|
|
28
|
+
onTimeout?.();
|
|
29
|
+
reject(new ValidationTimeoutError(service, timeoutMs));
|
|
30
|
+
}, timeoutMs);
|
|
31
|
+
promise
|
|
32
|
+
.then(value => {
|
|
33
|
+
if (settled)
|
|
34
|
+
return;
|
|
35
|
+
settled = true;
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
resolve(value);
|
|
38
|
+
})
|
|
39
|
+
.catch(error => {
|
|
40
|
+
if (settled)
|
|
41
|
+
return;
|
|
42
|
+
settled = true;
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
reject(error);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function missingChannelState(channel, detail) {
|
|
49
|
+
return {
|
|
50
|
+
channel,
|
|
51
|
+
configured: false,
|
|
52
|
+
ready: false,
|
|
53
|
+
validated: false,
|
|
54
|
+
status: 'missing',
|
|
55
|
+
detail,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function invalidChannelState(channel, detail) {
|
|
59
|
+
return {
|
|
60
|
+
channel,
|
|
61
|
+
configured: true,
|
|
62
|
+
ready: false,
|
|
63
|
+
validated: true,
|
|
64
|
+
status: 'invalid',
|
|
65
|
+
detail,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function errorChannelState(channel, detail) {
|
|
69
|
+
return {
|
|
70
|
+
channel,
|
|
71
|
+
configured: true,
|
|
72
|
+
ready: false,
|
|
73
|
+
validated: true,
|
|
74
|
+
status: 'error',
|
|
75
|
+
detail,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function readyChannelState(channel, detail) {
|
|
79
|
+
return {
|
|
80
|
+
channel,
|
|
81
|
+
configured: true,
|
|
82
|
+
ready: true,
|
|
83
|
+
validated: true,
|
|
84
|
+
status: 'ready',
|
|
85
|
+
detail,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function normalizeTelegramAllowedChatIds(raw) {
|
|
89
|
+
const value = String(raw || '').trim();
|
|
90
|
+
if (!value)
|
|
91
|
+
return { ok: true, normalized: '', ids: [], error: null };
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
const ids = [];
|
|
94
|
+
for (const part of value.split(',')) {
|
|
95
|
+
const trimmed = part.trim();
|
|
96
|
+
if (!trimmed)
|
|
97
|
+
continue;
|
|
98
|
+
if (!/^-?\d+$/.test(trimmed)) {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
normalized: value,
|
|
102
|
+
ids: [],
|
|
103
|
+
error: 'Allowed Chat IDs must be comma-separated numeric chat IDs.',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const parsed = Number(trimmed);
|
|
107
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
normalized: value,
|
|
111
|
+
ids: [],
|
|
112
|
+
error: 'Allowed Chat IDs contains a value outside the safe integer range.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (seen.has(parsed))
|
|
116
|
+
continue;
|
|
117
|
+
seen.add(parsed);
|
|
118
|
+
ids.push(parsed);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
normalized: ids.join(','),
|
|
123
|
+
ids,
|
|
124
|
+
error: null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function isTelegramNetworkError(error) {
|
|
128
|
+
const detail = String(error || '');
|
|
129
|
+
return detail.startsWith('Failed to reach Telegram:') || detail.startsWith('Telegram returned invalid JSON');
|
|
130
|
+
}
|
|
131
|
+
export async function validateTelegramConfig(token, allowedChatIds) {
|
|
132
|
+
const trimmedToken = String(token || '').trim();
|
|
133
|
+
if (!trimmedToken) {
|
|
134
|
+
return {
|
|
135
|
+
state: missingChannelState('telegram', 'Telegram bot token is not configured.'),
|
|
136
|
+
bot: null,
|
|
137
|
+
normalizedAllowedChatIds: '',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const ids = normalizeTelegramAllowedChatIds(allowedChatIds);
|
|
141
|
+
if (!ids.ok) {
|
|
142
|
+
return {
|
|
143
|
+
state: invalidChannelState('telegram', ids.error || 'Allowed Chat IDs is invalid.'),
|
|
144
|
+
bot: null,
|
|
145
|
+
normalizedAllowedChatIds: ids.normalized,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const tokenCheck = await validateTelegramToken(trimmedToken);
|
|
149
|
+
if (!tokenCheck.ok) {
|
|
150
|
+
return {
|
|
151
|
+
state: (isTelegramNetworkError(tokenCheck.error)
|
|
152
|
+
? errorChannelState('telegram', tokenCheck.error || 'Telegram validation failed.')
|
|
153
|
+
: invalidChannelState('telegram', tokenCheck.error || 'Telegram validation failed.')),
|
|
154
|
+
bot: null,
|
|
155
|
+
normalizedAllowedChatIds: ids.normalized,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const identity = tokenCheck.bot?.username
|
|
159
|
+
? `@${tokenCheck.bot.username}${tokenCheck.bot?.displayName ? ` (${tokenCheck.bot.displayName})` : ''}`
|
|
160
|
+
: 'Telegram bot verified.';
|
|
161
|
+
return {
|
|
162
|
+
state: readyChannelState('telegram', identity),
|
|
163
|
+
bot: tokenCheck.bot,
|
|
164
|
+
normalizedAllowedChatIds: ids.normalized,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export async function validateFeishuConfig(appId, appSecret, options = {}) {
|
|
168
|
+
const trimmedAppId = String(appId || '').trim();
|
|
169
|
+
const trimmedSecret = String(appSecret || '').trim();
|
|
170
|
+
const appLabel = maskAppId(trimmedAppId);
|
|
171
|
+
const apiDomain = String(process.env.FEISHU_DOMAIN || 'https://open.feishu.cn').trim().replace(/\/+$/, '');
|
|
172
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) && Number(options.timeoutMs) > 0
|
|
173
|
+
? Math.round(Number(options.timeoutMs))
|
|
174
|
+
: DEFAULT_FEISHU_VALIDATION_TIMEOUT_MS;
|
|
175
|
+
if (!trimmedAppId && !trimmedSecret) {
|
|
176
|
+
return {
|
|
177
|
+
state: missingChannelState('feishu', 'Feishu credentials are not configured.'),
|
|
178
|
+
app: null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (!trimmedAppId || !trimmedSecret) {
|
|
182
|
+
return {
|
|
183
|
+
state: invalidChannelState('feishu', 'Both App ID and App Secret are required.'),
|
|
184
|
+
app: null,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const startedAt = Date.now();
|
|
189
|
+
feishuValidationLog(appLabel, `start domain=${apiDomain} timeoutMs=${timeoutMs}`);
|
|
190
|
+
const sdkDomain = apiDomain.includes('larksuite.com')
|
|
191
|
+
? lark.Domain.Lark
|
|
192
|
+
: apiDomain === 'https://open.feishu.cn'
|
|
193
|
+
? lark.Domain.Feishu
|
|
194
|
+
: apiDomain;
|
|
195
|
+
const client = new lark.Client({
|
|
196
|
+
appId: trimmedAppId,
|
|
197
|
+
appSecret: trimmedSecret,
|
|
198
|
+
domain: sdkDomain,
|
|
199
|
+
loggerLevel: lark.LoggerLevel.warn,
|
|
200
|
+
});
|
|
201
|
+
const parsed = await withTimeout(client.auth.tenantAccessToken.internal({
|
|
202
|
+
data: { app_id: trimmedAppId, app_secret: trimmedSecret },
|
|
203
|
+
}), timeoutMs, 'Feishu validation');
|
|
204
|
+
feishuValidationLog(appLabel, `response code=${String(parsed?.code ?? '')} hasToken=${typeof parsed?.tenant_access_token === 'string'} elapsedMs=${Date.now() - startedAt}`);
|
|
205
|
+
if (parsed?.code !== 0 || typeof parsed?.tenant_access_token !== 'string' || !parsed.tenant_access_token) {
|
|
206
|
+
const detail = typeof parsed?.msg === 'string' && parsed.msg.trim() ? parsed.msg.trim() : 'credentials rejected';
|
|
207
|
+
feishuValidationLog(appLabel, `rejected code=${String(parsed?.code ?? '')} detail=${detail} elapsedMs=${Date.now() - startedAt}`);
|
|
208
|
+
return {
|
|
209
|
+
state: invalidChannelState('feishu', `Feishu rejected these credentials: ${detail}`),
|
|
210
|
+
app: null,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const app = { appId: trimmedAppId, displayName: null };
|
|
214
|
+
feishuValidationLog(appLabel, `verified elapsedMs=${Date.now() - startedAt}`);
|
|
215
|
+
return {
|
|
216
|
+
state: readyChannelState('feishu', `App ${trimmedAppId} verified.`),
|
|
217
|
+
app,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
feishuValidationLog(appLabel, `error ${(err instanceof Error ? err.message : String(err ?? 'unknown error'))}`);
|
|
222
|
+
if (err instanceof ValidationTimeoutError) {
|
|
223
|
+
return {
|
|
224
|
+
state: errorChannelState('feishu', `Failed to reach Feishu: ${err.message}`),
|
|
225
|
+
app: null,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const message = err instanceof Error ? err.message : String(err ?? 'unknown error');
|
|
229
|
+
return {
|
|
230
|
+
state: errorChannelState('feishu', `Failed to reach Feishu: ${message}`),
|
|
231
|
+
app: null,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
export async function collectChannelSetupStates(config) {
|
|
236
|
+
const [telegram, feishu] = await Promise.all([
|
|
237
|
+
validateTelegramConfig(config.telegramBotToken, config.telegramAllowedChatIds),
|
|
238
|
+
validateFeishuConfig(config.feishuAppId, config.feishuAppSecret),
|
|
239
|
+
]);
|
|
240
|
+
return [
|
|
241
|
+
telegram.state,
|
|
242
|
+
feishu.state,
|
|
243
|
+
];
|
|
244
|
+
}
|