@capekai/core 1.0.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 (148) hide show
  1. package/README.md +12 -0
  2. package/package.json +105 -0
  3. package/src/adapters/ai-sdk.ts +84 -0
  4. package/src/compaction/contracts.ts +82 -0
  5. package/src/compaction/executor.ts +161 -0
  6. package/src/compaction/policy.ts +318 -0
  7. package/src/compaction/recovery.ts +139 -0
  8. package/src/compaction/task.ts +540 -0
  9. package/src/configuration/contracts.ts +58 -0
  10. package/src/configuration/defaults.ts +27 -0
  11. package/src/configuration/runtime.ts +42 -0
  12. package/src/configuration/single-model.ts +75 -0
  13. package/src/context/assembler.ts +112 -0
  14. package/src/context/index.ts +2 -0
  15. package/src/context/sources.ts +119 -0
  16. package/src/context/workspace.ts +63 -0
  17. package/src/core/agent.ts +401 -0
  18. package/src/core/build-tools.ts +139 -0
  19. package/src/core/chat-handler.ts +858 -0
  20. package/src/core/error-handling.ts +18 -0
  21. package/src/core/fork.ts +103 -0
  22. package/src/core/interrupt.ts +192 -0
  23. package/src/core/message-utils.ts +261 -0
  24. package/src/core/model-utils.ts +149 -0
  25. package/src/core/part-utils.ts +88 -0
  26. package/src/core/provider-utils.ts +67 -0
  27. package/src/core/revert.ts +46 -0
  28. package/src/core/step-handlers.ts +157 -0
  29. package/src/core/stream/finalization.ts +65 -0
  30. package/src/core/stream/stream-config.ts +82 -0
  31. package/src/core/stream-handlers.ts +242 -0
  32. package/src/core/structured-output.ts +68 -0
  33. package/src/core/tool-builders/agent-tools.ts +71 -0
  34. package/src/core/tool-builders/external-tools.ts +179 -0
  35. package/src/core/tool-builders/types.ts +16 -0
  36. package/src/core/tool-builders/workspace-tools.ts +293 -0
  37. package/src/core/tool-capabilities.ts +65 -0
  38. package/src/goals/evaluator.ts +171 -0
  39. package/src/goals/index.ts +3 -0
  40. package/src/goals/loop.ts +167 -0
  41. package/src/goals/service.ts +39 -0
  42. package/src/index.ts +10 -0
  43. package/src/internal/ask-authority.ts +29 -0
  44. package/src/internal/composition.ts +44 -0
  45. package/src/internal/configuration.ts +22 -0
  46. package/src/internal/execution.ts +108 -0
  47. package/src/internal/hosts.ts +64 -0
  48. package/src/internal/plugins.ts +71 -0
  49. package/src/internal/providers.ts +32 -0
  50. package/src/internal/sandbox.ts +19 -0
  51. package/src/internal/tools.ts +48 -0
  52. package/src/internal/workspace.ts +25 -0
  53. package/src/kernel/diagnostics.ts +249 -0
  54. package/src/kernel/errors.ts +120 -0
  55. package/src/kernel/events.ts +82 -0
  56. package/src/kernel/index.ts +72 -0
  57. package/src/kernel/kernel.ts +62 -0
  58. package/src/kernel/lifecycle.ts +72 -0
  59. package/src/kernel/plugin.ts +218 -0
  60. package/src/kernel/registry.ts +493 -0
  61. package/src/kernel/scope.ts +776 -0
  62. package/src/kernel/service-key.ts +19 -0
  63. package/src/kernel/types.ts +317 -0
  64. package/src/memory/index.ts +2 -0
  65. package/src/memory/memory-tool.ts +75 -0
  66. package/src/memory/registry.ts +172 -0
  67. package/src/permission/ask-user-api.ts +70 -0
  68. package/src/permission/contracts.ts +135 -0
  69. package/src/permission/permission-request-manager.ts +58 -0
  70. package/src/permission/policy.ts +277 -0
  71. package/src/permission/runtime.ts +612 -0
  72. package/src/plugins/compaction-policy.ts +46 -0
  73. package/src/plugins/compose.ts +171 -0
  74. package/src/plugins/context-sections.ts +246 -0
  75. package/src/plugins/default-agent-driver.ts +14 -0
  76. package/src/plugins/facade-plugins.ts +129 -0
  77. package/src/plugins/goal-domain.ts +82 -0
  78. package/src/plugins/legacy-system-message.ts +152 -0
  79. package/src/plugins/loaded-tools.ts +23 -0
  80. package/src/plugins/memory-domain.ts +264 -0
  81. package/src/plugins/orchestrator-session.ts +29 -0
  82. package/src/plugins/permission-policy.ts +49 -0
  83. package/src/plugins/retry-policy.ts +28 -0
  84. package/src/plugins/scheduler-domain.ts +192 -0
  85. package/src/plugins/service-keys.ts +294 -0
  86. package/src/plugins/session-search-domain.ts +238 -0
  87. package/src/plugins/skills-domain.ts +272 -0
  88. package/src/plugins/subagent-domain.ts +287 -0
  89. package/src/plugins/tool-catalog.ts +78 -0
  90. package/src/plugins/tool-output-policy.ts +52 -0
  91. package/src/plugins/value-plugins.ts +150 -0
  92. package/src/plugins/workflow-domain.ts +198 -0
  93. package/src/plugins/workspace-policy.ts +37 -0
  94. package/src/providers/registry.ts +63 -0
  95. package/src/providers/types.ts +44 -0
  96. package/src/retry/policy.ts +282 -0
  97. package/src/retry/stream-chat.ts +312 -0
  98. package/src/runtime/agent-runtime.ts +83 -0
  99. package/src/runtime/default-agent-driver.ts +23 -0
  100. package/src/runtime/domain-tool-source.ts +156 -0
  101. package/src/runtime/events.ts +61 -0
  102. package/src/runtime/host-dependencies.ts +71 -0
  103. package/src/runtime/host-guidance.ts +22 -0
  104. package/src/runtime/host-layout.ts +23 -0
  105. package/src/runtime/host.ts +129 -0
  106. package/src/runtime/standalone-host.ts +118 -0
  107. package/src/sandbox/controller.ts +204 -0
  108. package/src/sandbox/model.ts +305 -0
  109. package/src/sandbox/provider.ts +53 -0
  110. package/src/sandbox/types.ts +110 -0
  111. package/src/scheduler/host.ts +22 -0
  112. package/src/scheduler/scheduler-tool.ts +172 -0
  113. package/src/session-search/host.ts +56 -0
  114. package/src/session-search/index.ts +23 -0
  115. package/src/session-search/session-search-tool.ts +151 -0
  116. package/src/skills/index.ts +3 -0
  117. package/src/skills/registry.ts +63 -0
  118. package/src/skills/skill-manage-tool.ts +205 -0
  119. package/src/skills/skill-tool.ts +42 -0
  120. package/src/storage/contracts.ts +159 -0
  121. package/src/storage/memory.ts +321 -0
  122. package/src/storage/options.ts +75 -0
  123. package/src/storage/runtime.ts +115 -0
  124. package/src/storage/sqlite-tool-output-artifacts.ts +106 -0
  125. package/src/storage/sqlite.ts +321 -0
  126. package/src/storage/tool-output-artifacts.ts +75 -0
  127. package/src/storage.ts +31 -0
  128. package/src/subagent/child-session.ts +282 -0
  129. package/src/subagent/guidance.ts +8 -0
  130. package/src/subagent/policy.ts +198 -0
  131. package/src/subagent/task-tool.ts +584 -0
  132. package/src/tool-output/contracts.ts +111 -0
  133. package/src/tool-output/policy.ts +410 -0
  134. package/src/tool.ts +1 -0
  135. package/src/tools/executor.ts +258 -0
  136. package/src/tools/install-manifest.ts +40 -0
  137. package/src/tools/llm-api.ts +77 -0
  138. package/src/tools/registry.ts +206 -0
  139. package/src/tools/tool-artifact.ts +182 -0
  140. package/src/tools/tool-source.ts +53 -0
  141. package/src/utils/errors.ts +334 -0
  142. package/src/utils/strip-visualization.ts +50 -0
  143. package/src/workflow/decomposer.ts +139 -0
  144. package/src/workflow/execution.ts +523 -0
  145. package/src/workflow/orchestrator-session.ts +161 -0
  146. package/src/workflow/synthesizer.ts +130 -0
  147. package/src/workspace/contracts.ts +135 -0
  148. package/src/workspace/policy.ts +327 -0
