mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
feat(serve): expose skill installation paths (#6811)
This commit is contained in:
1 parent
3d5dd41bc7
commit
be4c46d085
8 files changed
+55
-4
No files matched your search
@@ -0,0 +1,33 @@
|
||||
# Workspace Skill Installation Paths
|
||||
|
||||
Date: 2026-07-13
|
||||
|
||||
## Contract
|
||||
|
||||
Each skill returned by `GET /workspace/skills` and
|
||||
`GET /workspaces/:workspace/skills` includes `installedPath`, the existing
|
||||
absolute `SkillConfig.filePath` that points to its `SKILL.md` file. The value is
|
||||
copied as stored; the status layer does not resolve symlinks or canonicalize it
|
||||
again.
|
||||
|
||||
## Compatibility
|
||||
|
||||
This is an additive v1 field. The current daemon always emits it, while the ACP
|
||||
bridge and TypeScript SDK public status types keep it optional so clients stay
|
||||
compatible with older daemons. The protocol version and capability list do not
|
||||
change.
|
||||
|
||||
## Data Flow
|
||||
|
||||
`SkillManager.listSkills()` supplies `SkillConfig` records. The shared
|
||||
`mapSkillConfigToStatus()` function copies `filePath` to `installedPath`. Both
|
||||
the live ACP snapshot and daemon-local fallback use that mapper, so project,
|
||||
user, bundled, extension, inactive-extension, and disabled skills have the same
|
||||
shape. The workspace status service forwards that shared result to both route
|
||||
forms.
|
||||
|
||||
## Redaction Boundary
|
||||
|
||||
The status mapper remains an explicit metadata allowlist. It exposes the
|
||||
installation file path but not the skill body, hooks, `skillRoot`, or any other
|
||||
skill configuration. This change adds no UI behavior.
|
||||
@@ -819,14 +819,20 @@ Recommended poll cadence: aligned with whatever already polls `/workspace/mcp`;
|
||||
"description": "Review code",
|
||||
"level": "project",
|
||||
"modelInvocable": true,
|
||||
"installedPath": "/home/alice/project/.qwen/skills/review/SKILL.md",
|
||||
"argumentHint": "[path]"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`level` is one of `project`, `user`, `extension`, or `bundled`. `errors` is
|
||||
omitted when discovery succeeds.
|
||||
`level` is one of `project`, `user`, `extension`, or `bundled`.
|
||||
`installedPath` is the existing absolute path to the skill's `SKILL.md`; the
|
||||
daemon returns it as stored without separately resolving symlinks or
|
||||
canonicalizing it. Current daemons emit it for every skill, while clients must
|
||||
tolerate its absence from older v1 daemons. Skill bodies, hooks, `skillRoot`,
|
||||
and other skill configuration remain excluded. `errors` is omitted when
|
||||
discovery succeeds.
|
||||
|
||||
### `GET /workspace/providers`
|
||||
|
||||
|
||||
@@ -416,6 +416,7 @@ export interface ServeWorkspaceSkillStatus extends ServeStatusCell {
|
||||
description: string;
|
||||
level: ServeSkillLevel;
|
||||
modelInvocable: boolean;
|
||||
installedPath?: string;
|
||||
argumentHint?: string;
|
||||
model?: string;
|
||||
extensionName?: string;
|
||||
|
||||
@@ -2560,6 +2560,7 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
|
||||
level: 'project',
|
||||
argumentHint: '[path]',
|
||||
modelInvocable: true,
|
||||
installedPath: '/secret/SKILL.md',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
kind: 'skill',
|
||||
@@ -2577,6 +2578,7 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
|
||||
description: 'Disabled by settings',
|
||||
level: 'project',
|
||||
modelInvocable: true,
|
||||
installedPath: '/disabled/SKILL.md',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
kind: 'skill',
|
||||
@@ -2595,6 +2597,7 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
|
||||
level: 'extension',
|
||||
extensionName: 'GSD Core',
|
||||
modelInvocable: true,
|
||||
installedPath: '/ext/gsd-core/skills/gsd-display-stale/SKILL.md',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
kind: 'skill',
|
||||
@@ -2604,6 +2607,7 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
|
||||
level: 'extension',
|
||||
extensionName: 'GSD Core',
|
||||
modelInvocable: true,
|
||||
installedPath: '/ext/gsd-core/skills/gsd-config-only/SKILL.md',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
@@ -2622,7 +2626,7 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
|
||||
expect(JSON.stringify(skills)).not.toContain('extension secret body');
|
||||
expect(JSON.stringify(skills)).not.toContain('display stale body');
|
||||
expect(JSON.stringify(skills)).not.toContain('config only body');
|
||||
expect(JSON.stringify(skills)).not.toContain('/secret');
|
||||
expect(JSON.stringify(skills)).not.toContain('"skillRoot"');
|
||||
expect(JSON.stringify(skills)).not.toContain('secret-hook');
|
||||
|
||||
expect(providers).toMatchObject({
|
||||
|
||||
@@ -13,8 +13,10 @@ function makeSkill(overrides: Partial<SkillConfig> = {}): SkillConfig {
|
||||
name: 'review',
|
||||
description: 'Review changed code',
|
||||
level: 'bundled',
|
||||
filePath: '/skills/review/SKILL.md',
|
||||
body: 'Review instructions',
|
||||
...overrides,
|
||||
} as SkillConfig;
|
||||
};
|
||||
}
|
||||
|
||||
describe('mapSkillConfigToStatus', () => {
|
||||
@@ -31,6 +33,7 @@ describe('mapSkillConfigToStatus', () => {
|
||||
level: 'bundled',
|
||||
modelInvocable: true,
|
||||
argumentHint: '[pr-number]',
|
||||
installedPath: '/skills/review/SKILL.md',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export function mapSkillConfigToStatus(
|
||||
description: skill.description,
|
||||
level: skill.level,
|
||||
modelInvocable,
|
||||
installedPath: skill.filePath,
|
||||
...(skill.argumentHint ? { argumentHint: skill.argumentHint } : {}),
|
||||
...(skill.model ? { model: skill.model } : {}),
|
||||
...(skill.extensionName ? { extensionName: skill.extensionName } : {}),
|
||||
|
||||
@@ -99,10 +99,12 @@ describe('createWorkspaceSkillsStatusProvider', () => {
|
||||
{
|
||||
name: 'enabled',
|
||||
status: 'ok',
|
||||
installedPath: '/skills/enabled/SKILL.md',
|
||||
},
|
||||
{
|
||||
name: 'disabled',
|
||||
status: 'disabled',
|
||||
installedPath: '/skills/disabled/SKILL.md',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1073,6 +1073,7 @@ export interface DaemonWorkspaceSkillStatus extends DaemonStatusCell {
|
||||
description: string;
|
||||
level: DaemonSkillLevel;
|
||||
modelInvocable: boolean;
|
||||
installedPath?: string;
|
||||
argumentHint?: string;
|
||||
model?: string;
|
||||
extensionName?: string;
|
||||
|
||||
Reference in new issue
Block a user