diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index b9aa498962..a793bfb037 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -1,21 +1,15 @@ name: beta on: - push: - branches: [dev] - pull_request: - types: [opened, synchronize, labeled, unlabeled] + workflow_dispatch: + schedule: + - cron: "0 * * * *" jobs: sync: - if: | - github.event_name == 'push' || - (github.event_name == 'pull_request' && - contains(github.event.pull_request.labels.*.name, 'contributor')) runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: write - pull-requests: write steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6cbb16edc..2a36c07e14 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,9 @@ name: test on: + push: + branches: + - dev pull_request: workflow_dispatch: jobs: diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx index 718929d445..bd000e2ab0 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx @@ -345,8 +345,9 @@ export function Autocomplete(props: { const results: AutocompleteOption[] = [...command.slashes()] for (const serverCommand of sync.data.command) { + const label = serverCommand.source === "mcp" ? ":mcp" : serverCommand.source === "skill" ? ":skill" : "" results.push({ - display: "/" + serverCommand.name + (serverCommand.mcp ? " (MCP)" : ""), + display: "/" + serverCommand.name + label, description: serverCommand.description, onSelect: () => { const newText = "/" + serverCommand.name + " " diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 976f1cd51e..14dbeb6794 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -6,6 +6,7 @@ import { Identifier } from "../id/id" import PROMPT_INITIALIZE from "./template/initialize.txt" import PROMPT_REVIEW from "./template/review.txt" import { MCP } from "../mcp" +import { Skill } from "../skill" export namespace Command { export const Event = { @@ -26,7 +27,7 @@ export namespace Command { description: z.string().optional(), agent: z.string().optional(), model: z.string().optional(), - mcp: z.boolean().optional(), + source: z.enum(["command", "mcp", "skill"]).optional(), // workaround for zod not supporting async functions natively so we use getters // https://zod.dev/v4/changelog?id=zfunction template: z.promise(z.string()).or(z.string()), @@ -94,7 +95,7 @@ export namespace Command { for (const [name, prompt] of Object.entries(await MCP.prompts())) { result[name] = { name, - mcp: true, + source: "mcp", description: prompt.description, get template() { // since a getter can't be async we need to manually return a promise here @@ -118,6 +119,21 @@ export namespace Command { } } + // Add skills as invokable commands + for (const skill of await Skill.all()) { + // Skip if a command with this name already exists + if (result[skill.name]) continue + result[skill.name] = { + name: skill.name, + description: skill.description, + source: "skill", + get template() { + return skill.content + }, + hints: [], + } + } + return result }) diff --git a/packages/opencode/src/env/index.ts b/packages/opencode/src/env/index.ts index 8c40c08edd..003b59fc71 100644 --- a/packages/opencode/src/env/index.ts +++ b/packages/opencode/src/env/index.ts @@ -2,7 +2,9 @@ import { Instance } from "../project/instance" export namespace Env { const state = Instance.state(() => { - return process.env as Record + // Create a shallow copy to isolate environment per instance + // Prevents parallel tests from interfering with each other's env vars + return { ...process.env } as Record }) export function get(key: string) { diff --git a/packages/opencode/src/file/ripgrep.ts b/packages/opencode/src/file/ripgrep.ts index 0f6889779a..dd94cc6097 100644 --- a/packages/opencode/src/file/ripgrep.ts +++ b/packages/opencode/src/file/ripgrep.ts @@ -214,8 +214,8 @@ export namespace Ripgrep { input.signal?.throwIfAborted() const args = [await filepath(), "--files", "--glob=!.git/*"] - if (input.follow !== false) args.push("--follow") - if (input.hidden !== false) args.push("--hidden") + if (input.follow) args.push("--follow") + if (input.hidden) args.push("--hidden") if (input.maxDepth !== undefined) args.push(`--max-depth=${input.maxDepth}`) if (input.glob) { for (const g of input.glob) { @@ -381,7 +381,7 @@ export namespace Ripgrep { follow?: boolean }) { const args = [`${await filepath()}`, "--json", "--hidden", "--glob='!.git/*'"] - if (input.follow !== false) args.push("--follow") + if (input.follow) args.push("--follow") if (input.glob) { for (const g of input.glob) { diff --git a/packages/opencode/src/provider/transform.ts b/packages/opencode/src/provider/transform.ts index 39b25a4b5b..05e2272415 100644 --- a/packages/opencode/src/provider/transform.ts +++ b/packages/opencode/src/provider/transform.ts @@ -83,7 +83,11 @@ export namespace ProviderTransform { return msg }) } - if (model.providerID === "mistral" || model.api.id.toLowerCase().includes("mistral")) { + if ( + model.providerID === "mistral" || + model.api.id.toLowerCase().includes("mistral") || + model.api.id.toLocaleLowerCase().includes("devstral") + ) { const result: ModelMessage[] = [] for (let i = 0; i < msgs.length; i++) { const msg = msgs[i] diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts index 5b300a9287..6e05d013ae 100644 --- a/packages/opencode/src/skill/skill.ts +++ b/packages/opencode/src/skill/skill.ts @@ -18,6 +18,7 @@ export namespace Skill { name: z.string(), description: z.string(), location: z.string(), + content: z.string(), }) export type Info = z.infer @@ -74,6 +75,7 @@ export namespace Skill { name: parsed.data.name, description: parsed.data.description, location: match, + content: md.content, } } diff --git a/packages/opencode/src/tool/grep.ts b/packages/opencode/src/tool/grep.ts index 6cb70d022e..c10b4dfb88 100644 --- a/packages/opencode/src/tool/grep.ts +++ b/packages/opencode/src/tool/grep.ts @@ -37,15 +37,7 @@ export const GrepTool = Tool.define("grep", { await assertExternalDirectory(ctx, searchPath, { kind: "directory" }) const rgPath = await Ripgrep.filepath() - const args = [ - "-nH", - "--hidden", - "--follow", - "--no-messages", - "--field-match-separator=|", - "--regexp", - params.pattern, - ] + const args = ["-nH", "--hidden", "--no-messages", "--field-match-separator=|", "--regexp", params.pattern] if (params.include) { args.push("--glob", params.include) } diff --git a/packages/opencode/src/tool/skill.ts b/packages/opencode/src/tool/skill.ts index 76d9fd535e..8f285d5999 100644 --- a/packages/opencode/src/tool/skill.ts +++ b/packages/opencode/src/tool/skill.ts @@ -2,7 +2,6 @@ import path from "path" import z from "zod" import { Tool } from "./tool" import { Skill } from "../skill" -import { ConfigMarkdown } from "../config/markdown" import { PermissionNext } from "../permission/next" export const SkillTool = Tool.define("skill", async (ctx) => { @@ -62,7 +61,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => { always: [params.name], metadata: {}, }) - const content = (await ConfigMarkdown.parse(skill.location)).content + const content = skill.content const dir = path.dirname(skill.location) // Format output similar to plugin pattern diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a8c61c4daa..cb2f586775 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2116,7 +2116,7 @@ export type Command = { description?: string agent?: string model?: string - mcp?: boolean + source?: "command" | "mcp" | "skill" template: string subtask?: boolean hints: Array @@ -4913,6 +4913,7 @@ export type AppSkillsResponses = { name: string description: string location: string + content: string }> } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 91e7a46974..4be0a87f98 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -5723,9 +5723,12 @@ }, "location": { "type": "string" + }, + "content": { + "type": "string" } }, - "required": ["name", "description", "location"] + "required": ["name", "description", "location", "content"] } } } @@ -10770,8 +10773,9 @@ "model": { "type": "string" }, - "mcp": { - "type": "boolean" + "source": { + "type": "string", + "enum": ["command", "mcp", "skill"] }, "template": { "anyOf": [