-
-
Notifications
You must be signed in to change notification settings - Fork 731
feat(capabilities): Initial capability manifest support (legacy + map) #545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 10 commits
dd5a6c8
0dc7b1c
6ff1dbb
ee51e9a
507a4cb
446be4b
a0fd033
cd1abd2
e0d4948
da1dab3
f65d5e8
b7482e7
be990c8
6248053
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| const SKILL_CAPABILITIES = [ | ||
| 'shell', | ||
| 'filesystem', | ||
| 'network', | ||
| 'browser', | ||
| 'sessions', | ||
| 'messaging', | ||
| 'scheduling', | ||
| ] as const | ||
|
|
||
| export type SkillCapability = (typeof SKILL_CAPABILITIES)[number] | ||
|
|
||
| const SKILL_CAPABILITY_SET = new Set<string>(SKILL_CAPABILITIES) | ||
|
|
||
| const CAPABILITY_ALIASES: Record<string, SkillCapability> = { | ||
| // shell | ||
| bash: 'shell', | ||
| command: 'shell', | ||
| commands: 'shell', | ||
| exec: 'shell', | ||
| process: 'shell', | ||
| shell: 'shell', | ||
| terminal: 'shell', | ||
| shell_exec: 'shell', | ||
|
|
||
| // filesystem | ||
| apply_patch: 'filesystem', | ||
| edit: 'filesystem', | ||
| file: 'filesystem', | ||
| files: 'filesystem', | ||
| filesystem: 'filesystem', | ||
| fs: 'filesystem', | ||
| write: 'filesystem', | ||
|
|
||
| // network | ||
| fetch: 'network', | ||
| http: 'network', | ||
| mcp: 'network', | ||
| network: 'network', | ||
| web: 'network', | ||
| 'web-fetch': 'network', | ||
| web_fetch: 'network', | ||
| webfetch: 'network', | ||
| web_search: 'network', | ||
| 'network.fetch': 'network', | ||
| 'network.search': 'network', | ||
|
|
||
| // browser | ||
| browser: 'browser', | ||
| 'computer-use': 'browser', | ||
| computer_use: 'browser', | ||
| gui: 'browser', | ||
| screen: 'browser', | ||
| ui: 'browser', | ||
|
|
||
| // sessions | ||
| delegate: 'sessions', | ||
| orchestration: 'sessions', | ||
| sessions: 'sessions', | ||
| sessions_send: 'sessions', | ||
| sessions_spawn: 'sessions', | ||
| subagent: 'sessions', | ||
| subagents: 'sessions', | ||
|
|
||
| // messaging | ||
| chat: 'messaging', | ||
| message: 'messaging', | ||
| messages: 'messaging', | ||
| messaging: 'messaging', | ||
|
|
||
| // scheduling | ||
| cron: 'scheduling', | ||
| schedule: 'scheduling', | ||
| scheduler: 'scheduling', | ||
| scheduling: 'scheduling', | ||
| timer: 'scheduling', | ||
| } | ||
|
|
||
| function normalizeCapabilityName(input: string): SkillCapability | null { | ||
| const key = input.trim().toLowerCase() | ||
| if (!key) return null | ||
| if (SKILL_CAPABILITY_SET.has(key)) return key as SkillCapability | ||
| const alias = CAPABILITY_ALIASES[key] | ||
| if (alias) return alias | ||
| const firstSegment = key.split(/[._:-]/)[0] | ||
| if (SKILL_CAPABILITY_SET.has(firstSegment)) return firstSegment as SkillCapability | ||
| return null | ||
| } | ||
|
|
||
| function extractCapabilityNames(input: unknown): string[] { | ||
| if (!input) return [] | ||
| if (typeof input === 'string') return [input] | ||
| if (Array.isArray(input)) { | ||
| return input.flatMap((entry) => { | ||
| if (typeof entry === 'string') return [entry] | ||
| if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return [] | ||
| const obj = entry as Record<string, unknown> | ||
| const named = [obj.name, obj.type, obj.id, obj.capability].find( | ||
| (value) => typeof value === 'string', | ||
| ) | ||
| return typeof named === 'string' ? [named] : [] | ||
| }) | ||
| } | ||
| if (typeof input === 'object') { | ||
| return Object.keys(input as Record<string, unknown>) | ||
| } | ||
| return [] | ||
| } | ||
|
|
||
| export function normalizeCapabilities(input: unknown): SkillCapability[] { | ||
| const rawNames = extractCapabilityNames(input) | ||
| const out = new Set<SkillCapability>() | ||
| for (const rawName of rawNames) { | ||
| const normalized = normalizeCapabilityName(rawName) | ||
| if (normalized) out.add(normalized) | ||
| } | ||
| return Array.from(out) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -167,11 +167,12 @@ export const ApiV1SkillListResponseSchema = type({ | |||||||||||||||||||||||||
| stats: 'unknown', | ||||||||||||||||||||||||||
| createdAt: 'number', | ||||||||||||||||||||||||||
| updatedAt: 'number', | ||||||||||||||||||||||||||
| latestVersion: type({ | ||||||||||||||||||||||||||
| version: 'string', | ||||||||||||||||||||||||||
| createdAt: 'number', | ||||||||||||||||||||||||||
| changelog: 'string', | ||||||||||||||||||||||||||
| }).optional(), | ||||||||||||||||||||||||||
| latestVersion: type({ | ||||||||||||||||||||||||||
| version: 'string', | ||||||||||||||||||||||||||
| createdAt: 'number', | ||||||||||||||||||||||||||
| changelog: 'string', | ||||||||||||||||||||||||||
| capabilities: '("shell"|"filesystem"|"network"|"browser"|"sessions"|"messaging"|"scheduling")[]?', | ||||||||||||||||||||||||||
| }).optional(), | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| latestVersion: type({ | |
| version: 'string', | |
| createdAt: 'number', | |
| changelog: 'string', | |
| capabilities: '("shell"|"filesystem"|"network"|"browser"|"sessions"|"messaging"|"scheduling")[]?', | |
| }).optional(), | |
| latestVersion: type({ | |
| version: 'string', | |
| createdAt: 'number', | |
| changelog: 'string', | |
| capabilities: '("shell"|"filesystem"|"network"|"browser"|"sessions"|"messaging"|"scheduling")[]?', | |
| }).optional(), |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/schema/src/schemas.ts
Line: 170-175
Comment:
inconsistent indentation (should be 4 spaces like other fields, not 6)
```suggestion
latestVersion: type({
version: 'string',
createdAt: 'number',
changelog: 'string',
capabilities: '("shell"|"filesystem"|"network"|"browser"|"sessions"|"messaging"|"scheduling")[]?',
}).optional(),
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 6248053. Indentation has been normalized to match surrounding schema fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This normalization only reads
clawdisObj.capabilities, andclawdisObjcomes from the first metadata alias selected earlier (clawdbot/clawdisbeforeopenclaw). In mixed manifests that keep legacymetadata.clawdisfields but add newmetadata.openclaw.capabilities(the format documented in this PR), the declared capabilities are silently dropped, so downstream API consumers see[]instead of the author-declared capabilities.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in b7482e7 (+ coverage in be990c8). Capability parsing now prefers
metadata.openclaw.capabilitieswhen present, even when other legacy metadata namespaces exist in the same frontmatter.