@miphamai/cli 0.50.0 → 0.51.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/bin/mipham.ts +1 -9
- package/package.json +1 -1
- package/skills/standard/web-access/references/cdp-api.md +2 -1
- package/skills/standard/web-access/scripts/cdp-proxy.mjs +75 -1
- package/skills/standard/web-access/scripts/check-deps.mjs +17 -2
- package/skills/standard/web-access/scripts/find-url.mjs +0 -0
- package/skills/standard/web-access/scripts/match-site.mjs +0 -0
- package/skills/standard/web-access.SKILL.md +10 -3
- package/src/commands/project.ts +1 -1
- package/src/config/defaults.ts +5 -0
- package/src/core/auto-memory.ts +3 -3
- package/src/core/constitution-loader.ts +0 -2
- package/src/core/context.ts +0 -1
- package/src/core/credential-masker/index.ts +1 -0
- package/src/core/credential-masker/matcher.ts +18 -7
- package/src/core/credential-masker/search.ts +73 -0
- package/src/core/credential-masker.ts +1 -0
- package/src/core/crsi-sandbox.ts +0 -1
- package/src/core/dream-engine.ts +0 -2
- package/src/core/memory/memory-loader.ts +3 -1
- package/src/core/permission-rules.ts +23 -12
- package/src/core/rules-loader.ts +8 -10
- package/src/daemon/database.ts +0 -1
- package/src/daemon/feishu/adapter.ts +2 -1
- package/src/daemon/index.ts +18 -8
- package/src/daemon/server.ts +53 -10
- package/src/daemon/session-manager.ts +5 -4
- package/src/daemon/telegram/adapter.ts +70 -0
- package/src/daemon/telegram/api.ts +45 -0
- package/src/daemon/telegram/env.ts +14 -0
- package/src/daemon/telegram/poller.ts +64 -0
- package/src/daemon/telegram/types.ts +10 -0
- package/src/i18n-core/locales/en-US.json +123 -0
- package/src/i18n-core/locales/zh-CN.json +123 -0
- package/src/index.tsx +6 -0
- package/src/shared/package-info.ts +1 -1
- package/src/shared/types.ts +2 -0
- package/src/skills/bundled-skill-assets.ts +5 -5
- package/src/skills/bundled-skills.ts +1 -1
- package/src/tools/agent/exit-plan.ts +1 -3
- package/src/tools/agent/memory.ts +2 -1
- package/src/tools/file/glob.ts +38 -23
- package/src/tools/file/grep.ts +86 -64
- package/src/tools/index.ts +5 -5
- package/src/ui/commands.ts +188 -119
- package/src/ui/config-wizard.tsx +1 -1
package/src/daemon/index.ts
CHANGED
|
@@ -25,6 +25,8 @@ import type { DaemonStatus } from './types'
|
|
|
25
25
|
import { logger } from './logger'
|
|
26
26
|
import { parseFeishuEnv } from './feishu/env.js'
|
|
27
27
|
import type { FeishuConfig } from './feishu/types.js'
|
|
28
|
+
import { parseTelegramEnv } from './telegram/env.js'
|
|
29
|
+
import type { TelegramConfig } from './telegram/types.js'
|
|
28
30
|
import { loadConfig } from '../config/loader'
|
|
29
31
|
|
|
30
32
|
const HOME = homedir()
|
|
@@ -40,8 +42,6 @@ const DEFAULT_PORT = 45671
|
|
|
40
42
|
let activeServer: Server<unknown> | null = null
|
|
41
43
|
let activeDb: DaemonDatabase | null = null
|
|
42
44
|
let activePool: WorkerPool | null = null
|
|
43
|
-
let activeAgentManager: AgentManager | null = null
|
|
44
|
-
let activeMessageBus: MessageBus | null = null
|
|
45
45
|
let activeScheduleManager: ScheduleManager | null = null
|
|
46
46
|
let activeRateLimiter: RateLimiter | null = null
|
|
47
47
|
|
|
@@ -150,9 +150,7 @@ export async function startDaemon(): Promise<{ port: number; token: string }> {
|
|
|
150
150
|
|
|
151
151
|
// Create agent manager and message bus (Phase 3)
|
|
152
152
|
const agentManager = new AgentManager(db)
|
|
153
|
-
activeAgentManager = agentManager
|
|
154
153
|
const messageBus = new MessageBus()
|
|
155
|
-
activeMessageBus = messageBus
|
|
156
154
|
|
|
157
155
|
// Create goal manager and schedule manager (Phase 4)
|
|
158
156
|
const goalManager = new GoalManager(db)
|
|
@@ -181,6 +179,21 @@ export async function startDaemon(): Promise<{ port: number; token: string }> {
|
|
|
181
179
|
}
|
|
182
180
|
}
|
|
183
181
|
|
|
182
|
+
// Telegram remote-control adapter(env 未配置时跳过;配置则解析 session 默认 provider/model)
|
|
183
|
+
const telegramConfig = parseTelegramEnv()
|
|
184
|
+
let telegram: { config: TelegramConfig; cwd: string; provider: string; model: string } | undefined
|
|
185
|
+
if (telegramConfig) {
|
|
186
|
+
const cfg = loadConfig()
|
|
187
|
+
// 取第一个非 upcoming(即非「待上线」)的 provider 及其首个 model
|
|
188
|
+
const provider = cfg.providers.find((p) => p.status !== 'upcoming') ?? cfg.providers[0]
|
|
189
|
+
telegram = {
|
|
190
|
+
config: telegramConfig,
|
|
191
|
+
cwd: process.env.TELEGRAM_CWD || process.cwd(),
|
|
192
|
+
provider: provider?.id ?? 'anthropic',
|
|
193
|
+
model: provider?.models?.[0]?.id ?? 'claude-sonnet-5',
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
184
197
|
// Start HTTP server (Bun.serve starts listening immediately)
|
|
185
198
|
const server = createServer({
|
|
186
199
|
db,
|
|
@@ -196,6 +209,7 @@ export async function startDaemon(): Promise<{ port: number; token: string }> {
|
|
|
196
209
|
scheduleManager,
|
|
197
210
|
rateLimiter,
|
|
198
211
|
feishu,
|
|
212
|
+
telegram,
|
|
199
213
|
})
|
|
200
214
|
activeServer = server
|
|
201
215
|
|
|
@@ -250,10 +264,6 @@ export async function stopDaemon(force: boolean = false): Promise<void> {
|
|
|
250
264
|
activeRateLimiter = null
|
|
251
265
|
}
|
|
252
266
|
|
|
253
|
-
// Clean up agent manager and message bus (Phase 3)
|
|
254
|
-
activeAgentManager = null
|
|
255
|
-
activeMessageBus = null
|
|
256
|
-
|
|
257
267
|
// Close database (commits any pending WAL)
|
|
258
268
|
if (activeDb) {
|
|
259
269
|
activeDb.close()
|
package/src/daemon/server.ts
CHANGED
|
@@ -26,6 +26,9 @@ import type { ToolDefinition, PermissionMode, PermissionRestrictions } from '../
|
|
|
26
26
|
import { createFeishuAdapter } from './feishu/adapter.js'
|
|
27
27
|
import { createFeishuApi } from './feishu/api.js'
|
|
28
28
|
import type { FeishuConfig } from './feishu/types.js'
|
|
29
|
+
import { createTelegramAdapter } from './telegram/adapter.js'
|
|
30
|
+
import { createTelegramApi } from './telegram/api.js'
|
|
31
|
+
import type { TelegramConfig } from './telegram/types.js'
|
|
29
32
|
import { startHeartbeat } from './heartbeat'
|
|
30
33
|
|
|
31
34
|
interface ServerConfig {
|
|
@@ -42,6 +45,7 @@ interface ServerConfig {
|
|
|
42
45
|
scheduleManager: ScheduleManager
|
|
43
46
|
rateLimiter: RateLimiter
|
|
44
47
|
feishu?: { config: FeishuConfig; cwd: string; provider: string; model: string }
|
|
48
|
+
telegram?: { config: TelegramConfig; cwd: string; provider: string; model: string }
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
interface WsData {
|
|
@@ -71,13 +75,20 @@ function resolveDaemonPermission(): PermissionMode {
|
|
|
71
75
|
|
|
72
76
|
/**
|
|
73
77
|
* Build the daemon's PermissionSystem, honoring org-level restrictions
|
|
74
|
-
* (permissionRestrictions)
|
|
75
|
-
* so a `MIPHAM_DAEMON_PERMISSION=bypassPermissions`
|
|
76
|
-
* config forbids it — mirroring the CLI's fail-closed behavior.
|
|
78
|
+
* (permissionRestrictions) and user-defined allow/deny rules. setRestrictions
|
|
79
|
+
* re-clamps the env-derived mode, so a `MIPHAM_DAEMON_PERMISSION=bypassPermissions`
|
|
80
|
+
* is downgraded when the config forbids it — mirroring the CLI's fail-closed behavior.
|
|
77
81
|
*/
|
|
78
|
-
export function buildDaemonPermission(
|
|
82
|
+
export function buildDaemonPermission(
|
|
83
|
+
restrictions?: PermissionRestrictions,
|
|
84
|
+
rules?: { allow?: string[]; deny?: string[] },
|
|
85
|
+
): PermissionSystem {
|
|
79
86
|
const permission = new PermissionSystem(resolveDaemonPermission())
|
|
80
87
|
if (restrictions) permission.setRestrictions(restrictions)
|
|
88
|
+
if (rules) {
|
|
89
|
+
for (const rule of rules.allow ?? []) permission.allow(rule)
|
|
90
|
+
for (const rule of rules.deny ?? []) permission.deny(rule)
|
|
91
|
+
}
|
|
81
92
|
return permission
|
|
82
93
|
}
|
|
83
94
|
|
|
@@ -112,6 +123,7 @@ export function createServer(config: ServerConfig): Server<WsData> {
|
|
|
112
123
|
scheduleManager,
|
|
113
124
|
rateLimiter,
|
|
114
125
|
feishu,
|
|
126
|
+
telegram,
|
|
115
127
|
} = config
|
|
116
128
|
|
|
117
129
|
const wsClients = new Map<string, Set<ServerWebSocket<WsData>>>()
|
|
@@ -199,7 +211,11 @@ export function createServer(config: ServerConfig): Server<WsData> {
|
|
|
199
211
|
}
|
|
200
212
|
}
|
|
201
213
|
|
|
202
|
-
const
|
|
214
|
+
const daemonConfig = loadConfig(cwd)
|
|
215
|
+
const permission = buildDaemonPermission(
|
|
216
|
+
daemonConfig.permissionRestrictions,
|
|
217
|
+
daemonConfig.permissionRules,
|
|
218
|
+
)
|
|
203
219
|
const engine = new QueryEngine(sharedRegistry, context, sharedTools, permission)
|
|
204
220
|
engine.setSessionId(sessionId)
|
|
205
221
|
engineCache.set(sessionId, engine)
|
|
@@ -208,7 +224,7 @@ export function createServer(config: ServerConfig): Server<WsData> {
|
|
|
208
224
|
|
|
209
225
|
function getOrCreateWorker(
|
|
210
226
|
sessionId: string,
|
|
211
|
-
|
|
227
|
+
_ws?: ServerWebSocket<WsData>,
|
|
212
228
|
): SessionWorker | null {
|
|
213
229
|
try {
|
|
214
230
|
const session = db.getSession(sessionId)
|
|
@@ -245,14 +261,28 @@ export function createServer(config: ServerConfig): Server<WsData> {
|
|
|
245
261
|
})
|
|
246
262
|
: undefined
|
|
247
263
|
|
|
264
|
+
// ── Telegram remote-control adapter(长轮询,无需 webhook 路由)──
|
|
265
|
+
const telegramAdapter = telegram
|
|
266
|
+
? createTelegramAdapter(telegram.config, createTelegramApi(telegram.config), {
|
|
267
|
+
sm,
|
|
268
|
+
getOrCreateWorker,
|
|
269
|
+
rateLimiter,
|
|
270
|
+
cwd: telegram.cwd,
|
|
271
|
+
provider: telegram.provider,
|
|
272
|
+
model: telegram.model,
|
|
273
|
+
})
|
|
274
|
+
: undefined
|
|
275
|
+
telegramAdapter?.start()
|
|
276
|
+
|
|
248
277
|
// ── 心跳式通知:定时扫 pending(goal/schedule),只通知、不自主行动 ──
|
|
278
|
+
const heartbeatSource = {
|
|
279
|
+
listGoals: () => sm.listSessions().flatMap((s) => goalManager.getGoals(s.id)),
|
|
280
|
+
listSchedules: () => sm.listSessions().flatMap((s) => scheduleManager.getSchedules(s.id)),
|
|
281
|
+
}
|
|
249
282
|
if (feishu) {
|
|
250
283
|
const feishuApi = createFeishuApi(feishu.config)
|
|
251
284
|
startHeartbeat({
|
|
252
|
-
source:
|
|
253
|
-
listGoals: () => sm.listSessions().flatMap((s) => goalManager.getGoals(s.id)),
|
|
254
|
-
listSchedules: () => sm.listSessions().flatMap((s) => scheduleManager.getSchedules(s.id)),
|
|
255
|
-
},
|
|
285
|
+
source: heartbeatSource,
|
|
256
286
|
push: (message) => {
|
|
257
287
|
for (const openId of feishu.config.allowedOpenIds) {
|
|
258
288
|
void feishuApi.sendText(openId, message).catch(() => {
|
|
@@ -262,6 +292,19 @@ export function createServer(config: ServerConfig): Server<WsData> {
|
|
|
262
292
|
},
|
|
263
293
|
})
|
|
264
294
|
}
|
|
295
|
+
if (telegram) {
|
|
296
|
+
const telegramApi = createTelegramApi(telegram.config)
|
|
297
|
+
startHeartbeat({
|
|
298
|
+
source: heartbeatSource,
|
|
299
|
+
push: (message) => {
|
|
300
|
+
for (const chatId of telegram.config.allowedChatIds) {
|
|
301
|
+
void telegramApi.sendText(chatId, message).catch(() => {
|
|
302
|
+
/* 推送失败静默,不打断心跳 */
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
})
|
|
307
|
+
}
|
|
265
308
|
|
|
266
309
|
const server = Bun.serve<WsData>({
|
|
267
310
|
port,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DaemonDatabase } from './database'
|
|
2
|
-
import type { DaemonSession
|
|
2
|
+
import type { DaemonSession } from './types'
|
|
3
3
|
|
|
4
4
|
type SessionCloseCallback = (sessionId: string) => void
|
|
5
5
|
|
|
@@ -23,13 +23,14 @@ export class SessionManager {
|
|
|
23
23
|
return this.db.listSessions(status)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
getOrCreateByExternalUser(
|
|
27
|
+
channel: string,
|
|
28
|
+
userId: string,
|
|
28
29
|
cwd: string,
|
|
29
30
|
provider: string,
|
|
30
31
|
model: string,
|
|
31
32
|
): DaemonSession {
|
|
32
|
-
const name =
|
|
33
|
+
const name = `${channel}-${userId}`
|
|
33
34
|
const existing = this.db.listSessions().find((s) => s.name === name && s.status !== 'closed')
|
|
34
35
|
if (existing) return existing
|
|
35
36
|
return this.db.createSession({ name, cwd, provider, model })
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { TelegramApi } from './api.js'
|
|
2
|
+
import type { TelegramConfig, TelegramMessage } from './types.js'
|
|
3
|
+
import type { SessionManager } from '../session-manager'
|
|
4
|
+
import type { SessionWorker } from '../session-worker'
|
|
5
|
+
import type { RateLimiter } from '../rate-limiter'
|
|
6
|
+
import { startTelegramPoller } from './poller.js'
|
|
7
|
+
|
|
8
|
+
export interface TelegramAdapterDeps {
|
|
9
|
+
sm: SessionManager
|
|
10
|
+
getOrCreateWorker: (sessionId: string) => SessionWorker | null
|
|
11
|
+
rateLimiter: RateLimiter
|
|
12
|
+
cwd: string
|
|
13
|
+
provider: string
|
|
14
|
+
model: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TelegramAdapter {
|
|
18
|
+
start(): () => void
|
|
19
|
+
handleMessage(msg: TelegramMessage): Promise<void>
|
|
20
|
+
isAllowed(chatId: string): boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createTelegramAdapter(
|
|
24
|
+
config: TelegramConfig,
|
|
25
|
+
api: TelegramApi,
|
|
26
|
+
deps: TelegramAdapterDeps,
|
|
27
|
+
): TelegramAdapter {
|
|
28
|
+
const allowed = new Set(config.allowedChatIds)
|
|
29
|
+
let stopPoller: (() => void) | null = null
|
|
30
|
+
|
|
31
|
+
async function handleMessage(msg: TelegramMessage): Promise<void> {
|
|
32
|
+
try {
|
|
33
|
+
if (!allowed.has(msg.chatId)) return
|
|
34
|
+
if (!deps.rateLimiter.check(`telegram:${msg.chatId}`).allowed) return
|
|
35
|
+
|
|
36
|
+
const session = deps.sm.getOrCreateByExternalUser(
|
|
37
|
+
'telegram',
|
|
38
|
+
msg.chatId,
|
|
39
|
+
deps.cwd,
|
|
40
|
+
deps.provider,
|
|
41
|
+
deps.model,
|
|
42
|
+
)
|
|
43
|
+
const worker = deps.getOrCreateWorker(session.id)
|
|
44
|
+
if (!worker) {
|
|
45
|
+
await api.sendText(msg.chatId, '(会话初始化失败,请稍后重试)')
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
await worker.processPrompt(msg.text)
|
|
49
|
+
const result = worker.getLastAssistantContent()
|
|
50
|
+
await api.sendText(msg.chatId, result ? result.slice(0, 4096) : '(无回复)')
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error('[telegram] message handling failed:', err)
|
|
53
|
+
try {
|
|
54
|
+
await api.sendText(msg.chatId, '(处理失败,请稍后重试)')
|
|
55
|
+
} catch {
|
|
56
|
+
/* 忽略回送失败,不 rethrow */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
handleMessage,
|
|
63
|
+
isAllowed: (chatId) => allowed.has(chatId),
|
|
64
|
+
start() {
|
|
65
|
+
if (stopPoller) return stopPoller
|
|
66
|
+
stopPoller = startTelegramPoller(api, handleMessage)
|
|
67
|
+
return stopPoller
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TelegramConfig } from './types.js'
|
|
2
|
+
|
|
3
|
+
export interface TelegramApi {
|
|
4
|
+
getUpdates(
|
|
5
|
+
offset: number,
|
|
6
|
+
timeoutSeconds: number,
|
|
7
|
+
): Promise<Array<{ update_id: number; message?: unknown }>>
|
|
8
|
+
sendText(chatId: string, text: string): Promise<void>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** 裸 fetch 直连 Telegram Bot API,零依赖。 */
|
|
12
|
+
export function createTelegramApi(config: TelegramConfig): TelegramApi {
|
|
13
|
+
const base = `https://api.telegram.org/bot${config.botToken}`
|
|
14
|
+
return {
|
|
15
|
+
async getUpdates(offset, timeoutSeconds) {
|
|
16
|
+
const url =
|
|
17
|
+
`${base}/getUpdates?offset=${offset}&timeout=${timeoutSeconds}` +
|
|
18
|
+
`&limit=100&allowed_updates=%5B%22message%22%5D`
|
|
19
|
+
const res = await fetch(url)
|
|
20
|
+
if (!res.ok) throw new Error(`getUpdates ${res.status}`)
|
|
21
|
+
const data = (await res.json()) as {
|
|
22
|
+
ok: boolean
|
|
23
|
+
result?: Array<{ update_id: number; message?: unknown }>
|
|
24
|
+
error_code?: number
|
|
25
|
+
description?: string
|
|
26
|
+
}
|
|
27
|
+
if (!data.ok) throw new Error(`getUpdates failed: ${data.error_code ?? res.status}`)
|
|
28
|
+
return data.result ?? []
|
|
29
|
+
},
|
|
30
|
+
async sendText(chatId, text) {
|
|
31
|
+
const res = await fetch(`${base}/sendMessage`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { 'Content-Type': 'application/json' },
|
|
34
|
+
body: JSON.stringify({ chat_id: chatId, text }),
|
|
35
|
+
})
|
|
36
|
+
if (!res.ok) throw new Error(`sendMessage ${res.status}`)
|
|
37
|
+
const data = (await res.json()) as {
|
|
38
|
+
ok: boolean
|
|
39
|
+
error_code?: number
|
|
40
|
+
description?: string
|
|
41
|
+
}
|
|
42
|
+
if (!data.ok) throw new Error(`sendMessage failed: ${data.error_code ?? res.status}`)
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TelegramConfig } from './types.js'
|
|
2
|
+
|
|
3
|
+
/** fail-closed:缺 botToken → null(daemon 不启用 Telegram)。 */
|
|
4
|
+
export function parseTelegramEnv(): TelegramConfig | null {
|
|
5
|
+
const botToken = process.env.TELEGRAM_BOT_TOKEN
|
|
6
|
+
if (!botToken) return null
|
|
7
|
+
return {
|
|
8
|
+
botToken,
|
|
9
|
+
allowedChatIds: (process.env.TELEGRAM_ALLOWED_CHAT_IDS || '')
|
|
10
|
+
.split(',')
|
|
11
|
+
.map((s) => s.trim())
|
|
12
|
+
.filter(Boolean),
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { TelegramApi } from './api.js'
|
|
2
|
+
import type { TelegramMessage } from './types.js'
|
|
3
|
+
|
|
4
|
+
/** 从 update 提取文本消息;非文本/缺 message → null。chat.id 统一字符串化比对(id < 2^53;超范围 bigint 后续再引入)。 */
|
|
5
|
+
export function extractTextMessage(update: unknown): TelegramMessage | null {
|
|
6
|
+
const msg = (
|
|
7
|
+
update as { message?: { chat?: { id?: number | string }; message_id?: number; text?: string } }
|
|
8
|
+
)?.message
|
|
9
|
+
if (!msg?.text || msg.chat?.id == null) return null
|
|
10
|
+
return { chatId: String(msg.chat.id), messageId: msg.message_id ?? 0, text: msg.text }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 推进 offset:最大 update_id + 1;空列表沿用 prev。 */
|
|
14
|
+
export function nextOffset(updates: Array<{ update_id?: number }>, prevOffset: number): number {
|
|
15
|
+
if (updates.length === 0) return prevOffset
|
|
16
|
+
return Math.max(...updates.map((u) => u.update_id ?? prevOffset)) + 1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 指数退避,封顶 30s。 */
|
|
20
|
+
export function nextBackoff(currentMs: number): number {
|
|
21
|
+
return Math.min(currentMs * 2, 30_000)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 长轮询循环(unref 不阻退出)。返回 stop 函数。循环行为由 Task 7 集成测试覆盖。 */
|
|
25
|
+
export function startTelegramPoller(
|
|
26
|
+
api: TelegramApi,
|
|
27
|
+
onMessage: (msg: TelegramMessage) => Promise<void>,
|
|
28
|
+
opts?: { pollTimeoutSeconds?: number },
|
|
29
|
+
): () => void {
|
|
30
|
+
let offset = 0
|
|
31
|
+
let backoffMs = 1000
|
|
32
|
+
let stopped = false
|
|
33
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
34
|
+
|
|
35
|
+
async function loop() {
|
|
36
|
+
if (stopped) return
|
|
37
|
+
try {
|
|
38
|
+
const updates = await api.getUpdates(offset, opts?.pollTimeoutSeconds ?? 30)
|
|
39
|
+
for (const u of updates) {
|
|
40
|
+
const msg = extractTextMessage(u)
|
|
41
|
+
if (msg) {
|
|
42
|
+
try {
|
|
43
|
+
await onMessage(msg)
|
|
44
|
+
} catch {
|
|
45
|
+
/* 单条失败不中断轮询 */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
offset = nextOffset(updates, offset)
|
|
50
|
+
backoffMs = 1000
|
|
51
|
+
} catch {
|
|
52
|
+
backoffMs = nextBackoff(backoffMs)
|
|
53
|
+
}
|
|
54
|
+
if (stopped) return
|
|
55
|
+
timer = setTimeout(loop, backoffMs)
|
|
56
|
+
;(timer as { unref?: () => void }).unref?.()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
void loop()
|
|
60
|
+
return () => {
|
|
61
|
+
stopped = true
|
|
62
|
+
if (timer) clearTimeout(timer)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -612,6 +612,129 @@
|
|
|
612
612
|
},
|
|
613
613
|
"schedule": {
|
|
614
614
|
"failed": "Schedule check failed: {error}"
|
|
615
|
+
},
|
|
616
|
+
"crsi_critique": {
|
|
617
|
+
"not_init": "SelfCritique not initialized. Mipham Code >= v0.34.0 required.",
|
|
618
|
+
"on_title": "## 🔍 Self-Critique: ON",
|
|
619
|
+
"on_desc1": "The AI will now critique its own tool calls before execution.",
|
|
620
|
+
"on_desc2": "Safe + correct + necessary checks run before every targeted tool call.",
|
|
621
|
+
"on_hint": "💡 `/crsi critique off` to disable | `/crsi critique status` for current config",
|
|
622
|
+
"off_title": "## 🔍 Self-Critique: OFF\n\nTool calls will execute without pre-flight critique.",
|
|
623
|
+
"status_title": "## 🔍 Self-Critique Status",
|
|
624
|
+
"state": "State: **{state}**",
|
|
625
|
+
"model": "Model: **{model}**",
|
|
626
|
+
"threshold": "Threshold: **{pct}%**",
|
|
627
|
+
"threshold_detail": "Threshold: **{pct}%** (below this → correction or block)",
|
|
628
|
+
"target_tools": "Target tools: **{tools}**",
|
|
629
|
+
"timeout": "Timeout: **{ms}ms**",
|
|
630
|
+
"status_on_hint": "🔍 `/crsi critique on` — Enable self-critique",
|
|
631
|
+
"status_off_hint": "🔍 `/crsi critique off` — Disable self-critique",
|
|
632
|
+
"inspired": "*Inspired by Anthropic RLAIF — AI critiques its own actions before execution.*"
|
|
633
|
+
},
|
|
634
|
+
"crsi_interpret": {
|
|
635
|
+
"title": "## 🧠 CRSI Tool-Call Interpretability",
|
|
636
|
+
"no_sigs_for_tool": "No error signatures for tool `{tool}`. CRSI immune memory is clean for this tool.",
|
|
637
|
+
"no_sigs": "No active error signatures. CRSI immune memory is clean.",
|
|
638
|
+
"sigs_title": "### 🛡️ Error Signatures{tool}",
|
|
639
|
+
"sig_success": " Success: {bar} {pct}% | {occurrences}x | {strategy}",
|
|
640
|
+
"sig_fix": " Fix: `{fix}`",
|
|
641
|
+
"sigs_more": "... and {count} more signatures",
|
|
642
|
+
"reflection_title": "### 📊 CRSI Reflection Summary",
|
|
643
|
+
"reflections": "Turn reflections analyzed: **{count}**",
|
|
644
|
+
"usage_title": "### 💰 Token Usage",
|
|
645
|
+
"api_in": "API input tokens: {count}",
|
|
646
|
+
"api_out": "API output tokens: {count}",
|
|
647
|
+
"est_tokens": "Estimated tokens: {count}",
|
|
648
|
+
"health_title": "### 🏥 System Health",
|
|
649
|
+
"overall": "Overall: {bar} **{score}/100**",
|
|
650
|
+
"assessment": "Assessment: {assessment}",
|
|
651
|
+
"constitution_title": "### ⚖️ Constitution",
|
|
652
|
+
"principles": "Principles: **{total}** (🚫 {block} block | ⚠️ {warn} warn | 🔄 {auto} auto)",
|
|
653
|
+
"version": "Version: v{version}",
|
|
654
|
+
"empty": "_Start using tools to populate CRSI interpretability data._",
|
|
655
|
+
"filter_hint": "🔍 Filter by tool: `/crsi interpret <tool-name>` (e.g. `/crsi interpret Bash`)"
|
|
656
|
+
},
|
|
657
|
+
"crsi_red_team": {
|
|
658
|
+
"not_init": "Red-Team requires both ConstitutionLoader and PreFlightChecker to be initialized.",
|
|
659
|
+
"title": "## 🔴 CRSI Red-Team Report",
|
|
660
|
+
"score": "Overall Score: **{score}/100**",
|
|
661
|
+
"metric": "| Metric | Count |",
|
|
662
|
+
"metric_sep": "|--------|------|",
|
|
663
|
+
"total_attacks": "| Total attacks | {count} |",
|
|
664
|
+
"blocked": "| 🛡️ Correctly blocked | {count} |",
|
|
665
|
+
"passed_through": "| 🔴 Passed through (GAP) | {count} |",
|
|
666
|
+
"false_positives": "| ⚠️ False positives | {count} |",
|
|
667
|
+
"by_principle": "### By Principle",
|
|
668
|
+
"gaps_title": "### 🔴 Security Gaps (Should Have Been Blocked)",
|
|
669
|
+
"gap_tool": " Tool: `{tool}` → Params: `{params}`",
|
|
670
|
+
"blocked_title": "### 🛡️ Successfully Blocked",
|
|
671
|
+
"caught_by": "{principle}: {desc} → caught by **{caught}**",
|
|
672
|
+
"all_blocked": "🎉 **All attacks blocked!** The SIS immune system is fully operational.",
|
|
673
|
+
"good_coverage": "⚠️ Good coverage. Review the gaps above and add audit patterns to `ai-guardrails.yml`.",
|
|
674
|
+
"critical": "🔴 **Critical gaps detected.** Prioritize fixing the passed-through attacks above."
|
|
675
|
+
},
|
|
676
|
+
"dream": {
|
|
677
|
+
"not_init": "DreamEngine not initialized. Mipham Code >= v0.34.0 required.",
|
|
678
|
+
"no_history": "🌙 Auto-Dream has not run yet. Use `/dream` to trigger the first consolidation.",
|
|
679
|
+
"history_title": "## 🌙 Auto-Dream History",
|
|
680
|
+
"history_entry": "- **{time}**: {total} actions ({applied} auto-applied, {flagged} flagged for review)",
|
|
681
|
+
"done_title": "## 🌙 Auto-Dream Complete",
|
|
682
|
+
"integrate": "Memory integration: {before} → {after}",
|
|
683
|
+
"dedup": "- Deduplicated: {count} entries",
|
|
684
|
+
"contradiction": "- Contradictions: {count} (need human review)",
|
|
685
|
+
"merge": "- Merged: {count} groups",
|
|
686
|
+
"solidify": "- Fuzzy-marked: {count} entries (suggested to solidify)",
|
|
687
|
+
"prune": "- Pruned expired: {count} entries",
|
|
688
|
+
"review_hint": "⚠️ {count} items pending review. Use `/dream --aggressive` to auto-apply all.",
|
|
689
|
+
"clean": "✅ Memory is clean, nothing to consolidate."
|
|
690
|
+
},
|
|
691
|
+
"constitution": {
|
|
692
|
+
"not_init": "ConstitutionLoader not initialized. Mipham Code >= v0.34.0 required.",
|
|
693
|
+
"reload_title": "## ⚖️ Constitution Reloaded",
|
|
694
|
+
"version": "Version: **v{version}**",
|
|
695
|
+
"principles": "Principles: **{count}**",
|
|
696
|
+
"path": "Path: `{path}`",
|
|
697
|
+
"view_title": "## ⚖️ Mipham Constitution",
|
|
698
|
+
"view_summary": "Version: **v{version}** | Principles: **{count}** | Path: `{path}`",
|
|
699
|
+
"scope": " Scope: `{scope}`",
|
|
700
|
+
"tools": " Tools: {tools}",
|
|
701
|
+
"reload_hint": "🔧 `/constitution reload` — Reload after editing ai-guardrails.yml",
|
|
702
|
+
"edit_hint": "📝 Edit: `vi ~/.mipham/ai-guardrails.yml`",
|
|
703
|
+
"reset_hint": "🗑️ Reset: delete `~/.mipham/ai-guardrails.yml` then run `/constitution reload`",
|
|
704
|
+
"inspired": "*Inspired by Anthropic Constitutional AI. Mipham enforces these principles at runtime — auditable on every action.*"
|
|
705
|
+
},
|
|
706
|
+
"bug_report": {
|
|
707
|
+
"title": "## 🐛 Bug Report",
|
|
708
|
+
"hint": "> Copy the report below and paste it into your GitHub Issue.",
|
|
709
|
+
"env_title": "### Environment",
|
|
710
|
+
"mipham": "- **Mipham Code**: v{version}",
|
|
711
|
+
"session": "- **Session**: {id}",
|
|
712
|
+
"node": "- **Node**: {version}",
|
|
713
|
+
"platform": "- **Platform**: {platform} {arch}",
|
|
714
|
+
"os": "- **OS**: {os}",
|
|
715
|
+
"provider": "- **Provider**: {provider}",
|
|
716
|
+
"model": "- **Model**: {model}",
|
|
717
|
+
"sigs_title": "### Active Error Signatures",
|
|
718
|
+
"sig_entry": "- `{id}`: {pattern} ({category}, {occurrences}x, {pct}% success)",
|
|
719
|
+
"disabled_hooks": "### Disabled Hooks",
|
|
720
|
+
"hook_entry": "- `{key}`: {failures} failures, disabled at {time}",
|
|
721
|
+
"last_dream": "### Last Auto-Dream",
|
|
722
|
+
"ran_at": "- **Ran at**: {time}",
|
|
723
|
+
"actions": "- **Actions**: {total} ({applied} applied)",
|
|
724
|
+
"steps_title": "### Steps to Reproduce",
|
|
725
|
+
"expected": "### Expected Behavior",
|
|
726
|
+
"actual": "### Actual Behavior",
|
|
727
|
+
"generated": "🤖 Generated with Mipham Code v{version}"
|
|
728
|
+
},
|
|
729
|
+
"changelog": {
|
|
730
|
+
"title": "## 📋 Changelog",
|
|
731
|
+
"no_tags": "No version tags found.",
|
|
732
|
+
"current": "Current: **v{version}**",
|
|
733
|
+
"current_marker": " ← current",
|
|
734
|
+
"release_only": " _(release tag only)_",
|
|
735
|
+
"details_unavailable": " _(details unavailable)_",
|
|
736
|
+
"no_git": "Unable to read git history. Make sure you are in the Mipham Code repository.",
|
|
737
|
+
"full_history": "Full history: `git log --oneline`"
|
|
615
738
|
}
|
|
616
739
|
},
|
|
617
740
|
"ui": {
|