Skip to content

Commit 4913902

Browse files
authored
Merge pull request #99 from ktmage/release/v0.5.0
chore: release v0.5.0
2 parents e40ab45 + 0e62fb1 commit 4913902

41 files changed

Lines changed: 723 additions & 44 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
77

88
## [Unreleased]
99

10+
## [0.5.0] - 2026-03-08
11+
12+
### Added
13+
14+
- Explicit OpenCode skill selection in the chat input via context menu and `/` autocomplete (#98)
15+
- Scroll-to-bottom button for the messages area when viewing older messages (#95)
16+
17+
### Changed
18+
19+
- Chat input now loads skill metadata from OpenCode and prepends the selected skill as a synthetic slash command before sending
20+
1021
## [0.4.2] - 2026-03-08
1122

1223
### Added
@@ -129,7 +140,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
129140
- Todo display
130141
- i18n support (English, Japanese)
131142

132-
[Unreleased]: https://github.com/ktmage/opencode-gui/compare/v0.4.1...HEAD
143+
[Unreleased]: https://github.com/ktmage/opencode-gui/compare/v0.5.0...HEAD
144+
[0.5.0]: https://github.com/ktmage/opencode-gui/compare/v0.4.2...v0.5.0
133145
[0.4.1]: https://github.com/ktmage/opencode-gui/compare/v0.4.0...v0.4.1
134146
[0.4.0]: https://github.com/ktmage/opencode-gui/compare/v0.3.0...v0.4.0
135147
[0.3.0]: https://github.com/ktmage/opencode-gui/compare/v0.2.0...v0.3.0

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "opencodegui-monorepo",
33
"private": true,
4-
"version": "0.4.2",
4+
"version": "0.5.0",
55
"license": "MIT",
66
"repository": {
77
"type": "git",

packages/agents/opencode/src/__tests__/opencode-agent.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ function createMockSdkClient() {
4646
},
4747
app: {
4848
agents: vi.fn().mockResolvedValue({ data: [] }),
49+
skills: vi.fn().mockResolvedValue({ data: [] }),
4950
},
5051
mcp: {
5152
status: vi.fn().mockResolvedValue({ data: {} }),
@@ -366,6 +367,17 @@ describe("OpenCodeAgent", () => {
366367
expect(call.parts[1]).toEqual({ type: "agent", name: "code-reviewer" });
367368
});
368369

370+
it("should prepend synthetic skill command when skill is provided", async () => {
371+
await agent.connect();
372+
373+
await agent.sendMessage("sess-1", "Hello", { skill: "coding-guidelines" } as never);
374+
375+
const call = mockClient.session.promptAsync.mock.calls[0][0];
376+
expect(call.parts).toHaveLength(2);
377+
expect(call.parts[0]).toEqual({ type: "text", text: "/coding-guidelines", synthetic: true });
378+
expect(call.parts[1]).toEqual({ type: "text", text: "Hello" });
379+
});
380+
369381
it("should include all parts when files and agent are provided", async () => {
370382
await agent.connect();
371383
agent.workspaceFolder = "/ws";
@@ -684,6 +696,22 @@ describe("OpenCodeAgent", () => {
684696
});
685697
});
686698

699+
describe("getSkills()", () => {
700+
it("should call app.skills() and return mapped skills", async () => {
701+
mockClient.app.skills.mockResolvedValue({
702+
data: [{ name: "coding-guidelines", description: "desc", location: "/skills/coding-guidelines" }],
703+
});
704+
await agent.connect();
705+
706+
const result = await agent.getSkills();
707+
708+
expect(mockClient.app.skills).toHaveBeenCalled();
709+
expect(result).toEqual([
710+
{ name: "coding-guidelines", description: "desc", location: "/skills/coding-guidelines" },
711+
]);
712+
});
713+
});
714+
687715
// ============================================================
688716
// Config API
689717
// ============================================================

packages/agents/opencode/src/mappers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type {
3232
McpStatus,
3333
MessagePart,
3434
ProviderInfo,
35+
SkillInfo,
3536
TodoItem,
3637
ToolListItem,
3738
} from "@opencodegui/core";
@@ -129,6 +130,14 @@ export function mapAgents(agents: Agent[]): AgentInfo[] {
129130
return agents.map(mapAgent);
130131
}
131132

133+
export function mapSkills(skills: Array<{ name: string; description: string; location: string }>): SkillInfo[] {
134+
return skills.map((skill) => ({
135+
name: skill.name,
136+
description: skill.description,
137+
location: skill.location,
138+
}));
139+
}
140+
132141
// ============================================================
133142
// Config
134143
// ============================================================

packages/agents/opencode/src/opencode-agent.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
ProviderInfo,
3030
QuestionAnswer,
3131
SendMessageOptions,
32+
SkillInfo,
3233
TodoItem,
3334
ToolListItem,
3435
} from "@opencodegui/core";
@@ -44,6 +45,7 @@ import {
4445
mapProviders,
4546
mapSession,
4647
mapSessions,
48+
mapSkills,
4749
mapTodos,
4850
mapToolIds,
4951
} from "./mappers";
@@ -239,10 +241,16 @@ export class OpenCodeAgent implements IAgent {
239241
async sendMessage(sessionId: string, text: string, options?: SendMessageOptions): Promise<void> {
240242
const client = this.requireClient();
241243
const parts: Array<
242-
| { type: "text"; text: string }
244+
| { type: "text"; text: string; synthetic?: boolean }
243245
| { type: "file"; mime: string; url: string; filename: string }
244246
| { type: "agent"; name: string }
245-
> = [{ type: "text", text }];
247+
> = [];
248+
249+
if (options?.skill) {
250+
parts.push({ type: "text", text: `/${options.skill}`, synthetic: true });
251+
}
252+
253+
parts.push({ type: "text", text });
246254

247255
if (options?.files) {
248256
for (const file of options.files) {
@@ -320,6 +328,12 @@ export class OpenCodeAgent implements IAgent {
320328
return mapAgents(response.data!);
321329
}
322330

331+
async getSkills(): Promise<SkillInfo[]> {
332+
const client = this.requireClient();
333+
const response = await client.app.skills();
334+
return mapSkills(response.data!);
335+
}
336+
323337
async getChildSessions(sessionId: string): Promise<ChatSession[]> {
324338
const client = this.requireClient();
325339
const response = await client.session.children({

packages/core/src/agent.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {
2222
ProviderInfo,
2323
QuestionAnswer,
2424
SendMessageOptions,
25+
SkillInfo,
2526
TodoItem,
2627
ToolListItem,
2728
} from "./domain";
@@ -100,6 +101,7 @@ export interface IAgent {
100101

101102
// --- Agent list (capabilities.subAgent) ---
102103
getAgents(): Promise<AgentInfo[]>;
104+
getSkills(): Promise<SkillInfo[]>;
103105
getChildSessions(sessionId: string): Promise<ChatSession[]>;
104106

105107
// --- Permissions (capabilities.permission) ---

packages/core/src/domain.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,12 @@ export type AgentInfo = {
438438
color?: string;
439439
};
440440

441+
export type SkillInfo = {
442+
name: string;
443+
description?: string;
444+
location?: string;
445+
};
446+
441447
// ============================================================
442448
// App Config & Paths
443449
// ============================================================
@@ -460,6 +466,7 @@ export type SendMessageOptions = {
460466
files?: FileAttachment[];
461467
agent?: string;
462468
primaryAgent?: string;
469+
skill?: string;
463470
};
464471

465472
// ============================================================

packages/core/src/protocol.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
PermissionResponse,
2121
ProviderInfo,
2222
QuestionAnswer,
23+
SkillInfo,
2324
TodoItem,
2425
} from "./domain";
2526

@@ -59,6 +60,7 @@ export type UIToHostMessage =
5960
files?: FileAttachment[];
6061
agent?: string;
6162
primaryAgent?: string;
63+
skill?: string;
6264
}
6365
| {
6466
type: "editAndResend";
@@ -105,6 +107,7 @@ export type UIToHostMessage =
105107
| { type: "getSessionTodos"; sessionId: string }
106108
| { type: "getChildSessions"; sessionId: string }
107109
| { type: "getAgents" }
110+
| { type: "getSkills" }
108111

109112
// --- Model config ---
110113
| { type: "setModel"; model: string }
@@ -193,6 +196,7 @@ export type HostToUIMessage =
193196

194197
// --- Agent list ---
195198
| { type: "agents"; agents: AgentInfo[] }
199+
| { type: "skills"; skills: SkillInfo[] }
196200

197201
// --- Platform data ---
198202
| { type: "openEditors"; files: FileAttachment[] }

packages/platforms/vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "opencodegui",
33
"displayName": "%displayName%",
44
"description": "%description%",
5-
"version": "0.4.2",
5+
"version": "0.5.0",
66
"publisher": "ktmage",
77
"license": "MIT",
88
"repository": {

packages/platforms/vscode/src/__tests__/chat-view-provider.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function createMockAgent(): {
5858
getProviders: vi.fn().mockResolvedValue({ providers: [], default: {} }),
5959
listAllProviders: vi.fn().mockResolvedValue({ all: [], default: {}, connected: [] }),
6060
getAgents: vi.fn().mockResolvedValue([]),
61+
getSkills: vi.fn().mockResolvedValue([]),
6162
getChildSessions: vi.fn().mockResolvedValue([]),
6263
replyPermission: vi.fn().mockResolvedValue(undefined),
6364
getSessionDiff: vi.fn().mockResolvedValue([]),
@@ -499,12 +500,14 @@ describe("ChatViewProvider", () => {
499500
model: { providerID: "anthropic", modelID: "claude-4" },
500501
files: [{ filePath: "a.ts", fileName: "a.ts" }],
501502
agent: "reviewer",
503+
skill: "coding-guidelines",
502504
});
503505

504506
expect(mockAgent.sendMessage).toHaveBeenCalledWith("sess-1", "Hello", {
505507
model: { providerID: "anthropic", modelID: "claude-4" },
506508
files: [{ filePath: "a.ts", fileName: "a.ts" }],
507509
agent: "reviewer",
510+
skill: "coding-guidelines",
508511
});
509512
});
510513
});
@@ -909,6 +912,18 @@ describe("ChatViewProvider", () => {
909912
});
910913
});
911914

915+
describe("getSkills", () => {
916+
it("should send skills message", async () => {
917+
const skills = [{ name: "coding-guidelines" }];
918+
mockAgent.getSkills.mockResolvedValue(skills as never);
919+
920+
const { postMessage, sendMessage } = setupProvider(mockAgent);
921+
await sendMessage({ type: "getSkills" });
922+
923+
expect(postMessage).toHaveBeenCalledWith({ type: "skills", skills });
924+
});
925+
});
926+
912927
// ============================================================
913928
// shareSession
914929
// ============================================================

0 commit comments

Comments
 (0)