@vibe-forge/workspace-assets 3.2.0 → 3.2.2-alpha.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.
- package/__tests__/__snapshots__/workspace-assets-rich.snapshot.json +1 -1
- package/__tests__/adapter-asset-plan.spec.ts +0 -58
- package/__tests__/bundle.spec.ts +0 -238
- package/__tests__/prompt-builders.spec.ts +30 -3
- package/__tests__/skill-dependencies-cli.spec.ts +59 -252
- package/package.json +4 -4
- package/src/bundle-internal.ts +51 -4
- package/src/configured-skills.ts +5 -1
- package/src/plugin-skill-dependencies.ts +1 -0
- package/src/prompt-builders.ts +4 -2
- package/src/selection-internal.ts +9 -2
- package/src/skill-dependencies.ts +10 -108
- package/src/task-tool-guidance.ts +19 -6
- package/src/workspace-prompt.ts +4 -2
- package/src/skills-cli-dependency-helpers.ts +0 -94
- package/src/skills-cli-dependency.ts +0 -125
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
/* eslint-disable max-lines -- dependency normalization and graph expansion share the same local helpers */
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
import { parseScopedReference, resolveSkillIdentifier } from '@vibe-forge/definition-core'
|
|
7
|
-
import type { Config, Definition, Skill, WorkspaceAsset } from '@vibe-forge/types'
|
|
8
|
-
import { formatSkillsSpec, parseSkillsSpec, resolveRelativePath } from '@vibe-forge/utils'
|
|
2
|
+
import { parseScopedReference } from '@vibe-forge/definition-core'
|
|
3
|
+
import type { Config, Skill, WorkspaceAsset } from '@vibe-forge/types'
|
|
4
|
+
import { formatSkillsSpec, parseSkillsSpec } from '@vibe-forge/utils'
|
|
9
5
|
|
|
10
6
|
import { HOME_BRIDGE_RESOLVED_BY } from './home-bridge'
|
|
11
|
-
import { installSkillsCliDependency } from './skills-cli-dependency'
|
|
12
7
|
|
|
13
8
|
type SkillAsset = Extract<WorkspaceAsset, { kind: 'skill' }>
|
|
14
9
|
|
|
@@ -106,39 +101,6 @@ const findSkillAssetByRef = (
|
|
|
106
101
|
return resolveUniqueSkillByName(searchableAssets, ref)
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
const resolveDisplayName = (name: string, scope?: string) => (
|
|
110
|
-
scope != null && scope.trim() !== '' ? `${scope}/${name}` : name
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
const parseFrontmatterSkill = async (path: string): Promise<Definition<Skill>> => {
|
|
114
|
-
const content = await readFile(path, 'utf-8')
|
|
115
|
-
const { body, attributes } = fm<Skill>(content)
|
|
116
|
-
return {
|
|
117
|
-
path,
|
|
118
|
-
body,
|
|
119
|
-
attributes
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const createResolvedSkillAsset = (params: {
|
|
124
|
-
cwd: string
|
|
125
|
-
definition: Definition<Skill>
|
|
126
|
-
}) => {
|
|
127
|
-
const name = resolveSkillIdentifier(params.definition.path, params.definition.attributes.name)
|
|
128
|
-
const displayName = resolveDisplayName(name)
|
|
129
|
-
return {
|
|
130
|
-
id: `skill:workspace:workspace:${displayName}:${resolveRelativePath(params.cwd, params.definition.path)}`,
|
|
131
|
-
kind: 'skill',
|
|
132
|
-
name,
|
|
133
|
-
displayName,
|
|
134
|
-
origin: 'workspace',
|
|
135
|
-
sourcePath: params.definition.path,
|
|
136
|
-
payload: {
|
|
137
|
-
definition: params.definition
|
|
138
|
-
}
|
|
139
|
-
} satisfies SkillAsset
|
|
140
|
-
}
|
|
141
|
-
|
|
142
104
|
export const normalizeSkillDependency = (value: unknown): NormalizedSkillDependency | undefined => {
|
|
143
105
|
const stringValue = asNonEmptyString(value)
|
|
144
106
|
if (stringValue != null) return parseSkillsSpec(stringValue)
|
|
@@ -230,8 +192,6 @@ export const expandSkillAssetDependenciesWithRemoteResolution = async (
|
|
|
230
192
|
) => {
|
|
231
193
|
const selected: SkillAsset[] = []
|
|
232
194
|
const seen = new Set<string>()
|
|
233
|
-
const fetchedDependencyRefs = new Set<string>()
|
|
234
|
-
|
|
235
195
|
const removeSupersededHomeBridgeSkill = (displayName: string) => {
|
|
236
196
|
removeHomeBridgeSkillDuplicates(params.allAssets, displayName)
|
|
237
197
|
removeHomeBridgeSkillDuplicates(params.skillAssets, displayName)
|
|
@@ -239,54 +199,6 @@ export const expandSkillAssetDependenciesWithRemoteResolution = async (
|
|
|
239
199
|
removeHomeBridgeSkillDuplicates(selected, displayName)
|
|
240
200
|
}
|
|
241
201
|
|
|
242
|
-
const installDependencyAsset = async (
|
|
243
|
-
dependency: NormalizedSkillDependency,
|
|
244
|
-
currentInstancePath?: string
|
|
245
|
-
) => {
|
|
246
|
-
const fetchKey = dependency.ref
|
|
247
|
-
if (!fetchedDependencyRefs.has(fetchKey)) {
|
|
248
|
-
fetchedDependencyRefs.add(fetchKey)
|
|
249
|
-
const installed = await installSkillsCliDependency({
|
|
250
|
-
cwd: params.cwd,
|
|
251
|
-
configs: params.configs,
|
|
252
|
-
dependency
|
|
253
|
-
})
|
|
254
|
-
const definition = await parseFrontmatterSkill(installed.skillPath)
|
|
255
|
-
const dependencyAsset = createResolvedSkillAsset({
|
|
256
|
-
cwd: params.cwd,
|
|
257
|
-
definition
|
|
258
|
-
})
|
|
259
|
-
const existingAsset = findSkillDependencyAsset(
|
|
260
|
-
params.skillAssets,
|
|
261
|
-
dependency,
|
|
262
|
-
currentInstancePath,
|
|
263
|
-
{ includeHomeBridge: false }
|
|
264
|
-
) ??
|
|
265
|
-
params.skillAssets.find(existing => (
|
|
266
|
-
existing.resolvedBy !== HOME_BRIDGE_RESOLVED_BY &&
|
|
267
|
-
existing.displayName === dependencyAsset.displayName
|
|
268
|
-
))
|
|
269
|
-
if (existingAsset != null) {
|
|
270
|
-
removeSupersededHomeBridgeSkill(existingAsset.displayName)
|
|
271
|
-
return existingAsset
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
removeSupersededHomeBridgeSkill(dependencyAsset.displayName)
|
|
275
|
-
params.allAssets.push(dependencyAsset)
|
|
276
|
-
params.skillAssets.push(dependencyAsset)
|
|
277
|
-
return dependencyAsset
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// After the first fetch attempt, reuse whichever asset is now visible in the
|
|
281
|
-
// skill set: a newly installed registry skill, or a home-bridge fallback
|
|
282
|
-
// that was accepted by an earlier plain-name dependency resolution.
|
|
283
|
-
const resolvedAsset = findSkillDependencyAsset(params.skillAssets, dependency, currentInstancePath)
|
|
284
|
-
if (resolvedAsset != null && resolvedAsset.resolvedBy !== HOME_BRIDGE_RESOLVED_BY) {
|
|
285
|
-
removeSupersededHomeBridgeSkill(resolvedAsset.displayName)
|
|
286
|
-
}
|
|
287
|
-
return resolvedAsset
|
|
288
|
-
}
|
|
289
|
-
|
|
290
202
|
const addAsset = async (asset: SkillAsset): Promise<void> => {
|
|
291
203
|
if (params.excludedIds?.has(asset.id)) return
|
|
292
204
|
if (seen.has(asset.id)) return
|
|
@@ -311,25 +223,15 @@ export const expandSkillAssetDependenciesWithRemoteResolution = async (
|
|
|
311
223
|
continue
|
|
312
224
|
}
|
|
313
225
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const dependencyAsset = await installDependencyAsset(dependency, asset.instancePath).catch((error: unknown) => {
|
|
318
|
-
if (
|
|
319
|
-
localOrBridgedDependency != null &&
|
|
320
|
-
dependency.source == null
|
|
321
|
-
) {
|
|
322
|
-
return localOrBridgedDependency
|
|
323
|
-
}
|
|
324
|
-
throw error
|
|
325
|
-
}) ?? (
|
|
326
|
-
dependency.source == null
|
|
327
|
-
? localOrBridgedDependency
|
|
328
|
-
: undefined
|
|
329
|
-
)
|
|
226
|
+
const dependencyAsset = dependency.source == null
|
|
227
|
+
? localOrBridgedDependency
|
|
228
|
+
: undefined
|
|
330
229
|
|
|
331
230
|
if (dependencyAsset == null) {
|
|
332
|
-
throw new Error(
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Skill dependency ${dependency.ref} declared by ${asset.displayName} is missing. ` +
|
|
233
|
+
'Run vf skills install or vf skills update to materialize project skill dependencies.'
|
|
234
|
+
)
|
|
333
235
|
}
|
|
334
236
|
await addAsset(dependencyAsset)
|
|
335
237
|
}
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
export const resolveRuntimeProtocolCliCommand = (env: NodeJS.ProcessEnv = process.env) => {
|
|
4
|
+
const prefix = env.__VF_CLI_RESUME_COMMAND_PREFIX__?.trim()
|
|
5
|
+
if (prefix == null || prefix === '') {
|
|
6
|
+
return 'vf run'
|
|
7
|
+
}
|
|
8
|
+
return prefix.split(/\s+/).at(-1) === 'run' ? prefix : `${prefix} run`
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
export const buildManagedTaskToolGuidance = () => {
|
|
12
|
+
const runtimeCliCommand = resolveRuntimeProtocolCliCommand()
|
|
13
|
+
const runtimeProtocolCommand = `${runtimeCliCommand} --input-format stream-json --output-format stream-json`
|
|
14
|
+
|
|
2
15
|
return [
|
|
3
16
|
'Agent runtime guide:',
|
|
4
|
-
|
|
5
|
-
'- Send typed runtime protocol envelopes such as `session.start`, `session.message`, `session.status`, `session.events`, `session.submit`, and `session.stop`; do not treat dedicated
|
|
17
|
+
`- Use unified CLI protocol mode, \`${runtimeProtocolCommand}\`, to start a child runtime session when the work should run in a separate entity or continue independently from the current turn.`,
|
|
18
|
+
'- Send typed runtime protocol envelopes such as `session.start`, `session.message`, `session.status`, `session.events`, `session.submit`, and `session.stop`; do not treat dedicated agent subcommands as the standard integration surface.',
|
|
6
19
|
'- Ordinary new sessions stay session-scoped. A room is created or discovered only when a unified CLI runtime protocol start command launches a child runtime session from a server-managed host session and the server projects runtime store metadata/events.',
|
|
7
|
-
'- Do not use MCP task tools,
|
|
20
|
+
'- Do not use MCP task tools, dedicated agent subcommands, legacy StartTasks, hand-written DB edits, or ad-hoc TS scripts as the task consumer surface. Use CLI protocol mode and the runtime protocol/store for start, status, events, follow-up messages, input submission, and cancellation.',
|
|
8
21
|
'- Server-managed host sessions inject the current adapter, model, effort, and permission mode as runtime protocol defaults. Omit these fields to inherit the host selection, or set them explicitly only when a child task must use a different runtime profile.',
|
|
9
22
|
'- Copyable JSONL example; write one `session.start` line per child task, and use multiple lines for multiple subtasks:',
|
|
10
23
|
'```bash',
|
|
11
|
-
|
|
24
|
+
`cat <<'JSONL' | ${runtimeProtocolCommand}`,
|
|
12
25
|
'{"commandId":"start-planner","type":"session.start","payload":{"title":"Plan Agent Room UI fix","message":"Plan the frontend changes and tests for the Agent Room UI fix.","entity":"dev-planner","background":true},"title":"Plan Agent Room UI fix","message":"Plan the frontend changes and tests for the Agent Room UI fix.","entity":"dev-planner","background":true}',
|
|
13
26
|
'{"commandId":"start-reviewer","type":"session.start","payload":{"title":"Review Agent Room UI fix","message":"Review the implemented Agent Room UI fix for regressions and missing tests.","entity":"dev-reviewer","background":true},"title":"Review Agent Room UI fix","message":"Review the implemented Agent Room UI fix for regressions and missing tests.","entity":"dev-reviewer","background":true}',
|
|
14
27
|
'JSONL',
|
|
15
28
|
'```',
|
|
16
|
-
'- Keep `payload.title`, `payload.message`, `payload.entity`, and `payload.background: true` explicit in each start envelope. The mirrored top-level fields make the JSONL executable by the current
|
|
29
|
+
'- Keep `payload.title`, `payload.message`, `payload.entity`, and `payload.background: true` explicit in each start envelope. The mirrored top-level fields make the JSONL executable by the current runtime protocol reader.',
|
|
17
30
|
'- Include a short `title` when the task prompt is long; it becomes the child session title and room run label. Put any room or workspace context in the title and initial message.',
|
|
18
31
|
'- Read the returned `sessionId` and use it for follow-up protocol commands. Read the latest runtime snapshot from the runtime store or a `session.status` protocol command, and read progress from runtime events or a `session.events` protocol command.',
|
|
19
32
|
'- Use a follow/read-events workflow when you need to watch progress instead of repeatedly restarting work.',
|
|
@@ -21,7 +34,7 @@ export const buildManagedTaskToolGuidance = () => {
|
|
|
21
34
|
'- When the chat UI sends a `[ROOM_TASK_MESSAGE] ... [/ROOM_TASK_MESSAGE]` block, treat it as a runtime relay envelope instead of ordinary prose. Parse the `sessionId` or legacy `taskId`, `message`, and optional `mode` / `request` fields. If the envelope indicates `mode: interaction`, or runtime status shows `waiting_input` / pending input, use a `session.submit` protocol command. Otherwise use `session.message`. Do not reply inline instead of routing the relay.',
|
|
22
35
|
'- Use `session.submit` only when a runtime session is waiting for an explicit input or approval request. Do not use it for ordinary follow-up instructions.',
|
|
23
36
|
'- Use a `session.stop` protocol command for graceful cancellation and set `mode` to `force` only when stop cannot recover the session.',
|
|
24
|
-
'- Compatibility aliases such as
|
|
37
|
+
'- Compatibility aliases such as dedicated agent start/status/events/send/submit/stop subcommands may exist for debugging or legacy scripts, but they are not the primary guidance for new agent workflows.',
|
|
25
38
|
'- When a session is still making progress, use `wait` between checks and inspect status/events instead of starting a replacement session.'
|
|
26
39
|
].join('\n')
|
|
27
40
|
}
|
package/src/workspace-prompt.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { WorkspaceDefinitionPayload } from '@vibe-forge/types'
|
|
2
2
|
import { resolvePromptPath } from '@vibe-forge/utils'
|
|
3
3
|
|
|
4
|
-
import { buildManagedTaskToolGuidance } from './task-tool-guidance'
|
|
4
|
+
import { buildManagedTaskToolGuidance, resolveRuntimeProtocolCliCommand } from './task-tool-guidance'
|
|
5
5
|
|
|
6
6
|
export const generateWorkspaceRoutePrompt = (
|
|
7
7
|
cwd: string,
|
|
@@ -9,6 +9,8 @@ export const generateWorkspaceRoutePrompt = (
|
|
|
9
9
|
) => {
|
|
10
10
|
if (workspaces.length === 0) return ''
|
|
11
11
|
const taskToolGuidance = buildManagedTaskToolGuidance()
|
|
12
|
+
const runtimeProtocolCommand =
|
|
13
|
+
`${resolveRuntimeProtocolCliCommand()} --input-format stream-json --output-format stream-json`
|
|
12
14
|
|
|
13
15
|
const workspaceList = workspaces
|
|
14
16
|
.map((workspace) => {
|
|
@@ -25,7 +27,7 @@ export const generateWorkspaceRoutePrompt = (
|
|
|
25
27
|
'<system-prompt>\n' +
|
|
26
28
|
'The project includes the following registered workspaces:\n' +
|
|
27
29
|
`${workspaceList}\n` +
|
|
28
|
-
|
|
30
|
+
`When a user request targets one of these workspaces, start a child runtime session with \`${runtimeProtocolCommand}\` and a \`session.start\` envelope; include the workspace identifier and path in the title and message. ` +
|
|
29
31
|
'Do not directly edit files inside a registered workspace from the current session unless the user explicitly asks this session to work in that directory.\n' +
|
|
30
32
|
`${taskToolGuidance}\n` +
|
|
31
33
|
'</system-prompt>\n'
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { access, copyFile, lstat, mkdir, readdir } from 'node:fs/promises'
|
|
2
|
-
import { dirname, resolve } from 'node:path'
|
|
3
|
-
import process from 'node:process'
|
|
4
|
-
|
|
5
|
-
import { withDirectoryInstallLock } from '@vibe-forge/utils/install-lock'
|
|
6
|
-
import { resolveProjectSharedCachePath } from '@vibe-forge/utils/project-cache-path'
|
|
7
|
-
import { toSkillSlug } from '@vibe-forge/utils/skills-cli'
|
|
8
|
-
|
|
9
|
-
const toCacheSegment = (value: string) => (
|
|
10
|
-
value
|
|
11
|
-
.trim()
|
|
12
|
-
.toLowerCase()
|
|
13
|
-
.replace(/[^a-z0-9._-]+/g, '-')
|
|
14
|
-
.replace(/^-+|-+$/g, '') || 'default'
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
export const pathExists = async (targetPath: string) => {
|
|
18
|
-
try {
|
|
19
|
-
await access(targetPath)
|
|
20
|
-
return true
|
|
21
|
-
} catch {
|
|
22
|
-
return false
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const withInstallLock = async <T>(lockDir: string, callback: () => Promise<T>) => {
|
|
27
|
-
try {
|
|
28
|
-
return await withDirectoryInstallLock({ lockDir }, callback)
|
|
29
|
-
} catch (error) {
|
|
30
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
31
|
-
throw new Error(
|
|
32
|
-
message.replace('Timed out waiting for install lock', 'Timed out waiting for skill dependency install lock')
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const copyRegularFiles = async (sourceDir: string, targetDir: string) => {
|
|
38
|
-
let fileCount = 0
|
|
39
|
-
const entries = await readdir(sourceDir, { withFileTypes: true })
|
|
40
|
-
|
|
41
|
-
await mkdir(targetDir, { recursive: true })
|
|
42
|
-
|
|
43
|
-
for (const entry of entries) {
|
|
44
|
-
const sourcePath = resolve(sourceDir, entry.name)
|
|
45
|
-
const targetPath = resolve(targetDir, entry.name)
|
|
46
|
-
const stat = await lstat(sourcePath)
|
|
47
|
-
|
|
48
|
-
if (stat.isDirectory()) {
|
|
49
|
-
fileCount += await copyRegularFiles(sourcePath, targetPath)
|
|
50
|
-
continue
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!stat.isFile()) continue
|
|
54
|
-
|
|
55
|
-
await mkdir(dirname(targetPath), { recursive: true })
|
|
56
|
-
await copyFile(sourcePath, targetPath)
|
|
57
|
-
fileCount += 1
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return fileCount
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const pickSearchResult = <T extends { skill: string }>(
|
|
64
|
-
results: T[],
|
|
65
|
-
name: string
|
|
66
|
-
) => {
|
|
67
|
-
const slug = toSkillSlug(name)
|
|
68
|
-
return results.find(result => (
|
|
69
|
-
result.skill === name ||
|
|
70
|
-
toSkillSlug(result.skill) === slug
|
|
71
|
-
)) ?? results[0]
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export const buildInstallDir = (params: {
|
|
75
|
-
cwd: string
|
|
76
|
-
registry?: string
|
|
77
|
-
skill: string
|
|
78
|
-
source: string
|
|
79
|
-
version?: string
|
|
80
|
-
}) => {
|
|
81
|
-
const registry = params.registry ?? 'default'
|
|
82
|
-
return resolveProjectSharedCachePath(
|
|
83
|
-
params.cwd,
|
|
84
|
-
process.env,
|
|
85
|
-
'skill-dependencies',
|
|
86
|
-
'skills-cli',
|
|
87
|
-
toCacheSegment('skills'),
|
|
88
|
-
toCacheSegment('latest'),
|
|
89
|
-
toCacheSegment(registry),
|
|
90
|
-
...params.source.split('/').map(toCacheSegment),
|
|
91
|
-
toCacheSegment(params.version ?? 'latest'),
|
|
92
|
-
toCacheSegment(params.skill)
|
|
93
|
-
)
|
|
94
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { mkdir, rename, rm } from 'node:fs/promises'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import process from 'node:process'
|
|
4
|
-
|
|
5
|
-
import type { Config } from '@vibe-forge/types'
|
|
6
|
-
import { findSkillsCli, installSkillsCliRefToTemp, installSkillsCliSkillToTemp } from '@vibe-forge/utils/skills-cli'
|
|
7
|
-
|
|
8
|
-
import type { NormalizedSkillDependency } from './skill-dependencies'
|
|
9
|
-
import {
|
|
10
|
-
buildInstallDir,
|
|
11
|
-
copyRegularFiles,
|
|
12
|
-
pathExists,
|
|
13
|
-
pickSearchResult,
|
|
14
|
-
withInstallLock
|
|
15
|
-
} from './skills-cli-dependency-helpers'
|
|
16
|
-
|
|
17
|
-
const resolveAutoDownloadDependenciesEnabled = (
|
|
18
|
-
projectConfig: Config | undefined,
|
|
19
|
-
userConfig: Config | undefined
|
|
20
|
-
) => userConfig?.skills?.autoDownloadDependencies ?? projectConfig?.skills?.autoDownloadDependencies ?? true
|
|
21
|
-
|
|
22
|
-
export const installSkillsCliDependency = async (params: {
|
|
23
|
-
cwd: string
|
|
24
|
-
configs: [Config?, Config?]
|
|
25
|
-
dependency: NormalizedSkillDependency
|
|
26
|
-
}) => {
|
|
27
|
-
const [projectConfig, userConfig] = params.configs
|
|
28
|
-
const autoDownloadDependenciesEnabled = resolveAutoDownloadDependenciesEnabled(projectConfig, userConfig)
|
|
29
|
-
const resolvedTarget = await (async () => {
|
|
30
|
-
if (params.dependency.source != null) {
|
|
31
|
-
return {
|
|
32
|
-
skill: params.dependency.name,
|
|
33
|
-
source: params.dependency.source
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!autoDownloadDependenciesEnabled) {
|
|
38
|
-
throw new Error(
|
|
39
|
-
`Skill dependency automatic downloads are disabled; cannot resolve ${params.dependency.ref} without a source`
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return await (async () => {
|
|
44
|
-
const searchResults = await findSkillsCli({
|
|
45
|
-
registry: params.dependency.registry,
|
|
46
|
-
query: params.dependency.name
|
|
47
|
-
})
|
|
48
|
-
const selected = pickSearchResult(searchResults, params.dependency.name)
|
|
49
|
-
if (selected == null) {
|
|
50
|
-
throw new Error(`Skill ${params.dependency.name} was not found by the skills CLI search.`)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
installRef: selected.installRef,
|
|
55
|
-
skill: selected.skill,
|
|
56
|
-
source: selected.source
|
|
57
|
-
}
|
|
58
|
-
})()
|
|
59
|
-
})()
|
|
60
|
-
|
|
61
|
-
const installDir = buildInstallDir({
|
|
62
|
-
cwd: params.cwd,
|
|
63
|
-
registry: params.dependency.registry,
|
|
64
|
-
skill: resolvedTarget.skill,
|
|
65
|
-
source: resolvedTarget.source,
|
|
66
|
-
version: params.dependency.version
|
|
67
|
-
})
|
|
68
|
-
const skillPath = resolve(installDir, 'SKILL.md')
|
|
69
|
-
|
|
70
|
-
return await withInstallLock(`${installDir}.lock`, async () => {
|
|
71
|
-
if (await pathExists(skillPath)) {
|
|
72
|
-
return {
|
|
73
|
-
installDir,
|
|
74
|
-
skillPath
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (!autoDownloadDependenciesEnabled) {
|
|
79
|
-
throw new Error(`Skill dependency automatic downloads are disabled; cache not found for ${params.dependency.ref}`)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const tempInstallDir = `${installDir}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
83
|
-
await rm(tempInstallDir, { recursive: true, force: true })
|
|
84
|
-
await mkdir(tempInstallDir, { recursive: true })
|
|
85
|
-
|
|
86
|
-
const installResult = 'installRef' in resolvedTarget
|
|
87
|
-
? params.dependency.version == null
|
|
88
|
-
? await installSkillsCliRefToTemp({
|
|
89
|
-
installRef: resolvedTarget.installRef,
|
|
90
|
-
registry: params.dependency.registry
|
|
91
|
-
})
|
|
92
|
-
: await installSkillsCliSkillToTemp({
|
|
93
|
-
registry: params.dependency.registry,
|
|
94
|
-
skill: resolvedTarget.skill,
|
|
95
|
-
source: resolvedTarget.source,
|
|
96
|
-
version: params.dependency.version
|
|
97
|
-
})
|
|
98
|
-
: await installSkillsCliSkillToTemp({
|
|
99
|
-
registry: params.dependency.registry,
|
|
100
|
-
skill: resolvedTarget.skill,
|
|
101
|
-
source: resolvedTarget.source,
|
|
102
|
-
version: params.dependency.version
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
await copyRegularFiles(installResult.installedSkill.sourcePath, tempInstallDir)
|
|
107
|
-
if (!await pathExists(resolve(tempInstallDir, 'SKILL.md'))) {
|
|
108
|
-
throw new Error(`Skill dependency ${params.dependency.ref} did not include SKILL.md`)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
await rm(installDir, { recursive: true, force: true })
|
|
112
|
-
await rename(tempInstallDir, installDir)
|
|
113
|
-
} catch (error) {
|
|
114
|
-
await rm(tempInstallDir, { recursive: true, force: true })
|
|
115
|
-
throw error
|
|
116
|
-
} finally {
|
|
117
|
-
await rm(installResult.tempDir, { recursive: true, force: true })
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
installDir,
|
|
122
|
-
skillPath
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
}
|