@miphamai/cli 0.38.0 → 0.39.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.
Files changed (60) hide show
  1. package/bin/mipham.ts +25 -0
  2. package/package.json +1 -1
  3. package/src/agent/sub-agent.ts +4 -3
  4. package/src/config/defaults.ts +19 -1
  5. package/src/config/keys-manager.ts +11 -1
  6. package/src/config/loader.ts +20 -10
  7. package/src/core/context.ts +86 -56
  8. package/src/core/credential-masker/output-scrub.ts +5 -0
  9. package/src/core/crsi-sandbox.ts +19 -7
  10. package/src/core/engine.ts +50 -33
  11. package/src/core/instructions.ts +4 -42
  12. package/src/core/permission.ts +7 -1
  13. package/src/core/red-team.ts +4 -4
  14. package/src/core/self-critique.ts +3 -1
  15. package/src/core/session-log.ts +225 -0
  16. package/src/core/session-store.ts +119 -34
  17. package/src/daemon/auth.ts +9 -6
  18. package/src/daemon/cors.ts +24 -16
  19. package/src/daemon/rate-limiter.ts +4 -0
  20. package/src/daemon/server.ts +24 -3
  21. package/src/index.tsx +54 -28
  22. package/src/mcp/http-transport.ts +2 -0
  23. package/src/mcp/oauth.ts +1 -1
  24. package/src/providers/llm-replay.ts +36 -0
  25. package/src/providers/llm.ts +16 -0
  26. package/src/providers/registry.ts +2 -1
  27. package/src/security/gate.ts +10 -0
  28. package/src/security/url.ts +37 -14
  29. package/src/shared/constants.ts +4 -486
  30. package/src/shared/types.ts +6 -437
  31. package/src/skills/fork-executor.ts +3 -1
  32. package/src/skills/loader.ts +2 -1
  33. package/src/skills/seam.ts +18 -0
  34. package/src/tools/agent/agent.ts +1 -0
  35. package/src/tools/agent/skill.ts +1 -0
  36. package/src/tools/exec/bash.ts +140 -128
  37. package/src/tools/exec/git.ts +17 -0
  38. package/src/tools/file/read.ts +73 -66
  39. package/src/tools/file/write.ts +1 -1
  40. package/src/tools/index.ts +54 -132
  41. package/src/tools/network/web-fetch.ts +41 -26
  42. package/src/tools/seam.ts +25 -0
  43. package/src/tools/validation.ts +91 -0
  44. package/src/ui/commands.ts +17 -5
  45. package/src/vajra/compose/assemble.ts +19 -0
  46. package/src/vajra/compose/bundle.ts +39 -0
  47. package/src/vajra/compose/dump.ts +5 -0
  48. package/src/vajra/compose/index.ts +4 -0
  49. package/src/vajra/compose/mount.ts +30 -0
  50. package/src/vajra/context.ts +183 -0
  51. package/src/vajra/events.ts +13 -0
  52. package/src/vajra/index.ts +4 -0
  53. package/src/vajra/leaf/plan-runner.ts +94 -0
  54. package/src/vajra/service.ts +15 -0
  55. package/src/workflow/primitives/agent.ts +3 -1
  56. package/src/workflow/runtime.ts +23 -15
  57. package/src/workflow/sandbox.ts +51 -9
  58. package/src/core/compaction-progress.ts +0 -205
  59. package/src/core/context-compact.ts +0 -124
  60. package/src/core/context-drain.ts +0 -113
@@ -1,10 +1,4 @@
1
1
  import dns from 'node:dns'
2
- const { lookupSync } = dns as unknown as {
3
- lookupSync: (
4
- hostname: string,
5
- options?: { all?: boolean },
6
- ) => Array<{ address: string; family: number }>
7
- }
8
2
 
9
3
  // ── Protocol allow-list ──
10
4
  const ALLOWED_PROTOCOLS = ['http:', 'https:']
@@ -20,7 +14,7 @@ const IPV4_BLOCKED = [
20
14
  ]
21
15
 
22
16
  /** Validate a URL for SSRF safety. Returns null if safe, or an error string. */
