diff --git a/src/acp/client.ts b/src/acp/client.ts index ac59d7ae7232..c1a25fb24ca3 100644 --- a/src/acp/client.ts +++ b/src/acp/client.ts @@ -117,7 +117,7 @@ async function createAcpClient(opts: AcpClientOptions = {}): Promise ({ provider.trim().toLowerCase() === "codex-cli" ? "openai-codex" : provider.trim().toLowerCase(), })); -vi.mock("./skills.js", () => ({ +vi.mock("../skills/index.js", () => ({ buildWorkspaceSkillSnapshot: (workspaceDir: string, opts: unknown) => state.buildWorkspaceSkillSnapshotMock(workspaceDir, opts), })); -vi.mock("./skills/filter.js", () => ({ +vi.mock("../skills/filter.js", () => ({ matchesSkillFilter: () => true, })); -vi.mock("./skills/refresh-state.js", () => ({ +vi.mock("../skills/refresh-state.js", () => ({ getSkillsSnapshotVersion: () => 0, shouldRefreshSnapshotForVersion: () => false, })); diff --git a/src/agents/agent-command.ts b/src/agents/agent-command.ts index 9465d57193ac..32db4862fa9a 100644 --- a/src/agents/agent-command.ts +++ b/src/agents/agent-command.ts @@ -39,6 +39,8 @@ import { import { resolveSendPolicy } from "../sessions/send-policy.js"; import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { hydrateResolvedSkillsAsync } from "../skills/snapshot-hydration.js"; +import type { SkillSnapshot } from "../skills/types.js"; import { sanitizeForLog } from "../terminal/ansi.js"; import { createTrajectoryRuntimeRecorder } from "../trajectory/runtime.js"; import { resolveUserPath } from "../utils.js"; @@ -98,8 +100,6 @@ import { } from "./model-visibility-policy.js"; import { listOpenAIAuthProfileProvidersForAgentRuntime } from "./openai-codex-routing.js"; import { resolveProviderIdForAuth } from "./provider-auth-aliases.js"; -import { hydrateResolvedSkillsAsync } from "./skills/snapshot-hydration.js"; -import type { SkillSnapshot } from "./skills/types.js"; import { normalizeSpawnedRunMetadata } from "./spawned-context.js"; import { resolveAgentTimeoutMs } from "./timeout.js"; import { ensureAgentWorkspace } from "./workspace.js"; @@ -117,9 +117,9 @@ type CliCompactionRuntime = typeof import("./command/cli-compaction.js"); type TranscriptResolveRuntime = typeof import("../config/sessions/transcript-resolve.runtime.js"); type CliDepsRuntime = typeof import("../cli/deps.js"); type ExecDefaultsRuntime = typeof import("./exec-defaults.js"); -type SkillsRuntime = typeof import("./skills.js"); -type SkillsFilterRuntime = typeof import("./skills/filter.js"); -type SkillsRefreshStateRuntime = typeof import("./skills/refresh-state.js"); +type SkillsRuntime = typeof import("../skills/index.js"); +type SkillsFilterRuntime = typeof import("../skills/filter.js"); +type SkillsRefreshStateRuntime = typeof import("../skills/refresh-state.js"); type SkillsRemoteRuntime = typeof import("../infra/skills-remote.js"); const attemptExecutionRuntimeLoader = createLazyImportLoader( @@ -153,12 +153,14 @@ const cliDepsRuntimeLoader = createLazyImportLoader(() => import const execDefaultsRuntimeLoader = createLazyImportLoader( () => import("./exec-defaults.js"), ); -const skillsRuntimeLoader = createLazyImportLoader(() => import("./skills.js")); +const skillsRuntimeLoader = createLazyImportLoader( + () => import("../skills/index.js"), +); const skillsFilterRuntimeLoader = createLazyImportLoader( - () => import("./skills/filter.js"), + () => import("../skills/filter.js"), ); const skillsRefreshStateRuntimeLoader = createLazyImportLoader( - () => import("./skills/refresh-state.js"), + () => import("../skills/refresh-state.js"), ); const skillsRemoteRuntimeLoader = createLazyImportLoader( () => import("../infra/skills-remote.js"), diff --git a/src/agents/agent-scope.ts b/src/agents/agent-scope.ts index a2e6df5c3505..45c943426db1 100644 --- a/src/agents/agent-scope.ts +++ b/src/agents/agent-scope.ts @@ -21,6 +21,7 @@ import { normalizeOptionalString, resolvePrimaryStringValue, } from "../shared/string-coerce.js"; +import { resolveEffectiveAgentSkillFilter } from "../skills/agent-filter.js"; import { resolveUserPath } from "../utils.js"; import { listAgentIds, @@ -28,7 +29,6 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId, } from "./agent-scope-config.js"; -import { resolveEffectiveAgentSkillFilter } from "./skills/agent-filter.js"; export { listAgentEntries, listAgentIds, diff --git a/src/agents/agent-tools.before-tool-call.ts b/src/agents/agent-tools.before-tool-call.ts index 991a5715445f..efc0d9f891f4 100644 --- a/src/agents/agent-tools.before-tool-call.ts +++ b/src/agents/agent-tools.before-tool-call.ts @@ -31,6 +31,8 @@ import { type PluginHookToolKind, } from "../plugins/types.js"; import { createLazyRuntimeSurface } from "../shared/lazy-runtime.js"; +import { resolveSkillTelemetrySource, resolveSkillTelemetrySourceValue } from "../skills/source.js"; +import type { SkillSnapshot, SkillTelemetrySource } from "../skills/types.js"; import { isPlainObject } from "../utils.js"; import { adjustedParamsByToolCallId } from "./agent-tools.before-tool-call.state.js"; import { copyChannelAgentToolMeta, getChannelAgentToolMeta } from "./channel-tools.js"; @@ -42,8 +44,6 @@ import { reconcileCodeModeExecBeforeHookParams, } from "./code-mode-control-tools.js"; import type { SandboxFsBridge } from "./sandbox/fs-bridge.js"; -import { resolveSkillTelemetrySource, resolveSkillTelemetrySourceValue } from "./skills/source.js"; -import type { SkillSnapshot, SkillTelemetrySource } from "./skills/types.js"; import { normalizeToolName } from "./tool-policy.js"; import type { AnyAgentTool } from "./tools/common.js"; import { callGatewayTool } from "./tools/gateway.js"; diff --git a/src/agents/agent-tools.ts b/src/agents/agent-tools.ts index 05a24de569d2..68f4b852cac7 100644 --- a/src/agents/agent-tools.ts +++ b/src/agents/agent-tools.ts @@ -21,6 +21,7 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, } from "../shared/string-coerce.js"; +import type { SkillSnapshot } from "../skills/types.js"; import { resolveGatewayMessageChannel } from "../utils/message-channel.js"; import { resolveAgentConfig } from "./agent-scope.js"; import { wrapToolWithAbortSignal } from "./agent-tools.abort.js"; @@ -70,7 +71,6 @@ import type { SandboxContext } from "./sandbox.js"; import { SANDBOX_AGENT_WORKSPACE_MOUNT } from "./sandbox/constants.js"; import { resolveSenderToolPolicy } from "./sender-tool-policy.js"; import { createCodingTools, createReadTool } from "./sessions/index.js"; -import type { SkillSnapshot } from "./skills/types.js"; import { isSubagentEnvelopeSession, resolveSubagentCapabilityStore, diff --git a/src/agents/cli-runner/claude-skills-plugin.ts b/src/agents/cli-runner/claude-skills-plugin.ts index 715e61b619d6..2b938fde3dc6 100644 --- a/src/agents/cli-runner/claude-skills-plugin.ts +++ b/src/agents/cli-runner/claude-skills-plugin.ts @@ -3,7 +3,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js"; import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; -import type { SkillSnapshot } from "../skills.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import { cliBackendLog } from "./log.js"; const CLAUDE_CLI_BACKEND_ID = "claude-cli"; diff --git a/src/agents/cli-runner/execute.ts b/src/agents/cli-runner/execute.ts index 97afc0897ac9..25d3009d2a8e 100644 --- a/src/agents/cli-runner/execute.ts +++ b/src/agents/cli-runner/execute.ts @@ -11,6 +11,7 @@ import { requestHeartbeat as requestHeartbeatImpl } from "../../infra/heartbeat- import { sanitizeHostExecEnv } from "../../infra/host-env-security.js"; import { enqueueSystemEvent as enqueueSystemEventImpl } from "../../infra/system-events.js"; import { getProcessSupervisor as getProcessSupervisorImpl } from "../../process/supervisor/index.js"; +import { applySkillEnvOverridesFromSnapshot } from "../../skills/index.js"; import { appendBootstrapPromptWarning } from "../bootstrap-budget.js"; import { createCliJsonlStreamingParser, @@ -22,7 +23,6 @@ import { classifyFailoverReason } from "../embedded-agent-helpers.js"; import { sanitizeToolArgs, sanitizeToolResult } from "../embedded-agent-subscribe.tools.js"; import { FailoverError, resolveFailoverStatus } from "../failover-error.js"; import { applyPluginTextReplacements } from "../plugin-text-transforms.js"; -import { applySkillEnvOverridesFromSnapshot } from "../skills.js"; import { runClaudeLiveSessionTurn, shouldUseClaudeLiveSession } from "./claude-live-session.js"; import { prepareClaudeCliSkillsPlugin } from "./claude-skills-plugin.js"; import { diff --git a/src/agents/cli-runner/prepare.ts b/src/agents/cli-runner/prepare.ts index 84cebe45c96b..73e1355349f8 100644 --- a/src/agents/cli-runner/prepare.ts +++ b/src/agents/cli-runner/prepare.ts @@ -21,6 +21,7 @@ import { buildAgentHookContextChannelFields } from "../../plugins/hook-agent-con import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import { annotateInterSessionPromptText } from "../../sessions/input-provenance.js"; import { uniqueStrings } from "../../shared/string-normalization.js"; +import { resolveSkillsPromptForRun } from "../../skills/index.js"; import { resolveUserPath } from "../../utils.js"; import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js"; import { externalCliDiscoveryForProviderAuth } from "../auth-profiles/external-cli-discovery.js"; @@ -55,6 +56,7 @@ import { buildCurrentInboundPrompt } from "../embedded-agent-runner/run/runtime- import { resolveHeartbeatPromptForSystemPrompt } from "../heartbeat-system-prompt.js"; import { applyPluginTextReplacements } from "../plugin-text-transforms.js"; import { resolveSkillsPromptForRun } from "../skills.js"; +import { resolveSystemPromptOverride } from "../system-prompt-override.js"; import { buildSystemPromptReport } from "../system-prompt-report.js"; import { appendModelIdentitySystemPrompt } from "../system-prompt.js"; import { redactRunIdentifier, resolveRunWorkspaceDir } from "../workspace-run.js"; diff --git a/src/agents/cli-runner/types.ts b/src/agents/cli-runner/types.ts index ad86adf1ba26..7d777ae4813e 100644 --- a/src/agents/cli-runner/types.ts +++ b/src/agents/cli-runner/types.ts @@ -14,6 +14,7 @@ import type { PersistedUserTurnMessage, UserTurnTranscriptRecorder, } from "../../sessions/user-turn-transcript.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import type { BootstrapContextMode } from "../bootstrap-files.js"; import type { ResolvedCliBackend } from "../cli-backends.js"; import type { ContextWindowInfo } from "../context-window-guard.js"; @@ -22,7 +23,6 @@ import type { CurrentInboundPromptContext, EmbeddedRunTrigger, } from "../embedded-agent-runner/run/params.js"; -import type { SkillSnapshot } from "../skills.js"; import type { SilentReplyPromptMode } from "../system-prompt.types.js"; export type RunCliAgentParams = { diff --git a/src/agents/command/attempt-execution.ts b/src/agents/command/attempt-execution.ts index 814a9947013e..86e6826a7f27 100644 --- a/src/agents/command/attempt-execution.ts +++ b/src/agents/command/attempt-execution.ts @@ -19,6 +19,7 @@ import { appendUserTurnTranscriptMessage, type PersistedUserTurnMessage, } from "../../sessions/user-turn-transcript.js"; +import { buildWorkspaceSkillSnapshot } from "../../skills/index.js"; import { sanitizeForLog } from "../../terminal/ansi.js"; import { resolveMessageChannel } from "../../utils/message-channel.js"; import { resolveAuthProfileOrder } from "../auth-profiles/order.js"; @@ -36,7 +37,6 @@ import { resolveOpenAIRuntimeProvider } from "../openai-codex-routing.js"; import { buildAgentRuntimeAuthPlan } from "../runtime-plan/auth.js"; import type { AgentMessage } from "../runtime/index.js"; import { acquireSessionWriteLock, resolveSessionWriteLockOptions } from "../session-write-lock.js"; -import { buildWorkspaceSkillSnapshot } from "../skills.js"; import { buildUsageWithNoCost } from "../stream-message-shared.js"; import { buildClaudeCliFallbackContextPrelude, diff --git a/src/agents/command/cli-compaction.ts b/src/agents/command/cli-compaction.ts index 9955c6f340e7..98db4bf8f921 100644 --- a/src/agents/command/cli-compaction.ts +++ b/src/agents/command/cli-compaction.ts @@ -5,6 +5,7 @@ import { ensureContextEnginesInitialized as ensureContextEnginesInitializedImpl import { resolveContextEngine as resolveContextEngineImpl } from "../../context-engine/registry.js"; import type { ContextEngine } from "../../context-engine/types.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import { createPreparedEmbeddedAgentSettingsManager as createPreparedEmbeddedAgentSettingsManagerImpl } from "../agent-project-settings.js"; import { OPENCLAW_AGENT_RUNTIME_ID } from "../agent-runtime-id.js"; import { normalizeOptionalAgentRuntimeId } from "../agent-runtime-id.js"; @@ -27,7 +28,6 @@ import { ensureSelectedAgentHarnessPlugin as ensureSelectedAgentHarnessPluginImp import { maybeCompactAgentHarnessSession as maybeCompactAgentHarnessSessionImpl } from "../harness/selection.js"; import type { AgentMessage } from "../runtime/index.js"; import { SessionManager } from "../sessions/index.js"; -import type { SkillSnapshot } from "../skills.js"; import { recordCliCompactionInStore as recordCliCompactionInStoreImpl } from "./session-store.js"; type SessionManagerLike = ReturnType; diff --git a/src/agents/embedded-agent-runner/compact.hooks.harness.ts b/src/agents/embedded-agent-runner/compact.hooks.harness.ts index aa548e1320bc..513f451f5e5f 100644 --- a/src/agents/embedded-agent-runner/compact.hooks.harness.ts +++ b/src/agents/embedded-agent-runner/compact.hooks.harness.ts @@ -755,7 +755,7 @@ export async function loadCompactHooksHarness(): Promise<{ limitHistoryTurns: vi.fn((msgs: unknown[]) => msgs.slice(0, 2)), })); - vi.doMock("../skills.js", () => ({ + vi.doMock("../../skills/index.js", () => ({ applySkillEnvOverrides: vi.fn(() => () => {}), applySkillEnvOverridesFromSnapshot: vi.fn(() => () => {}), loadWorkspaceSkillEntries: vi.fn(() => []), diff --git a/src/agents/embedded-agent-runner/compact.ts b/src/agents/embedded-agent-runner/compact.ts index 3e7b4681cdf0..1a6433b81317 100644 --- a/src/agents/embedded-agent-runner/compact.ts +++ b/src/agents/embedded-agent-runner/compact.ts @@ -25,6 +25,11 @@ import { transformProviderSystemPrompt, } from "../../plugins/provider-runtime.js"; import { isCronSessionKey, isSubagentSessionKey } from "../../routing/session-key.js"; +import { + applySkillEnvOverrides, + applySkillEnvOverridesFromSnapshot, + resolveSkillsPromptForRun, +} from "../../skills/index.js"; import { resolveUserPath } from "../../utils.js"; import { normalizeMessageChannel } from "../../utils/message-channel.js"; import { isReasoningTagProvider } from "../../utils/provider-utils.js"; @@ -106,6 +111,7 @@ import { applySkillEnvOverridesFromSnapshot, resolveSkillsPromptForRun, } from "../skills.js"; +import { resolveSystemPromptOverride } from "../system-prompt-override.js"; import { filterRuntimeCompatibleTools } from "../tool-schema-projection.js"; import { logRuntimeToolSchemaQuarantine } from "../tool-schema-quarantine.js"; import { diff --git a/src/agents/embedded-agent-runner/compact.types.ts b/src/agents/embedded-agent-runner/compact.types.ts index 4b379f30334e..20355e28ca31 100644 --- a/src/agents/embedded-agent-runner/compact.types.ts +++ b/src/agents/embedded-agent-runner/compact.types.ts @@ -3,9 +3,9 @@ import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { ContextEngine, ContextEngineRuntimeContext } from "../../context-engine/types.js"; import type { CommandQueueEnqueueFn } from "../../process/command-queue.types.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import type { ExecElevatedDefaults, ExecToolDefaults } from "../bash-tools.exec-types.js"; import type { AgentRuntimePlan } from "../runtime-plan/types.js"; -import type { SkillSnapshot } from "../skills.js"; export type CompactEmbeddedAgentSessionParams = { sessionId: string; diff --git a/src/agents/embedded-agent-runner/compaction-runtime-context.ts b/src/agents/embedded-agent-runner/compaction-runtime-context.ts index 0f82a0bdeb23..3b56cd637915 100644 --- a/src/agents/embedded-agent-runner/compaction-runtime-context.ts +++ b/src/agents/embedded-agent-runner/compaction-runtime-context.ts @@ -1,13 +1,13 @@ import type { SourceReplyDeliveryMode } from "../../auto-reply/get-reply-options.types.js"; import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import { listActiveProcessSessionReferences, type ActiveProcessSessionReference, } from "../bash-process-references.js"; import type { ExecElevatedDefaults } from "../bash-tools.js"; import { resolveSelectedOpenAIRuntimeProvider } from "../openai-codex-routing.js"; -import type { SkillSnapshot } from "../skills.js"; export type EmbeddedCompactionRuntimeContext = { sessionKey?: string; diff --git a/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.test-support.ts b/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.test-support.ts index f43333820eb3..ea5aacb17079 100644 --- a/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.test-support.ts +++ b/src/agents/embedded-agent-runner/run/attempt.spawn-workspace.test-support.ts @@ -379,7 +379,7 @@ vi.mock("../../bootstrap-files.js", async () => { }; }); -vi.mock("../../skills.js", () => ({ +vi.mock("../../../skills/index.js", () => ({ applySkillEnvOverrides: () => () => {}, applySkillEnvOverridesFromSnapshot: () => () => {}, resolveSkillsPromptForRun: (...args: unknown[]) => hoisted.resolveSkillsPromptForRunMock(...args), diff --git a/src/agents/embedded-agent-runner/run/attempt.ts b/src/agents/embedded-agent-runner/run/attempt.ts index 924304b3fd01..82a0e8199833 100644 --- a/src/agents/embedded-agent-runner/run/attempt.ts +++ b/src/agents/embedded-agent-runner/run/attempt.ts @@ -52,6 +52,11 @@ import { getPluginToolMeta } from "../../../plugins/tools.js"; import { isSubagentSessionKey } from "../../../routing/session-key.js"; import { annotateInterSessionPromptText } from "../../../sessions/input-provenance.js"; import { normalizeOptionalString } from "../../../shared/string-coerce.js"; +import { + applySkillEnvOverrides, + applySkillEnvOverridesFromSnapshot, + resolveSkillsPromptForRun, +} from "../../../skills/index.js"; import { buildTrajectoryArtifacts, buildTrajectoryRunMetadata, @@ -160,11 +165,6 @@ import { sanitizeToolUseResultPairing } from "../../session-transcript-repair.js import { acquireSessionWriteLock } from "../../session-write-lock.js"; import { createAgentSession, SessionManager } from "../../sessions/index.js"; import { detectRuntimeShell } from "../../shell-utils.js"; -import { - applySkillEnvOverrides, - applySkillEnvOverridesFromSnapshot, - resolveSkillsPromptForRun, -} from "../../skills.js"; import { buildActiveSubagentSystemPromptAddition } from "../../subagent-active-context.js"; import { isSubagentEnvelopeSession, diff --git a/src/agents/embedded-agent-runner/run/params.ts b/src/agents/embedded-agent-runner/run/params.ts index 2b3a8fff818d..49d7409ab430 100644 --- a/src/agents/embedded-agent-runner/run/params.ts +++ b/src/agents/embedded-agent-runner/run/params.ts @@ -12,6 +12,7 @@ import type { PromptImageOrderEntry } from "../../../media/prompt-image-order.js import type { CommandQueueEnqueueFn } from "../../../process/command-queue.types.js"; import type { InputProvenance } from "../../../sessions/input-provenance.js"; import type { UserTurnTranscriptRecorder } from "../../../sessions/user-turn-transcript.js"; +import type { SkillSnapshot } from "../../../skills/index.js"; import type { ExecElevatedDefaults, ExecToolDefaults } from "../../bash-tools.exec-types.js"; import type { AgentStreamParams, ClientToolDefinition } from "../../command/shared-types.js"; import type { BlockReplyPayload } from "../../embedded-agent-payloads.js"; @@ -22,7 +23,6 @@ import type { } from "../../embedded-agent-subscribe.shared-types.js"; import type { AgentInternalEvent } from "../../internal-events.js"; import type { AgentMessage } from "../../runtime/index.js"; -import type { SkillSnapshot } from "../../skills.js"; import type { SilentReplyPromptMode } from "../../system-prompt.types.js"; import type { PromptMode } from "../../system-prompt.types.js"; import type { EmbeddedAgentExecutionPhase } from "../execution-phase.js"; diff --git a/src/agents/embedded-agent-runner/skills-runtime.test.ts b/src/agents/embedded-agent-runner/skills-runtime.test.ts index a9161914a41d..eb37413c2dfb 100644 --- a/src/agents/embedded-agent-runner/skills-runtime.test.ts +++ b/src/agents/embedded-agent-runner/skills-runtime.test.ts @@ -4,8 +4,8 @@ import { setRuntimeConfigSnapshot, type OpenClawConfig, } from "../../config/config.js"; -import * as skillsModule from "../skills.js"; -import type { SkillSnapshot } from "../skills.js"; +import * as skillsModule from "../../skills/index.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import { resolveEmbeddedRunSkillEntries } from "./skills-runtime.js"; describe("resolveEmbeddedRunSkillEntries", () => { diff --git a/src/agents/embedded-agent-runner/skills-runtime.ts b/src/agents/embedded-agent-runner/skills-runtime.ts index f93ef00f6f14..04de78571a5a 100644 --- a/src/agents/embedded-agent-runner/skills-runtime.ts +++ b/src/agents/embedded-agent-runner/skills-runtime.ts @@ -1,6 +1,10 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { loadWorkspaceSkillEntries, type SkillEntry, type SkillSnapshot } from "../skills.js"; -import { resolveSkillRuntimeConfig } from "../skills/runtime-config.js"; +import { + loadWorkspaceSkillEntries, + type SkillEntry, + type SkillSnapshot, +} from "../../skills/index.js"; +import { resolveSkillRuntimeConfig } from "../../skills/runtime-config.js"; export function resolveEmbeddedRunSkillEntries(params: { workspaceDir: string; diff --git a/src/agents/sandbox-agent-config.agent-specific-sandbox-config.e2e.test.ts b/src/agents/sandbox-agent-config.agent-specific-sandbox-config.e2e.test.ts index a3102dd40efa..ea98bca95cb1 100644 --- a/src/agents/sandbox-agent-config.agent-specific-sandbox-config.e2e.test.ts +++ b/src/agents/sandbox-agent-config.agent-specific-sandbox-config.e2e.test.ts @@ -46,7 +46,7 @@ vi.mock("node:child_process", () => ({ }, })); -vi.mock("./skills.js", () => ({ +vi.mock("../skills/index.js", () => ({ syncSkillsToWorkspace: vi.fn(async () => undefined), })); diff --git a/src/agents/sandbox.resolveSandboxContext.test.ts b/src/agents/sandbox.resolveSandboxContext.test.ts index 211a7c850c9d..10e638eae5a2 100644 --- a/src/agents/sandbox.resolveSandboxContext.test.ts +++ b/src/agents/sandbox.resolveSandboxContext.test.ts @@ -41,7 +41,7 @@ vi.mock("./exec-defaults.js", () => ({ canExecRequestNode: vi.fn(() => false), })); -vi.mock("./skills.js", () => ({ +vi.mock("../skills/index.js", () => ({ syncSkillsToWorkspace: syncSkillsToWorkspaceMock, })); diff --git a/src/agents/sandbox/context.ts b/src/agents/sandbox/context.ts index 5929934a3abd..1fdfc9d17a82 100644 --- a/src/agents/sandbox/context.ts +++ b/src/agents/sandbox/context.ts @@ -57,7 +57,7 @@ async function ensureSandboxWorkspaceLayout(params: { await Promise.all([ import("../../infra/skills-remote.js"), import("../exec-defaults.js"), - import("../skills.js"), + import("../../skills/index.js"), ]); await syncSkillsToWorkspace({ sourceWorkspaceDir: agentWorkspaceDir, diff --git a/src/agents/skills-install-download.ts b/src/agents/skills-install-download.ts index acf4a7392c76..aac83118ae29 100644 --- a/src/agents/skills-install-download.ts +++ b/src/agents/skills-install-download.ts @@ -12,11 +12,11 @@ import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js"; import { isWithinDir } from "../infra/path-safety.js"; import { createLazyImportLoader } from "../shared/lazy-promise.js"; import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; +import type { SkillEntry, SkillInstallSpec } from "../skills/index.js"; +import { resolveSkillToolsRootDir } from "../skills/tools-dir.js"; import { ensureDir, resolveUserPath } from "../utils.js"; import { formatInstallFailureMessage } from "./skills-install-output.js"; import type { SkillInstallResult } from "./skills-install.types.js"; -import type { SkillEntry, SkillInstallSpec } from "./skills.js"; -import { resolveSkillToolsRootDir } from "./skills/tools-dir.js"; const extractModuleLoader = createLazyImportLoader(() => import("./skills-install-extract.js")); diff --git a/src/agents/skills-install-extract.ts b/src/agents/skills-install-extract.ts index c61a377ba03d..975c2e209e9c 100644 --- a/src/agents/skills-install-extract.ts +++ b/src/agents/skills-install-extract.ts @@ -10,8 +10,8 @@ import { import { formatErrorMessage } from "../infra/errors.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { normalizeStringEntries } from "../shared/string-normalization.js"; +import { hasBinary } from "../skills/index.js"; import { parseTarVerboseMetadata } from "./skills-install-tar-verbose.js"; -import { hasBinary } from "./skills.js"; export type ArchiveExtractResult = { stdout: string; stderr: string; code: number | null }; type TarPreflightResult = { diff --git a/src/agents/skills-install-fallback.test.ts b/src/agents/skills-install-fallback.test.ts index b7172a006345..1a2db20ea6a9 100644 --- a/src/agents/skills-install-fallback.test.ts +++ b/src/agents/skills-install-fallback.test.ts @@ -2,9 +2,9 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import type { SkillEntry, SkillInstallSpec } from "../skills/index.js"; import { captureEnv } from "../test-utils/env.js"; import { hasBinaryMock, runCommandWithTimeoutMock } from "./skills-install.test-mocks.js"; -import type { SkillEntry, SkillInstallSpec } from "./skills.js"; const skillsMocks = vi.hoisted(() => ({ loadWorkspaceSkillEntries: vi.fn(), @@ -18,8 +18,8 @@ vi.mock("../plugins/install-security-scan.js", () => ({ scanSkillInstallSource: vi.fn(async () => undefined), })); -vi.mock("./skills.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("../skills/index.js", async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, loadWorkspaceSkillEntries: skillsMocks.loadWorkspaceSkillEntries, diff --git a/src/agents/skills-install.download.test.ts b/src/agents/skills-install.download.test.ts index 437bed8523de..96ef1d732b0b 100644 --- a/src/agents/skills-install.download.test.ts +++ b/src/agents/skills-install.download.test.ts @@ -3,6 +3,8 @@ import os from "node:os"; import path from "node:path"; import { Readable } from "node:stream"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import { resolveSkillToolsRootDir } from "../skills/tools-dir.js"; +import type { SkillEntry, SkillInstallSpec } from "../skills/types.js"; import { installDownloadSpec } from "./skills-install-download.js"; import { setTempStateDir } from "./skills-install.download-test-utils.js"; import { @@ -11,8 +13,6 @@ import { runCommandWithTimeoutMock, } from "./skills-install.test-mocks.js"; import { createCanonicalFixtureSkill } from "./skills.test-helpers.js"; -import { resolveSkillToolsRootDir } from "./skills/tools-dir.js"; -import type { SkillEntry, SkillInstallSpec } from "./skills/types.js"; vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args), @@ -22,7 +22,7 @@ vi.mock("../infra/net/fetch-guard.js", () => ({ fetchWithSsrFGuard: (...args: unknown[]) => fetchWithSsrFGuardMock(...args), })); -vi.mock("./skills.js", () => ({ +vi.mock("../skills/index.js", () => ({ hasBinary: (bin: string) => hasBinaryMock(bin), })); diff --git a/src/agents/skills-install.test.ts b/src/agents/skills-install.test.ts index 62618b8f7138..c54fef244244 100644 --- a/src/agents/skills-install.test.ts +++ b/src/agents/skills-install.test.ts @@ -6,6 +6,9 @@ import { resetGlobalHookRunner, } from "../plugins/hook-runner-global.js"; import { createMockPluginRegistry } from "../plugins/hooks.test-helpers.js"; +import { resolveOpenClawMetadata, resolveSkillInvocationPolicy } from "../skills/frontmatter.js"; +import { loadSkillsFromDirSafe, readSkillFrontmatterSafe } from "../skills/local-loader.js"; +import type { SkillEntry } from "../skills/types.js"; import { captureEnv } from "../test-utils/env.js"; import { createFixtureSuite } from "../test-utils/fixture-suite.js"; import { installSkill, testing as skillsInstallTesting } from "./skills-install.js"; @@ -13,9 +16,6 @@ import { runCommandWithTimeoutMock, scanDirectoryWithSummaryMock, } from "./skills-install.test-mocks.js"; -import { resolveOpenClawMetadata, resolveSkillInvocationPolicy } from "./skills/frontmatter.js"; -import { loadSkillsFromDirSafe, readSkillFrontmatterSafe } from "./skills/local-loader.js"; -import type { SkillEntry } from "./skills/types.js"; vi.mock("../process/exec.js", () => ({ runCommandWithTimeout: (...args: unknown[]) => runCommandWithTimeoutMock(...args), @@ -25,7 +25,7 @@ vi.mock("../security/skill-scanner.js", () => ({ scanDirectoryWithSummary: (...args: unknown[]) => scanDirectoryWithSummaryMock(...args), })); -vi.mock("./skills/plugin-skills.js", () => ({ +vi.mock("../skills/plugin-skills.js", () => ({ resolvePluginSkillDirs: () => [], })); diff --git a/src/agents/skills-install.ts b/src/agents/skills-install.ts index 26854925148b..82da96a8c611 100644 --- a/src/agents/skills-install.ts +++ b/src/agents/skills-install.ts @@ -11,10 +11,6 @@ import { type SkillInstallSpecMetadata, } from "../plugins/install-security-scan.js"; import { runCommandWithTimeout, type CommandOptions } from "../process/exec.js"; -import { resolveUserPath } from "../utils.js"; -import { installDownloadSpec } from "./skills-install-download.js"; -import { formatInstallFailureMessage } from "./skills-install-output.js"; -import type { SkillInstallResult } from "./skills-install.types.js"; import { hasBinary as defaultHasBinary, loadWorkspaceSkillEntries as defaultLoadWorkspaceSkillEntries, @@ -22,8 +18,12 @@ import { type SkillEntry, type SkillInstallSpec, type SkillsInstallPreferences, -} from "./skills.js"; -import { resolveSkillSource } from "./skills/source.js"; +} from "../skills/index.js"; +import { resolveSkillSource } from "../skills/source.js"; +import { resolveUserPath } from "../utils.js"; +import { installDownloadSpec } from "./skills-install-download.js"; +import { formatInstallFailureMessage } from "./skills-install-output.js"; +import type { SkillInstallResult } from "./skills-install.types.js"; export type SkillInstallRequest = InstallSafetyOverrides & { workspaceDir: string; diff --git a/src/agents/skills-source-install.ts b/src/agents/skills-source-install.ts index 1327a266b15a..7da8995a988f 100644 --- a/src/agents/skills-source-install.ts +++ b/src/agents/skills-source-install.ts @@ -7,11 +7,11 @@ import { writeJson } from "../infra/json-files.js"; import { parseGitPluginSpec } from "../plugins/git-install.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { parseFrontmatter } from "../skills/frontmatter.js"; import { sanitizeForLog } from "../terminal/ansi.js"; import { resolveUserPath } from "../utils.js"; import { installExtractedSkillRoot, validateRequestedSkillSlug } from "./skills-archive-install.js"; import { untrackClawHubSkill } from "./skills-clawhub.js"; -import { parseFrontmatter } from "./skills/frontmatter.js"; type Logger = { info?: (message: string) => void; diff --git a/src/agents/skills-status.test.ts b/src/agents/skills-status.test.ts index 60196b5749aa..b637b74fbd1b 100644 --- a/src/agents/skills-status.test.ts +++ b/src/agents/skills-status.test.ts @@ -2,10 +2,10 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import type { SkillEntry } from "../skills/types.js"; import { readLocalSkillCardContentSync } from "./skills-clawhub.js"; import { buildWorkspaceSkillStatus } from "./skills-status.js"; import { createCanonicalFixtureSkill } from "./skills.test-helpers.js"; -import type { SkillEntry } from "./skills/types.js"; type SkillStatus = ReturnType["skills"][number]; diff --git a/src/agents/skills-status.ts b/src/agents/skills-status.ts index fd3f2426af80..27321215ad6c 100644 --- a/src/agents/skills-status.ts +++ b/src/agents/skills-status.ts @@ -2,15 +2,8 @@ import path from "node:path"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { evaluateEntryRequirementsForCurrentPlatform } from "../shared/entry-status.js"; import type { RequirementConfigCheck, Requirements } from "../shared/requirements.js"; -import { CONFIG_DIR } from "../utils.js"; -import { - readClawHubSkillsLockfileStatusSync, - resolveClawHubSkillStatusLinkSync, - resolveLocalSkillCardStatusSync, - type ClawHubSkillStatusLink, - type ClawHubSkillsLockfileStatusRead, - type LocalSkillCardStatus, -} from "./skills-clawhub.js"; +import { resolveEffectiveAgentSkillFilter } from "../skills/agent-filter.js"; +import { resolveBundledSkillsContext } from "../skills/bundled-context.js"; import { hasBinary, isBundledSkillAllowed, @@ -23,10 +16,17 @@ import { type SkillEligibilityContext, type SkillInstallSpec, type SkillsInstallPreferences, -} from "./skills.js"; -import { resolveEffectiveAgentSkillFilter } from "./skills/agent-filter.js"; -import { resolveBundledSkillsContext } from "./skills/bundled-context.js"; -import { resolveSkillSource } from "./skills/source.js"; +} from "../skills/index.js"; +import { resolveSkillSource } from "../skills/source.js"; +import { CONFIG_DIR } from "../utils.js"; +import { + readClawHubSkillsLockfileStatusSync, + resolveClawHubSkillStatusLinkSync, + resolveLocalSkillCardStatusSync, + type ClawHubSkillStatusLink, + type ClawHubSkillsLockfileStatusRead, + type LocalSkillCardStatus, +} from "./skills-clawhub.js"; export type SkillStatusConfigCheck = RequirementConfigCheck; diff --git a/src/agents/skills.agents-skills-directory.test.ts b/src/agents/skills.agents-skills-directory.test.ts index 97da7fdc4d27..205e7f4203d7 100644 --- a/src/agents/skills.agents-skills-directory.test.ts +++ b/src/agents/skills.agents-skills-directory.test.ts @@ -2,15 +2,15 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { writeSkill } from "./skills.test-helpers.js"; import { restoreMockSkillsHomeEnv, setMockSkillsHomeEnv, type SkillsHomeEnvSnapshot, -} from "./skills/home-env.test-support.js"; -import { buildWorkspaceSkillsPrompt } from "./skills/workspace.js"; +} from "../skills/home-env.test-support.js"; +import { buildWorkspaceSkillsPrompt } from "../skills/workspace.js"; +import { writeSkill } from "./skills.test-helpers.js"; -vi.mock("./skills/plugin-skills.js", () => ({ +vi.mock("../skills/plugin-skills.js", () => ({ resolvePluginSkillDirs: () => [], })); diff --git a/src/agents/skills.build-workspace-skills-prompt.applies-bundled-allowlist-without-affecting-workspace-skills.test.ts b/src/agents/skills.build-workspace-skills-prompt.applies-bundled-allowlist-without-affecting-workspace-skills.test.ts index f50260ff7785..0bc74c597fe8 100644 --- a/src/agents/skills.build-workspace-skills-prompt.applies-bundled-allowlist-without-affecting-workspace-skills.test.ts +++ b/src/agents/skills.build-workspace-skills-prompt.applies-bundled-allowlist-without-affecting-workspace-skills.test.ts @@ -2,9 +2,9 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { buildWorkspaceSkillsPrompt } from "../skills/workspace.js"; import { captureEnv } from "../test-utils/env.js"; import { writeSkill } from "./skills.e2e-test-helpers.js"; -import { buildWorkspaceSkillsPrompt } from "./skills/workspace.js"; describe("buildWorkspaceSkillsPrompt", () => { it("applies bundled allowlist without affecting workspace skills", async () => { diff --git a/src/agents/skills.build-workspace-skills-prompt.prefers-workspace-skills-managed-skills.test.ts b/src/agents/skills.build-workspace-skills-prompt.prefers-workspace-skills-managed-skills.test.ts index b1d783ada221..a96c7eb69952 100644 --- a/src/agents/skills.build-workspace-skills-prompt.prefers-workspace-skills-managed-skills.test.ts +++ b/src/agents/skills.build-workspace-skills-prompt.prefers-workspace-skills-managed-skills.test.ts @@ -1,13 +1,13 @@ import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { createSyntheticSourceInfo } from "../skills/skill-contract.js"; +import type { OpenClawSkillMetadata, SkillEntry } from "../skills/types.js"; +import { buildWorkspaceSkillsPrompt } from "../skills/workspace.js"; import { withEnv } from "../test-utils/env.js"; import { createFixtureSuite } from "../test-utils/fixture-suite.js"; import { writeSkill } from "./skills.e2e-test-helpers.js"; -import { createSyntheticSourceInfo } from "./skills/skill-contract.js"; -import type { OpenClawSkillMetadata, SkillEntry } from "./skills/types.js"; -import { buildWorkspaceSkillsPrompt } from "./skills/workspace.js"; -vi.mock("./skills/plugin-skills.js", () => ({ +vi.mock("../skills/plugin-skills.js", () => ({ resolvePluginSkillDirs: () => [], })); diff --git a/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts b/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts index d11b1af79888..bfe57f76c1b8 100644 --- a/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts +++ b/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts @@ -2,11 +2,11 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { buildWorkspaceSkillsPrompt, syncSkillsToWorkspace } from "../skills/workspace.js"; import { withEnv, withEnvAsync } from "../test-utils/env.js"; import { writeSkill } from "./skills.e2e-test-helpers.js"; -import { buildWorkspaceSkillsPrompt, syncSkillsToWorkspace } from "./skills/workspace.js"; -vi.mock("./skills/plugin-skills.js", () => ({ +vi.mock("../skills/plugin-skills.js", () => ({ resolvePluginSkillDirs: () => [], })); diff --git a/src/agents/skills.buildworkspaceskillsnapshot.test.ts b/src/agents/skills.buildworkspaceskillsnapshot.test.ts index 88bddc13509e..9fe11002d62e 100644 --- a/src/agents/skills.buildworkspaceskillsnapshot.test.ts +++ b/src/agents/skills.buildworkspaceskillsnapshot.test.ts @@ -1,18 +1,18 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; -import { withPathResolutionEnv } from "../test-utils/env.js"; -import { createFixtureSuite } from "../test-utils/fixture-suite.js"; -import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js"; -import { writeSkill, writeWorkspaceSkills } from "./skills.e2e-test-helpers.js"; import { restoreMockSkillsHomeEnv, setMockSkillsHomeEnv, type SkillsHomeEnvSnapshot, -} from "./skills/home-env.test-support.js"; -import { buildWorkspaceSkillSnapshot, buildWorkspaceSkillsPrompt } from "./skills/workspace.js"; +} from "../skills/home-env.test-support.js"; +import { buildWorkspaceSkillSnapshot, buildWorkspaceSkillsPrompt } from "../skills/workspace.js"; +import { withPathResolutionEnv } from "../test-utils/env.js"; +import { createFixtureSuite } from "../test-utils/fixture-suite.js"; +import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js"; +import { writeSkill, writeWorkspaceSkills } from "./skills.e2e-test-helpers.js"; -vi.mock("./skills/plugin-skills.js", () => ({ +vi.mock("../skills/plugin-skills.js", () => ({ resolvePluginSkillDirs: () => [], })); diff --git a/src/agents/skills.buildworkspaceskillstatus.test.ts b/src/agents/skills.buildworkspaceskillstatus.test.ts index 94f2cc5d233e..69b1030afcf1 100644 --- a/src/agents/skills.buildworkspaceskillstatus.test.ts +++ b/src/agents/skills.buildworkspaceskillstatus.test.ts @@ -2,12 +2,12 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; +import type { SkillEntry } from "../skills/types.js"; +import { loadWorkspaceSkillEntries } from "../skills/workspace.js"; import { withEnv, withEnvAsync } from "../test-utils/env.js"; import { buildWorkspaceSkillStatus } from "./skills-status.js"; import { writeSkill } from "./skills.e2e-test-helpers.js"; import { createCanonicalFixtureSkill } from "./skills.test-helpers.js"; -import type { SkillEntry } from "./skills/types.js"; -import { loadWorkspaceSkillEntries } from "./skills/workspace.js"; const tempDirs: string[] = []; diff --git a/src/agents/skills.bundled-frontmatter.test.ts b/src/agents/skills.bundled-frontmatter.test.ts index c975a987c8ad..e81c9df4caf2 100644 --- a/src/agents/skills.bundled-frontmatter.test.ts +++ b/src/agents/skills.bundled-frontmatter.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; -import { parseFrontmatter } from "./skills/frontmatter.js"; +import { parseFrontmatter } from "../skills/frontmatter.js"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."); diff --git a/src/agents/skills.compact-skill-paths.test.ts b/src/agents/skills.compact-skill-paths.test.ts index 30c4a686fb68..fe4945523491 100644 --- a/src/agents/skills.compact-skill-paths.test.ts +++ b/src/agents/skills.compact-skill-paths.test.ts @@ -1,11 +1,11 @@ import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { createCanonicalFixtureSkill } from "./skills.test-helpers.js"; import { testing as workspaceSkillsTesting, buildWorkspaceSkillsPrompt, -} from "./skills/workspace.js"; +} from "../skills/workspace.js"; +import { createCanonicalFixtureSkill } from "./skills.test-helpers.js"; describe("compactSkillPaths", () => { function buildPromptForFixtureSkill(params: { diff --git a/src/agents/skills.loadworkspaceskillentries.test.ts b/src/agents/skills.loadworkspaceskillentries.test.ts index 4b049d5b2703..de3bf979f835 100644 --- a/src/agents/skills.loadworkspaceskillentries.test.ts +++ b/src/agents/skills.loadworkspaceskillentries.test.ts @@ -13,14 +13,14 @@ import { import { resolveInstalledPluginIndexPolicyHash } from "../plugins/installed-plugin-index-policy.js"; import type { PluginManifestRecord, PluginManifestRegistry } from "../plugins/manifest-registry.js"; import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; -import { writeSkill, writeWorkspaceSkills } from "./skills.e2e-test-helpers.js"; import { restoreMockSkillsHomeEnv, setMockSkillsHomeEnv, type SkillsHomeEnvSnapshot, -} from "./skills/home-env.test-support.js"; -import { readSkillFrontmatterSafe } from "./skills/local-loader.js"; -import { loadWorkspaceSkillEntries } from "./skills/workspace.js"; +} from "../skills/home-env.test-support.js"; +import { readSkillFrontmatterSafe } from "../skills/local-loader.js"; +import { loadWorkspaceSkillEntries } from "../skills/workspace.js"; +import { writeSkill, writeWorkspaceSkills } from "./skills.e2e-test-helpers.js"; import { writePluginWithSkill } from "./test-helpers/skill-plugin-fixtures.js"; vi.mock("../plugins/manifest-registry.js", async () => { diff --git a/src/agents/skills.plugin-skills-sandbox-sync.test.ts b/src/agents/skills.plugin-skills-sandbox-sync.test.ts index 329efc15cf07..4d22d804506b 100644 --- a/src/agents/skills.plugin-skills-sandbox-sync.test.ts +++ b/src/agents/skills.plugin-skills-sandbox-sync.test.ts @@ -3,13 +3,13 @@ import fsPromises from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { buildWorkspaceSkillsPrompt, syncSkillsToWorkspace } from "../skills/workspace.js"; import { writeSkill } from "./skills.e2e-test-helpers.js"; -import { buildWorkspaceSkillsPrompt, syncSkillsToWorkspace } from "./skills/workspace.js"; // Mock resolvePluginSkillDirs to return our test plugin skill directories const mockResolvePluginSkillDirs = vi.hoisted(() => vi.fn(() => [] as string[])); -vi.mock("./skills/plugin-skills.js", () => ({ +vi.mock("../skills/plugin-skills.js", () => ({ resolvePluginSkillDirs: mockResolvePluginSkillDirs, })); diff --git a/src/agents/skills.resolveskillspromptforrun.test.ts b/src/agents/skills.resolveskillspromptforrun.test.ts index e7a5c70e8396..28f4fed8ed47 100644 --- a/src/agents/skills.resolveskillspromptforrun.test.ts +++ b/src/agents/skills.resolveskillspromptforrun.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; +import type { SkillEntry } from "../skills/types.js"; +import { resolveSkillsPromptForRun } from "../skills/workspace.js"; import { createCanonicalFixtureSkill } from "./skills.test-helpers.js"; -import type { SkillEntry } from "./skills/types.js"; -import { resolveSkillsPromptForRun } from "./skills/workspace.js"; describe("resolveSkillsPromptForRun", () => { it("prefers snapshot prompt when available", () => { diff --git a/src/agents/skills.summarize-skill-description.test.ts b/src/agents/skills.summarize-skill-description.test.ts index e8b4a2373467..2431777108b7 100644 --- a/src/agents/skills.summarize-skill-description.test.ts +++ b/src/agents/skills.summarize-skill-description.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { parseFrontmatter } from "./skills/frontmatter.js"; +import { parseFrontmatter } from "../skills/frontmatter.js"; describe("skills/summarize frontmatter", () => { it("mentions podcasts, local files, and transcription use cases", () => { diff --git a/src/agents/skills.test-helpers.ts b/src/agents/skills.test-helpers.ts index 36110c2d4323..a22b2f7000ad 100644 --- a/src/agents/skills.test-helpers.ts +++ b/src/agents/skills.test-helpers.ts @@ -1,6 +1,6 @@ import fs from "node:fs/promises"; import path from "node:path"; -import { createSyntheticSourceInfo, type Skill } from "./skills/skill-contract.js"; +import { createSyntheticSourceInfo, type Skill } from "../skills/skill-contract.js"; export async function writeSkill(params: { dir: string; diff --git a/src/agents/skills.test.ts b/src/agents/skills.test.ts index ea8090fc1055..e41d72c44046 100644 --- a/src/agents/skills.test.ts +++ b/src/agents/skills.test.ts @@ -7,25 +7,25 @@ import { } from "../config/runtime-snapshot.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { clearPluginMetadataLifecycleCaches } from "../plugins/plugin-metadata-lifecycle.js"; +import { buildWorkspaceSkillCommandSpecs } from "../skills/command-specs.js"; import { captureEnv, withPathResolutionEnv } from "../test-utils/env.js"; import { createFixtureSuite } from "../test-utils/fixture-suite.js"; import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js"; import { writeSkill } from "./skills.e2e-test-helpers.js"; -import { buildWorkspaceSkillCommandSpecs } from "./skills/command-specs.js"; import { applySkillEnvOverrides, applySkillEnvOverridesFromSnapshot, getActiveSkillEnvKeys, -} from "./skills/env-overrides.js"; +} from "../skills/env-overrides.js"; import { restoreMockSkillsHomeEnv, setMockSkillsHomeEnv, type SkillsHomeEnvSnapshot, -} from "./skills/home-env.test-support.js"; -import type { SkillEntry, SkillSnapshot } from "./skills/types.js"; -import { buildWorkspaceSkillsPrompt } from "./skills/workspace.js"; +} from "../skills/home-env.test-support.js"; +import type { SkillEntry, SkillSnapshot } from "../skills/types.js"; +import { buildWorkspaceSkillsPrompt } from "../skills/workspace.js"; -vi.mock("./skills/plugin-skills.js", () => ({ +vi.mock("../skills/plugin-skills.js", () => ({ resolvePluginSkillDirs: () => [], })); diff --git a/src/agents/skills.ts b/src/agents/skills.ts index 85ebdc671214..48df090be0fb 100644 --- a/src/agents/skills.ts +++ b/src/agents/skills.ts @@ -1,51 +1 @@ -import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { - normalizeLowercaseStringOrEmpty, - normalizeOptionalString, -} from "../shared/string-coerce.js"; -import type { SkillsInstallPreferences } from "./skills/types.js"; - -export { - hasBinary, - isBundledSkillAllowed, - isConfigPathTruthy, - resolveBundledAllowlist, - resolveConfigPath, - resolveRuntimePlatform, - resolveSkillConfig, -} from "./skills/config.js"; -export { - applySkillEnvOverrides, - applySkillEnvOverridesFromSnapshot, -} from "./skills/env-overrides.js"; -export type { - OpenClawSkillMetadata, - SkillEligibilityContext, - SkillCommandSpec, - SkillEntry, - SkillInstallSpec, - SkillSnapshot, - SkillTelemetrySource, - SkillsInstallPreferences, -} from "./skills/types.js"; -export { - buildWorkspaceSkillSnapshot, - buildWorkspaceSkillsPrompt, - filterWorkspaceSkillEntries, - filterWorkspaceSkillEntriesWithOptions, - loadWorkspaceSkillEntries, - resolveSkillsPromptForRun, - syncSkillsToWorkspace, -} from "./skills/workspace.js"; -export { buildWorkspaceSkillCommandSpecs } from "./skills/command-specs.js"; - -export function resolveSkillsInstallPreferences(config?: OpenClawConfig): SkillsInstallPreferences { - const raw = config?.skills?.install; - const preferBrew = raw?.preferBrew ?? true; - const manager = normalizeLowercaseStringOrEmpty(normalizeOptionalString(raw?.nodeManager)); - const nodeManager: SkillsInstallPreferences["nodeManager"] = - manager === "pnpm" || manager === "yarn" || manager === "bun" || manager === "npm" - ? manager - : "npm"; - return { preferBrew, nodeManager }; -} +export * from "../skills/index.js"; diff --git a/src/auto-reply/command-status-builders.ts b/src/auto-reply/command-status-builders.ts index c16c8f11d4de..ac39b37e0bef 100644 --- a/src/auto-reply/command-status-builders.ts +++ b/src/auto-reply/command-status-builders.ts @@ -1,4 +1,3 @@ -import type { SkillCommandSpec } from "../agents/skills.js"; import { getChannelPlugin } from "../channels/plugins/index.js"; import { isCommandFlagEnabled } from "../config/commands.flags.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -8,6 +7,7 @@ import { normalizeOptionalLowercaseString, normalizeOptionalString, } from "../shared/string-coerce.js"; +import type { SkillCommandSpec } from "../skills/index.js"; import { listChatCommands, listChatCommandsForConfig, diff --git a/src/auto-reply/commands-registry-list.ts b/src/auto-reply/commands-registry-list.ts index 53dca563f40e..1c9158ee8f7b 100644 --- a/src/auto-reply/commands-registry-list.ts +++ b/src/auto-reply/commands-registry-list.ts @@ -1,6 +1,6 @@ -import type { SkillCommandSpec } from "../agents/skills/types.js"; import { isCommandFlagEnabled } from "../config/commands.flags.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import type { SkillCommandSpec } from "../skills/types.js"; import { getChatCommands } from "./commands-registry.data.js"; import type { ChatCommandDefinition } from "./commands-registry.types.js"; diff --git a/src/auto-reply/commands-registry.ts b/src/auto-reply/commands-registry.ts index 7cdc791e4a1a..2ec3e0466cbf 100644 --- a/src/auto-reply/commands-registry.ts +++ b/src/auto-reply/commands-registry.ts @@ -3,10 +3,10 @@ import { buildConfiguredModelCatalog, resolveConfiguredModelRef, } from "../agents/model-selection.js"; -import type { SkillCommandSpec } from "../agents/skills.js"; import { getChannelPlugin, getLoadedChannelPlugin } from "../channels/plugins/index.js"; import type { OpenClawConfig } from "../config/types.js"; import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js"; +import type { SkillCommandSpec } from "../skills/index.js"; import { listChatCommands, listChatCommandsForConfig } from "./commands-registry-list.js"; import { normalizeCommandBody } from "./commands-registry-normalize.js"; import { getChatCommands } from "./commands-registry.data.js"; diff --git a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts index d54e634d5495..b3a861ce9f66 100644 --- a/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts +++ b/src/auto-reply/reply.directive.directive-behavior.e2e-harness.ts @@ -1,6 +1,5 @@ import { afterEach, beforeEach, vi } from "vitest"; import { clearRuntimeAuthProfileStoreSnapshots } from "../agents/auth-profiles.js"; -import { resetSkillsRefreshForTest } from "../agents/skills/refresh.js"; import { clearSessionStoreCacheForTest } from "../config/sessions.js"; import { resetSystemEventsForTest } from "../infra/system-events.js"; import { createEmptyPluginRegistry } from "../plugins/registry-empty.js"; @@ -8,6 +7,7 @@ import type { PluginProviderRegistration } from "../plugins/registry.js"; import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js"; import type { ProviderPlugin } from "../plugins/types.js"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; +import { resetSkillsRefreshForTest } from "../skills/refresh.js"; import { clearSessionAuthProfileOverrideMock, compactEmbeddedAgentSessionMock, diff --git a/src/auto-reply/reply/commands-system-prompt.test.ts b/src/auto-reply/reply/commands-system-prompt.test.ts index 081038961b73..a1a92cb99a4f 100644 --- a/src/auto-reply/reply/commands-system-prompt.test.ts +++ b/src/auto-reply/reply/commands-system-prompt.test.ts @@ -22,11 +22,11 @@ vi.mock("../../agents/sandbox.js", () => ({ resolveSandboxRuntimeStatus: vi.fn(() => ({ sandboxed: false, mode: "off" })), })); -vi.mock("../../agents/skills.js", () => ({ +vi.mock("../../skills/index.js", () => ({ buildWorkspaceSkillSnapshot: vi.fn(() => ({ prompt: "", skills: [], resolvedSkills: [] })), })); -vi.mock("../../agents/skills/refresh.js", () => ({ +vi.mock("../../skills/refresh.js", () => ({ getSkillsSnapshotVersion: vi.fn(() => "test-snapshot"), })); diff --git a/src/auto-reply/reply/commands-system-prompt.ts b/src/auto-reply/reply/commands-system-prompt.ts index cd1ff12ac1e9..c2a23bd0aa10 100644 --- a/src/auto-reply/reply/commands-system-prompt.ts +++ b/src/auto-reply/reply/commands-system-prompt.ts @@ -9,13 +9,13 @@ import { resolveDefaultModelForAgent } from "../../agents/model-selection.js"; import { resolveAgentPromptSurfaceForSessionKey } from "../../agents/prompt-surface.js"; import type { AgentTool } from "../../agents/runtime/index.js"; import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js"; -import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js"; -import { getSkillsSnapshotVersion } from "../../agents/skills/refresh-state.js"; import { buildConfiguredAgentSystemPrompt } from "../../agents/system-prompt-config.js"; import { buildSystemPromptParams } from "../../agents/system-prompt-params.js"; import type { WorkspaceBootstrapFile } from "../../agents/workspace.js"; import { getRemoteSkillEligibility } from "../../infra/skills-remote.js"; import { listRegisteredPluginAgentPromptGuidance } from "../../plugins/command-registry-state.js"; +import { buildWorkspaceSkillSnapshot } from "../../skills/index.js"; +import { getSkillsSnapshotVersion } from "../../skills/refresh-state.js"; import type { HandleCommandsParams } from "./commands-types.js"; import { resolveRuntimePolicySessionKey } from "./runtime-policy-session-key.js"; diff --git a/src/auto-reply/reply/commands-types.ts b/src/auto-reply/reply/commands-types.ts index 662dfa388271..bffa21789784 100644 --- a/src/auto-reply/reply/commands-types.ts +++ b/src/auto-reply/reply/commands-types.ts @@ -1,8 +1,8 @@ import type { BlockReplyChunking } from "../../agents/embedded-agent-block-chunker.js"; -import type { SkillCommandSpec } from "../../agents/skills.js"; import type { ChannelId } from "../../channels/plugins/types.public.js"; import type { SessionEntry, SessionScope } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; +import type { SkillCommandSpec } from "../../skills/index.js"; import type { MsgContext } from "../templating.js"; import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "../thinking.js"; import type { GetReplyOptions, ReplyPayload } from "../types.js"; diff --git a/src/auto-reply/reply/get-reply-directive-aliases.ts b/src/auto-reply/reply/get-reply-directive-aliases.ts index 2b00423b4c1c..34c65ed83e65 100644 --- a/src/auto-reply/reply/get-reply-directive-aliases.ts +++ b/src/auto-reply/reply/get-reply-directive-aliases.ts @@ -1,9 +1,9 @@ -import type { SkillCommandSpec } from "../../agents/skills.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "../../shared/string-coerce.js"; +import type { SkillCommandSpec } from "../../skills/index.js"; export function reserveSkillCommandNames(params: { reservedCommands: Set; diff --git a/src/auto-reply/reply/get-reply-directives.ts b/src/auto-reply/reply/get-reply-directives.ts index 25610b49bb1b..afb5766892d3 100644 --- a/src/auto-reply/reply/get-reply-directives.ts +++ b/src/auto-reply/reply/get-reply-directives.ts @@ -3,7 +3,6 @@ import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js"; import { resolveFastModeState } from "../../agents/fast-mode.js"; import { type ModelAliasIndex, resolveModelRefFromString } from "../../agents/model-selection.js"; import { resolveSandboxRuntimeStatus } from "../../agents/sandbox/runtime-status.js"; -import type { SkillCommandSpec } from "../../agents/skills.js"; import type { SessionEntry } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { normalizeAgentId } from "../../routing/session-key.js"; @@ -12,6 +11,7 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "../../shared/string-coerce.js"; +import type { SkillCommandSpec } from "../../skills/index.js"; import { shouldHandleTextCommands } from "../commands-text-routing.js"; import type { MsgContext, TemplateContext } from "../templating.js"; import { diff --git a/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts b/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts index 36bdb0db337d..88f28c8e58d5 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.skip-when-config-empty.test.ts @@ -2,9 +2,9 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import type { SkillCommandSpec } from "../../agents/skills.js"; import type { SessionEntry } from "../../config/sessions.js"; import { getReplyPayloadMetadata } from "../reply-payload.js"; +import type { SkillCommandSpec } from "../../skills/index.js"; import type { TemplateContext } from "../templating.js"; import { clearInlineDirectives } from "./get-reply-directives-utils.js"; import { handleInlineActions } from "./get-reply-inline-actions.js"; diff --git a/src/auto-reply/reply/get-reply-inline-actions.ts b/src/auto-reply/reply/get-reply-inline-actions.ts index c464f1409652..4835cd845710 100644 --- a/src/auto-reply/reply/get-reply-inline-actions.ts +++ b/src/auto-reply/reply/get-reply-inline-actions.ts @@ -1,6 +1,5 @@ import { collectTextContentBlocks } from "../../agents/content-blocks.js"; import type { BlockReplyChunking } from "../../agents/embedded-agent-block-chunker.js"; -import type { SkillCommandSpec } from "../../agents/skills.js"; import { getChannelPlugin } from "../../channels/plugins/index.js"; import type { SessionEntry } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; @@ -13,6 +12,7 @@ import { normalizeOptionalString, } from "../../shared/string-coerce.js"; import { markReplyPayloadForSourceSuppressionDelivery } from "../reply-payload.js"; +import type { SkillCommandSpec } from "../../skills/index.js"; import { listReservedChatSlashCommandNames, resolveSkillCommandInvocation, diff --git a/src/auto-reply/reply/get-reply-native-slash-fast-path.ts b/src/auto-reply/reply/get-reply-native-slash-fast-path.ts index 20df07f85feb..3047cbdbc2fd 100644 --- a/src/auto-reply/reply/get-reply-native-slash-fast-path.ts +++ b/src/auto-reply/reply/get-reply-native-slash-fast-path.ts @@ -3,10 +3,10 @@ import { resolveThinkingDefaultWithRuntimeCatalog, type ModelAliasIndex, } from "../../agents/model-selection.js"; -import type { SkillCommandSpec } from "../../agents/skills.js"; import type { OpenClawConfig } from "../../config/config.js"; import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import type { SkillCommandSpec } from "../../skills/index.js"; import { isNativeCommandTurn, resolveCommandTurnContext } from "../command-turn-context.js"; import type { GetReplyOptions } from "../get-reply-options.types.js"; import type { ReplyPayload } from "../reply-payload.js"; diff --git a/src/auto-reply/reply/queue/types.ts b/src/auto-reply/reply/queue/types.ts index 908818c72000..8ec7fa3158b6 100644 --- a/src/auto-reply/reply/queue/types.ts +++ b/src/auto-reply/reply/queue/types.ts @@ -1,7 +1,6 @@ import type { AutoFallbackPrimaryProbe } from "../../../agents/agent-scope.js"; import type { ExecToolDefaults } from "../../../agents/bash-tools.js"; import type { CurrentInboundPromptContext } from "../../../agents/embedded-agent-runner/run/params.js"; -import type { SkillSnapshot } from "../../../agents/skills.js"; import type { SilentReplyPromptMode } from "../../../agents/system-prompt.types.js"; import type { InboundEventKind } from "../../../channels/inbound-event/kind.js"; import type { SessionEntry } from "../../../config/sessions.js"; @@ -9,6 +8,7 @@ import type { OpenClawConfig } from "../../../config/types.openclaw.js"; import type { PromptImageOrderEntry } from "../../../media/prompt-image-order.js"; import type { InputProvenance } from "../../../sessions/input-provenance.js"; import type { UserTurnTranscriptRecorder } from "../../../sessions/user-turn-transcript.js"; +import type { SkillSnapshot } from "../../../skills/index.js"; import type { QueuedReplyDeliveryCorrelation, QueuedReplyLifecycle, diff --git a/src/auto-reply/reply/session-updates.test.ts b/src/auto-reply/reply/session-updates.test.ts index 6533f338b137..d856bb0222cf 100644 --- a/src/auto-reply/reply/session-updates.test.ts +++ b/src/auto-reply/reply/session-updates.test.ts @@ -53,15 +53,15 @@ vi.mock("../../agents/agent-scope.js", () => ({ resolveSessionAgentId: resolveSessionAgentIdMock, })); -vi.mock("../../agents/skills.js", () => ({ +vi.mock("../../skills/index.js", () => ({ buildWorkspaceSkillSnapshot: buildWorkspaceSkillSnapshotMock, })); -vi.mock("../../agents/skills/refresh.js", () => ({ +vi.mock("../../skills/refresh.js", () => ({ ensureSkillsWatcher: ensureSkillsWatcherMock, })); -vi.mock("../../agents/skills/refresh-state.js", () => ({ +vi.mock("../../skills/refresh-state.js", () => ({ getSkillsSnapshotVersion: getSkillsSnapshotVersionMock, shouldRefreshSnapshotForVersion: shouldRefreshSnapshotForVersionMock, })); diff --git a/src/auto-reply/reply/session-updates.ts b/src/auto-reply/reply/session-updates.ts index 85645c4fcce7..9f8eccc8bdc8 100644 --- a/src/auto-reply/reply/session-updates.ts +++ b/src/auto-reply/reply/session-updates.ts @@ -2,14 +2,6 @@ import crypto from "node:crypto"; import path from "node:path"; import { resolveSessionAgentId } from "../../agents/agent-scope.js"; import { canExecRequestNode } from "../../agents/exec-defaults.js"; -import { buildWorkspaceSkillSnapshot, type SkillSnapshot } from "../../agents/skills.js"; -import { matchesSkillFilter } from "../../agents/skills/filter.js"; -import { - getSkillsSnapshotVersion, - shouldRefreshSnapshotForVersion, -} from "../../agents/skills/refresh-state.js"; -import { ensureSkillsWatcher } from "../../agents/skills/refresh.js"; -import { hydrateResolvedSkills } from "../../agents/skills/snapshot-hydration.js"; import { stableStringify } from "../../agents/stable-stringify.js"; import { canonicalizeAbsoluteSessionFilePath, @@ -30,6 +22,14 @@ import { getRemoteSkillEligibility } from "../../infra/skills-remote.js"; import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { matchesSkillFilter } from "../../skills/filter.js"; +import { buildWorkspaceSkillSnapshot, type SkillSnapshot } from "../../skills/index.js"; +import { + getSkillsSnapshotVersion, + shouldRefreshSnapshotForVersion, +} from "../../skills/refresh-state.js"; +import { ensureSkillsWatcher } from "../../skills/refresh.js"; +import { hydrateResolvedSkills } from "../../skills/snapshot-hydration.js"; import { buildSessionEndHookPayload, buildSessionStartHookPayload } from "./session-hooks.js"; export { drainFormattedSystemEvents } from "./session-system-events.js"; diff --git a/src/auto-reply/reply/skill-tool-dispatch.runtime.ts b/src/auto-reply/reply/skill-tool-dispatch.runtime.ts index 62237b0cde92..62e272e9bd6e 100644 --- a/src/auto-reply/reply/skill-tool-dispatch.runtime.ts +++ b/src/auto-reply/reply/skill-tool-dispatch.runtime.ts @@ -8,7 +8,6 @@ import type { AnyAgentTool } from "../../agents/agent-tools.types.js"; import { createOpenClawTools } from "../../agents/openclaw-tools.runtime.js"; import { resolveSandboxRuntimeStatus } from "../../agents/sandbox/runtime-status.js"; import { resolveSenderToolPolicy } from "../../agents/sender-tool-policy.js"; -import type { SkillCommandSpec } from "../../agents/skills.js"; import { isSubagentEnvelopeSession, resolveSubagentCapabilityStore, @@ -29,6 +28,7 @@ import type { SessionEntry } from "../../config/sessions.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { logVerbose } from "../../globals.js"; import { getPluginToolMeta } from "../../plugins/tools.js"; +import type { SkillCommandSpec } from "../../skills/index.js"; import { resolveGatewayMessageChannel } from "../../utils/message-channel.js"; import type { MsgContext } from "../templating.js"; import { extractExplicitGroupId } from "./group-id.js"; diff --git a/src/auto-reply/skill-commands-base.ts b/src/auto-reply/skill-commands-base.ts index e763200f3c13..6e7df6db4c12 100644 --- a/src/auto-reply/skill-commands-base.ts +++ b/src/auto-reply/skill-commands-base.ts @@ -1,8 +1,8 @@ -import type { SkillCommandSpec } from "../agents/skills.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, } from "../shared/string-coerce.js"; +import type { SkillCommandSpec } from "../skills/index.js"; import { getChatCommands } from "./commands-registry.data.js"; export function listReservedChatSlashCommandNames(extraNames: string[] = []): Set { diff --git a/src/auto-reply/skill-commands.test.ts b/src/auto-reply/skill-commands.test.ts index f508407a030f..131cc7cc4244 100644 --- a/src/auto-reply/skill-commands.test.ts +++ b/src/auto-reply/skill-commands.test.ts @@ -131,7 +131,7 @@ vi.mock("../infra/skills-remote.js", () => ({ getRemoteSkillEligibility: () => ({}), })); -vi.mock("../agents/skills.js", () => ({ +vi.mock("../skills/index.js", () => ({ buildWorkspaceSkillCommandSpecs, })); diff --git a/src/auto-reply/skill-commands.ts b/src/auto-reply/skill-commands.ts index 51d1ebf0b5d2..455dd69ccea6 100644 --- a/src/auto-reply/skill-commands.ts +++ b/src/auto-reply/skill-commands.ts @@ -5,7 +5,6 @@ import { resolveAgentWorkspaceDir, } from "../agents/agent-scope.js"; import { canExecRequestNode } from "../agents/exec-defaults.js"; -import { buildWorkspaceSkillCommandSpecs, type SkillCommandSpec } from "../agents/skills.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { logVerbose } from "../globals.js"; import { getRemoteSkillEligibility } from "../infra/skills-remote.js"; @@ -14,6 +13,7 @@ import { normalizeOptionalLowercaseString, } from "../shared/string-coerce.js"; import { uniqueStrings } from "../shared/string-normalization.js"; +import { buildWorkspaceSkillCommandSpecs, type SkillCommandSpec } from "../skills/index.js"; import { listReservedChatSlashCommandNames } from "./skill-commands-base.js"; export { listReservedChatSlashCommandNames, diff --git a/src/cli/skills-cli.formatting.test.ts b/src/cli/skills-cli.formatting.test.ts index f46bda7adc93..64b3550c5cf2 100644 --- a/src/cli/skills-cli.formatting.test.ts +++ b/src/cli/skills-cli.formatting.test.ts @@ -3,8 +3,8 @@ import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { buildWorkspaceSkillStatus } from "../agents/skills-status.js"; -import type { SkillEntry } from "../agents/skills.js"; import { createCanonicalFixtureSkill } from "../agents/skills.test-helpers.js"; +import type { SkillEntry } from "../skills/index.js"; import { captureEnv } from "../test-utils/env.js"; import { formatSkillInfo, formatSkillsCheck, formatSkillsList } from "./skills-cli.format.js"; diff --git a/src/commands/agent-command.test-mocks.ts b/src/commands/agent-command.test-mocks.ts index c14b3a85e3f7..1951f0ae5bfa 100644 --- a/src/commands/agent-command.test-mocks.ts +++ b/src/commands/agent-command.test-mocks.ts @@ -240,21 +240,21 @@ vi.mock("../agents/workspace.js", () => ({ ensureAgentWorkspace: vi.fn(async ({ dir }: { dir: string }) => ({ dir })), })); -vi.mock("../agents/skills.js", () => ({ +vi.mock("../skills/index.js", () => ({ buildWorkspaceSkillSnapshot: vi.fn(() => undefined), loadWorkspaceSkillEntries: vi.fn(() => []), })); -vi.mock("../agents/skills/refresh.js", () => ({ +vi.mock("../skills/refresh.js", () => ({ getSkillsSnapshotVersion: vi.fn(() => 0), })); -vi.mock("../agents/skills/refresh-state.js", () => ({ +vi.mock("../skills/refresh-state.js", () => ({ getSkillsSnapshotVersion: vi.fn(() => 0), shouldRefreshSnapshotForVersion: vi.fn(() => false), })); -vi.mock("../agents/skills/filter.js", () => ({ +vi.mock("../skills/filter.js", () => ({ normalizeSkillFilter: vi.fn((skillFilter?: ReadonlyArray) => skillFilter ? normalizeStringEntries(skillFilter) : undefined, ), diff --git a/src/commands/doctor-session-snapshots.ts b/src/commands/doctor-session-snapshots.ts index 9cbd959d7433..122a737e3e81 100644 --- a/src/commands/doctor-session-snapshots.ts +++ b/src/commands/doctor-session-snapshots.ts @@ -1,6 +1,5 @@ import fs from "node:fs"; import path from "node:path"; -import { resolveBundledSkillsDir } from "../agents/skills/bundled-dir.js"; import { resolveStateDir } from "../config/paths.js"; import { hydrateSessionStoreSkillPromptRefs } from "../config/sessions/skill-prompt-blobs.js"; import { resolveAllAgentSessionStoreTargetsSync } from "../config/sessions/targets.js"; @@ -8,6 +7,7 @@ import type { SessionEntry } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { expandHomePrefix } from "../infra/home-dir.js"; import { isRecord } from "../shared/record-coerce.js"; +import { resolveBundledSkillsDir } from "../skills/bundled-dir.js"; import { note } from "../terminal/note.js"; import { shortenHomePath } from "../utils.js"; diff --git a/src/commands/doctor-skills.test.ts b/src/commands/doctor-skills.test.ts index 2be5f606e6cf..628b7d97b436 100644 --- a/src/commands/doctor-skills.test.ts +++ b/src/commands/doctor-skills.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from "vitest"; import type { SkillStatusEntry, SkillStatusReport } from "../agents/skills-status.js"; -import type { GhConfigDiscoveryInput } from "../agents/skills/gh-config-discovery.js"; import { createEmptyInstallChecks } from "../cli/requirements-test-fixtures.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import type { GhConfigDiscoveryInput } from "../skills/gh-config-discovery.js"; import { collectUnavailableAgentSkills, describeGhConfigDirHintFromDiscovery, diff --git a/src/commands/doctor-skills.ts b/src/commands/doctor-skills.ts index 6fcaa1c7f535..bda85123e19f 100644 --- a/src/commands/doctor-skills.ts +++ b/src/commands/doctor-skills.ts @@ -2,14 +2,14 @@ import { existsSync } from "node:fs"; import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; import type { SkillStatusEntry } from "../agents/skills-status.js"; import { buildWorkspaceSkillStatus } from "../agents/skills-status.js"; +import { formatCliCommand } from "../cli/command-format.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; import { detectGhConfigDirMismatch, formatGhConfigDirMismatchHint, type GhConfigDiscoveryInput, type GhConfigDiscoveryResult, -} from "../agents/skills/gh-config-discovery.js"; -import { formatCliCommand } from "../cli/command-format.js"; -import type { OpenClawConfig } from "../config/types.openclaw.js"; +} from "../skills/gh-config-discovery.js"; import { note } from "../terminal/note.js"; import type { DoctorPrompter } from "./doctor-prompter.js"; import { diff --git a/src/config/sessions/store.skills-stripping.test.ts b/src/config/sessions/store.skills-stripping.test.ts index b36886289730..66eb31170a7d 100644 --- a/src/config/sessions/store.skills-stripping.test.ts +++ b/src/config/sessions/store.skills-stripping.test.ts @@ -4,11 +4,11 @@ import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { resolveEmbeddedRunSkillEntries } from "../../agents/embedded-agent-runner/skills-runtime.js"; import { createCanonicalFixtureSkill } from "../../agents/skills.test-helpers.js"; -import type { Skill } from "../../agents/skills/skill-contract.js"; +import type { Skill } from "../../skills/skill-contract.js"; import { hydrateResolvedSkills, hydrateResolvedSkillsAsync, -} from "../../agents/skills/snapshot-hydration.js"; +} from "../../skills/snapshot-hydration.js"; import { createSuiteTempRootTracker } from "../../test-helpers/temp-dir.js"; import type { SessionEntry, SessionSkillPromptRef, SessionSkillSnapshot } from "./types.js"; diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index 1ebddf58f4d3..c92beae551c5 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -1,9 +1,9 @@ import crypto from "node:crypto"; -import type { Skill } from "../../agents/skills/skill-contract.js"; import type { ChatType } from "../../channels/chat-type.js"; import type { ChannelId } from "../../channels/plugins/channel-id.types.js"; import type { ChannelRouteRef } from "../../plugin-sdk/channel-route.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import type { Skill } from "../../skills/skill-contract.js"; import type { DeliveryContext } from "../../utils/delivery-context.types.js"; import type { TtsAutoMode } from "../types.tts.js"; diff --git a/src/cron/isolated-agent/run-executor.ts b/src/cron/isolated-agent/run-executor.ts index 9daeb4648646..7ba2c7bb8501 100644 --- a/src/cron/isolated-agent/run-executor.ts +++ b/src/cron/isolated-agent/run-executor.ts @@ -1,12 +1,12 @@ import { createHash } from "node:crypto"; import type { BootstrapContextMode } from "../../agents/bootstrap-files.js"; import { resolveCliRuntimeExecutionProvider } from "../../agents/model-runtime-aliases.js"; -import type { SkillSnapshot } from "../../agents/skills.js"; import type { ThinkLevel, VerboseLevel } from "../../auto-reply/thinking.js"; import type { AgentDefaultsConfig } from "../../config/types.agent-defaults.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import type { SourceDeliveryPlan } from "../../infra/outbound/source-delivery-plan.js"; import { createLazyImportLoader } from "../../shared/lazy-promise.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import type { CronAgentExecutionPhaseUpdate, CronJob } from "../types.js"; import { resolveCronChannelOutputPolicy, diff --git a/src/cron/isolated-agent/run-session-state.ts b/src/cron/isolated-agent/run-session-state.ts index a3c16abc1ed8..b84a68e553a1 100644 --- a/src/cron/isolated-agent/run-session-state.ts +++ b/src/cron/isolated-agent/run-session-state.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import type { LiveSessionModelSelection } from "../../agents/live-model-switch.js"; -import type { SkillSnapshot } from "../../agents/skills.js"; import type { SessionEntry } from "../../config/sessions.js"; import { isCronSessionKey } from "../../sessions/session-key-utils.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import type { resolveCronSession } from "./session.js"; type MutableSessionStore = Record; diff --git a/src/cron/isolated-agent/run.message-tool-policy.test.ts b/src/cron/isolated-agent/run.message-tool-policy.test.ts index 44cfb23ea9ee..f7d7705083d0 100644 --- a/src/cron/isolated-agent/run.message-tool-policy.test.ts +++ b/src/cron/isolated-agent/run.message-tool-policy.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import type { SkillSnapshot } from "../../agents/skills.js"; import { createSourceDeliveryPlan } from "../../infra/outbound/source-delivery-plan.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import type { CronDeliveryMode } from "../types.js"; import type { MutableCronSession } from "./run-session-state.js"; import { diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index b36665027764..2277011066d7 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -2,7 +2,6 @@ import { retireSessionMcpRuntime } from "../../agents/agent-bundle-mcp-tools.js" import { hasAnyAuthProfileStoreSource } from "../../agents/auth-profiles/source-check.js"; import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js"; import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-codex-routing.js"; -import type { SkillSnapshot } from "../../agents/skills.js"; import { expandToolGroups, normalizeToolName } from "../../agents/tool-policy.js"; import type { ThinkLevel } from "../../auto-reply/thinking.js"; import type { CliDeps } from "../../cli/outbound-send-deps.js"; @@ -27,6 +26,7 @@ import { isCommandLaneTaskTimeoutError } from "../../process/command-queue.js"; import { CommandLane } from "../../process/lanes.js"; import { createLazyImportLoader } from "../../shared/lazy-promise.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import type { SkillSnapshot } from "../../skills/index.js"; import { hasExplicitCronDeliveryTarget, resolveCronDeliveryPlan, diff --git a/src/cron/isolated-agent/skills-snapshot.runtime.ts b/src/cron/isolated-agent/skills-snapshot.runtime.ts index 8ba8be30ab02..673c5771102a 100644 --- a/src/cron/isolated-agent/skills-snapshot.runtime.ts +++ b/src/cron/isolated-agent/skills-snapshot.runtime.ts @@ -1,5 +1,5 @@ export { canExecRequestNode } from "../../agents/exec-defaults.js"; export { resolveAgentSkillsFilter } from "../../agents/agent-scope.js"; -export { buildWorkspaceSkillSnapshot } from "../../agents/skills.js"; -export { getSkillsSnapshotVersion } from "../../agents/skills/refresh-state.js"; +export { buildWorkspaceSkillSnapshot } from "../../skills/index.js"; +export { getSkillsSnapshotVersion } from "../../skills/refresh-state.js"; export { getRemoteSkillEligibility } from "../../infra/skills-remote.js"; diff --git a/src/cron/isolated-agent/skills-snapshot.ts b/src/cron/isolated-agent/skills-snapshot.ts index 7ce63a25b567..c917428bc38d 100644 --- a/src/cron/isolated-agent/skills-snapshot.ts +++ b/src/cron/isolated-agent/skills-snapshot.ts @@ -1,7 +1,7 @@ -import type { SkillSnapshot } from "../../agents/skills.js"; -import { matchesSkillFilter } from "../../agents/skills/filter.js"; import type { OpenClawConfig } from "../../config/types.openclaw.js"; import { createLazyImportLoader } from "../../shared/lazy-promise.js"; +import { matchesSkillFilter } from "../../skills/filter.js"; +import type { SkillSnapshot } from "../../skills/index.js"; const skillsSnapshotRuntimeLoader = createLazyImportLoader( () => import("./skills-snapshot.runtime.js"), diff --git a/src/gateway/config-reload.test.ts b/src/gateway/config-reload.test.ts index 13430b177173..74f33b294c05 100644 --- a/src/gateway/config-reload.test.ts +++ b/src/gateway/config-reload.test.ts @@ -1,9 +1,5 @@ import chokidar from "chokidar"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - getSkillsSnapshotVersion, - resetSkillsRefreshStateForTest, -} from "../agents/skills/refresh-state.js"; import { listChannelPlugins } from "../channels/plugins/index.js"; import type { ChannelPlugin } from "../channels/plugins/types.js"; import type { @@ -17,6 +13,10 @@ import { resetPluginRuntimeStateForTest, setActivePluginRegistry, } from "../plugins/runtime.js"; +import { + getSkillsSnapshotVersion, + resetSkillsRefreshStateForTest, +} from "../skills/refresh-state.js"; import { createTestRegistry } from "../test-utils/channel-plugins.js"; import { buildGatewayReloadPlan, diff --git a/src/gateway/config-reload.ts b/src/gateway/config-reload.ts index e844bed9d827..d6a00d6e1fb2 100644 --- a/src/gateway/config-reload.ts +++ b/src/gateway/config-reload.ts @@ -1,5 +1,4 @@ import chokidar from "chokidar"; -import { bumpSkillsSnapshotVersion } from "../agents/skills/refresh-state.js"; import type { ConfigWriteNotification } from "../config/io.js"; import { formatConfigIssueLines } from "../config/issue-format.js"; import { resolveConfigWriteFollowUp } from "../config/runtime-snapshot.js"; @@ -9,6 +8,7 @@ import { loadInstalledPluginIndexInstallRecords, loadInstalledPluginIndexInstallRecordsSync, } from "../plugins/installed-plugin-index-records.js"; +import { bumpSkillsSnapshotVersion } from "../skills/refresh-state.js"; import { diffConfigPaths } from "./config-diff.js"; import { buildGatewayReloadPlan, diff --git a/src/gateway/server-methods/skills.ts b/src/gateway/server-methods/skills.ts index 946aa0d2bf4b..19b306140f1a 100644 --- a/src/gateway/server-methods/skills.ts +++ b/src/gateway/server-methods/skills.ts @@ -25,7 +25,6 @@ import { } from "../../agents/skills-clawhub.js"; import { installSkill } from "../../agents/skills-install.js"; import { buildWorkspaceSkillStatus } from "../../agents/skills-status.js"; -import { loadWorkspaceSkillEntries, type SkillEntry } from "../../agents/skills.js"; import { listAgentWorkspaceDirs } from "../../agents/workspace-dirs.js"; import { redactConfigObject } from "../../config/redact-snapshot.js"; import { @@ -38,6 +37,7 @@ import { formatErrorMessage } from "../../infra/errors.js"; import { getRemoteSkillEligibility } from "../../infra/skills-remote.js"; import { normalizeAgentId } from "../../routing/session-key.js"; import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { loadWorkspaceSkillEntries, type SkillEntry } from "../../skills/index.js"; import { updateSkillConfigEntry } from "./skills-config-mutations.js"; import { installUploadedSkillArchive, skillsUploadHandlers } from "./skills-upload.js"; import type { GatewayRequestContext, GatewayRequestHandlers } from "./types.js"; diff --git a/src/gateway/server-startup-early.ts b/src/gateway/server-startup-early.ts index 800eec2ede6d..bd96ca8bdd05 100644 --- a/src/gateway/server-startup-early.ts +++ b/src/gateway/server-startup-early.ts @@ -130,10 +130,7 @@ export async function startGatewayEarlyRuntime(params: { ? () => {} : await measureStartup(params.startupTrace, "runtime.early.skills-listener", async () => { const [{ registerSkillsChangeListener }, { refreshRemoteBinsForConnectedNodes }] = - await Promise.all([ - import("../agents/skills/refresh.js"), - import("../infra/skills-remote.js"), - ]); + await Promise.all([import("../skills/refresh.js"), import("../infra/skills-remote.js")]); return registerSkillsChangeListener((event) => { if (event.reason === "remote-node") { return; diff --git a/src/hooks/gmail-setup-utils.ts b/src/hooks/gmail-setup-utils.ts index cf665662470d..6bda6a8796d5 100644 --- a/src/hooks/gmail-setup-utils.ts +++ b/src/hooks/gmail-setup-utils.ts @@ -1,9 +1,9 @@ import fs from "node:fs"; import path from "node:path"; -import { hasBinary } from "../agents/skills.js"; import { formatErrorMessage } from "../infra/errors.js"; import { resolveExecutable } from "../infra/executable-path.js"; import { runCommandWithTimeout, type SpawnResult } from "../process/exec.js"; +import { hasBinary } from "../skills/index.js"; import { resolveUserPath } from "../utils.js"; import { normalizeServePath } from "./gmail.js"; diff --git a/src/hooks/gmail-watcher.test.ts b/src/hooks/gmail-watcher.test.ts index ca9a2fd11c80..18651341c0ae 100644 --- a/src/hooks/gmail-watcher.test.ts +++ b/src/hooks/gmail-watcher.test.ts @@ -16,7 +16,7 @@ vi.mock("node:child_process", async () => { ); }); -vi.mock("../agents/skills.js", () => ({ +vi.mock("../skills/index.js", () => ({ hasBinary: mocks.hasBinary, })); diff --git a/src/hooks/gmail-watcher.ts b/src/hooks/gmail-watcher.ts index 8994d747a398..90164069e98b 100644 --- a/src/hooks/gmail-watcher.ts +++ b/src/hooks/gmail-watcher.ts @@ -6,10 +6,10 @@ */ import { type ChildProcess, spawn } from "node:child_process"; -import { hasBinary } from "../agents/skills.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { runCommandWithTimeout } from "../process/exec.js"; +import { hasBinary } from "../skills/index.js"; import { ensureTailscaleEndpoint } from "./gmail-setup-utils.js"; import { isAddressInUseError } from "./gmail-watcher-errors.js"; import { diff --git a/src/infra/skills-remote.test.ts b/src/infra/skills-remote.test.ts index bc8ba2c40f00..e154ff176e91 100644 --- a/src/infra/skills-remote.test.ts +++ b/src/infra/skills-remote.test.ts @@ -3,9 +3,9 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { getSkillsSnapshotVersion, resetSkillsRefreshForTest } from "../agents/skills/refresh.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { NodeRegistry } from "../gateway/node-registry.js"; +import { getSkillsSnapshotVersion, resetSkillsRefreshForTest } from "../skills/refresh.js"; import { getRemoteSkillEligibility, recordRemoteNodeBins, diff --git a/src/infra/skills-remote.ts b/src/infra/skills-remote.ts index 066159b974e1..735ac48dd5bd 100644 --- a/src/infra/skills-remote.ts +++ b/src/infra/skills-remote.ts @@ -1,6 +1,3 @@ -import { bumpSkillsSnapshotVersion } from "../agents/skills/refresh-state.js"; -import type { SkillEligibilityContext, SkillEntry } from "../agents/skills/types.js"; -import { loadWorkspaceSkillEntries } from "../agents/skills/workspace.js"; import { listAgentWorkspaceDirs } from "../agents/workspace-dirs.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import type { NodeRegistry } from "../gateway/node-registry.js"; @@ -10,6 +7,9 @@ import { normalizeOptionalString, } from "../shared/string-coerce.js"; import { normalizeStringEntries } from "../shared/string-normalization.js"; +import { bumpSkillsSnapshotVersion } from "../skills/refresh-state.js"; +import type { SkillEligibilityContext, SkillEntry } from "../skills/types.js"; +import { loadWorkspaceSkillEntries } from "../skills/workspace.js"; import { listNodePairing, updatePairedNodeMetadata } from "./node-pairing.js"; type RemoteNodeRecord = { diff --git a/src/plugin-sdk/command-auth.ts b/src/plugin-sdk/command-auth.ts index ec7fb4ac61a0..a931171ac2d3 100644 --- a/src/plugin-sdk/command-auth.ts +++ b/src/plugin-sdk/command-auth.ts @@ -99,7 +99,7 @@ export { resolveSkillCommandInvocation, } from "../auto-reply/skill-commands.js"; export { getPluginCommandSpecs, listProviderPluginCommandSpecs } from "../plugins/command-specs.js"; -export type { SkillCommandSpec } from "../agents/skills.js"; +export type { SkillCommandSpec } from "../skills/index.js"; export { buildModelsProviderData, formatModelsAvailableHeader, diff --git a/src/plugin-sdk/skills-runtime.ts b/src/plugin-sdk/skills-runtime.ts index aa83554ce928..17a9c982fce1 100644 --- a/src/plugin-sdk/skills-runtime.ts +++ b/src/plugin-sdk/skills-runtime.ts @@ -4,4 +4,4 @@ export { registerSkillsChangeListener, shouldRefreshSnapshotForVersion, type SkillsChangeEvent, -} from "../agents/skills/refresh-state.js"; +} from "../skills/refresh-state.js"; diff --git a/src/security/audit-extra.async.test.ts b/src/security/audit-extra.async.test.ts index 362e31b7cc78..a6cc3ed6321d 100644 --- a/src/security/audit-extra.async.test.ts +++ b/src/security/audit-extra.async.test.ts @@ -9,7 +9,7 @@ import { } from "./audit-extra.async.js"; import * as skillScanner from "./skill-scanner.js"; -vi.mock("../agents/skills.js", () => ({ +vi.mock("../skills/index.js", () => ({ loadWorkspaceSkillEntries: (workspaceDir: string) => { const sep = workspaceDir.includes("\\") ? "\\" : "/"; const baseDir = `${workspaceDir}${sep}skills${sep}evil-skill`; diff --git a/src/security/audit-extra.async.ts b/src/security/audit-extra.async.ts index 3fde222ea716..c3766056f623 100644 --- a/src/security/audit-extra.async.ts +++ b/src/security/audit-extra.async.ts @@ -47,13 +47,13 @@ type ExecDockerRawFn = ( const DEFAULT_SANDBOX_BROWSER_DOCKER_PROBE_TIMEOUT_MS = 5000; type CodeSafetySummaryCache = Map>; -let skillsModulePromise: Promise | undefined; +let skillsModulePromise: Promise | undefined; let configModulePromise: Promise | undefined; let agentScopeModulePromise: Promise | undefined; let agentWorkspaceDirsModulePromise: | Promise | undefined; -let skillSourceModulePromise: Promise | undefined; +let skillSourceModulePromise: Promise | undefined; let sandboxDockerModulePromise: Promise | undefined; let sandboxConstantsModulePromise: | Promise @@ -63,7 +63,7 @@ let auditFsModulePromise: Promise | undefined; let skillScannerModulePromise: Promise | undefined; function loadSkillsModule() { - skillsModulePromise ??= import("../agents/skills.js"); + skillsModulePromise ??= import("../skills/index.js"); return skillsModulePromise; } @@ -88,7 +88,7 @@ function loadAgentWorkspaceDirsModule() { } function loadSkillSourceModule() { - skillSourceModulePromise ??= import("../agents/skills/source.js"); + skillSourceModulePromise ??= import("../skills/source.js"); return skillSourceModulePromise; } diff --git a/src/agents/skills/agent-filter.ts b/src/skills/agent-filter.ts similarity index 92% rename from src/agents/skills/agent-filter.ts rename to src/skills/agent-filter.ts index dfb3a1c2b015..871711683762 100644 --- a/src/agents/skills/agent-filter.ts +++ b/src/skills/agent-filter.ts @@ -1,5 +1,5 @@ -import type { OpenClawConfig } from "../../config/types.js"; -import { normalizeAgentId } from "../../routing/session-key.js"; +import type { OpenClawConfig } from "../config/types.js"; +import { normalizeAgentId } from "../routing/session-key.js"; import { normalizeSkillFilter } from "./filter.js"; type AgentSkillsLimits = { diff --git a/src/agents/skills/bundled-context.ts b/src/skills/bundled-context.ts similarity index 94% rename from src/agents/skills/bundled-context.ts rename to src/skills/bundled-context.ts index 027930fb4c36..dbd3aec9d653 100644 --- a/src/agents/skills/bundled-context.ts +++ b/src/skills/bundled-context.ts @@ -1,4 +1,4 @@ -import { createSubsystemLogger } from "../../logging/subsystem.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveBundledSkillsDir, type BundledSkillsResolveOptions } from "./bundled-dir.js"; import { loadSkillsFromDirSafe } from "./local-loader.js"; diff --git a/src/agents/skills/bundled-dir.test.ts b/src/skills/bundled-dir.test.ts similarity index 97% rename from src/agents/skills/bundled-dir.test.ts rename to src/skills/bundled-dir.test.ts index 2204e04b1779..594419a0030d 100644 --- a/src/agents/skills/bundled-dir.test.ts +++ b/src/skills/bundled-dir.test.ts @@ -3,8 +3,8 @@ import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { captureEnv } from "../../test-utils/env.js"; import { writeSkill } from "../skills.e2e-test-helpers.js"; +import { captureEnv } from "../test-utils/env.js"; import { resolveBundledSkillsDir } from "./bundled-dir.js"; describe("resolveBundledSkillsDir", () => { diff --git a/src/agents/skills/bundled-dir.ts b/src/skills/bundled-dir.ts similarity index 96% rename from src/agents/skills/bundled-dir.ts rename to src/skills/bundled-dir.ts index 22da4fa46eed..a034ce3b782e 100644 --- a/src/agents/skills/bundled-dir.ts +++ b/src/skills/bundled-dir.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; -import { resolveOpenClawPackageRootSync } from "../../infra/openclaw-root.js"; +import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js"; function looksLikeSkillsDir(dir: string): boolean { try { diff --git a/src/agents/skills/command-specs.ts b/src/skills/command-specs.ts similarity index 96% rename from src/agents/skills/command-specs.ts rename to src/skills/command-specs.ts index 3069a132023e..91086eff4acc 100644 --- a/src/agents/skills/command-specs.ts +++ b/src/skills/command-specs.ts @@ -1,10 +1,10 @@ -import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { createSubsystemLogger } from "../../logging/subsystem.js"; -import { loadEnabledClaudeBundleCommands } from "../../plugins/bundle-commands.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; +import { loadEnabledClaudeBundleCommands } from "../plugins/bundle-commands.js"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, -} from "../../shared/string-coerce.js"; +} from "../shared/string-coerce.js"; import { resolveEffectiveAgentSkillFilter } from "./agent-filter.js"; import { resolveSkillTelemetrySource } from "./source.js"; import type { SkillEligibilityContext, SkillCommandSpec, SkillEntry } from "./types.js"; diff --git a/src/agents/skills/compact-format.test.ts b/src/skills/compact-format.test.ts similarity index 99% rename from src/agents/skills/compact-format.test.ts rename to src/skills/compact-format.test.ts index f8411b7cf142..3253c16d30ef 100644 --- a/src/agents/skills/compact-format.test.ts +++ b/src/skills/compact-format.test.ts @@ -1,7 +1,7 @@ import os from "node:os"; import { formatSkillsForPrompt as upstreamFormatSkillsForPrompt } from "openclaw/plugin-sdk/agent-sessions"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import type { OpenClawConfig } from "../../config/config.js"; +import type { OpenClawConfig } from "../config/config.js"; import { createCanonicalFixtureSkill } from "../skills.test-helpers.js"; import { restoreMockSkillsHomeEnv, diff --git a/src/agents/skills/config.ts b/src/skills/config.ts similarity index 92% rename from src/agents/skills/config.ts rename to src/skills/config.ts index ab7a8d7ef935..8e4ed3de649b 100644 --- a/src/agents/skills/config.ts +++ b/src/skills/config.ts @@ -1,13 +1,13 @@ -import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import type { SkillConfig } from "../../config/types.skills.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import type { SkillConfig } from "../config/types.skills.js"; import { evaluateRuntimeEligibility, hasBinary, isConfigPathTruthyWithDefaults, resolveConfigPath, resolveRuntimePlatform, -} from "../../shared/config-eval.js"; -import { normalizeStringEntries } from "../../shared/string-normalization.js"; +} from "../shared/config-eval.js"; +import { normalizeStringEntries } from "../shared/string-normalization.js"; import { resolveSkillKey } from "./frontmatter.js"; import { resolveSkillSource } from "./source.js"; import type { SkillEligibilityContext, SkillEntry } from "./types.js"; diff --git a/src/agents/skills/env-overrides.runtime.ts b/src/skills/env-overrides.runtime.ts similarity index 100% rename from src/agents/skills/env-overrides.runtime.ts rename to src/skills/env-overrides.runtime.ts diff --git a/src/agents/skills/env-overrides.ts b/src/skills/env-overrides.ts similarity index 95% rename from src/agents/skills/env-overrides.ts rename to src/skills/env-overrides.ts index 5f164079e32b..239995451515 100644 --- a/src/agents/skills/env-overrides.ts +++ b/src/skills/env-overrides.ts @@ -1,11 +1,11 @@ -import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { normalizeResolvedSecretInputString } from "../../config/types.secrets.js"; +import { sanitizeEnvVars, validateEnvVarValue } from "../agents/sandbox/sanitize-env-vars.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { normalizeResolvedSecretInputString } from "../config/types.secrets.js"; import { isDangerousHostEnvOverrideVarName, isDangerousHostEnvVarName, -} from "../../infra/host-env-security.js"; -import { createSubsystemLogger } from "../../logging/subsystem.js"; -import { sanitizeEnvVars, validateEnvVarValue } from "../sandbox/sanitize-env-vars.js"; +} from "../infra/host-env-security.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; import { resolveSkillConfig } from "./config.js"; import { resolveSkillKey } from "./frontmatter.js"; import { resolveSkillRuntimeConfig } from "./runtime-config.js"; diff --git a/src/agents/skills/filter.test.ts b/src/skills/filter.test.ts similarity index 100% rename from src/agents/skills/filter.test.ts rename to src/skills/filter.test.ts diff --git a/src/agents/skills/filter.ts b/src/skills/filter.ts similarity index 91% rename from src/agents/skills/filter.ts rename to src/skills/filter.ts index af912934a562..31f86c2117f3 100644 --- a/src/agents/skills/filter.ts +++ b/src/skills/filter.ts @@ -1,4 +1,4 @@ -import { normalizeStringEntries, sortUniqueStrings } from "../../shared/string-normalization.js"; +import { normalizeStringEntries, sortUniqueStrings } from "../shared/string-normalization.js"; export function normalizeSkillFilter(skillFilter?: ReadonlyArray): string[] | undefined { if (skillFilter === undefined) { diff --git a/src/agents/skills/frontmatter.test.ts b/src/skills/frontmatter.test.ts similarity index 100% rename from src/agents/skills/frontmatter.test.ts rename to src/skills/frontmatter.test.ts diff --git a/src/agents/skills/frontmatter.ts b/src/skills/frontmatter.ts similarity index 96% rename from src/agents/skills/frontmatter.ts rename to src/skills/frontmatter.ts index 277bdcc36549..c30eb991c260 100644 --- a/src/agents/skills/frontmatter.ts +++ b/src/skills/frontmatter.ts @@ -1,5 +1,5 @@ -import { validateRegistryNpmSpec } from "../../infra/npm-registry-spec.js"; -import { parseFrontmatterBlock } from "../../markdown/frontmatter.js"; +import { validateRegistryNpmSpec } from "../infra/npm-registry-spec.js"; +import { parseFrontmatterBlock } from "../markdown/frontmatter.js"; import { applyOpenClawManifestInstallCommonFields, getFrontmatterString, @@ -10,8 +10,8 @@ import { resolveOpenClawManifestInstall, resolveOpenClawManifestOs, resolveOpenClawManifestRequires, -} from "../../shared/frontmatter.js"; -import { readStringValue } from "../../shared/string-coerce.js"; +} from "../shared/frontmatter.js"; +import { readStringValue } from "../shared/string-coerce.js"; import type { Skill } from "./skill-contract.js"; import type { OpenClawSkillMetadata, diff --git a/src/agents/skills/gh-config-discovery.test.ts b/src/skills/gh-config-discovery.test.ts similarity index 100% rename from src/agents/skills/gh-config-discovery.test.ts rename to src/skills/gh-config-discovery.test.ts diff --git a/src/agents/skills/gh-config-discovery.ts b/src/skills/gh-config-discovery.ts similarity index 100% rename from src/agents/skills/gh-config-discovery.ts rename to src/skills/gh-config-discovery.ts diff --git a/src/agents/skills/home-env.test-support.ts b/src/skills/home-env.test-support.ts similarity index 100% rename from src/agents/skills/home-env.test-support.ts rename to src/skills/home-env.test-support.ts diff --git a/src/skills/index.ts b/src/skills/index.ts new file mode 100644 index 000000000000..41128e83105d --- /dev/null +++ b/src/skills/index.ts @@ -0,0 +1,64 @@ +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "../shared/string-coerce.js"; +import type { SkillsInstallPreferences } from "./types.js"; + +export { + hasBinary, + isBundledSkillAllowed, + isConfigPathTruthy, + resolveBundledAllowlist, + resolveConfigPath, + resolveRuntimePlatform, + resolveSkillConfig, +} from "./config.js"; +export { applySkillEnvOverrides, applySkillEnvOverridesFromSnapshot } from "./env-overrides.js"; +export type { + OpenClawSkillMetadata, + SkillEligibilityContext, + SkillCommandSpec, + SkillEntry, + SkillInstallSpec, + SkillSnapshot, + SkillTelemetrySource, + SkillsInstallPreferences, +} from "./types.js"; +export { + buildWorkspaceSkillsPrompt, + filterWorkspaceSkillEntries, + filterWorkspaceSkillEntriesWithOptions, + loadWorkspaceSkillEntries, + resolveSkillsPromptForRun, + syncSkillsToWorkspace, +} from "./workspace.js"; +export { buildWorkspaceSkillCommandSpecs } from "./command-specs.js"; +export type { SkillIndex, SkillIndexEntry } from "./registry.js"; +export { buildSkillIndex, skillIndexEntries, skillIndexResolvedSkills } from "./registry.js"; +export type { SkillSourceKind, SkillTrustInfo, SkillWritablePolicy } from "./trust.js"; +export { + classifySkillSourceKind, + resolveSkillOwner, + resolveSkillTrustInfo, + resolveSkillWritablePolicy, +} from "./trust.js"; +export type { SkillIndexRequest, SkillSnapshotBuildOptions } from "./service.js"; +export { + SkillsService, + buildSkillIndexCacheKey, + buildSkillSnapshotFromIndex, + buildWorkspaceSkillSnapshot, + skillsService, +} from "./service.js"; + +export function resolveSkillsInstallPreferences(config?: OpenClawConfig): SkillsInstallPreferences { + const raw = config?.skills?.install; + const preferBrew = raw?.preferBrew ?? true; + const manager = normalizeLowercaseStringOrEmpty(normalizeOptionalString(raw?.nodeManager)); + const nodeManager: SkillsInstallPreferences["nodeManager"] = + manager === "pnpm" || manager === "yarn" || manager === "bun" || manager === "npm" + ? manager + : "npm"; + return { preferBrew, nodeManager }; +} diff --git a/src/agents/skills/local-loader.ts b/src/skills/local-loader.ts similarity index 98% rename from src/agents/skills/local-loader.ts rename to src/skills/local-loader.ts index 7993f5071a82..4966ef1628ab 100644 --- a/src/agents/skills/local-loader.ts +++ b/src/skills/local-loader.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import { openRootFileSync } from "../../infra/boundary-file-read.js"; +import { openRootFileSync } from "../infra/boundary-file-read.js"; import { parseFrontmatter, resolveSkillInvocationPolicy } from "./frontmatter.js"; import { createSyntheticSourceInfo, type Skill } from "./skill-contract.js"; import type { ParsedSkillFrontmatter } from "./types.js"; diff --git a/src/agents/skills/plugin-skills.test.ts b/src/skills/plugin-skills.test.ts similarity index 98% rename from src/agents/skills/plugin-skills.test.ts rename to src/skills/plugin-skills.test.ts index f7533faffae6..860ed9004fc7 100644 --- a/src/agents/skills/plugin-skills.test.ts +++ b/src/skills/plugin-skills.test.ts @@ -5,10 +5,10 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vite import { testing as acpRuntimeTesting, registerAcpRuntimeBackend, -} from "../../acp/runtime/registry.js"; -import type { OpenClawConfig } from "../../config/config.js"; -import type { PluginManifestRegistry } from "../../plugins/manifest-registry.js"; -import { createTrackedTempDirs } from "../../test-utils/tracked-temp-dirs.js"; +} from "../acp/runtime/registry.js"; +import type { OpenClawConfig } from "../config/config.js"; +import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; +import { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js"; import { testing } from "./plugin-skills.js"; const hoisted = vi.hoisted(() => { @@ -32,16 +32,16 @@ const hoisted = vi.hoisted(() => { }; }); -vi.mock("../../plugins/manifest-registry-installed.js", () => ({ +vi.mock("../plugins/manifest-registry-installed.js", () => ({ loadPluginManifestRegistryForInstalledIndex: hoisted.loadPluginManifestRegistryForInstalledIndex, })); -vi.mock("../../plugins/plugin-registry.js", () => ({ +vi.mock("../plugins/plugin-registry.js", () => ({ loadPluginManifestRegistryForPluginRegistry: hoisted.loadPluginManifestRegistryForPluginRegistry, loadPluginRegistrySnapshot: hoisted.loadPluginRegistrySnapshot, })); -vi.mock("../../plugins/plugin-metadata-snapshot.js", () => ({ +vi.mock("../plugins/plugin-metadata-snapshot.js", () => ({ loadPluginMetadataSnapshot: hoisted.loadPluginMetadataSnapshot, resolvePluginMetadataSnapshot: hoisted.loadPluginMetadataSnapshot, })); diff --git a/src/agents/skills/plugin-skills.ts b/src/skills/plugin-skills.ts similarity index 94% rename from src/agents/skills/plugin-skills.ts rename to src/skills/plugin-skills.ts index dd9e74dd8e7e..7896d293b71d 100644 --- a/src/agents/skills/plugin-skills.ts +++ b/src/skills/plugin-skills.ts @@ -1,18 +1,18 @@ import fs from "node:fs"; import path from "node:path"; -import { isAcpRuntimeSpawnAvailable } from "../../acp/runtime/availability.js"; -import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { walkDirectorySync } from "../../infra/fs-safe.js"; -import { createSubsystemLogger } from "../../logging/subsystem.js"; +import { isAcpRuntimeSpawnAvailable } from "../acp/runtime/availability.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { walkDirectorySync } from "../infra/fs-safe.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; import { normalizePluginsConfigWithResolver, resolveEffectivePluginActivationState, resolveMemorySlotDecision, -} from "../../plugins/config-policy.js"; -import { resolvePluginMetadataSnapshot } from "../../plugins/plugin-metadata-snapshot.js"; -import { hasKind } from "../../plugins/slots.js"; -import { isPathInsideWithRealpath } from "../../security/scan-paths.js"; -import { CONFIG_DIR } from "../../utils.js"; +} from "../plugins/config-policy.js"; +import { resolvePluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; +import { hasKind } from "../plugins/slots.js"; +import { isPathInsideWithRealpath } from "../security/scan-paths.js"; +import { CONFIG_DIR } from "../utils.js"; const log = createSubsystemLogger("skills"); diff --git a/src/agents/skills/refresh-state.ts b/src/skills/refresh-state.ts similarity index 100% rename from src/agents/skills/refresh-state.ts rename to src/skills/refresh-state.ts diff --git a/src/agents/skills/refresh.test.ts b/src/skills/refresh.test.ts similarity index 100% rename from src/agents/skills/refresh.test.ts rename to src/skills/refresh.test.ts diff --git a/src/agents/skills/refresh.ts b/src/skills/refresh.ts similarity index 98% rename from src/agents/skills/refresh.ts rename to src/skills/refresh.ts index 33a9f1b94000..1d570fb09134 100644 --- a/src/agents/skills/refresh.ts +++ b/src/skills/refresh.ts @@ -2,10 +2,10 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import chokidar, { type FSWatcher } from "chokidar"; -import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { createSubsystemLogger } from "../../logging/subsystem.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; -import { CONFIG_DIR, resolveUserPath } from "../../utils.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { CONFIG_DIR, resolveUserPath } from "../utils.js"; import { resolvePluginSkillDirs } from "./plugin-skills.js"; import { bumpSkillsSnapshotVersion, diff --git a/src/skills/registry.ts b/src/skills/registry.ts new file mode 100644 index 000000000000..f111bf19ab4c --- /dev/null +++ b/src/skills/registry.ts @@ -0,0 +1,94 @@ +import type { Skill } from "./skill-contract.js"; +import { resolveSkillTrustInfo, type SkillSourceKind } from "./trust.js"; +import type { SkillEntry } from "./types.js"; + +export type SkillIndexEntry = { + id: string; + name: string; + description: string; + path: string; + baseDir: string; + sourceLabel: string; + sourceKind: SkillSourceKind; + owner: string; + writable: boolean; + writableReason: string; + entry: SkillEntry; +}; + +export type SkillIndex = { + cacheKey: string; + builtAt: number; + entries: SkillIndexEntry[]; + byId: ReadonlyMap; + byName: ReadonlyMap; + byPath: ReadonlyMap; +}; + +export function createSkillId(params: { + sourceKind: SkillSourceKind; + sourceLabel: string; + name: string; + path: string; +}): string { + return `${params.sourceKind}:${params.sourceLabel}:${params.name}:${params.path}`; +} + +export function buildSkillIndex(params: { + cacheKey: string; + entries: SkillEntry[]; + builtAt?: number; +}): SkillIndex { + const indexed = params.entries.map((entry) => { + const trust = resolveSkillTrustInfo(entry); + return { + id: createSkillId({ + sourceKind: trust.sourceKind, + sourceLabel: trust.sourceLabel, + name: entry.skill.name, + path: entry.skill.filePath, + }), + name: entry.skill.name, + description: entry.skill.description, + path: entry.skill.filePath, + baseDir: entry.skill.baseDir, + sourceLabel: trust.sourceLabel, + sourceKind: trust.sourceKind, + owner: trust.owner, + writable: trust.writable, + writableReason: trust.writableReason, + entry, + } satisfies SkillIndexEntry; + }); + + const byId = new Map(); + const byName = new Map(); + const byPath = new Map(); + for (const item of indexed) { + byId.set(item.id, item); + byPath.set(item.path, item); + const named = byName.get(item.name); + if (named) { + named.push(item); + } else { + byName.set(item.name, [item]); + } + } + + return { + cacheKey: params.cacheKey, + builtAt: params.builtAt ?? Date.now(), + entries: indexed, + byId, + byName, + byPath, + }; +} + +export function skillIndexEntries(index: SkillIndex): SkillEntry[] { + return index.entries.map((entry) => entry.entry); +} + +export function skillIndexResolvedSkills(index: SkillIndex): Skill[] { + return index.entries.map((entry) => entry.entry.skill); +} diff --git a/src/agents/skills/runtime-config.ts b/src/skills/runtime-config.ts similarity index 81% rename from src/agents/skills/runtime-config.ts rename to src/skills/runtime-config.ts index ad839fa9418c..8dde193e4f60 100644 --- a/src/agents/skills/runtime-config.ts +++ b/src/skills/runtime-config.ts @@ -1,6 +1,6 @@ -import { getRuntimeConfigSnapshot } from "../../config/runtime-snapshot.js"; -import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { coerceSecretRef } from "../../config/types.secrets.js"; +import { getRuntimeConfigSnapshot } from "../config/runtime-snapshot.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { coerceSecretRef } from "../config/types.secrets.js"; function hasConfiguredSkillApiKeyRef(config?: OpenClawConfig): boolean { const entries = config?.skills?.entries; diff --git a/src/agents/skills/serialize.ts b/src/skills/serialize.ts similarity index 100% rename from src/agents/skills/serialize.ts rename to src/skills/serialize.ts diff --git a/src/skills/service.test.ts b/src/skills/service.test.ts new file mode 100644 index 000000000000..cff4878447ee --- /dev/null +++ b/src/skills/service.test.ts @@ -0,0 +1,98 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { writeWorkspaceSkills } from "../agents/skills.e2e-test-helpers.js"; +import { SkillsService } from "./service.js"; +import { buildWorkspaceSkillSnapshot as buildLegacyWorkspaceSkillSnapshot } from "./workspace.js"; + +const tempDirs: string[] = []; + +async function makeTempWorkspace(): Promise { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-skills-service-")); + tempDirs.push(dir); + return dir; +} + +function isolatedSkillRoots(workspaceDir: string) { + return { + managedSkillsDir: path.join(workspaceDir, ".managed"), + bundledSkillsDir: path.join(workspaceDir, ".bundled"), + pluginSkillsDir: path.join(workspaceDir, ".plugin-skills"), + }; +} + +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); +}); + +describe("SkillsService", () => { + it("builds snapshots from the index without changing prompt output", async () => { + const workspaceDir = await makeTempWorkspace(); + await writeWorkspaceSkills(workspaceDir, [ + { name: "alpha", description: "Alpha workflow" }, + { name: "beta", description: "Beta workflow" }, + ]); + const service = new SkillsService(); + const roots = isolatedSkillRoots(workspaceDir); + + const actual = service.buildSnapshot(workspaceDir, { + ...roots, + snapshotVersion: 42, + }); + const expected = buildLegacyWorkspaceSkillSnapshot(workspaceDir, { + ...roots, + snapshotVersion: 42, + }); + + expect(actual.prompt).toBe(expected.prompt); + expect(actual.skills).toEqual(expected.skills); + expect(actual.resolvedSkills).toEqual(expected.resolvedSkills); + expect(actual.version).toBe(42); + }); + + it("caches the source-aware index until the version key changes", async () => { + const workspaceDir = await makeTempWorkspace(); + await writeWorkspaceSkills(workspaceDir, [ + { name: "service-cache-alpha", description: "Alpha workflow" }, + ]); + const service = new SkillsService(); + const roots = isolatedSkillRoots(workspaceDir); + + const first = service.getIndex({ workspaceDir, ...roots, snapshotVersion: 1 }); + const second = service.getIndex({ workspaceDir, ...roots, snapshotVersion: 1 }); + const third = service.getIndex({ workspaceDir, ...roots, snapshotVersion: 2 }); + + expect(second).toBe(first); + expect(third).not.toBe(first); + expect(first.entries.find((entry) => entry.name === "service-cache-alpha")).toMatchObject({ + name: "service-cache-alpha", + sourceKind: "workspace", + owner: "workspace", + writable: true, + writableReason: "workspace-owned-skill", + }); + }); + + it("keeps legacy snapshot calls uncached unless a version is supplied", async () => { + const workspaceDir = await makeTempWorkspace(); + await writeWorkspaceSkills(workspaceDir, [ + { name: "service-legacy-alpha", description: "Alpha workflow" }, + ]); + const service = new SkillsService(); + const roots = isolatedSkillRoots(workspaceDir); + + const before = service.buildSnapshot(workspaceDir, roots); + await writeWorkspaceSkills(workspaceDir, [ + { name: "service-legacy-beta", description: "Beta workflow" }, + ]); + const after = service.buildSnapshot(workspaceDir, roots); + const beforeNames = before.skills.map((skill) => skill.name); + const afterNames = after.skills.map((skill) => skill.name); + + expect(beforeNames).toContain("service-legacy-alpha"); + expect(beforeNames).not.toContain("service-legacy-beta"); + expect(afterNames).toContain("service-legacy-alpha"); + expect(afterNames).toContain("service-legacy-beta"); + }); +}); diff --git a/src/skills/service.ts b/src/skills/service.ts new file mode 100644 index 000000000000..cecde5027082 --- /dev/null +++ b/src/skills/service.ts @@ -0,0 +1,124 @@ +import crypto from "node:crypto"; +import path from "node:path"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { getSkillsSnapshotVersion } from "./refresh-state.js"; +import { buildSkillIndex, skillIndexEntries, type SkillIndex } from "./registry.js"; +import type { SkillEligibilityContext, SkillSnapshot } from "./types.js"; +import { + buildWorkspaceSkillSnapshot as buildWorkspaceSkillSnapshotFromEntries, + loadWorkspaceSkillEntries, +} from "./workspace.js"; + +export type SkillIndexRequest = { + workspaceDir: string; + config?: OpenClawConfig; + managedSkillsDir?: string; + bundledSkillsDir?: string; + pluginSkillsDir?: string; + snapshotVersion?: number; +}; + +export type SkillSnapshotBuildOptions = { + config?: OpenClawConfig; + managedSkillsDir?: string; + bundledSkillsDir?: string; + pluginSkillsDir?: string; + agentId?: string; + skillFilter?: string[]; + eligibility?: SkillEligibilityContext; + snapshotVersion?: number; +}; + +export class SkillsService { + private readonly cache = new Map(); + + getIndex(request: SkillIndexRequest): SkillIndex { + const cacheKey = buildSkillIndexCacheKey(request); + const cached = this.cache.get(cacheKey); + if (cached) { + return cached; + } + const index = this.loadIndex(request, cacheKey); + this.cache.set(cacheKey, index); + return index; + } + + private loadIndex(request: SkillIndexRequest, cacheKey: string): SkillIndex { + const entries = loadWorkspaceSkillEntries(request.workspaceDir, { + config: request.config, + managedSkillsDir: request.managedSkillsDir, + bundledSkillsDir: request.bundledSkillsDir, + pluginSkillsDir: request.pluginSkillsDir, + }); + return buildSkillIndex({ cacheKey, entries }); + } + + buildSnapshot(workspaceDir: string, opts?: SkillSnapshotBuildOptions): SkillSnapshot { + const request = { + workspaceDir, + config: opts?.config, + managedSkillsDir: opts?.managedSkillsDir, + bundledSkillsDir: opts?.bundledSkillsDir, + pluginSkillsDir: opts?.pluginSkillsDir, + snapshotVersion: opts?.snapshotVersion, + }; + const index = + opts?.snapshotVersion === undefined + ? this.loadIndex(request, buildSkillIndexCacheKey(request)) + : this.getIndex(request); + return buildSkillSnapshotFromIndex(workspaceDir, index, opts); + } + + invalidate(): void { + this.cache.clear(); + } +} + +export const skillsService = new SkillsService(); + +export function buildSkillSnapshotFromIndex( + workspaceDir: string, + index: SkillIndex, + opts?: SkillSnapshotBuildOptions, +): SkillSnapshot { + return buildWorkspaceSkillSnapshotFromEntries(workspaceDir, { + entries: skillIndexEntries(index), + config: opts?.config, + agentId: opts?.agentId, + skillFilter: opts?.skillFilter, + eligibility: opts?.eligibility, + snapshotVersion: opts?.snapshotVersion, + }); +} + +export function buildWorkspaceSkillSnapshot( + workspaceDir: string, + opts?: SkillSnapshotBuildOptions, +): SkillSnapshot { + return skillsService.buildSnapshot(workspaceDir, opts); +} + +function stableConfigHash(config?: OpenClawConfig): string { + const skillsConfig = config?.skills ?? {}; + return crypto + .createHash("sha256") + .update(JSON.stringify(skillsConfig)) + .digest("hex") + .slice(0, 16); +} + +function normalizedOptionalPath(value?: string): string { + return value ? path.resolve(value) : ""; +} + +export function buildSkillIndexCacheKey(request: SkillIndexRequest): string { + const snapshotVersion = request.snapshotVersion ?? getSkillsSnapshotVersion(request.workspaceDir); + return JSON.stringify({ + workspaceDir: path.resolve(request.workspaceDir), + managedSkillsDir: normalizedOptionalPath(request.managedSkillsDir), + bundledSkillsDir: normalizedOptionalPath(request.bundledSkillsDir), + pluginSkillsDir: normalizedOptionalPath(request.pluginSkillsDir), + skillsConfig: stableConfigHash(request.config), + snapshotVersion, + }); +} diff --git a/src/agents/skills/skill-contract.ts b/src/skills/skill-contract.ts similarity index 93% rename from src/agents/skills/skill-contract.ts rename to src/skills/skill-contract.ts index 1718ab50e3cc..0f0ee545622b 100644 --- a/src/agents/skills/skill-contract.ts +++ b/src/skills/skill-contract.ts @@ -1,5 +1,5 @@ -import type { Skill as CanonicalSkill } from "../sessions/skills.js"; -import type { SourceInfo } from "../sessions/source-info.js"; +import type { Skill as CanonicalSkill } from "../agents/sessions/skills.js"; +import type { SourceInfo } from "../agents/sessions/source-info.js"; export type SourceScope = "user" | "project" | "temporary"; export type SourceOrigin = "package" | "top-level"; diff --git a/src/agents/skills/snapshot-hydration.ts b/src/skills/snapshot-hydration.ts similarity index 100% rename from src/agents/skills/snapshot-hydration.ts rename to src/skills/snapshot-hydration.ts diff --git a/src/agents/skills/source.ts b/src/skills/source.ts similarity index 94% rename from src/agents/skills/source.ts rename to src/skills/source.ts index a4e7e96923a9..e02cbfe725c1 100644 --- a/src/agents/skills/source.ts +++ b/src/skills/source.ts @@ -1,4 +1,4 @@ -import { normalizeOptionalString } from "../../shared/string-coerce.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; import type { Skill } from "./skill-contract.js"; import type { SkillTelemetrySource } from "./types.js"; diff --git a/src/agents/skills/tools-dir.ts b/src/skills/tools-dir.ts similarity index 74% rename from src/agents/skills/tools-dir.ts rename to src/skills/tools-dir.ts index 06e1f3fb68ba..8a9592a53144 100644 --- a/src/agents/skills/tools-dir.ts +++ b/src/skills/tools-dir.ts @@ -1,6 +1,6 @@ import path from "node:path"; -import { safePathSegmentHashed } from "../../infra/install-safe-path.js"; -import { resolveConfigDir } from "../../utils.js"; +import { safePathSegmentHashed } from "../infra/install-safe-path.js"; +import { resolveConfigDir } from "../utils.js"; import { resolveSkillKey } from "./frontmatter.js"; import type { SkillEntry } from "./types.js"; diff --git a/src/skills/trust.ts b/src/skills/trust.ts new file mode 100644 index 000000000000..3f6de52c4787 --- /dev/null +++ b/src/skills/trust.ts @@ -0,0 +1,92 @@ +import path from "node:path"; +import { resolveSkillSource } from "./source.js"; +import type { SkillEntry } from "./types.js"; + +export type SkillSourceKind = + | "workspace" + | "generated" + | "bundled" + | "clawhub" + | "plugin" + | "extra" + | "system"; + +export type SkillWritablePolicy = { + writable: boolean; + reason: string; +}; + +export type SkillTrustInfo = { + sourceLabel: string; + sourceKind: SkillSourceKind; + owner: string; + writable: boolean; + writableReason: string; +}; + +export function classifySkillSourceKind(sourceLabel: string): SkillSourceKind { + switch (sourceLabel) { + case "openclaw-workspace": + case "agents-skills-project": + return "workspace"; + case "openclaw-managed": + return "clawhub"; + case "openclaw-bundled": + return "bundled"; + case "agents-skills-personal": + case "openclaw-extra": + return "extra"; + default: + return "extra"; + } +} + +export function resolveSkillOwner(params: { + sourceKind: SkillSourceKind; + sourceLabel: string; + skillPath: string; +}): string { + if (params.sourceKind === "workspace") return "workspace"; + if (params.sourceKind === "generated") return "workspace"; + if (params.sourceKind === "bundled") return "openclaw-release"; + if (params.sourceKind === "clawhub") return "clawhub"; + if (params.sourceKind === "plugin") return "plugin"; + if (params.sourceKind === "system") return "openclaw-system"; + if (params.sourceLabel === "agents-skills-personal") return "user"; + return path.basename(path.dirname(params.skillPath)) || "extra"; +} + +export function resolveSkillWritablePolicy(sourceKind: SkillSourceKind): SkillWritablePolicy { + switch (sourceKind) { + case "workspace": + case "generated": + return { writable: true, reason: "workspace-owned-skill" }; + case "bundled": + return { writable: false, reason: "release-owned-skill" }; + case "clawhub": + return { writable: false, reason: "installer-owned-skill" }; + case "plugin": + return { writable: false, reason: "plugin-owned-skill" }; + case "system": + return { writable: false, reason: "system-owned-skill" }; + case "extra": + return { writable: false, reason: "extra-root-load-only" }; + } +} + +export function resolveSkillTrustInfo(entry: SkillEntry): SkillTrustInfo { + const sourceLabel = resolveSkillSource(entry.skill); + const sourceKind = classifySkillSourceKind(sourceLabel); + const writable = resolveSkillWritablePolicy(sourceKind); + return { + sourceLabel, + sourceKind, + owner: resolveSkillOwner({ + sourceKind, + sourceLabel, + skillPath: entry.skill.filePath, + }), + writable: writable.writable, + writableReason: writable.reason, + }; +} diff --git a/src/agents/skills/types.ts b/src/skills/types.ts similarity index 100% rename from src/agents/skills/types.ts rename to src/skills/types.ts diff --git a/src/agents/skills/workspace.ts b/src/skills/workspace.ts similarity index 98% rename from src/agents/skills/workspace.ts rename to src/skills/workspace.ts index f6633ba44af4..86638ca8cbf9 100644 --- a/src/agents/skills/workspace.ts +++ b/src/skills/workspace.ts @@ -1,15 +1,15 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import type { OpenClawConfig } from "../../config/types.openclaw.js"; -import { walkDirectorySync } from "../../infra/fs-safe.js"; -import { resolveOsHomeDir } from "../../infra/home-dir.js"; -import { isPathInside } from "../../infra/path-guards.js"; -import { createSubsystemLogger } from "../../logging/subsystem.js"; -import { normalizeOptionalString } from "../../shared/string-coerce.js"; -import { normalizeTrimmedStringList, uniqueStrings } from "../../shared/string-normalization.js"; -import { CONFIG_DIR, resolveHomeDir, resolveUserPath } from "../../utils.js"; -import { resolveSandboxPath } from "../sandbox-paths.js"; +import { resolveSandboxPath } from "../agents/sandbox-paths.js"; +import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { walkDirectorySync } from "../infra/fs-safe.js"; +import { resolveOsHomeDir } from "../infra/home-dir.js"; +import { isPathInside } from "../infra/path-guards.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; +import { normalizeOptionalString } from "../shared/string-coerce.js"; +import { normalizeTrimmedStringList, uniqueStrings } from "../shared/string-normalization.js"; +import { CONFIG_DIR, resolveHomeDir, resolveUserPath } from "../utils.js"; import { resolveEffectiveAgentSkillFilter, resolveEffectiveAgentSkillsLimits, diff --git a/src/trajectory/metadata.test.ts b/src/trajectory/metadata.test.ts index b1fb31546d85..3ec787307cd9 100644 --- a/src/trajectory/metadata.test.ts +++ b/src/trajectory/metadata.test.ts @@ -1,5 +1,4 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import type { SkillSnapshot } from "../agents/skills.js"; import { REDACTED_SENTINEL } from "../config/redact-snapshot.js"; import { redactPathForSupport, @@ -7,6 +6,7 @@ import { } from "../logging/diagnostic-support-redaction.js"; import { createEmptyPluginRegistry } from "../plugins/registry-empty.js"; import { resetPluginRuntimeStateForTest, setActivePluginRegistry } from "../plugins/runtime.js"; +import type { SkillSnapshot } from "../skills/index.js"; type ResolvedSkillEntry = NonNullable[number]; diff --git a/src/trajectory/metadata.ts b/src/trajectory/metadata.ts index 9f0fb3cb653e..c9105a5f2ba2 100644 --- a/src/trajectory/metadata.ts +++ b/src/trajectory/metadata.ts @@ -1,4 +1,3 @@ -import type { SkillSnapshot } from "../agents/skills.js"; import { resolveStateDir } from "../config/paths.js"; import { redactConfigObject } from "../config/redact-snapshot.js"; import type { SessionSystemPromptReport } from "../config/sessions/types.js"; @@ -12,6 +11,7 @@ import { } from "../logging/diagnostic-support-redaction.js"; import { loadPluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js"; import { getActivePluginRegistry, listImportedRuntimePluginIds } from "../plugins/runtime.js"; +import type { SkillSnapshot } from "../skills/index.js"; import { VERSION } from "../version.js"; type BuildTrajectoryRunMetadataParams = {