Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8b4aed446 | ||
|
|
03de0c406d | ||
|
|
faf8da8743 | ||
|
|
3386908fd6 | ||
|
|
5a8847952a | ||
|
|
87d21ebf2b | ||
|
|
a524fc545c | ||
|
|
4316edaf43 | ||
|
|
d845924e8b | ||
|
|
a29b322bdd | ||
|
|
9723ffa7a6 | ||
|
|
f06cd88773 |
@@ -97,20 +97,21 @@ if (!snapshot) {
|
|||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => data.commits || [])
|
.then((data) => data.commits || [])
|
||||||
|
|
||||||
const notes = commits
|
const notes =
|
||||||
.map((commit: any) => `- ${commit.commit.message.split("\n")[0]}`)
|
commits
|
||||||
.filter((x: string) => {
|
.map((commit: any) => `- ${commit.commit.message.split("\n")[0]}`)
|
||||||
const lower = x.toLowerCase()
|
.filter((x: string) => {
|
||||||
return (
|
const lower = x.toLowerCase()
|
||||||
!lower.includes("ignore:") &&
|
return (
|
||||||
!lower.includes("chore:") &&
|
!lower.includes("ignore:") &&
|
||||||
!lower.includes("ci:") &&
|
!lower.includes("chore:") &&
|
||||||
!lower.includes("wip:") &&
|
!lower.includes("ci:") &&
|
||||||
!lower.includes("docs:") &&
|
!lower.includes("wip:") &&
|
||||||
!lower.includes("doc:")
|
!lower.includes("docs:") &&
|
||||||
)
|
!lower.includes("doc:")
|
||||||
})
|
)
|
||||||
.join("\n")
|
})
|
||||||
|
.join("\n") || "No notable changes"
|
||||||
|
|
||||||
if (!dry) await $`gh release create v${version} --title "v${version}" --notes ${notes} ./dist/*.zip`
|
if (!dry) await $`gh release create v${version} --title "v${version}" --notes ${notes} ./dist/*.zip`
|
||||||
|
|
||||||
@@ -152,6 +153,7 @@ if (!snapshot) {
|
|||||||
for (const pkg of ["opencode", "opencode-bin"]) {
|
for (const pkg of ["opencode", "opencode-bin"]) {
|
||||||
await $`rm -rf ./dist/aur-${pkg}`
|
await $`rm -rf ./dist/aur-${pkg}`
|
||||||
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
||||||
|
await $`cd ./dist/aur-${pkg} && git checkout master`
|
||||||
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
|
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
|
||||||
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
||||||
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import path from "path"
|
|||||||
|
|
||||||
export const AuthCopilot = lazy(async () => {
|
export const AuthCopilot = lazy(async () => {
|
||||||
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
|
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
|
||||||
|
const exists = await file.exists()
|
||||||
const response = fetch("https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts")
|
const response = fetch("https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts")
|
||||||
.then((x) => Bun.write(file, x))
|
.then((x) => Bun.write(file, x))
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
|
|
||||||
if (!file.exists()) {
|
if (!exists) {
|
||||||
const worked = await response
|
const worked = await response
|
||||||
if (!worked) return
|
if (!worked) return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,13 @@ export namespace ConfigHooks {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Bus.subscribe(Session.Event.Idle, async () => {
|
Bus.subscribe(Session.Event.Idle, async (payload) => {
|
||||||
const cfg = await Config.get()
|
const cfg = await Config.get()
|
||||||
if (cfg.experimental?.hook?.session_completed) {
|
if (cfg.experimental?.hook?.session_completed) {
|
||||||
|
const session = await Session.get(payload.properties.sessionID)
|
||||||
|
// Only fire hook for top-level sessions (not subagent sessions)
|
||||||
|
if (session.parentID) return
|
||||||
|
|
||||||
for (const item of cfg.experimental.hook.session_completed) {
|
for (const item of cfg.experimental.hook.session_completed) {
|
||||||
log.info("session_completed", {
|
log.info("session_completed", {
|
||||||
command: item.command,
|
command: item.command,
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ export namespace Session {
|
|||||||
if (msgs.length === 0 && !session.parentID) {
|
if (msgs.length === 0 && !session.parentID) {
|
||||||
const small = (await Provider.getSmallModel(input.providerID)) ?? model
|
const small = (await Provider.getSmallModel(input.providerID)) ?? model
|
||||||
generateText({
|
generateText({
|
||||||
maxOutputTokens: input.providerID === "google" ? 1024 : 20,
|
maxOutputTokens: small.info.reasoning ? 1024 : 20,
|
||||||
providerOptions: {
|
providerOptions: {
|
||||||
[input.providerID]: small.info.options,
|
[input.providerID]: small.info.options,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ func main() {
|
|||||||
stream := httpClient.Event.ListStreaming(ctx)
|
stream := httpClient.Event.ListStreaming(ctx)
|
||||||
for stream.Next() {
|
for stream.Next() {
|
||||||
evt := stream.Current().AsUnion()
|
evt := stream.Current().AsUnion()
|
||||||
|
if _, ok := evt.(opencode.EventListResponseEventStorageWrite); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
program.Send(evt)
|
program.Send(evt)
|
||||||
}
|
}
|
||||||
if err := stream.Err(); err != nil {
|
if err := stream.Err(); err != nil {
|
||||||
|
|||||||
@@ -196,8 +196,6 @@ func renderText(
|
|||||||
case opencode.UserMessage:
|
case opencode.UserMessage:
|
||||||
ts = time.UnixMilli(int64(casted.Time.Created))
|
ts = time.UnixMilli(int64(casted.Time.Created))
|
||||||
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
|
base := styles.NewStyle().Foreground(t.Text()).Background(backgroundColor)
|
||||||
|
|
||||||
// Process @ mentions and styling with hyphen preservation
|
|
||||||
words := strings.Fields(text)
|
words := strings.Fields(text)
|
||||||
for i, word := range words {
|
for i, word := range words {
|
||||||
if strings.HasPrefix(word, "@") {
|
if strings.HasPrefix(word, "@") {
|
||||||
@@ -206,14 +204,9 @@ func renderText(
|
|||||||
words[i] = base.Render(word + " ")
|
words[i] = base.Render(word + " ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
styledText := strings.Join(words, "")
|
text = strings.Join(words, "")
|
||||||
|
text = ansi.WordwrapWc(text, width-6, " -")
|
||||||
// Apply word wrapping with hyphen preservation
|
content = base.Width(width - 6).Render(text)
|
||||||
frameSize := util.GetMessageContainerFrame()
|
|
||||||
wrappedText := util.ProcessTextWithHyphens(styledText, func(t string) string {
|
|
||||||
return ansi.WordwrapWc(t, width-frameSize, " ")
|
|
||||||
})
|
|
||||||
content = base.Width(width - frameSize).Render(wrappedText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp := ts.
|
timestamp := ts.
|
||||||
@@ -271,6 +264,8 @@ func renderToolDetails(
|
|||||||
toolCall opencode.ToolPart,
|
toolCall opencode.ToolPart,
|
||||||
width int,
|
width int,
|
||||||
) string {
|
) string {
|
||||||
|
measure := util.Measure("chat.renderToolDetails")
|
||||||
|
defer measure("tool", toolCall.Tool)
|
||||||
ignoredTools := []string{"todoread"}
|
ignoredTools := []string{"todoread"}
|
||||||
if slices.Contains(ignoredTools, toolCall.Tool) {
|
if slices.Contains(ignoredTools, toolCall.Tool) {
|
||||||
return ""
|
return ""
|
||||||
@@ -375,13 +370,14 @@ func renderToolDetails(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "bash":
|
case "bash":
|
||||||
|
command := toolInputMap["command"].(string)
|
||||||
|
body = fmt.Sprintf("```console\n$ %s\n", command)
|
||||||
stdout := metadata["stdout"]
|
stdout := metadata["stdout"]
|
||||||
if stdout != nil {
|
if stdout != nil {
|
||||||
command := toolInputMap["command"].(string)
|
body += ansi.Strip(fmt.Sprintf("%s", stdout))
|
||||||
out := ansi.Strip(fmt.Sprintf("%s", stdout))
|
|
||||||
body = fmt.Sprintf("```console\n> %s\n%s```", command, out)
|
|
||||||
body = util.ToMarkdown(body, width, backgroundColor)
|
|
||||||
}
|
}
|
||||||
|
body += "```"
|
||||||
|
body = util.ToMarkdown(body, width, backgroundColor)
|
||||||
case "webfetch":
|
case "webfetch":
|
||||||
if format, ok := toolInputMap["format"].(string); ok && result != nil {
|
if format, ok := toolInputMap["format"].(string); ok && result != nil {
|
||||||
body = *result
|
body = *result
|
||||||
@@ -715,4 +711,5 @@ func renderDiagnostics(
|
|||||||
// if !ok {
|
// if !ok {
|
||||||
// return ""
|
// return ""
|
||||||
// }
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ func (m *messagesComponent) Init() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
measure := util.Measure("messages.Update")
|
||||||
|
defer measure("from", fmt.Sprintf("%T", msg))
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
@@ -102,6 +104,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.loading = false
|
m.loading = false
|
||||||
m.tail = m.viewport.AtBottom()
|
m.tail = m.viewport.AtBottom()
|
||||||
m.viewport = msg.viewport
|
m.viewport = msg.viewport
|
||||||
|
m.header = msg.header
|
||||||
if m.dirty {
|
if m.dirty {
|
||||||
cmds = append(cmds, m.renderView())
|
cmds = append(cmds, m.renderView())
|
||||||
}
|
}
|
||||||
@@ -117,12 +120,12 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
type renderCompleteMsg struct {
|
type renderCompleteMsg struct {
|
||||||
viewport viewport.Model
|
viewport viewport.Model
|
||||||
|
header string
|
||||||
partCount int
|
partCount int
|
||||||
lineCount int
|
lineCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) renderView() tea.Cmd {
|
func (m *messagesComponent) renderView() tea.Cmd {
|
||||||
m.header = m.renderHeader()
|
|
||||||
|
|
||||||
if m.rendering {
|
if m.rendering {
|
||||||
slog.Debug("pending render, skipping")
|
slog.Debug("pending render, skipping")
|
||||||
@@ -138,6 +141,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
tail := m.tail
|
tail := m.tail
|
||||||
|
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
|
header := m.renderHeader()
|
||||||
measure := util.Measure("messages.renderView")
|
measure := util.Measure("messages.renderView")
|
||||||
defer measure()
|
defer measure()
|
||||||
|
|
||||||
@@ -230,6 +234,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case opencode.AssistantMessage:
|
case opencode.AssistantMessage:
|
||||||
|
messageMeasure := util.Measure("messages.Render")
|
||||||
hasTextPart := false
|
hasTextPart := false
|
||||||
for partIndex, p := range message.Parts {
|
for partIndex, p := range message.Parts {
|
||||||
switch part := p.(type) {
|
switch part := p.(type) {
|
||||||
@@ -361,6 +366,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
messageMeasure()
|
||||||
}
|
}
|
||||||
|
|
||||||
error := ""
|
error := ""
|
||||||
@@ -405,6 +411,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return renderCompleteMsg{
|
return renderCompleteMsg{
|
||||||
|
header: header,
|
||||||
viewport: viewport,
|
viewport: viewport,
|
||||||
partCount: partCount,
|
partCount: partCount,
|
||||||
lineCount: lineCount,
|
lineCount: lineCount,
|
||||||
|
|||||||
@@ -2051,10 +2051,6 @@ func wrapInterfaces(content []any, width int) [][]any {
|
|||||||
if unicode.IsSpace(r) {
|
if unicode.IsSpace(r) {
|
||||||
isSpace = true
|
isSpace = true
|
||||||
}
|
}
|
||||||
// Use hyphen-aware word boundary detection
|
|
||||||
if r == '-' {
|
|
||||||
isSpace = false
|
|
||||||
}
|
|
||||||
itemW = rw.RuneWidth(r)
|
itemW = rw.RuneWidth(r)
|
||||||
} else if att, ok := item.(*Attachment); ok {
|
} else if att, ok := item.(*Attachment); ok {
|
||||||
itemW = uniseg.StringWidth(att.Display)
|
itemW = uniseg.StringWidth(att.Display)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func (a appModel) Init() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
measure := util.Measure("Update")
|
measure := util.Measure("app.Update")
|
||||||
defer measure("from", fmt.Sprintf("%T", msg))
|
defer measure("from", fmt.Sprintf("%T", msg))
|
||||||
|
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
@@ -782,7 +782,8 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
|
|||||||
return a, toast.NewErrorToast("Something went wrong, couldn't open editor")
|
return a, toast.NewErrorToast("Something went wrong, couldn't open editor")
|
||||||
}
|
}
|
||||||
tmpfile.Close()
|
tmpfile.Close()
|
||||||
c := exec.Command(editor, tmpfile.Name()) //nolint:gosec
|
parts := strings.Fields(editor)
|
||||||
|
c := exec.Command(parts[0], append(parts[1:], tmpfile.Name())...) //nolint:gosec
|
||||||
c.Stdin = os.Stdin
|
c.Stdin = os.Stdin
|
||||||
c.Stdout = os.Stdout
|
c.Stdout = os.Stdout
|
||||||
c.Stderr = os.Stderr
|
c.Stderr = os.Stderr
|
||||||
|
|||||||
@@ -83,17 +83,11 @@ func Extension(path string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
|
func ToMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
|
||||||
renderWidth := width - GetMarkdownContainerFrame()
|
r := styles.GetMarkdownRenderer(width-6, backgroundColor)
|
||||||
r := styles.GetMarkdownRenderer(renderWidth, backgroundColor)
|
|
||||||
content = strings.ReplaceAll(content, RootPath+"/", "")
|
content = strings.ReplaceAll(content, RootPath+"/", "")
|
||||||
|
rendered, _ := r.Render(content)
|
||||||
// Apply hyphen preservation during markdown rendering
|
|
||||||
rendered := ProcessTextWithHyphens(content, func(t string) string {
|
|
||||||
result, _ := r.Render(t)
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
lines := strings.Split(rendered, "\n")
|
lines := strings.Split(rendered, "\n")
|
||||||
// Clean up empty lines at start/end
|
|
||||||
if len(lines) > 0 {
|
if len(lines) > 0 {
|
||||||
firstLine := lines[0]
|
firstLine := lines[0]
|
||||||
cleaned := ansi.Strip(firstLine)
|
cleaned := ansi.Strip(firstLine)
|
||||||
|
|||||||
Reference in New Issue
Block a user