23
- export function validateUrl(urlStr: string): string | null {
17
+ export async function validateUrl(urlStr: string): Promise<string | null> {
24
18
  let parsed: URL
25
19
  try {
26
20
  parsed = new URL(urlStr)
@@ -41,18 +35,17 @@ export function validateUrl(urlStr: string): string | null {
41
35
  return `URL hostname "${hostname}" resolves to a blocked IP range (private / loopback / link-local).`
42
36
  }
43
37
 
44
- // 3. Check for raw IPv6 loopback / private
45
- // Strip brackets from IPv6 hostnames (Bun's URL parser keeps them)
38
+ // 3. Check for raw IPv6 loopback / private / IPv4-mapped
46
39
  const ipv6Host = hostname.startsWith('[') ? hostname.slice(1, -1) : hostname
47
40
  if (isBlockedIPv6(ipv6Host)) {
48
41
  return `URL hostname "${hostname}" resolves to a blocked IPv6 address.`
49
42
  }
50
43
 
51
- // 4. DNS rebinding protection: resolve hostname and check resolved IPs
52
- // Use the bracket-less version for DNS lookups
53
- const dnsHostname = hostname.startsWith('[') ? hostname.slice(1, -1) : hostname
44
+ // 4. DNS rebinding protection: resolve hostname and check resolved IPs.
45
+ // Uses async dns.promises.lookup — the old sync `dns.lookupSync` never existed,
46
+ // so this check was previously dead code (always threw → swallowed → "safe").
54
47
  try {
55
- const addresses = lookupSync(dnsHostname, { all: true })
48
+ const addresses = await dns.promises.lookup(ipv6Host, { all: true })
56
49
  for (const addr of addresses) {
57
50
  if (isBlockedIPv4(addr.address)) {
58
51
  return `URL hostname "${hostname}" resolved to ${addr.address} (blocked IP range).`
@@ -62,7 +55,7 @@ export function validateUrl(urlStr: string): string | null {
62
55
  }
63
56
  }
64
57
  } catch {
65
- // DNS resolution failed — that's OK for validation; the fetch will also fail
58
+ // DNS resolution failed — the subsequent fetch will fail too; treat as safe.
66
59
  }
67
60
 
68
61
  return null // safe
@@ -92,5 +85,35 @@ function isBlockedIPv6(ip: string): boolean {
92
85
  if (ip.startsWith('fc') || ip.startsWith('fd')) return true
93
86
  // IPv6 unspecified
94
87
  if (ip === '::' || ip === '0:0:0:0:0:0:0:0') return true
88
+
89
+ // IPv4-mapped (::ffff:a.b.c.d / ::ffff:xxxx:xxxx) and IPv4-compatible (::a.b.c.d / ::xxxx:xxxx)
90
+ // — these can smuggle a private IPv4 past the literal-IPv6 checks above.
91
+ const embedded = extractEmbeddedIPv4(ip)
92
+ if (embedded && isBlockedIPv4(embedded)) return true
93
+
95
94
  return false
96
95
  }
96
+
97
+ /** Extract the IPv4 address embedded in an IPv4-mapped / IPv4-compatible IPv6 address. */
98
+ function extractEmbeddedIPv4(ip: string): string | null {
99
+ let rest: string | null = null
100
+ const mapped = ip.match(/^::ffff:(.+)$/i)
101
+ const compat = ip.match(/^::([0-9a-fA-F]{1,4}):([0-9a-fA-F]{1,4})$/)
102
+ if (mapped) {
103
+ rest = mapped[1]!
104
+ } else if (compat) {
105
+ rest = `${compat[1]}:${compat[2]}`
106
+ } else {
107
+ return null
108
+ }
109
+
110
+ // Dotted-quad form (::ffff:127.0.0.1)
111
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(rest)) return rest
112
+
113
+ // Hex form (::ffff:7f00:1)
114
+ const hex = rest.match(/^([0-9a-fA-F]{1,4}):([0-9a-fA-F]{1,4})$/)
115
+ if (!hex) return null
116
+ const hi = parseInt(hex[1]!, 16)
117
+ const lo = parseInt(hex[2]!, 16)
118
+ return `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`
119
+ }
@@ -1,486 +1,4 @@
1
- import type { ProviderConfig } from './types'
2
-
3
- // Providers: alphabetical by id. Models: Lite/Flash → Text → Vision → Code → Pro/Think
4
- export const DEFAULT_PROVIDERS: ProviderConfig[] = [
5
- {
6
- id: 'anthropic',
7
- name: 'Anthropic Claude',
8
- protocol: 'anthropic',
9
- apiKey: '${ANTHROPIC_API_KEY}',
10
- models: [
11
- {
12
- id: 'claude-haiku-4-5-20251001',
13
- name: 'Claude Haiku 4.5',
14
- providerId: 'anthropic',
15
- contextWindow: 200_000,
16
- maxOutput: 32_000,
17
- vision: true,
18
- status: 'active',
19
- },
20
- {
21
- id: 'claude-sonnet-4-6',
22
- name: 'Claude Sonnet 4.6',
23
- providerId: 'anthropic',
24
- contextWindow: 1_000_000,
25
- maxOutput: 128_000,
26
- vision: true,
27
- status: 'active',
28
- },
29
- {
30
- id: 'claude-opus-4-8',
31
- name: 'Claude Opus 4.8',
32
- providerId: 'anthropic',
33
- contextWindow: 1_000_000,
34
- maxOutput: 128_000,
35
- vision: true,
36
- status: 'active',
37
- },
38
- {
39
- id: 'claude-fable-5',
40
- name: 'Claude Fable 5',
41
- providerId: 'anthropic',
42
- contextWindow: 1_000_000,
43
- maxOutput: 128_000,
44
- vision: true,
45
- status: 'active',
46
- },
47
- {
48
- id: 'claude-sonnet-5',
49
- name: 'Claude Sonnet 5',
50
- providerId: 'anthropic',
51
- contextWindow: 1_000_000,
52
- maxOutput: 128_000,
53
- vision: true,
54
- status: 'active',
55
- },
56
- {
57
- id: 'claude-opus-5',
58
- name: 'Claude Opus 5',
59
- providerId: 'anthropic',
60
- contextWindow: 1_000_000,
61
- maxOutput: 128_000,
62
- vision: true,
63
- status: 'active',
64
- },
65
- ],
66
- },
67
- {
68
- id: 'deepseek',
69
- name: 'DeepSeek',
70
- protocol: 'openai-compatible',
71
- baseUrl: 'https://api.deepseek.com/v1',
72
- apiKey: '${DEEPSEEK_API_KEY}',
73
- models: [
74
- {
75
- id: 'deepseek-v4-flash',
76
- name: 'DeepSeek V4 Flash',
77
- providerId: 'deepseek',
78
- contextWindow: 1_000_000,
79
- maxOutput: 384_000,
80
- vision: false,
81
- status: 'active',
82
- },
83
- {
84
- id: 'deepseek-v4-pro',
85
- name: 'DeepSeek V4 Pro',
86
- providerId: 'deepseek',
87
- contextWindow: 1_000_000,
88
- maxOutput: 384_000,
89
- vision: false,
90
- status: 'active',
91
- },
92
- ],
93
- },
94
- {
95
- id: 'google',
96
- name: 'Google Gemini',
97
- protocol: 'openai-compatible',
98
- baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
99
- apiKey: '${GEMINI_API_KEY}',
100
- models: [
101
- {
102
- id: 'gemini-3.0-flash',
103
- name: 'Gemini 3.0 Flash',
104
- providerId: 'google',
105
- contextWindow: 1_000_000,
106
- maxOutput: 64_000,
107
- vision: true,
108
- status: 'active',
109
- },
110
- {
111
- id: 'gemini-3.0-pro',
112
- name: 'Gemini 3.0 Pro',
113
- providerId: 'google',
114
- contextWindow: 1_000_000,
115
- maxOutput: 128_000,
116
- vision: true,
117
- status: 'active',
118
- },
119
- {
120
- id: 'gemini-2.5-pro',
121
- name: 'Gemini 2.5 Pro',
122
- providerId: 'google',
123
- contextWindow: 1_000_000,
124
- maxOutput: 64_000,
125
- vision: true,
126
- status: 'active',
127
- },
128
- ],
129
- },
130
- {
131
- id: 'doubao',
132
- name: '豆包 (字节跳动)',
133
- protocol: 'openai-compatible',
134
- baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
135
- apiKey: '${DOUBAO_API_KEY}',
136
- models: [
137
- {
138
- id: 'doubao-seed-1.6-flash',
139
- name: '豆包 Seed 1.6 Flash',
140
- providerId: 'doubao',
141
- contextWindow: 256_000,
142
- maxOutput: 128_000,
143
- vision: true,
144
- status: 'active',
145
- },
146
- {
147
- id: 'doubao-seed-2.0-mini',
148
- name: '豆包 Seed 2.0 Mini',
149
- providerId: 'doubao',
150
- contextWindow: 256_000,
151
- maxOutput: 128_000,
152
- vision: true,
153
- status: 'active',
154
- },
155
- {
156
- id: 'doubao-seed-2.0-lite',
157
- name: '豆包 Seed 2.0 Lite',
158
- providerId: 'doubao',
159
- contextWindow: 256_000,
160
- maxOutput: 128_000,
161
- vision: true,
162
- status: 'active',
163
- },
164
- {
165
- id: 'doubao-seed-1.6',
166
- name: '豆包 Seed 1.6',
167
- providerId: 'doubao',
168
- contextWindow: 256_000,
169
- maxOutput: 128_000,
170
- vision: true,
171
- status: 'active',
172
- },
173
- {
174
- id: 'doubao-seed-2.0-pro',
175
- name: '豆包 Seed 2.0 Pro',
176
- providerId: 'doubao',
177
- contextWindow: 256_000,
178
- maxOutput: 128_000,
179
- vision: true,
180
- status: 'active',
181
- },
182
- {
183
- id: 'doubao-seed-2.0-code',
184
- name: '豆包 Seed 2.0 Code',
185
- providerId: 'doubao',
186
- contextWindow: 256_000,
187
- maxOutput: 128_000,
188
- vision: true,
189
- status: 'active',
190
- },
191
- ],
192
- },
193
- {
194
- id: 'hunyuan',
195
- name: '腾讯混元',
196
- protocol: 'openai-compatible',
197
- baseUrl: 'https://api.hunyuan.cloud.tencent.com/v1',
198
- apiKey: '${HUNYUAN_API_KEY}',
199
- models: [
200
- {
201
- id: 'hunyuan-lite',
202
- name: '混元 Lite (免费)',
203
- providerId: 'hunyuan',
204
- contextWindow: 256_000,
205
- maxOutput: 6_000,
206
- vision: false,
207
- status: 'active',
208
- },
209
- {
210
- id: 'hunyuan-turbos-latest',
211
- name: '混元 TurboS',
212
- providerId: 'hunyuan',
213
- contextWindow: 32_000,
214
- maxOutput: 16_000,
215
- vision: false,
216
- status: 'active',
217
- },
218
- {
219
- id: 'hunyuan-2.0-instruct-20251111',
220
- name: '混元 2.0 Instruct',
221
- providerId: 'hunyuan',
222
- contextWindow: 144_000,
223
- maxOutput: 16_000,
224
- vision: false,
225
- status: 'active',
226
- },
227
- {
228
- id: 'hunyuan-t1-vision-20250916',
229
- name: '混元 T1 Vision',
230
- providerId: 'hunyuan',
231
- contextWindow: 32_000,
232
- maxOutput: 64_000,
233
- vision: true,
234
- status: 'active',
235
- },
236
- {
237
- id: 'hunyuan-a13b',
238
- name: '混元 A13B',
239
- providerId: 'hunyuan',
240
- contextWindow: 224_000,
241
- maxOutput: 32_000,
242
- vision: false,
243
- status: 'active',
244
- },
245
- {
246
- id: 'hunyuan-2.0-thinking-20251109',
247
- name: '混元 2.0 Think',
248
- providerId: 'hunyuan',
249
- contextWindow: 192_000,
250
- maxOutput: 64_000,
251
- vision: false,
252
- status: 'active',
253
- },
254
- {
255
- id: 'hunyuan-t1-latest',
256
- name: '混元 T1 (推理)',
257
- providerId: 'hunyuan',
258
- contextWindow: 32_000,
259
- maxOutput: 64_000,
260
- vision: false,
261
- status: 'active',
262
- },
263
- {
264
- id: 'hy3-preview',
265
- name: '混元 Hy3 Preview',
266
- providerId: 'hunyuan',
267
- contextWindow: 256_000,
268
- maxOutput: 128_000,
269
- vision: false,
270
- status: 'active',
271
- },
272
- ],
273
- },
274
- {
275
- id: 'kimi',
276
- name: 'Kimi (月之暗面)',
277
- protocol: 'openai-compatible',
278
- baseUrl: 'https://api.moonshot.cn/v1',
279
- apiKey: '${KIMI_API_KEY}',
280
- models: [
281
- {
282
- id: 'kimi-latest',
283
- name: 'Kimi Latest',
284
- providerId: 'kimi',
285
- contextWindow: 128_000,
286
- maxOutput: 16_000,
287
- vision: true,
288
- status: 'active',
289
- },
290
- {
291
- id: 'kimi-k3',
292
- name: 'Kimi K3',
293
- providerId: 'kimi',
294
- contextWindow: 1_000_000,
295
- maxOutput: 128_000,
296
- vision: true,
297
- status: 'active',
298
- },
299
- {
300
- id: 'moonshot-v1-8k',
301
- name: 'Moonshot v1 8K',
302
- providerId: 'kimi',
303
- contextWindow: 8_000,
304
- maxOutput: 4_000,
305
- vision: false,
306
- status: 'active',
307
- },
308
- {
309
- id: 'moonshot-v1-32k',
310
- name: 'Moonshot v1 32K',
311
- providerId: 'kimi',
312
- contextWindow: 32_000,
313
- maxOutput: 4_000,
314
- vision: false,
315
- status: 'active',
316
- },
317
- {
318
- id: 'moonshot-v1-128k',
319
- name: 'Moonshot v1 128K',
320
- providerId: 'kimi',
321
- contextWindow: 128_000,
322
- maxOutput: 4_000,
323
- vision: false,
324
- status: 'active',
325
- },
326
- ],
327
- },
328
- {
329
- id: 'mipham',
330
- name: 'MiphamAI',
331
- protocol: 'openai-compatible',
332
- baseUrl: 'http://127.0.0.1:8080/v1', // Engine 本地端点(om-V5 生产部署待定)
333
- apiKey: '${MIPHAM_API_KEY}',
334
- models: [
335
- {
336
- id: 'om-v5-flash',
337
- name: 'OM V5 Flash',
338
- providerId: 'mipham',
339
- contextWindow: 1_000_000,
340
- maxOutput: 128_000,
341
- vision: false,
342
- status: 'active',
343
- },
344
- {
345
- id: 'om-v5-pro',
346
- name: 'OM V5 Pro',
347
- providerId: 'mipham',
348
- contextWindow: 1_000_000,
349
- maxOutput: 128_000,
350
- vision: false,
351
- status: 'active',
352
- },
353
- {
354
- id: 'om-v5-visual',
355
- name: 'OM V5 Visual',
356
- providerId: 'mipham',
357
- contextWindow: 200_000,
358
- maxOutput: 32_000,
359
- vision: true,
360
- status: 'active',
361
- },
362
- {
363
- id: 'om-v5-apex',
364
- name: 'OM V5 Apex',
365
- providerId: 'mipham',
366
- contextWindow: 1_000_000,
367
- maxOutput: 128_000,
368
- vision: false,
369
- status: 'active',
370
- },
371
- ],
372
- status: 'active',
373
- },
374
- {
375
- id: 'ollama',
376
- name: 'Ollama (本地)',
377
- protocol: 'openai-compatible' as const,
378
- baseUrl: 'http://localhost:11434/v1',
379
- apiKey: 'ollama-local',
380
- models: [], // 动态发现,初值为空
381
- },
382
- {
383
- id: 'openai',
384
- name: 'OpenAI',
385
- protocol: 'openai-compatible',
386
- baseUrl: 'https://api.openai.com/v1',
387
- apiKey: '${OPENAI_API_KEY}',
388
- models: [
389
- {
390
- id: 'gpt-5.4-mini',
391
- name: 'GPT-5.4 Mini',
392
- providerId: 'openai',
393
- contextWindow: 400_000,
394
- maxOutput: 32_000,
395
- vision: true,
396
- status: 'active',
397
- },
398
- {
399
- id: 'gpt-5.4',
400
- name: 'GPT-5.4',
401
- providerId: 'openai',
402
- contextWindow: 1_050_000,
403
- maxOutput: 128_000,
404
- vision: true,
405
- status: 'active',
406
- },
407
- {
408
- id: 'gpt-5.5',
409
- name: 'GPT-5.5',
410
- providerId: 'openai',
411
- contextWindow: 1_050_000,
412
- maxOutput: 128_000,
413
- vision: true,
414
- status: 'active',
415
- },
416
- {
417
- id: 'gpt-5.3-codex',
418
- name: 'GPT-5.3 Codex',
419
- providerId: 'openai',
420
- contextWindow: 400_000,
421
- maxOutput: 64_000,
422
- vision: false,
423
- status: 'active',
424
- },
425
- ],
426
- },
427
- {
428
- id: 'qwen',
429
- name: '通义千问',
430
- protocol: 'openai-compatible',
431
- baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
432
- apiKey: '${QWEN_API_KEY}',
433
- models: [
434
- {
435
- id: 'qwen-plus',
436
- name: 'Qwen Plus',
437
- providerId: 'qwen',
438
- contextWindow: 128_000,
439
- maxOutput: 32_000,
440
- vision: true,
441
- status: 'active',
442
- },
443
- {
444
- id: 'qwen-max',
445
- name: 'Qwen Max',
446
- providerId: 'qwen',
447
- contextWindow: 128_000,
448
- maxOutput: 32_000,
449
- vision: true,
450
- status: 'active',
451
- },
452
- ],
453
- },
454
- ]
455
-
456
- export const PROTOCOL_LABELS: Record<string, string> = {
457
- 'openai-compatible': 'OpenAI Compatible',
458
- anthropic: 'Anthropic',
459
- custom: 'Custom Protocol',
460
- }
461
-
462
- export const TOOL_CATEGORIES = ['file', 'exec', 'agent', 'network', 'system', 'artifact'] as const
463
-
464
- /** 预置 Ollama 模型 — 与 ollama list 结果合并去重后展示 */
465
- export const OLLAMA_PRESET_MODELS = [
466
- { id: 'om-v5-flash', source: 'MiphamAI' },
467
- { id: 'om-v5-pro', source: 'MiphamAI' },
468
- { id: 'om-v5-visual', source: 'MiphamAI' },
469
- { id: 'om-v5-apex', source: 'MiphamAI' },
470
- { id: 'deepseek-r1:70b', source: '热门' },
471
- { id: 'deepseek-v3', source: '热门' },
472
- { id: 'qwen2.5:72b', source: '热门' },
473
- { id: 'qwen3', source: '热门' },
474
- ] as const
475
-
476
- export const CONFIG_FILE_NAME = 'config.yml'
477
- export const MIPHAM_DIR = '.mipham'
478
- export const USER_CONFIG_DIR = '.mipham'
479
- export const MEMORY_DIR = 'memory'
480
-
481
- // ── Artifact Constants ──
482
- export const ARTIFACTS_DIR = 'artifacts'
483
- export const ARTIFACT_PORT = 9876
484
- export const ARTIFACT_MAX_PORT_TRIES = 10
485
- export const ARTIFACT_MAX_SIZE = 5 * 1024 * 1024 // 5MB
486
- export const ARTIFACT_ALLOWED_EXTENSIONS = ['.html', '.svg']
1
+ // ── 常量唯一真源:@mipham/shared/constants ──
2
+ // DEFAULT_PROVIDERS / PROTOCOL_LABELS / TOOL_CATEGORIES / OLLAMA_PRESET_MODELS
3
+ // 及路径(.mipham/memory)/Artifact 常量均在此包,CLI 直接 re-export。
4
+ export * from '@mipham/shared/constants'