@@ -0,0 +1,238 @@
1
+ import type { PermissionRiskLevel } from '@capekai/tool';
2
+ import {
3
+ validateContextAssemblyData,
4
+ type ContextAssemblyData,
5
+ } from '../context/assembler';
6
+ import { serviceKey } from '../kernel/service-key';
7
+ import type {
8
+ CapekPlugin,
9
+ ContextSectionContribution,
10
+ PluginContext,
11
+ ToolDefinition as KernelToolDefinition,
12
+ } from '../kernel/types';
13
+ import {
14
+ DOMAIN_TOOL_PAYLOAD_FIELD,
15
+ registerDomainToolFallback,
16
+ type DomainToolExecuteContext,
17
+ type DomainToolPayload,
18
+ } from '../runtime/domain-tool-source';
19
+ import { getHostGuidance } from '../runtime/host-guidance';
20
+ import type { SessionSearchHost } from '../session-search/host';
21
+ import {
22
+ executeSessionSearchTool,
23
+ executeSessionSearchToolWithHost,
24
+ sessionSearchToolDefinition,
25
+ type SessionSearchResult,
26
+ } from '../session-search/session-search-tool';
27
+ import type { StorageBundle } from '../storage/contracts';
28
+ import { getWorkspace } from '../storage/runtime';
29
+ import { capekSessionSearchHostKey, capekStorageKey } from './service-keys';
30
+
31
+ /**
32
+ * C5 session-search domain plugin. Owns the domain service (agent scope),
33
+ * the `session_search` tool contribution, and the `session-search-guidance`
34
+ * section. The composed payload executes through the process-scoped host
35
+ * service and the scope storage captured at setup; one `isEnabled`
36
+ * predicate gates both the tool and the guidance.
37
+ */
38
+
39
+ export const CURRENT_SESSION_SEARCH_DOMAIN_PLUGIN_ID = 'current.session-search-domain';
40
+ export const SESSION_SEARCH_TOOL_CONTRIBUTION_ID = 'session-search.session_search';
41
+ export const SESSION_SEARCH_TOOL_CONTRIBUTION_ORDER = 700;
42
+ export const SESSION_SEARCH_GUIDANCE_SECTION_ID = 'session-search-guidance';
43
+
44
+ export interface SessionSearchDomainService {
45
+ readonly tools: readonly DomainToolPayload[];
46
+ /** Single availability predicate for the workspace settings gate,
47
+ * shared by the tool gate and the guidance section. */
48
+ isEnabled(workspaceId: string): boolean | Promise<boolean>;
49
+ readonly guidance: string;
50
+ }
51
+
52
+ export const capekSessionSearchDomainKey = serviceKey<SessionSearchDomainService>(
53
+ 'capek.session-search-domain',
54
+ 'agent',
55
+ );
56
+
57
+ interface SessionSearchPayloadDeps {
58
+ readonly isEnabled: (workspaceId: string) => boolean | Promise<boolean>;
59
+ readonly run: (
60
+ input: Record<string, unknown>,
61
+ context: DomainToolExecuteContext,
62
+ includeToolResults: boolean,
63
+ risk: PermissionRiskLevel,
64
+ agentId: string | null,
65
+ ) => Promise<SessionSearchResult>;
66
+ }
67
+
68
+ function shapeSessionSearchResult(result: SessionSearchResult): Record<string, unknown> {
69
+ if (!result.success) {
70
+ return { error: result.error ?? 'Session search failed' };
71
+ }
72
+ return {
73
+ success: result.success,
74
+ mode: result.mode,
75
+ title: result.title,
76
+ ...(result.sessions !== undefined && { sessions: result.sessions }),
77
+ ...(result.query !== undefined && { query: result.query }),
78
+ ...(result.scope !== undefined && { scope: result.scope }),
79
+ ...(result.results !== undefined && { results: result.results }),
80
+ ...(result.sessionId !== undefined && { sessionId: result.sessionId }),
81
+ ...(result.sessionTitle !== undefined && { sessionTitle: result.sessionTitle }),
82
+ ...(result.anchorMessageId !== undefined && { anchorMessageId: result.anchorMessageId }),
83
+ ...(result.anchorInferred !== undefined && { anchorInferred: result.anchorInferred }),
84
+ ...(result.messagesBefore !== undefined && { messagesBefore: result.messagesBefore }),
85
+ ...(result.messagesAfter !== undefined && { messagesAfter: result.messagesAfter }),
86
+ ...(result.messages !== undefined && { messages: result.messages }),
87
+ };
88
+ }
89
+
90
+ function sessionSearchPayload(deps: SessionSearchPayloadDeps): DomainToolPayload {
91
+ return {
92
+ name: sessionSearchToolDefinition.name,
93
+ description: sessionSearchToolDefinition.description,
94
+ inputSchema: sessionSearchToolDefinition.inputSchema as Readonly<Record<string, unknown>>,
95
+ display: { summary: '{action} {query}' },
96
+ isEnabled: deps.isEnabled,
97
+ visualize: (_input, result) => {
98
+ const results = Array.isArray(result.results) ? result.results : undefined;
99
+ if (results) {
100
+ return {
101
+ type: 'file-list',
102
+ collapsed: true,
103
+ badge: `${results.length} result${results.length === 1 ? '' : 's'}`,
104
+ title: String(result.query ?? String(result.title ?? 'Search')),
105
+ files: results.slice(0, 20).map((r) => ({
106
+ path: String((r as { sessionTitle?: string }).sessionTitle ?? (r as { sessionId?: string }).sessionId ?? ''),
107
+ })),
108
+ total: results.length,
109
+ };
110
+ }
111
+ const sessions = Array.isArray(result.sessions) ? result.sessions : undefined;
112
+ if (sessions) {
113
+ return {
114
+ type: 'file-list',
115
+ collapsed: true,
116
+ badge: `${sessions.length} session${sessions.length === 1 ? '' : 's'}`,
117
+ files: sessions.slice(0, 20).map((s) => ({
118
+ path: String((s as { title?: string }).title ?? (s as { id?: string }).id ?? ''),
119
+ })),
120
+ total: sessions.length,
121
+ };
122
+ }
123
+ const messages = Array.isArray(result.messages) ? result.messages : undefined;
124
+ if (messages) {
125
+ return {
126
+ type: 'none',
127
+ badge: `${messages.length} message${messages.length === 1 ? '' : 's'}`,
128
+ message: String(result.sessionTitle ?? 'Session context'),
129
+ };
130
+ }
131
+ return { type: 'none', message: String(result.title ?? 'Session search completed') };
132
+ },
133
+ execute: async (input, context) => {
134
+ // workspace-tools captures permissionRisk and includeToolResults at
135
+ // build time exactly like pre-C5 and passes them through the execution
136
+ // context; the payload never re-reads workspace settings at execution
137
+ // time.
138
+ const includeToolResults = context.includeToolResults === true;
139
+ const risk = context.permissionRisk as PermissionRiskLevel;
140
+ const agentId = typeof context.agentId === 'string' ? context.agentId : null;
141
+ const result = await deps.run(input, context, includeToolResults, risk, agentId);
142
+ return shapeSessionSearchResult(result);
143
+ },
144
+ };
145
+ }
146
+
147
+ /** Unscoped compatibility payload: keeps the pre-C5 execute-time module host
148
+ * and storage reads. Installed through `installSessionSearchToolFallback`,
149
+ * never at module load. */
150
+ export function createSessionSearchToolFallbackPayload(): DomainToolPayload {
151
+ return sessionSearchPayload({
152
+ isEnabled: async (workspaceId) => Boolean(
153
+ (await getWorkspace(workspaceId))?.settings?.sessionSearch?.enabled,
154
+ ),
155
+ run: (input, context, includeToolResults, risk, agentId) => executeSessionSearchTool(
156
+ input,
157
+ context.workspaceId,
158
+ context.sessionId,
159
+ includeToolResults,
160
+ risk,
161
+ context.ask,
162
+ agentId,
163
+ ),
164
+ });
165
+ }
166
+
167
+ /** Explicitly installs the unscoped legacy fallback. Called by the Jean2
168
+ * compatibility bindings installation (server bootstrap) and by focused
169
+ * tests; no module-load registration exists. */
170
+ export function installSessionSearchToolFallback(): void {
171
+ registerDomainToolFallback(
172
+ sessionSearchToolDefinition.name,
173
+ createSessionSearchToolFallbackPayload(),
174
+ );
175
+ }
176
+
177
+ type GuidanceSectionContribution = ContextSectionContribution<ContextAssemblyData>;
178
+
179
+ export function sessionSearchDomainPlugin(id: string): CapekPlugin<unknown> {
180
+ return {
181
+ id,
182
+ scope: 'agent',
183
+ provides: [capekSessionSearchDomainKey],
184
+ requires: [capekStorageKey, capekSessionSearchHostKey],
185
+ setup(context: PluginContext) {
186
+ const storage: StorageBundle = context.require(capekStorageKey);
187
+ const host: SessionSearchHost = context.require(capekSessionSearchHostKey);
188
+ const isEnabled = async (workspaceId: string): Promise<boolean> => Boolean(
189
+ (await storage.workspaces.get(workspaceId))?.settings?.sessionSearch?.enabled,
190
+ );
191
+ const payload = sessionSearchPayload({
192
+ isEnabled,
193
+ run: (input, executionContext, includeToolResults, risk, agentId) =>
194
+ executeSessionSearchToolWithHost(
195
+ host,
196
+ input,
197
+ executionContext.workspaceId,
198
+ executionContext.sessionId,
199
+ includeToolResults,
200
+ risk,
201
+ executionContext.ask,
202
+ agentId,
203
+ ),
204
+ });
205
+ const service: SessionSearchDomainService = {
206
+ tools: [payload],
207
+ isEnabled,
208
+ guidance: getHostGuidance().sessionSearch,
209
+ };
210
+
211
+ const guidance: GuidanceSectionContribution = {
212
+ id: SESSION_SEARCH_GUIDANCE_SECTION_ID,
213
+ phase: 'workspace',
214
+ order: 50,
215
+ provide: async (build) => {
216
+ const data = validateContextAssemblyData(build.data);
217
+ if (!data.workspaceId) return null;
218
+ return await isEnabled(data.workspaceId) ? service.guidance : null;
219
+ },
220
+ };
221
+
222
+ context.provide(capekSessionSearchDomainKey, service);
223
+ context.contributeTool({
224
+ id: SESSION_SEARCH_TOOL_CONTRIBUTION_ID,
225
+ order: SESSION_SEARCH_TOOL_CONTRIBUTION_ORDER,
226
+ definition: {
227
+ name: payload.name,
228
+ description: payload.description,
229
+ inputSchema: payload.inputSchema,
230
+ timeout: sessionSearchToolDefinition.timeout,
231
+ [DOMAIN_TOOL_PAYLOAD_FIELD]: payload,
232
+ } as KernelToolDefinition,
233
+ requiredCapabilities: [capekSessionSearchDomainKey],
234
+ });
235
+ context.contributeContext(guidance);
236
+ },
237
+ };
238
+ }
@@ -0,0 +1,272 @@
1
+ import { getHostGuidance } from '../runtime/host-guidance';
2
+ import { getHostLayout } from '../runtime/host-layout';
3
+ import type { PermissionRiskLevel } from '@capekai/tool';
4
+ import { serviceKey } from '../kernel/service-key';
5
+ import type {
6
+ CapekPlugin,
7
+ ContextSectionContribution,
8
+ PluginContext,
9
+ ToolDefinition as KernelToolDefinition,
10
+ } from '../kernel/types';
11
+ import {
12
+ DOMAIN_TOOL_PAYLOAD_FIELD,
13
+ registerDomainToolFallback,
14
+ type DomainToolPayload,
15
+ } from '../runtime/domain-tool-source';
16
+ import { validateContextAssemblyData, type ContextAssemblyData } from '../context/assembler';
17
+ import {
18
+ buildSkillManageToolDescription,
19
+ buildSkillToolDefinition,
20
+ executeSkillManageTool,
21
+ executeSkillTool,
22
+ skillManageToolDefinition,
23
+ } from '../skills';
24
+ import type { StorageBundle } from '../storage/contracts';
25
+ import { capekStorageKey } from './service-keys';
26
+
27
+ /**
28
+ * C5 skills domain plugin. Owns the agent-scoped skills service: the
29
+ * workspace `skill` and `skill_manage` tool payloads, the agent
30
+ * `agent_skill_manage` tool payload, and the skill-management-guidance
31
+ * context section. Tool building stays in the core tool builders over the
32
+ * generic contributed-domain-tool seam; the payloads read only the build
33
+ * context the builders capture (workspace path, allowed skills, agent
34
+ * skills directory, permission risk), never module globals. The unscoped
35
+ * fallback installs explicitly, never at module load.
36
+ */
37
+
38
+ export const CURRENT_SKILLS_DOMAIN_PLUGIN_ID = 'current.skills-domain';
39
+ export const SKILL_TOOL_CONTRIBUTION_ID = 'skills.skill';
40
+ export const SKILL_TOOL_CONTRIBUTION_ORDER = 660;
41
+ export const SKILL_MANAGE_TOOL_CONTRIBUTION_ID = 'skills.skill_manage';
42
+ export const SKILL_MANAGE_TOOL_CONTRIBUTION_ORDER = 695;
43
+ export const AGENT_SKILL_MANAGE_TOOL_CONTRIBUTION_ID = 'skills.agent_skill_manage';
44
+ export const AGENT_SKILL_MANAGE_TOOL_CONTRIBUTION_ORDER = 801;
45
+
46
+ export interface SkillsDomainService {
47
+ readonly tools: readonly DomainToolPayload[];
48
+ }
49
+
50
+ export const capekSkillsDomainKey = serviceKey<SkillsDomainService>(
51
+ 'capek.skills-domain',
52
+ 'agent',
53
+ );
54
+
55
+ export function createSkillToolPayload(): DomainToolPayload {
56
+ return {
57
+ name: 'skill',
58
+ description: 'Load a specialized skill that provides domain-specific instructions and workflows.',
59
+ inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
60
+ display: { summary: '{name}' },
61
+ visualize: (input, result) => {
62
+ const output = (result as { output?: unknown }).output;
63
+ const content = typeof output === 'string' ? output : undefined;
64
+ const title = (result as { title?: unknown }).title;
65
+ if (content && content.length <= 20_000) {
66
+ return {
67
+ type: 'markdown',
68
+ collapsed: true,
69
+ badge: `${(content.length / 1024).toFixed(1)} KB`,
70
+ content: content.slice(0, 20_000),
71
+ title: typeof title === 'string' ? title : String(input.name ?? 'Skill'),
72
+ };
73
+ }
74
+ return {
75
+ type: 'none',
76
+ message: typeof title === 'string' ? title : `Loaded skill: ${String(input.name ?? '')}`,
77
+ };
78
+ },
79
+ resolveDefinition: async (sessionId, options) => {
80
+ const definition = await buildSkillToolDefinition(
81
+ options?.workspacePath as string,
82
+ options?.allowedSkills as string[] | null | undefined,
83
+ sessionId,
84
+ options?.agentSkillsDir as string | undefined,
85
+ );
86
+ if (!definition) return null;
87
+ return { description: definition.description, inputSchema: definition.inputSchema };
88
+ },
89
+ execute: async (input, context) => {
90
+ return executeSkillTool(
91
+ input.name as string,
92
+ context.workspacePath as string,
93
+ context.allowedSkills as string[] | null | undefined,
94
+ context.sessionId,
95
+ context.agentSkillsDir as string | undefined,
96
+ ) as unknown as Record<string, unknown>;
97
+ },
98
+ };
99
+ }
100
+
101
+ export function createSkillManageToolPayload(): DomainToolPayload {
102
+ return {
103
+ name: 'skill_manage',
104
+ description: skillManageToolDefinition.description,
105
+ inputSchema: skillManageToolDefinition.inputSchema,
106
+ display: { summary: '{action} {name}' },
107
+ visualize: (_input, result) => {
108
+ const skills = Array.isArray(result.skills) ? result.skills : undefined;
109
+ if (skills) {
110
+ return {
111
+ type: 'file-list',
112
+ collapsed: true,
113
+ badge: `${skills.length} skill${skills.length === 1 ? '' : 's'}`,
114
+ files: skills.slice(0, 20).map((s) => ({
115
+ path: String((s as { name?: string }).name ?? ''),
116
+ content: (s as { description?: string }).description,
117
+ })),
118
+ total: skills.length,
119
+ };
120
+ }
121
+ return { type: 'none', message: String(result.title ?? 'Skill updated') };
122
+ },
123
+ resolveDefinition: async (_sessionId, options) => {
124
+ const description = await buildSkillManageToolDescription(
125
+ getHostLayout().workspaceSkillsDir(options?.workspacePath as string),
126
+ );
127
+ return { description, inputSchema: skillManageToolDefinition.inputSchema };
128
+ },
129
+ execute: async (input, context) => {
130
+ const risk = (context.permissionRisk ?? 'none') as PermissionRiskLevel;
131
+ const result = await executeSkillManageTool(
132
+ input,
133
+ getHostLayout().workspaceSkillsDir(context.workspacePath as string),
134
+ risk,
135
+ context.ask,
136
+ );
137
+ if (!result.success) {
138
+ return { error: result.error ?? 'Skill management operation failed' };
139
+ }
140
+ return {
141
+ title: result.title,
142
+ action: result.action,
143
+ name: result.name,
144
+ description: result.description,
145
+ path: result.path,
146
+ summary: result.summary,
147
+ skills: result.skills,
148
+ };
149
+ },
150
+ };
151
+ }
152
+
153
+ export function createAgentSkillManageToolPayload(): DomainToolPayload {
154
+ return {
155
+ name: 'agent_skill_manage',
156
+ description: skillManageToolDefinition.description,
157
+ inputSchema: skillManageToolDefinition.inputSchema,
158
+ display: { summary: '{action} {name}' },
159
+ visualize: (_input, result) => {
160
+ const skills = Array.isArray(result.skills) ? result.skills : undefined;
161
+ if (skills) {
162
+ return {
163
+ type: 'file-list',
164
+ collapsed: true,
165
+ badge: `${skills.length} skill${skills.length === 1 ? '' : 's'}`,
166
+ files: skills.slice(0, 20).map((s) => ({
167
+ path: String((s as { name?: string }).name ?? ''),
168
+ content: (s as { description?: string }).description,
169
+ })),
170
+ total: skills.length,
171
+ };
172
+ }
173
+ return { type: 'none', message: String(result.title ?? 'Skill updated') };
174
+ },
175
+ resolveDefinition: async (_sessionId, options) => {
176
+ const description = await buildSkillManageToolDescription(
177
+ getHostLayout().agentSkillsDir(options?.agentDir as string),
178
+ );
179
+ return { description, inputSchema: skillManageToolDefinition.inputSchema };
180
+ },
181
+ execute: async (input, context) => {
182
+ const result = await executeSkillManageTool(
183
+ input,
184
+ getHostLayout().agentSkillsDir(context.agentDir as string),
185
+ 'none',
186
+ );
187
+ if (!result.success) {
188
+ return { error: result.error ?? 'Agent skill management failed' };
189
+ }
190
+ return {
191
+ title: result.title,
192
+ action: result.action,
193
+ name: result.name,
194
+ description: result.description,
195
+ path: result.path,
196
+ summary: result.summary,
197
+ skills: result.skills,
198
+ };
199
+ },
200
+ };
201
+ }
202
+
203
+ /** Explicitly installs the unscoped legacy fallbacks. Called by the Jean2
204
+ * compatibility bindings installation (server bootstrap) and by focused
205
+ * tests; no module-load registration exists. */
206
+ export function installSkillsToolFallback(): void {
207
+ registerDomainToolFallback('skill', createSkillToolPayload());
208
+ registerDomainToolFallback('skill_manage', createSkillManageToolPayload());
209
+ registerDomainToolFallback('agent_skill_manage', createAgentSkillManageToolPayload());
210
+ }
211
+
212
+ type SkillSectionContribution = ContextSectionContribution<ContextAssemblyData>;
213
+
214
+ function skillManagementGuidanceSection(storage: StorageBundle): SkillSectionContribution {
215
+ return {
216
+ id: 'skill-management-guidance',
217
+ phase: 'workspace',
218
+ order: 40,
219
+ provide: async (build) => {
220
+ const data = validateContextAssemblyData(build.data);
221
+ if (!data.workspaceId) return null;
222
+ return (await storage.workspaces.get(data.workspaceId))?.settings?.skills?.managementEnabled
223
+ ? getHostGuidance().skillManage
224
+ : null;
225
+ }
226
+ };
227
+ }
228
+
229
+ export function skillsDomainPlugin(id: string): CapekPlugin<unknown> {
230
+ return {
231
+ id,
232
+ scope: 'agent',
233
+ provides: [capekSkillsDomainKey],
234
+ requires: [capekStorageKey],
235
+ setup(context: PluginContext) {
236
+ const storage: StorageBundle = context.require(capekStorageKey);
237
+
238
+ const service: SkillsDomainService = {
239
+ tools: [
240
+ createSkillToolPayload(),
241
+ createSkillManageToolPayload(),
242
+ createAgentSkillManageToolPayload(),
243
+ ],
244
+ };
245
+
246
+ context.provide(capekSkillsDomainKey, service);
247
+ for (const payload of service.tools) {
248
+ context.contributeTool({
249
+ id: payload.name === 'skill'
250
+ ? SKILL_TOOL_CONTRIBUTION_ID
251
+ : payload.name === 'skill_manage'
252
+ ? SKILL_MANAGE_TOOL_CONTRIBUTION_ID
253
+ : AGENT_SKILL_MANAGE_TOOL_CONTRIBUTION_ID,
254
+ order: payload.name === 'skill'
255
+ ? SKILL_TOOL_CONTRIBUTION_ORDER
256
+ : payload.name === 'skill_manage'
257
+ ? SKILL_MANAGE_TOOL_CONTRIBUTION_ORDER
258
+ : AGENT_SKILL_MANAGE_TOOL_CONTRIBUTION_ORDER,
259
+ definition: {
260
+ name: payload.name,
261
+ description: payload.description,
262
+ inputSchema: payload.inputSchema,
263
+ timeout: payload.name === 'skill' ? 5000 : 10000,
264
+ [DOMAIN_TOOL_PAYLOAD_FIELD]: payload,
265
+ } as KernelToolDefinition,
266
+ requiredCapabilities: [capekSkillsDomainKey],
267
+ });
268
+ }
269
+ context.contributeContext(skillManagementGuidanceSection(storage));
270
+ },
271
+ };
272
+ }