Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4b5f76f
section 1: Phase 1 — Stop recomputing the session diff on every poll
chriswritescode-dev Oct 8, 2026
7bf6c35
section 2: Phase 2 — Content-hashed hunk ids and stop status
chriswritescode-dev Oct 8, 2026
583a99f
section 3: Phase 3 — Mechanical changes stop
chriswritescode-dev Oct 8, 2026
daf9a96
section 4: Phase 4 — One retry per model call
chriswritescode-dev Oct 8, 2026
76fb634
section 5: Phase 5 — Outline and plan prompt (pure functions)
chriswritescode-dev Oct 8, 2026
24b7af2
section 6: Phase 6 — Explain stops in parallel with progressive saves
chriswritescode-dev Oct 8, 2026
64f8869
section 7: Phase 7 — Incremental reuse and per-stop retry
chriswritescode-dev Oct 8, 2026
7ed9374
section 8: Phase 8 — Panel shows stops as they finish
chriswritescode-dev Oct 8, 2026
229b16c
section 9: Phase 9 — Walkthrough model preference (backend)
chriswritescode-dev Oct 8, 2026
df1891a
section 10: Phase 10 — Walkthrough model setting (frontend)
chriswritescode-dev Oct 8, 2026
11c23da
section 11: Phase 11 — Diff sources: contract and git reader
chriswritescode-dev Oct 8, 2026
d5f56fc
section 12: Phase 12 — Per-source storage, service and routes
chriswritescode-dev Oct 8, 2026
aeea96b
section 13: Phase 13 — Source picker in the walkthrough panel
chriswritescode-dev Oct 8, 2026
88b3543
feat(walkthrough): plan-then-explain generation with selectable diff …
chriswritescode-dev Oct 8, 2026
4119fec
fix: apply CodeRabbit auto-fixes
chriswritescode-dev Oct 8, 2026
b01d3ab
Merge branch 'main' into feat/walkthrough-plan-explain
chriswritescode-dev Oct 8, 2026
0d65edf
feat(walkthrough): move source picker into the panel header
chriswritescode-dev Oct 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions backend/src/db/change-walkthroughs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import type { Database } from 'bun:sqlite'
import { ChangeWalkthroughSchema, type ChangeWalkthrough } from '@opencode-manager/shared/schemas'
import {
ChangeWalkthroughSchema,
walkthroughSourceKey,
type ChangeWalkthrough,
} from '@opencode-manager/shared/schemas'

