@miphamai/cli 0.38.0 → 0.39.1
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 +25 -0
- package/package.json +1 -1
- package/src/agent/sub-agent.ts +4 -3
- package/src/config/defaults.ts +19 -1
- package/src/config/keys-manager.ts +11 -1
- package/src/config/loader.ts +20 -10
- package/src/core/context.ts +86 -56
- package/src/core/credential-masker/output-scrub.ts +5 -0
- package/src/core/crsi-sandbox.ts +19 -7
- package/src/core/engine.ts +50 -33
- package/src/core/instructions.ts +4 -42
- package/src/core/permission.ts +7 -1
- package/src/core/red-team.ts +4 -4
- package/src/core/self-critique.ts +3 -1
- package/src/core/session-log.ts +225 -0
- package/src/core/session-store.ts +119 -34
- package/src/daemon/auth.ts +9 -6
- package/src/daemon/cors.ts +24 -16
- package/src/daemon/rate-limiter.ts +4 -0
- package/src/daemon/server.ts +24 -3
- package/src/index.tsx +54 -28
- package/src/mcp/http-transport.ts +2 -0
- package/src/mcp/oauth.ts +1 -1
- package/src/providers/llm-replay.ts +36 -0
- package/src/providers/llm.ts +16 -0
- package/src/providers/registry.ts +2 -1
- package/src/security/gate.ts +10 -0
- package/src/security/url.ts +37 -14
- package/src/shared/constants.ts +40 -54
- package/src/shared/mipham-models.json +50 -0
- package/src/shared/types.ts +53 -49
- package/src/skills/fork-executor.ts +3 -1
- package/src/skills/loader.ts +2 -1
- package/src/skills/seam.ts +18 -0
- package/src/tools/agent/agent.ts +1 -0
- package/src/tools/agent/skill.ts +1 -0
- package/src/tools/exec/bash.ts +140 -128
- package/src/tools/exec/git.ts +17 -0
- package/src/tools/file/read.ts +73 -66
- package/src/tools/file/write.ts +1 -1
- package/src/tools/index.ts +54 -132
- package/src/tools/network/web-fetch.ts +41 -26
- package/src/tools/seam.ts +25 -0
- package/src/tools/validation.ts +91 -0
- package/src/ui/commands.ts +17 -5
- package/src/ui/input.tsx +8 -1
- package/src/vajra/compose/assemble.ts +19 -0
- package/src/vajra/compose/bundle.ts +39 -0
- package/src/vajra/compose/dump.ts +5 -0
- package/src/vajra/compose/index.ts +4 -0
- package/src/vajra/compose/mount.ts +30 -0
- package/src/vajra/context.ts +183 -0
- package/src/vajra/events.ts +13 -0
- package/src/vajra/index.ts +4 -0
- package/src/vajra/leaf/plan-runner.ts +94 -0
- package/src/vajra/service.ts +15 -0
- package/src/workflow/primitives/agent.ts +3 -1
- package/src/workflow/runtime.ts +23 -15
- package/src/workflow/sandbox.ts +51 -9
- package/src/core/compaction-progress.ts +0 -205
- package/src/core/context-compact.ts +0 -124
- package/src/core/context-drain.ts +0 -113
package/src/security/url.ts
CHANGED
|
@@ -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
|
-
//
|
|
53
|
-
|
|
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 =
|
|
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 —
|
|
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
|
+
}
|
package/src/shared/constants.ts
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
|
-
import type { ProviderConfig } from './types'
|
|
1
|
+
import type { ProviderConfig, ModelInfo } from './types'
|
|
2
|
+
import miphamModelsSnapshot from './mipham-models.json' with { type: 'json' }
|
|
3
|
+
|
|
4
|
+
// Build MiphamAI provider from synced model definitions.
|
|
5
|
+
// Source of truth: Engine GET /v1/mipham-models → packages/shared/src/mipham-models.json
|
|
6
|
+
// Run `bun run scripts/sync-mipham-models.ts` to update from Engine.
|
|
7
|
+
const MIPHAM_MODELS: ModelInfo[] = miphamModelsSnapshot.models.map((m) => ({
|
|
8
|
+
id: m.id,
|
|
9
|
+
name: m.name,
|
|
10
|
+
providerId: m.providerId,
|
|
11
|
+
contextWindow: m.contextWindow,
|
|
12
|
+
maxOutput: m.maxOutput,
|
|
13
|
+
vision: m.vision,
|
|
14
|
+
status: m.status as 'active' | 'upcoming' | 'deprecated',
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
const MIPHAM_PROVIDER: ProviderConfig = {
|
|
18
|
+
id: miphamModelsSnapshot.provider.id,
|
|
19
|
+
name: miphamModelsSnapshot.provider.name,
|
|
20
|
+
protocol: miphamModelsSnapshot.provider.protocol as 'openai-compatible',
|
|
21
|
+
baseUrl: miphamModelsSnapshot.provider.baseUrl,
|
|
22
|
+
apiKey: miphamModelsSnapshot.provider.apiKey,
|
|
23
|
+
models: MIPHAM_MODELS,
|
|
24
|
+
status: miphamModelsSnapshot.provider.status as 'active' | 'upcoming',
|
|
25
|
+
}
|
|
2
26
|
|
|
3
27
|
// Providers: alphabetical by id. Models: Lite/Flash → Text → Vision → Code → Pro/Think
|
|
4
28
|
export const DEFAULT_PROVIDERS: ProviderConfig[] = [
|
|
@@ -325,56 +349,11 @@ export const DEFAULT_PROVIDERS: ProviderConfig[] = [
|
|
|
325
349
|
},
|
|
326
350
|
],
|
|
327
351
|
},
|
|
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
|
-
},
|
|
352
|
+
MIPHAM_PROVIDER,
|
|
374
353
|
{
|
|
375
354
|
id: 'ollama',
|
|
376
355
|
name: 'Ollama (本地)',
|
|
377
|
-
protocol: 'openai-compatible'
|
|
356
|
+
protocol: 'openai-compatible',
|
|
378
357
|
baseUrl: 'http://localhost:11434/v1',
|
|
379
358
|
apiKey: 'ollama-local',
|
|
380
359
|
models: [], // 动态发现,初值为空
|
|
@@ -459,7 +438,19 @@ export const PROTOCOL_LABELS: Record<string, string> = {
|
|
|
459
438
|
custom: 'Custom Protocol',
|
|
460
439
|
}
|
|
461
440
|
|
|
462
|
-
export const TOOL_CATEGORIES = [
|
|
441
|
+
export const TOOL_CATEGORIES = [
|
|
442
|
+
'file',
|
|
443
|
+
'exec',
|
|
444
|
+
'agent',
|
|
445
|
+
'network',
|
|
446
|
+
'system',
|
|
447
|
+
'artifact',
|
|
448
|
+
'scheduling',
|
|
449
|
+
] as const
|
|
450
|
+
export const CONFIG_FILE_NAME = 'config.yml'
|
|
451
|
+
export const MIPHAM_DIR = '.mipham'
|
|
452
|
+
export const USER_CONFIG_DIR = '.mipham'
|
|
453
|
+
export const MEMORY_DIR = 'memory'
|
|
463
454
|
|
|
464
455
|
/** 预置 Ollama 模型 — 与 ollama list 结果合并去重后展示 */
|
|
465
456
|
export const OLLAMA_PRESET_MODELS = [
|
|
@@ -473,11 +464,6 @@ export const OLLAMA_PRESET_MODELS = [
|
|
|
473
464
|
{ id: 'qwen3', source: '热门' },
|
|
474
465
|
] as const
|
|
475
466
|
|
|
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
467
|
// ── Artifact Constants ──
|
|
482
468
|
export const ARTIFACTS_DIR = 'artifacts'
|
|
483
469
|
export const ARTIFACT_PORT = 9876
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Mipham model definitions — synced from Engine GET /v1/mipham-models. Run 'bun run scripts/sync-mipham-models.ts' to update.",
|
|
3
|
+
"provider": {
|
|
4
|
+
"id": "mipham",
|
|
5
|
+
"name": "MiphamAI",
|
|
6
|
+
"protocol": "openai-compatible",
|
|
7
|
+
"baseUrl": "https://api.onemipham.com/v1",
|
|
8
|
+
"apiKey": "${MIPHAM_API_KEY}",
|
|
9
|
+
"status": "active"
|
|
10
|
+
},
|
|
11
|
+
"models": [
|
|
12
|
+
{
|
|
13
|
+
"id": "om-v5-flash",
|
|
14
|
+
"name": "OM V5 Flash",
|
|
15
|
+
"providerId": "mipham",
|
|
16
|
+
"contextWindow": 16384,
|
|
17
|
+
"maxOutput": 4096,
|
|
18
|
+
"vision": false,
|
|
19
|
+
"status": "active"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "om-v5-pro",
|
|
23
|
+
"name": "OM V5 Pro",
|
|
24
|
+
"providerId": "mipham",
|
|
25
|
+
"contextWindow": 131072,
|
|
26
|
+
"maxOutput": 16384,
|
|
27
|
+
"vision": false,
|
|
28
|
+
"status": "active"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "om-v5-visual",
|
|
32
|
+
"name": "OM V5 Visual",
|
|
33
|
+
"providerId": "mipham",
|
|
34
|
+
"contextWindow": 65536,
|
|
35
|
+
"maxOutput": 4096,
|
|
36
|
+
"vision": true,
|
|
37
|
+
"status": "active"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "om-v5-apex",
|
|
41
|
+
"name": "OM V5 Apex",
|
|
42
|
+
"providerId": "mipham",
|
|
43
|
+
"contextWindow": 200000,
|
|
44
|
+
"maxOutput": 32768,
|
|
45
|
+
"vision": false,
|
|
46
|
+
"status": "active"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"synced_at": "2026-08-04T00:00:00Z"
|
|
50
|
+
}
|
package/src/shared/types.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// ── 类型唯一真源(vendored from packages/shared/src/types.ts)──
|
|
2
|
+
// 为让 npm 包自包含(运行时无需 @mipham/shared workspace 依赖),
|
|
3
|
+
// 此处从 packages/shared/src/types.ts 同步。改动共享类型时两边同步。
|
|
1
4
|
// ── Provider Types ──
|
|
2
5
|
export type ProtocolType = 'openai-compatible' | 'anthropic' | 'custom'
|
|
3
6
|
|
|
@@ -60,23 +63,6 @@ export type ToolPermission = 'auto' | 'ask' | 'bypass'
|
|
|
60
63
|
export type ToolCategory =
|
|
61
64
|
'file' | 'exec' | 'agent' | 'network' | 'system' | 'artifact' | 'scheduling'
|
|
62
65
|
|
|
63
|
-
export interface ToolContext {
|
|
64
|
-
cwd: string
|
|
65
|
-
sessionId: string
|
|
66
|
-
provider: string
|
|
67
|
-
model: string
|
|
68
|
-
skillsLoader?: import('../skills/loader').SkillsLoader
|
|
69
|
-
registry?: import('../providers/registry').ProviderRegistry
|
|
70
|
-
toolRegistry?: Map<string, ToolDefinition>
|
|
71
|
-
artifactServer?: import('../artifacts/server').ArtifactServer
|
|
72
|
-
agentRegistry?: import('../agent/agent-registry').AgentRegistry
|
|
73
|
-
backgroundAgentRegistry?: import('../agent/background-registry').BackgroundAgentRegistry
|
|
74
|
-
permissionSystem?: import('../core/permission').PermissionSystem
|
|
75
|
-
ruleEngine?: import('../core/rule-engine').ExperienceRuleEngine
|
|
76
|
-
/** Files read this session — used by Write tool to check read-before-write */
|
|
77
|
-
readFiles?: Set<string>
|
|
78
|
-
}
|
|
79
|
-
|
|
80
66
|
// ── Artifact Types ──
|
|
81
67
|
export interface ArtifactEntry {
|
|
82
68
|
name: string
|
|
@@ -102,15 +88,6 @@ export interface ToolResult {
|
|
|
102
88
|
error?: string
|
|
103
89
|
}
|
|
104
90
|
|
|
105
|
-
export interface ToolDefinition {
|
|
106
|
-
name: string
|
|
107
|
-
description: string
|
|
108
|
-
category: ToolCategory
|
|
109
|
-
permission: ToolPermission
|
|
110
|
-
parameters: Record<string, unknown>
|
|
111
|
-
execute: (params: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult>
|
|
112
|
-
}
|
|
113
|
-
|
|
114
91
|
// ── Task Notification Types ──
|
|
115
92
|
export interface TaskNotification {
|
|
116
93
|
taskId: string
|
|
@@ -207,29 +184,6 @@ export interface McpServerConfig {
|
|
|
207
184
|
}
|
|
208
185
|
}
|
|
209
186
|
|
|
210
|
-
// ── Skill Types ──
|
|
211
|
-
export interface SkillDefinition {
|
|
212
|
-
name: string
|
|
213
|
-
description: string
|
|
214
|
-
version: string
|
|
215
|
-
type: 'standard' | 'mipham'
|
|
216
|
-
tools?: ToolDefinition[]
|
|
217
|
-
hooks?: HookDefinition[]
|
|
218
|
-
prompts?: Record<string, string>
|
|
219
|
-
/** Frontmatter: 'fork' means execute in isolated subagent, undefined means inline */
|
|
220
|
-
context?: string
|
|
221
|
-
/** Model override for fork execution */
|
|
222
|
-
model?: string
|
|
223
|
-
/** Tool whitelist for fork mode */
|
|
224
|
-
allowedTools?: string[]
|
|
225
|
-
/** When true, the skill is NOT shown in system-reminder for AI auto-triggering */
|
|
226
|
-
disableModelInvocation?: boolean
|
|
227
|
-
/** When true, users can invoke this skill directly via /<name> */
|
|
228
|
-
userInvocable?: boolean
|
|
229
|
-
/** The markdown body content of the skill file (instructions for the AI to follow). */
|
|
230
|
-
body?: string
|
|
231
|
-
}
|
|
232
|
-
|
|
233
187
|
// ── Hook Types ──
|
|
234
188
|
export type HookEvent =
|
|
235
189
|
| 'PreToolUse'
|
|
@@ -482,3 +436,53 @@ export interface BackgroundAgentConfig {
|
|
|
482
436
|
auto_worktree: boolean
|
|
483
437
|
commit_coauthors: boolean
|
|
484
438
|
}
|
|
439
|
+
|
|
440
|
+
// ── CLI 内部类型(引用活服务,不可共享)──
|
|
441
|
+
export interface ToolContext {
|
|
442
|
+
cwd: string
|
|
443
|
+
sessionId: string
|
|
444
|
+
provider: string
|
|
445
|
+
model: string
|
|
446
|
+
skillsLoader?: import('../skills/seam').Skills
|
|
447
|
+
registry?: import('../providers/registry').ProviderRegistry
|
|
448
|
+
toolRegistry?: Map<string, ToolDefinition>
|
|
449
|
+
artifactServer?: import('../artifacts/server').ArtifactServer
|
|
450
|
+
agentRegistry?: import('../agent/agent-registry').AgentRegistry
|
|
451
|
+
backgroundAgentRegistry?: import('../agent/background-registry').BackgroundAgentRegistry
|
|
452
|
+
permissionSystem?: import('../core/permission').PermissionSystem
|
|
453
|
+
ruleEngine?: import('../core/rule-engine').ExperienceRuleEngine
|
|
454
|
+
llm?: import('../providers/llm').Llm
|
|
455
|
+
/** Files read this session — used by Write tool to check read-before-write */
|
|
456
|
+
readFiles?: Set<string>
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export interface ToolDefinition {
|
|
460
|
+
name: string
|
|
461
|
+
description: string
|
|
462
|
+
category: ToolCategory
|
|
463
|
+
permission: ToolPermission
|
|
464
|
+
parameters: Record<string, unknown>
|
|
465
|
+
execute: (params: Record<string, unknown>, ctx: ToolContext) => Promise<ToolResult>
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export interface SkillDefinition {
|
|
469
|
+
name: string
|
|
470
|
+
description: string
|
|
471
|
+
version: string
|
|
472
|
+
type: 'standard' | 'mipham'
|
|
473
|
+
tools?: ToolDefinition[]
|
|
474
|
+
hooks?: HookDefinition[]
|
|
475
|
+
prompts?: Record<string, string>
|
|
476
|
+
/** Frontmatter: 'fork' means execute in isolated subagent, undefined means inline */
|
|
477
|
+
context?: string
|
|
478
|
+
/** Model override for fork execution */
|
|
479
|
+
model?: string
|
|
480
|
+
/** Tool whitelist for fork mode */
|
|
481
|
+
allowedTools?: string[]
|
|
482
|
+
/** When true, the skill is NOT shown in system-reminder for AI auto-triggering */
|
|
483
|
+
disableModelInvocation?: boolean
|
|
484
|
+
/** When true, users can invoke this skill directly via /<name> */
|
|
485
|
+
userInvocable?: boolean
|
|
486
|
+
/** The markdown body content of the skill file (instructions for the AI to follow). */
|
|
487
|
+
body?: string
|
|
488
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SubAgent } from '../agent/sub-agent'
|
|
2
2
|
import type { ProviderRegistry } from '../providers/registry'
|
|
3
|
+
import type { Llm } from '../providers/llm'
|
|
3
4
|
import type { ToolDefinition, SkillDefinition } from '../shared/index.ts'
|
|
4
5
|
import type { AgentDefinition } from '../agent/types'
|
|
5
6
|
import type { PermissionSystem } from '../core/permission'
|
|
@@ -20,6 +21,7 @@ export async function executeForkedSkill(
|
|
|
20
21
|
registry: ProviderRegistry,
|
|
21
22
|
toolRegistry: Map<string, ToolDefinition>,
|
|
22
23
|
permissionSystem?: PermissionSystem,
|
|
24
|
+
llm?: Llm,
|
|
23
25
|
): Promise<string> {
|
|
24
26
|
const agentDef: AgentDefinition = {
|
|
25
27
|
name: `skill:${skill.name}`,
|
|
@@ -32,7 +34,7 @@ export async function executeForkedSkill(
|
|
|
32
34
|
source: 'builtin',
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
const sub = new SubAgent(registry, toolRegistry, permissionSystem)
|
|
37
|
+
const sub = new SubAgent(registry, toolRegistry, permissionSystem, undefined, undefined, llm)
|
|
36
38
|
const prompt = args
|
|
37
39
|
? `Execute the "${skill.name}" skill with arguments: ${args}`
|
|
38
40
|
: `Execute the "${skill.name}" skill.`
|
package/src/skills/loader.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from 'node:path'
|
|
|
3
3
|
import { homedir } from 'node:os'
|
|
4
4
|
import { parse as parseYaml } from 'yaml'
|
|
5
5
|
import type { SkillDefinition } from '../shared/index.ts'
|
|
6
|
+
import type { Skills } from './seam'
|
|
6
7
|
import { sanitizeSkillDescription, sanitizeSkillBody, checkSkillShadow } from './sanitizer.js'
|
|
7
8
|
|
|
8
9
|
interface FrontmatterResult {
|
|
@@ -21,7 +22,7 @@ function parseFrontmatter(raw: string): FrontmatterResult {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export class SkillsLoader {
|
|
25
|
+
export class SkillsLoader implements Skills {
|
|
25
26
|
private skills = new Map<string, SkillDefinition>()
|
|
26
27
|
|
|
27
28
|
loadBuiltin(basePath: string): void {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Context, Disposer } from '../vajra'
|
|
2
|
+
import type { SkillDefinition } from '../shared'
|
|
3
|
+
|
|
4
|
+
/** 技能加载缝的 capability —— 读能力。换 provider = 换技能来源,engine 零 fork 跟随。 */
|
|
5
|
+
export interface Skills {
|
|
6
|
+
get(name: string): SkillDefinition | undefined
|
|
7
|
+
list(): SkillDefinition[]
|
|
8
|
+
has(name: string): boolean
|
|
9
|
+
buildSystemReminder(maxTokens?: number): string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 缝键:ctx.skills。 */
|
|
13
|
+
export const SKILLS_KEY = 'skills'
|
|
14
|
+
|
|
15
|
+
/** 把一个 Skills(如 SkillsLoader 或测试替身)挂载为 ctx.skills。 */
|
|
16
|
+
export function mountSkills(ctx: Context, skills: Skills): Disposer {
|
|
17
|
+
return ctx.provide(SKILLS_KEY, skills)
|
|
18
|
+
}
|
package/src/tools/agent/agent.ts
CHANGED
package/src/tools/agent/skill.ts
CHANGED