Runtime skill model marks system skills
apps/backend/internal/agent/runtime/lifecycle/skill/types.go ↗The runtime model now carries IsSystem so the deployer can tell bundled Office skills from user skills.
Skill struct
type Skill struct {
Slug string
Content string
Files []SkillFile
SourceType string
// IsSystem marks a bundled Office skill (e.g. kandev-protocol,
// kandev-task-ops) rather than a user-authored one. The deployer
// includes system skills only when the launch has Office runtime support.
IsSystem bool
}
Manifest gate drops system skills for routine launches
apps/backend/internal/agent/runtime/lifecycle/skill/manifest.go ↗The manifest builder skips system skills when OfficeRuntime is false and logs the skip at debug level.
Gate
func (d *Deployer) appendSkills(ctx context.Context, manifest *Manifest, profile *settingsmodels.AgentProfile, officeRuntime bool) {
if d.skillReader == nil {
return
}
for _, key := range mergedSkillKeys(profile) {
skill, err := d.skillReader.GetSkillFromConfig(ctx, key)
if err != nil || skill == nil {
d.logger.Debug("skip skill in manifest", zap.String("key", key), zap.Error(err))
continue
}
if skill.IsSystem && !officeRuntime {
d.logger.Debug("skip system skill: no office runtime env", zap.String("key", key))
continue
}
manifest.Skills = append(manifest.Skills, *skill)
}
}
Deployer request carries OfficeRuntime
apps/backend/internal/agent/runtime/lifecycle/skill/deployer.go ↗The deployer request now includes OfficeRuntime and passes it to buildManifest.
Request
type Request struct {
Profile *settingsmodels.AgentProfile
WorkspacePath string
ExecutorType string
WorkspaceID string
SessionID string
// OfficeRuntime reports whether backend selected Office mode and the
// finalized launch env contains a non-empty KANDEV_CLI.
OfficeRuntime bool
}
Deploy
func (d *Deployer) Deploy(ctx context.Context, req Request) (DeployResult, error) {
if req.Profile == nil {
return DeployResult{}, errors.New("skill deploy: profile is required")
}
manifest := d.buildManifest(ctx, req.Profile, d.workspaceSlugFn(req.WorkspaceID), req.OfficeRuntime)
result := d.deliver(ctx, manifest, req.ExecutorType, req.WorkspacePath)
return result, nil
}
Manager computes OfficeRuntime from launch env
apps/backend/internal/agent/runtime/lifecycle/skill_deploy.go ↗The manager sets OfficeRuntime only when McpMode is Office and KANDEV_CLI is non-empty after trim.
Compute flag
req := SkillDeployRequest{
Profile: profile,
WorkspacePath: prepared.WorkspacePath,
ExecutorType: prepared.ExecutorType,
WorkspaceID: profile.WorkspaceID,
SessionID: original.SessionID,
OfficeRuntime: prepared.McpMode == mcpmode.Office && strings.TrimSpace(prepared.Env["KANDEV_CLI"]) != "",
}
Adapter forwards OfficeRuntime
apps/backend/internal/agent/runtime/lifecycle/skill_deployer_adapter.go ↗The adapter maps the lifecycle request to the runtime request and preserves OfficeRuntime.
Forward
func (a *skillDeployerAdapter) DeploySkills(ctx context.Context, req SkillDeployRequest) (SkillDeployResult, error) {
if a.inner == nil {
return SkillDeployResult{}, nil
}
res, err := a.inner.Deploy(ctx, skill.Request{
Profile: req.Profile,
WorkspacePath: req.WorkspacePath,
ExecutorType: req.ExecutorType,
WorkspaceID: req.WorkspaceID,
SessionID: req.SessionID,
OfficeRuntime: req.OfficeRuntime,
})
if err != nil {
return SkillDeployResult{}, err
}
return SkillDeployResult{Metadata: res.Metadata, InstructionsDir: res.InstructionsDir}, nil
}
Office adapter maps IsSystem
apps/backend/internal/office/skills/runtime_adapter.go ↗The office adapter copies IsSystem from the office model to the runtime model so the gate can read it.
Map IsSystem
return &runtimeskill.Skill{
Slug: sk.Slug,
Content: sk.Content,
Files: runtimeSkillFiles(sk.FileInventory),
SourceType: string(sk.SourceType),
IsSystem: sk.IsSystem,
}, nil