interface ChangeWalkthroughRow {
session_id: string
source_key: string
diff_hash: string
payload: string
created_at: number
Expand All @@ -19,10 +24,16 @@ export function ensureChangeWalkthroughTable(db: Database): void {
`)
}

export function getChangeWalkthrough(db: Database, sessionId: string): ChangeWalkthrough | null {
export function getChangeWalkthrough(
db: Database,
sessionId: string,
sourceKey: string,
): ChangeWalkthrough | null {
const row = db
.prepare('SELECT session_id, diff_hash, payload, created_at FROM change_walkthroughs WHERE session_id = ?')
.get(sessionId) as ChangeWalkthroughRow | undefined
.prepare(
'SELECT session_id, source_key, diff_hash, payload, created_at FROM change_walkthroughs WHERE session_id = ? AND source_key = ?',
)
.get(sessionId, sourceKey) as ChangeWalkthroughRow | undefined
if (!row) {
return null
}
Expand All @@ -33,17 +44,24 @@ export function getChangeWalkthrough(db: Database, sessionId: string): ChangeWal
}
}

export function deleteChangeWalkthrough(db: Database, sessionId: string): void {
export function deleteChangeWalkthroughs(db: Database, sessionId: string): void {
db.prepare('DELETE FROM change_walkthroughs WHERE session_id = ?').run(sessionId)
}

export function saveChangeWalkthrough(db: Database, walkthrough: ChangeWalkthrough): void {
const sourceKey = walkthroughSourceKey(walkthrough.source)
db.prepare(`
INSERT INTO change_walkthroughs(session_id, diff_hash, payload, created_at)
VALUES(?,?,?,?)
ON CONFLICT(session_id) DO UPDATE SET
INSERT INTO change_walkthroughs(session_id, source_key, diff_hash, payload, created_at)
VALUES(?,?,?,?,?)
ON CONFLICT(session_id, source_key) DO UPDATE SET
diff_hash = excluded.diff_hash,
payload = excluded.payload,
created_at = excluded.created_at
`).run(walkthrough.sessionId, walkthrough.diffHash, JSON.stringify(walkthrough), walkthrough.createdAt)
`).run(
walkthrough.sessionId,
sourceKey,
walkthrough.diffHash,
JSON.stringify(walkthrough),
walkthrough.createdAt,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Migration } from '../migration-runner'

const migration: Migration = {
id: '202610081200-change-walkthrough-sources',
up(db) {
const columns = db.prepare('PRAGMA table_info(change_walkthroughs)').all() as Array<{ name: string }>
if (columns.some((column) => column.name === 'source_key')) {
return
}

db.run('DROP TABLE IF EXISTS change_walkthroughs')
db.run(`
CREATE TABLE change_walkthroughs (
session_id TEXT NOT NULL,
source_key TEXT NOT NULL,
diff_hash TEXT NOT NULL,
payload TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY (session_id, source_key)
)
`)
},
down(db) {
db.run('DROP TABLE IF EXISTS change_walkthroughs')
},
}

export default migration
2 changes: 2 additions & 0 deletions backend/src/db/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import migration027 from './027-multi-runs'
import migration202610061345 from './202610061345-schedule-workspace-mode'
import migration202610061700 from './202610061700-multi-run-fusions'
import migration202610061701 from './202610061701-change-walkthroughs'
import migration202610081200 from './202610081200-change-walkthrough-sources'

export const allMigrations: Migration[] = [
migration001,
Expand Down Expand Up @@ -61,4 +62,5 @@ export const allMigrations: Migration[] = [
migration202610061345,
migration202610061700,
migration202610061701,
migration202610081200,
]
5 changes: 3 additions & 2 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ const terminalService = new TerminalService(
new CredentialProvider(db),
() => getOpenCodeUpstreamBaseUrl(opencodeServerManager.getEffectiveServerHost()),
)
const projectConfigService = new ProjectConfigService(db, createGitService(gitAuthService), gitAuthService)
const gitService = createGitService(gitAuthService)
const projectConfigService = new ProjectConfigService(db, gitService, gitAuthService)
const scheduleWorktreeManager = new ScheduleWorktreeManager(gitAuthService, db)
const scheduleService = new ScheduleService(db, openCodeClient, scheduleWorktreeManager)
scheduleService.setWorktreeRemovedHandler(async (directory) => {
Expand Down Expand Up @@ -272,7 +273,7 @@ const sessionGoalService = new SessionGoalService(db, openCodeClient, sessionSet
sessionGoalService.loadOpenGoals()

const multiRunService = new MultiRunService(db, openCodeClient, repoWorkspaces, sessionPermissionModeService)
const changeWalkthroughService = new ChangeWalkthroughService(db, openCodeClient)
const changeWalkthroughService = new ChangeWalkthroughService(db, openCodeClient, sessionSettingsService, gitAuthService, gitService)

sseAggregator.onEvent((directory, event) => {
sessionPermissionModeService.handleEvent(directory, event).catch((err) => {
Expand Down
15 changes: 13 additions & 2 deletions backend/src/routes/change-walkthroughs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Hono } from 'hono'
import { GenerateChangeWalkthroughRequestSchema } from '@opencode-manager/shared/schemas'
import {
DEFAULT_WALKTHROUGH_SOURCE,
GenerateChangeWalkthroughRequestSchema,
parseWalkthroughSourceKey,
} from '@opencode-manager/shared/schemas'
import type { ChangeWalkthroughService } from '../services/change-walkthroughs'
import { handleServiceError, parseJsonBody } from '../utils/route-helpers'
import { ServiceError } from '../utils/service-error'
Expand All @@ -8,8 +12,15 @@ export function createChangeWalkthroughRoutes(service: ChangeWalkthroughService)
const app = new Hono()

app.get('/:sessionId', async (c) => {
const sourceParam = c.req.query('source')
const source = sourceParam === undefined ? DEFAULT_WALKTHROUGH_SOURCE : parseWalkthroughSourceKey(sourceParam)
if (!source) {
return c.json({ error: 'Invalid walkthrough source' }, 400)
}

try {
const state = await service.getState(c.req.param('sessionId'))
const hunksFor = c.req.query('hunksFor')
const state = await service.getState(c.req.param('sessionId'), source, hunksFor)
return c.json(state)
} catch (error) {
return handleServiceError(c, error, 'Failed to read change walkthrough', ServiceError)
Expand Down
27 changes: 19 additions & 8 deletions backend/src/services/assistant-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
fileExists,
ensureDirectoryExists,
} from './file-operations'
import { ASSISTANT_NOTIFICATION_LIMITS, OpenCodeConfigSchema, DEFAULT_KEYBOARD_SHORTCUTS, DEFAULT_LEADER_KEY } from '@opencode-manager/shared/schemas'
import { ASSISTANT_NOTIFICATION_LIMITS, AssistantSettingsPatchSchema, OpenCodeConfigSchema, DEFAULT_KEYBOARD_SHORTCUTS, DEFAULT_LEADER_KEY } from '@opencode-manager/shared/schemas'
import { MANAGER_COLOR_THEME_ID, OPENCODE_THEMES } from '@opencode-manager/shared/themes'
import { ASSISTANT_REPO_ID, ASSISTANT_REPO_PATH, ASSISTANT_OPENCODE_DIR_NAME } from '@opencode-manager/shared/utils'
import { getAssistantModePath, getReposPath } from '@opencode-manager/shared/config/env'
Expand Down Expand Up @@ -581,6 +581,23 @@ Sending is rate limited to **10 notifications per minute**. Beyond that the tool
`
}

type AssistantSettingsKey = keyof typeof AssistantSettingsPatchSchema.shape

const ASSISTANT_SETTINGS_KEY_NOTES: Partial<Record<AssistantSettingsKey, string>> = {
tts: 'Non-secret TTS preferences (`enabled`, `provider`, `autoPlay`, `voice`, `model`, `speed`). TTS must already be configured in the UI (the endpoint returns 400 otherwise).',
stt: 'Non-secret STT preferences (`enabled`, `provider`, `model`, `language`). STT must already be configured in the UI (the endpoint returns 400 otherwise).',
}

function buildAllowedSettingsKeysList(): string {
const keys = Object.keys(AssistantSettingsPatchSchema.shape) as AssistantSettingsKey[]
return keys
.map((key) => {
const note = ASSISTANT_SETTINGS_KEY_NOTES[key]
return note === undefined ? `- \`${key}\`` : `- \`${key}\` — ${note}`
})
.join('\n')
}

export function buildSettingsSkill(): string {
return `---
name: manager-settings
Expand Down Expand Up @@ -660,13 +677,7 @@ Update a subset of safe user preferences.

**Allowed Keys:**
The following preference keys can be modified:
- \`theme\`, \`colorTheme\`, \`mode\`, \`defaultModel\`, \`defaultAgent\`
- \`autoScroll\`, \`expandDiffs\`, \`expandToolCalls\`, \`showReasoning\`
- \`simpleChatMode\`, \`leaderKey\`, \`directShortcuts\`
- \`keyboardShortcuts\`, \`customCommands\`, \`notifications\`
- \`repoOrder\`, \`repoSortMode\`
- \`tts\` — Non-secret TTS preferences (\`enabled\`, \`provider\`, \`autoPlay\`, \`voice\`, \`model\`, \`speed\`). TTS must already be configured in the UI (the endpoint returns 400 otherwise).
- \`stt\` — Non-secret STT preferences (\`enabled\`, \`provider\`, \`model\`, \`language\`). STT must already be configured in the UI (the endpoint returns 400 otherwise).
${buildAllowedSettingsKeysList()}

**DO NOT attempt to set:**
- \`gitCredentials\` - Git credentials must be managed via the full UI
Expand Down
8 changes: 8 additions & 0 deletions backend/src/services/change-walkthrough-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ServiceError } from '../utils/service-error'

export class ChangeWalkthroughError extends ServiceError {
constructor(message: string, status: number, options?: { code?: string; details?: unknown }) {
super(message, status, options)
this.name = 'ChangeWalkthroughError'
}
}
Loading