Skip to content

Commit 617962f

Browse files
feat: directory files upload system, deferred restart model, cache invalidation consolidation, and misc improvements (#281)
* feat: show variant label on model picker and add folder upload for agents/commands - Replace three-dot variant menu trigger with selected variant label - Add POST /api/settings/opencode-directory-files/install endpoint - Upload agents/commands markdown folders into .config/opencode/{agents,commands}/ - Reload OpenCode server after upload so files are picked up * fix: revalidate branch/git caches when mobile sheet opens and on vcs.branch.updated SSE - add shared invalidateRepoGitCaches() helper for consistent invalidation - revalidate repo list when mobile repo quick-switch sheet opens - revalidate current repo when mobile More drawer opens - revalidate repo/git caches when Source Control dialog opens - handle vcs.branch.updated SSE event with optimistic cache update - use helper in branch create/switch mutations in useGit and BranchesTab * style: make variant selector look more like a button with chevron and compact sizing * refactor: consolidate settings editors, add folder upload support, and clean up unused code * refactor: consolidate settings editors, add folder upload support, and clean up unused code * feat: add opencode-directory-files listing service and consolidate settings editors * fix: distinguish scroll from tap in suggestion popups on touch devices * refactor: consolidate settings editors and add DirectoryFilesList component * refactor: target repo status cache updates only to affected batch entries * refactor: streamline settings, git hooks, and query invalidation logic
1 parent e53b4fd commit 617962f

48 files changed

Lines changed: 1518 additions & 289 deletions

Some content is hidden

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

backend/src/routes/health.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export function createHealthRoutes(db: Database, openCodeSupervisor?: OpenCodeSu
9191
opencodeMinVersion: opencodeServerManager.getMinVersion(),
9292
opencodeVersionSupported: opencodeServerManager.isVersionSupported(),
9393
opencodeManagerVersion,
94+
opencodeRestartPending: opencodeServerManager.isRestartPending(),
9495
}
9596

9697
if (lifecycle) {

backend/src/routes/settings.test.ts

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
22
import { Hono } from 'hono'
33
import { Database } from 'bun:sqlite'
4+
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'fs/promises'
5+
import { tmpdir } from 'os'
6+
import { join } from 'path'
47
import { migrate } from '../db/migration-runner'
58
import { allMigrations } from '../db/migrations'
69
import { createSettingsRoutes } from './settings'
10+
import { opencodeServerManager } from '../services/opencode-single-server'
711
import type { GitAuthService } from '../services/git-auth'
12+
import type { OpenCodeSupervisor } from '../services/opencode-supervisor'
813
import { createStubOpenCodeClient } from '../../test/helpers/stub-opencode-client'
914

1015
interface TestUserPreferenceRow {
@@ -205,9 +210,9 @@ function createTestDb(): Database {
205210
return db
206211
}
207212

208-
function createTestApp(db: Database): Hono {
213+
function createTestApp(db: Database, openCodeSupervisor?: OpenCodeSupervisor): Hono {
209214
const app = new Hono()
210-
app.route('/settings', createSettingsRoutes(db, mockGitAuthService, createStubOpenCodeClient()))
215+
app.route('/settings', createSettingsRoutes(db, mockGitAuthService, createStubOpenCodeClient(), openCodeSupervisor))
211216
return app
212217
}
213218

@@ -291,3 +296,78 @@ describe('settings routes — serverEnvVars', () => {
291296
])
292297
})
293298
})
299+
300+
describe('settings routes — OpenCode directory file upload', () => {
301+
let db: Database
302+
let app: Hono
303+
let originalWorkspacePath: string | undefined
304+
let workspacePath: string
305+
const restart = vi.fn(async () => undefined)
306+
let markRestartPendingSpy: ReturnType<typeof vi.spyOn>
307+
308+
beforeEach(async () => {
309+
db = createTestDb()
310+
restart.mockClear()
311+
markRestartPendingSpy = vi.spyOn(opencodeServerManager, 'markRestartPending').mockImplementation(() => undefined)
312+
originalWorkspacePath = process.env.WORKSPACE_PATH
313+
workspacePath = await mkdtemp(join(tmpdir(), 'ocm-command-upload-'))
314+
process.env.WORKSPACE_PATH = workspacePath
315+
app = createTestApp(db, { restart } as unknown as OpenCodeSupervisor)
316+
})
317+
318+
afterEach(async () => {
319+
if (originalWorkspacePath) {
320+
process.env.WORKSPACE_PATH = originalWorkspacePath
321+
} else {
322+
delete process.env.WORKSPACE_PATH
323+
}
324+
await rm(workspacePath, { recursive: true, force: true })
325+
db.close()
326+
markRestartPendingSpy.mockRestore()
327+
})
328+
329+
it('installs uploaded command markdown files into the OpenCode commands directory', async () => {
330+
const formData = new FormData()
331+
formData.append('kind', 'commands')
332+
formData.append('fileManifest', JSON.stringify([
333+
{ fieldName: 'file0', relativePath: 'commands/git/commit.md' },
334+
{ fieldName: 'file1', relativePath: 'commands/.DS_Store' },
335+
]))
336+
formData.append('file0', new File(['commit body'], 'commit.md', { type: 'text/markdown' }))
337+
338+
const res = await app.request('/settings/opencode-directory-files/install', {
339+
method: 'POST',
340+
body: formData,
341+
})
342+
343+
expect(res.status).toBe(200)
344+
await expect(res.json()).resolves.toEqual({
345+
kind: 'commands',
346+
filesInstalled: ['git/commit.md'],
347+
restartRequired: true,
348+
})
349+
await expect(readFile(join(workspacePath, '.config/opencode/commands/git/commit.md'), 'utf8')).resolves.toBe('commit body')
350+
expect(restart).not.toHaveBeenCalled()
351+
expect(markRestartPendingSpy).toHaveBeenCalledTimes(1)
352+
})
353+
354+
it('lists uploaded command and agent directory files', async () => {
355+
await mkdir(join(workspacePath, '.config/opencode/commands/git'), { recursive: true })
356+
await mkdir(join(workspacePath, '.config/opencode/agents/team'), { recursive: true })
357+
await writeFile(join(workspacePath, '.config/opencode/commands/git/commit.md'), 'commit body')
358+
await writeFile(join(workspacePath, '.config/opencode/commands/git/.DS_Store'), 'metadata')
359+
await writeFile(join(workspacePath, '.config/opencode/agents/team/planner.md'), 'planner body')
360+
361+
const commandsRes = await app.request('/settings/opencode-directory-files?kind=commands')
362+
const agentsRes = await app.request('/settings/opencode-directory-files?kind=agents')
363+
364+
expect(commandsRes.status).toBe(200)
365+
expect(agentsRes.status).toBe(200)
366+
await expect(commandsRes.json()).resolves.toEqual([
367+
{ kind: 'commands', name: 'commit', relativePath: 'git/commit.md' },
368+
])
369+
await expect(agentsRes.json()).resolves.toEqual([
370+
{ kind: 'agents', name: 'planner', relativePath: 'team/planner.md' },
371+
])
372+
})
373+
})

0 commit comments

Comments
 (0)