mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 23:04:55 +00:00
Merge branch 'dev' into feat/dialog-esc-click
This commit is contained in:
12
.github/workflows/beta.yml
vendored
12
.github/workflows/beta.yml
vendored
@@ -1,21 +1,15 @@
|
|||||||
name: beta
|
name: beta
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
branches: [dev]
|
schedule:
|
||||||
pull_request:
|
- cron: "0 * * * *"
|
||||||
types: [opened, synchronize, labeled, unlabeled]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
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
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
3
.github/workflows/test.yml
vendored
3
.github/workflows/test.yml
vendored
@@ -1,6 +1,9 @@
|
|||||||
name: test
|
name: test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -345,8 +345,9 @@ export function Autocomplete(props: {
|
|||||||
const results: AutocompleteOption[] = [...command.slashes()]
|
const results: AutocompleteOption[] = [...command.slashes()]
|
||||||
|
|
||||||
for (const serverCommand of sync.data.command) {
|
for (const serverCommand of sync.data.command) {
|
||||||
|
const label = serverCommand.source === "mcp" ? ":mcp" : serverCommand.source === "skill" ? ":skill" : ""
|
||||||
results.push({
|
results.push({
|
||||||
display: "/" + serverCommand.name + (serverCommand.mcp ? " (MCP)" : ""),
|
display: "/" + serverCommand.name + label,
|
||||||
description: serverCommand.description,
|
description: serverCommand.description,
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
const newText = "/" + serverCommand.name + " "
|
const newText = "/" + serverCommand.name + " "
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Identifier } from "../id/id"
|
|||||||
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
import PROMPT_INITIALIZE from "./template/initialize.txt"
|
||||||
import PROMPT_REVIEW from "./template/review.txt"
|
import PROMPT_REVIEW from "./template/review.txt"
|
||||||
import { MCP } from "../mcp"
|
import { MCP } from "../mcp"
|
||||||
|
import { Skill } from "../skill"
|
||||||
|
|
||||||
export namespace Command {
|
export namespace Command {
|
||||||
export const Event = {
|
export const Event = {
|
||||||
@@ -26,7 +27,7 @@ export namespace Command {
|
|||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
agent: z.string().optional(),
|
agent: z.string().optional(),
|
||||||
model: 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
|
// workaround for zod not supporting async functions natively so we use getters
|
||||||
// https://zod.dev/v4/changelog?id=zfunction
|
// https://zod.dev/v4/changelog?id=zfunction
|
||||||
template: z.promise(z.string()).or(z.string()),
|
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())) {
|
for (const [name, prompt] of Object.entries(await MCP.prompts())) {
|
||||||
result[name] = {
|
result[name] = {
|
||||||
name,
|
name,
|
||||||
mcp: true,
|
source: "mcp",
|
||||||
description: prompt.description,
|
description: prompt.description,
|
||||||
get template() {
|
get template() {
|
||||||
// since a getter can't be async we need to manually return a promise here
|
// 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
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
4
packages/opencode/src/env/index.ts
vendored
4
packages/opencode/src/env/index.ts
vendored
@@ -2,7 +2,9 @@ import { Instance } from "../project/instance"
|
|||||||
|
|
||||||
export namespace Env {
|
export namespace Env {
|
||||||
const state = Instance.state(() => {
|
const state = Instance.state(() => {
|
||||||
return process.env as Record<string, string | undefined>
|
// 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<string, string | undefined>
|
||||||
})
|
})
|
||||||
|
|
||||||
export function get(key: string) {
|
export function get(key: string) {
|
||||||
|
|||||||
@@ -214,8 +214,8 @@ export namespace Ripgrep {
|
|||||||
input.signal?.throwIfAborted()
|
input.signal?.throwIfAborted()
|
||||||
|
|
||||||
const args = [await filepath(), "--files", "--glob=!.git/*"]
|
const args = [await filepath(), "--files", "--glob=!.git/*"]
|
||||||
if (input.follow !== false) args.push("--follow")
|
if (input.follow) args.push("--follow")
|
||||||
if (input.hidden !== false) args.push("--hidden")
|
if (input.hidden) args.push("--hidden")
|
||||||
if (input.maxDepth !== undefined) args.push(`--max-depth=${input.maxDepth}`)
|
if (input.maxDepth !== undefined) args.push(`--max-depth=${input.maxDepth}`)
|
||||||
if (input.glob) {
|
if (input.glob) {
|
||||||
for (const g of input.glob) {
|
for (const g of input.glob) {
|
||||||
@@ -381,7 +381,7 @@ export namespace Ripgrep {
|
|||||||
follow?: boolean
|
follow?: boolean
|
||||||
}) {
|
}) {
|
||||||
const args = [`${await filepath()}`, "--json", "--hidden", "--glob='!.git/*'"]
|
const args = [`${await filepath()}`, "--json", "--hidden", "--glob='!.git/*'"]
|
||||||
if (input.follow !== false) args.push("--follow")
|
if (input.follow) args.push("--follow")
|
||||||
|
|
||||||
if (input.glob) {
|
if (input.glob) {
|
||||||
for (const g of input.glob) {
|
for (const g of input.glob) {
|
||||||
|
|||||||
@@ -83,7 +83,11 @@ export namespace ProviderTransform {
|
|||||||
return msg
|
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[] = []
|
const result: ModelMessage[] = []
|
||||||
for (let i = 0; i < msgs.length; i++) {
|
for (let i = 0; i < msgs.length; i++) {
|
||||||
const msg = msgs[i]
|
const msg = msgs[i]
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export namespace Skill {
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
location: z.string(),
|
location: z.string(),
|
||||||
|
content: z.string(),
|
||||||
})
|
})
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ export namespace Skill {
|
|||||||
name: parsed.data.name,
|
name: parsed.data.name,
|
||||||
description: parsed.data.description,
|
description: parsed.data.description,
|
||||||
location: match,
|
location: match,
|
||||||
|
content: md.content,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,15 +37,7 @@ export const GrepTool = Tool.define("grep", {
|
|||||||
await assertExternalDirectory(ctx, searchPath, { kind: "directory" })
|
await assertExternalDirectory(ctx, searchPath, { kind: "directory" })
|
||||||
|
|
||||||
const rgPath = await Ripgrep.filepath()
|
const rgPath = await Ripgrep.filepath()
|
||||||
const args = [
|
const args = ["-nH", "--hidden", "--no-messages", "--field-match-separator=|", "--regexp", params.pattern]
|
||||||
"-nH",
|
|
||||||
"--hidden",
|
|
||||||
"--follow",
|
|
||||||
"--no-messages",
|
|
||||||
"--field-match-separator=|",
|
|
||||||
"--regexp",
|
|
||||||
params.pattern,
|
|
||||||
]
|
|
||||||
if (params.include) {
|
if (params.include) {
|
||||||
args.push("--glob", params.include)
|
args.push("--glob", params.include)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import path from "path"
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import { Skill } from "../skill"
|
import { Skill } from "../skill"
|
||||||
import { ConfigMarkdown } from "../config/markdown"
|
|
||||||
import { PermissionNext } from "../permission/next"
|
import { PermissionNext } from "../permission/next"
|
||||||
|
|
||||||
export const SkillTool = Tool.define("skill", async (ctx) => {
|
export const SkillTool = Tool.define("skill", async (ctx) => {
|
||||||
@@ -62,7 +61,7 @@ export const SkillTool = Tool.define("skill", async (ctx) => {
|
|||||||
always: [params.name],
|
always: [params.name],
|
||||||
metadata: {},
|
metadata: {},
|
||||||
})
|
})
|
||||||
const content = (await ConfigMarkdown.parse(skill.location)).content
|
const content = skill.content
|
||||||
const dir = path.dirname(skill.location)
|
const dir = path.dirname(skill.location)
|
||||||
|
|
||||||
// Format output similar to plugin pattern
|
// Format output similar to plugin pattern
|
||||||
|
|||||||
@@ -2116,7 +2116,7 @@ export type Command = {
|
|||||||
description?: string
|
description?: string
|
||||||
agent?: string
|
agent?: string
|
||||||
model?: string
|
model?: string
|
||||||
mcp?: boolean
|
source?: "command" | "mcp" | "skill"
|
||||||
template: string
|
template: string
|
||||||
subtask?: boolean
|
subtask?: boolean
|
||||||
hints: Array<string>
|
hints: Array<string>
|
||||||
@@ -4913,6 +4913,7 @@ export type AppSkillsResponses = {
|
|||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
location: string
|
location: string
|
||||||
|
content: string
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5723,9 +5723,12 @@
|
|||||||
},
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["name", "description", "location"]
|
"required": ["name", "description", "location", "content"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10770,8 +10773,9 @@
|
|||||||
"model": {
|
"model": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"mcp": {
|
"source": {
|
||||||
"type": "boolean"
|
"type": "string",
|
||||||
|
"enum": ["command", "mcp", "skill"]
|
||||||
},
|
},
|
||||||
"template": {
|
"template": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
|||||||
Reference in New Issue
Block a user