Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,21 @@
datasourceTemplate: 'github-releases',
extractVersionTemplate: '^bun-v(?<version>.*)$',
},
{
customType: 'regex',
managerFilePatterns: ['/src\\/shared\\/constants\\.ts/'],
matchStrings: ["DEFAULT_SYSTEMATIC_VERSION = '(?<currentValue>\\d+\\.\\d+\\.\\d+)'"],
depNameTemplate: '@fro.bot/systematic',
datasourceTemplate: 'npm',
},
],
packageRules: [
{matchFileNames: ['.github/workflows/**'], semanticCommitType: 'ci'},
{matchDatasources: ['docker'], semanticCommitType: 'build'},
{
matchPackageNames: [
'@bfra.me/tsconfig',
'@fro.bot/systematic',
'@opencode-ai/sdk',
'@semantic-release/git',
'anomalyco/opencode',
Expand Down
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,22 +362,23 @@ concurrency:

### Action Inputs

| Input | Required | Default | Description |
| ------------------- | -------- | ---------- | -------------------------------------------------- |
| `github-token` | Yes | — | GitHub token with write permissions |
| `auth-json` | Yes | — | JSON object mapping LLM providers to credentials |
| `prompt` | No | — | Custom prompt for the agent |
| `agent` | No | `Sisyphus` | Agent to use (must be primary agent, not subagent) |
| `model` | No | — | Model override in `provider/model` format |
| `timeout` | No | `1800000` | Execution timeout in milliseconds (0 = no limit) |
| `opencode-version` | No | `1.2.24` | OpenCode CLI version for installation |
| `session-retention` | No | `50` | Number of sessions to retain before pruning |
| `s3-backup` | No | `false` | Enable S3 write-through backup |
| `s3-bucket` | No | — | S3 bucket name (required if `s3-backup` is true) |
| `aws-region` | No | — | AWS region for S3 bucket |
| `skip-cache` | No | `false` | Skip cache restore (useful for debugging) |
| `omo-config` | No | — | Custom oMo configuration JSON (deep-merged) |
| `opencode-config` | No | — | Custom OpenCode configuration JSON (deep-merged) |
| Input | Required | Default | Description |
| -------------------- | -------- | ---------- | -------------------------------------------------- |
| `github-token` | Yes | — | GitHub token with write permissions |
| `auth-json` | Yes | — | JSON object mapping LLM providers to credentials |
| `prompt` | No | — | Custom prompt for the agent |
| `agent` | No | `Sisyphus` | Agent to use (must be primary agent, not subagent) |
| `model` | No | — | Model override in `provider/model` format |
| `timeout` | No | `1800000` | Execution timeout in milliseconds (0 = no limit) |
| `opencode-version` | No | `1.2.24` | OpenCode CLI version for installation |
| `systematic-version` | No | `2.1.0` | Systematic plugin version for OpenCode |
| `session-retention` | No | `50` | Number of sessions to retain before pruning |
| `s3-backup` | No | `false` | Enable S3 write-through backup |
| `s3-bucket` | No | — | S3 bucket name (required if `s3-backup` is true) |
| `aws-region` | No | — | AWS region for S3 bucket |
| `skip-cache` | No | `false` | Skip cache restore (useful for debugging) |
| `omo-config` | No | — | Custom oMo configuration JSON (deep-merged) |
| `opencode-config` | No | — | Custom OpenCode configuration JSON (deep-merged) |

### Action Outputs

Expand Down
3 changes: 3 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ inputs:
omo-version:
description: oMo (Oh My OpenAgent) version to install if auto-setup is needed. Defaults to version pinned in source code if not set.
required: false
systematic-version:
description: Systematic plugin version to register with OpenCode. Defaults to version pinned in source code if not set.
required: false
skip-cache:
description: Skip session cache restore (default false)
required: false
Expand Down
116 changes: 58 additions & 58 deletions dist/artifact-vQ1nBsQQ.js → dist/artifact-DnDoCHSt.js

Large diffs are not rendered by default.

28 changes: 14 additions & 14 deletions dist/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/post.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/features/agent/opencode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,7 @@ describe('ensureOpenCodeAvailable', () => {
githubToken: 'ghs_test_token',
authJson: '{"anthropic": {"api_key": "sk-ant-test"}}',
omoVersion: '3.7.4',
systematicVersion: '2.1.0',
omoProviders: {
claude: 'no',
copilot: 'no',
Expand Down Expand Up @@ -1075,6 +1076,7 @@ describe('ensureOpenCodeAvailable', () => {
githubToken: 'ghs_test_token',
authJson: '{"anthropic": {"api_key": "sk-ant-test"}}',
omoVersion: '3.7.4',
systematicVersion: '2.1.0',
omoProviders: {
claude: 'no',
copilot: 'no',
Expand Down
2 changes: 2 additions & 0 deletions src/features/agent/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export interface EnsureOpenCodeOptions {
githubToken: string
authJson: string
omoVersion: string
systematicVersion: string
omoProviders: SetupInputs['omoProviders']
opencodeConfig: string | null
}
Expand All @@ -92,6 +93,7 @@ export async function ensureOpenCodeAvailable(options: EnsureOpenCodeOptions): P
opencodeConfig: options.opencodeConfig,
omoConfig: null,
omoVersion: options.omoVersion,
systematicVersion: options.systematicVersion,
omoProviders: options.omoProviders,
}
const setupResult = await runSetup(setupInputs, options.githubToken)
Expand Down
36 changes: 35 additions & 1 deletion src/harness/config/inputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,45 @@ describe('parseActionInputs', () => {
})

const result = parseActionInputs()

expect(result.success).toBe(true)
expect(result.success && result.data.timeoutMs).toBe(300000)
})

it('parses systematic-version input with default value', () => {
const mockGetInput = core.getInput as ReturnType<typeof vi.fn>

mockGetInput.mockImplementation((name: string) => {
const inputs: Record<string, string> = {
'github-token': 'ghp_test123',
'auth-json': '{"anthropic":{"type":"api","key":"sk-ant-test"}}',
}
return inputs[name] ?? ''
})

const result = parseActionInputs()

expect(result.success).toBe(true)
expect(result.success && result.data.systematicVersion).toBe('2.1.0')
})

it('parses custom systematic-version input', () => {
const mockGetInput = core.getInput as ReturnType<typeof vi.fn>

mockGetInput.mockImplementation((name: string) => {
const inputs: Record<string, string> = {
'github-token': 'ghp_test123',
'auth-json': '{"anthropic":{"type":"api","key":"sk-ant-test"}}',
'systematic-version': '2.2.0',
}
return inputs[name] ?? ''
})

const result = parseActionInputs()

expect(result.success).toBe(true)
expect(result.success && result.data.systematicVersion).toBe('2.2.0')
})

it('accepts zero timeout for no limit', () => {
const mockGetInput = core.getInput as ReturnType<typeof vi.fn>

Expand Down
5 changes: 5 additions & 0 deletions src/harness/config/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
DEFAULT_OMO_VERSION,
DEFAULT_OPENCODE_VERSION,
DEFAULT_SESSION_RETENTION,
DEFAULT_SYSTEMATIC_VERSION,
DEFAULT_TIMEOUT_MS,
} from '../../shared/constants.js'
import {err, ok} from '../../shared/types.js'
Expand Down Expand Up @@ -190,6 +191,9 @@ export function parseActionInputs(): Result<ActionInputs, Error> {
const omoVersionRaw = core.getInput('omo-version').trim()
const omoVersion = omoVersionRaw.length > 0 ? omoVersionRaw : DEFAULT_OMO_VERSION

const systematicVersionRaw = core.getInput('systematic-version').trim()
const systematicVersion = systematicVersionRaw.length > 0 ? systematicVersionRaw : DEFAULT_SYSTEMATIC_VERSION

const omoProvidersRaw = core.getInput('omo-providers').trim()
const omoProviders = parseOmoProviders(omoProvidersRaw.length > 0 ? omoProvidersRaw : DEFAULT_OMO_PROVIDERS)

Expand Down Expand Up @@ -228,6 +232,7 @@ export function parseActionInputs(): Result<ActionInputs, Error> {
opencodeVersion,
skipCache,
omoVersion,
systematicVersion,
omoProviders,
opencodeConfig,
dedupWindow,
Expand Down
1 change: 1 addition & 0 deletions src/harness/phases/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export async function runBootstrap(bootstrapLogger: Logger): Promise<BootstrapPh
githubToken: inputs.githubToken,
authJson: inputs.authJson,
omoVersion: inputs.omoVersion,
systematicVersion: inputs.systematicVersion,
omoProviders: inputs.omoProviders,
opencodeConfig: inputs.opencodeConfig,
})
Expand Down
45 changes: 40 additions & 5 deletions src/services/setup/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function createSetupInputs(overrides: Partial<SetupInputs> = {}): SetupInputs {
opencodeConfig: null,
omoConfig: null,
omoVersion: '3.7.4',
systematicVersion: '2.1.0',
omoProviders: {
claude: 'no',
copilot: 'no',
Expand Down Expand Up @@ -244,7 +245,7 @@ describe('setup', () => {
expect(core.exportVariable).toHaveBeenCalledWith('GH_TOKEN', 'ghs_test_token')
})

it('exports OPENCODE_CONFIG_CONTENT with autoupdate:false baseline', async () => {
it('exports OPENCODE_CONFIG_CONTENT with autoupdate:false baseline and Systematic plugin', async () => {
// #given - no opencode-config input
vi.mocked(tc.find).mockReturnValue('/cached/opencode/1.0.300')
vi.mocked(exec.getExecOutput).mockResolvedValue({exitCode: 0, stdout: '', stderr: ''})
Expand All @@ -257,7 +258,10 @@ describe('setup', () => {
await runSetup(createSetupInputs(), 'ghs_test_token')

// #then
expect(core.exportVariable).toHaveBeenCalledWith('OPENCODE_CONFIG_CONTENT', JSON.stringify({autoupdate: false}))
expect(core.exportVariable).toHaveBeenCalledWith(
'OPENCODE_CONFIG_CONTENT',
JSON.stringify({autoupdate: false, plugins: ['@fro.bot/systematic@2.1.0']}),
)
})

it('merges user opencode-config input on top of OPENCODE_CONFIG_CONTENT baseline', async () => {
Expand All @@ -275,10 +279,38 @@ describe('setup', () => {
'ghs_test_token',
)

// #then - user config wins on conflicting keys (autoupdate:true overrides false baseline)
// #then - user config wins on conflicting keys; Systematic plugin appended
expect(core.exportVariable).toHaveBeenCalledWith(
'OPENCODE_CONFIG_CONTENT',
JSON.stringify({autoupdate: true, model: 'claude-opus-4-5'}),
JSON.stringify({autoupdate: true, model: 'claude-opus-4-5', plugins: ['@fro.bot/systematic@2.1.0']}),
)
})

it('preserves user-provided Systematic plugin entry without appending a duplicate', async () => {
// #given - user already pins Systematic in opencode-config
vi.mocked(tc.find).mockReturnValue('/cached/opencode/1.0.300')
vi.mocked(exec.getExecOutput).mockResolvedValue({exitCode: 0, stdout: '', stderr: ''})
vi.mocked(exec.exec).mockResolvedValue(0)
vi.mocked(fs.writeFile).mockResolvedValue()
vi.mocked(fs.mkdir).mockResolvedValue(undefined)
vi.mocked(fs.access).mockRejectedValue(new Error('not found'))

// #when
await runSetup(
createSetupInputs({
opencodeConfig: '{"plugins": ["custom-plugin@1.0.0", "@fro.bot/systematic@9.9.9"]}',
systematicVersion: '2.1.0',
}),
'ghs_test_token',
)

// #then - existing Systematic entry wins and is not duplicated
expect(core.exportVariable).toHaveBeenCalledWith(
'OPENCODE_CONFIG_CONTENT',
JSON.stringify({
autoupdate: false,
plugins: ['custom-plugin@1.0.0', '@fro.bot/systematic@9.9.9'],
}),
)
})

Expand Down Expand Up @@ -331,7 +363,10 @@ describe('setup', () => {
// #then
expect(result).not.toBeNull()
expect(core.setFailed).not.toHaveBeenCalled()
expect(core.exportVariable).toHaveBeenCalledWith('OPENCODE_CONFIG_CONTENT', JSON.stringify({autoupdate: false}))
expect(core.exportVariable).toHaveBeenCalledWith(
'OPENCODE_CONFIG_CONTENT',
JSON.stringify({autoupdate: false, plugins: ['@fro.bot/systematic@2.1.0']}),
)
})

it('fails when opencode-config is an array', async () => {
Expand Down
10 changes: 10 additions & 0 deletions src/services/setup/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,16 @@ export async function runSetup(inputs: SetupInputs, githubToken: string): Promis
Object.assign(ciConfig, parsed)
}

// Ensure the Systematic plugin is always registered in the plugins array.
// User-provided plugins via opencode-config are preserved; Systematic is appended
// only when no @fro.bot/systematic entry already exists (so user version pins win).
const systematicPlugin = `@fro.bot/systematic@${inputs.systematicVersion}`
const rawPlugins: unknown[] = Array.isArray(ciConfig.plugins) ? (ciConfig.plugins as unknown[]) : []
const hasSystematic = rawPlugins.some((p: unknown) => typeof p === 'string' && p.startsWith('@fro.bot/systematic'))
if (!hasSystematic) {
ciConfig.plugins = [...rawPlugins, systematicPlugin]
}

core.exportVariable('OPENCODE_CONFIG_CONTENT', JSON.stringify(ciConfig))

if (!toolsCacheResult.hit) {
Expand Down
1 change: 1 addition & 0 deletions src/services/setup/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface SetupInputs {
readonly opencodeConfig: string | null
readonly omoConfig: string | null
readonly omoVersion: string
readonly systematicVersion: string
readonly omoProviders: OmoProviders
}

Expand Down
1 change: 1 addition & 0 deletions src/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const DEFAULT_OPENCODE_VERSION = '1.3.6'
export const DEFAULT_BUN_VERSION = '1.3.11'
export const DEFAULT_OMO_VERSION = '3.14.0'
export const DEFAULT_OMO_PROVIDERS = ''
export const DEFAULT_SYSTEMATIC_VERSION = '2.1.0'

// Retry configuration
export const RETRY_DELAYS_MS = [30_000, 60_000, 120_000] as const
Expand Down
1 change: 1 addition & 0 deletions src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface ActionInputs {
readonly opencodeVersion: string
readonly skipCache: boolean
readonly omoVersion: string
readonly systematicVersion: string
// oMo provider configuration
readonly omoProviders: OmoProviders
// OpenCode config to merge with baseline
Expand Down
Loading