Commit Graph

588 Commits

Author SHA1 Message Date
Choi Kijin / 최 기진 / チョイ キジン 25548f2561 fix(model-fallback): retry forbidden provider errors
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-28 15:29:33 +09:00
Choi Kijin / 최 기진 / チョイ キジン 034744cbf2 fix(model-error-classifier): retry forbidden provider errors
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-22 18:38:38 +09:00
YeonGyu-Kim fe44363bf8 fix(model-capabilities): drop pdf modality from gpt-5.4-mini-fast
OpenAI's mini-fast variant only accepts text and image input; advertising
pdf risks unsupported requests hitting runtime errors.
2026-04-21 13:38:23 +09:00
YeonGyu-Kim d2e5ddd73d fix(model-requirements): route primary agents to mini-fast
Use gpt-5.4-mini-fast as the primary runtime model for librarian and explore.\nKeep the fallback chain intact so older providers still resolve.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-21 13:29:55 +09:00
YeonGyu-Kim 680dd161b4 fix(model-capabilities): bundle gpt-5.4-mini-fast caps
Keep the supplemental OpenAI model available when the bundled snapshot omits it.\nMerge its capabilities at runtime so downstream model resolution can use it.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-21 13:29:49 +09:00
Sisyphus 017e4ef1c7 Merge pull request #3514 from andomeder/fix/cli-attach-auth 2026-04-19 02:38:19 +09:00
William Obino ef5c74e972 fix(cli): inject server auth for attach clients 2026-04-18 17:49:05 +03:00
YeonGyu-Kim e35ac38bbf test(tmux): rewrite stale-sweep tests via DI to eliminate cross-file mock leak
Oracle flagged that the previous test file monkey-patched process.kill
and relied on mock.module for 5 modules. Running it after manager.test.ts
in the same Bun process reproduced 2 failures - the test resolution of
`./session-kill` specifier interacted badly with manager.test.ts's
`../../shared/tmux` barrel mock.

Solution: refactor stale-session-sweep.ts to expose
`sweepStaleOmoAgentSessionsWith(deps)` that accepts a SweepDeps record
(isInsideTmux, getTmuxPath, listCandidateSessions, killSession,
processAlive, currentPid, log). The public `sweepStaleOmoAgentSessions()`
still uses runtime-built deps so call sites are unchanged.

The test file now imports the pure function directly and constructs a
fixture with fake deps. Zero mock.module calls, zero process.kill
patching, zero cache-bust dynamic imports. 8 tests (up from 6) run
deterministically in any order with any neighbor.

Before: combined run with manager.test.ts = 2 fail, 50 pass.
After:  combined run with manager.test.ts = 0 fail, 54 pass.
2026-04-18 21:00:34 +09:00
YeonGyu-Kim d1fc46da42 test(tmux): restore process.kill in afterEach to prevent cross-file leak
Oracle noted that loadSweeper() monkey-patches process.kill without
ever restoring it. Added afterEach hook to set process.kill back to the
captured original. Individual file runs already passed, and
script/run-ci-tests.ts confirms the full CI suite - 4781 pass, 0 fail
across 491 files - but this makes the test file safe under non-isolated
local runs as well.
2026-04-18 20:51:24 +09:00
YeonGyu-Kim 104523051d feat(tmux): sweep stale omo-agents-<pid> sessions on first spawn
Follow-up to PR #3507 addressing the Oracle-noted operational limitation:
per-PID isolated session names (getIsolatedSessionName(process.pid)) mean
that when an opencode process is SIGKILL'd (or the machine hard-reboots),
the old omo-agents-<old-pid> tmux session survives forever because nothing
is around to kill it.

Added sweepStaleOmoAgentSessions() that:
1. Lists tmux sessions matching /^omo-agents-(\d+)$/
2. For each, checks process.kill(pid, 0) to detect a dead PID
3. Skips our own PID
4. Calls killTmuxSessionIfExists for every session whose owner process is gone

Wired into TmuxSessionManager.onSessionCreated() as a one-shot (guarded by
staleSweepCompleted flag) so it runs lazily on the first subagent spawn when
isolation="session". The flag is reset in cleanup() so subsequent process
restarts re-run the sweep.

6 new tests cover: outside-tmux no-op, no matching sessions, multiple dead
PIDs, current PID skip, live PID skip, list-sessions failure.

Manual E2E verified on real tmux:
- Created omo-agents-99999, sweep killed it
- Spawned our own omo-agents-<pid>, closeTmuxPane returned true even after
  pane auto-destroy from Ctrl+C
- Final tmux list-sessions shows zero omo-agents-* orphans
2026-04-18 20:22:00 +09:00
YeonGyu-Kim 257b6cf951 fix(tmux): scope isolated session name per plugin instance (Oracle review)
Oracle flagged the previous commit: "omo-agents" was a shared constant,
so when two plugin instances ran in the same tmux server they wrote into
the same session. One instance's cleanup would then kill-session on the
shared name and tear down the other instance's live attached panes.

Replace the const ISOLATED_SESSION_NAME with getIsolatedSessionName(pid)
which defaults to process.pid, so every opencode process owns its own
"omo-agents-<pid>" session. spawnTmuxSession and cleanup both resolve
the name through this helper. Discovery is straightforward from the
host tmux via 'tmux list-sessions | grep omo-agents-'.

Manager test covers two concurrent managers and asserts each kills a
per-pid session name, proving they no longer collide on a global name.
2026-04-18 19:47:36 +09:00
YeonGyu-Kim ea4f3c81f4 fix(tmux): treat pane-already-closed as success in closeTmuxPane
After send-keys C-c the subprocess running inside the pane (for example
"opencode attach") exits on SIGINT, which causes tmux to destroy the
pane automatically. The subsequent kill-pane then returns exit 1 with
stderr "can't find pane: %NN" even though the end state is exactly
what we wanted.

Before this fix closeTmuxPane reported failure for that branch, which
kept TmuxSessionManager's retryPendingCloses loop marking the (now
deleted) pane as still-pending forever and left stale entries behind
in the tracked sessions map. This is the behavior the user observed
as "screen opens, streaming runs, but cleanup doesn't finish" when
running with tmux.isolation="session".

Now we detect the "can't find pane" stderr and return true, treating
the auto-destroy path the same as an explicit successful kill.
2026-04-18 19:34:54 +09:00
YeonGyu-Kim de8a0167e6 feat(tmux): add killTmuxSessionIfExists utility for explicit session teardown
Adds killTmuxSessionIfExists(sessionName), a best-effort no-op when the
named session is absent. Drains both stdio streams so it does not leak
pipe buffers the way closeTmuxPane historically did.

Also exports ISOLATED_SESSION_NAME ("omo-agents") from session-spawn so
callers can tear down the shared isolated session without hard-coding
the name in multiple places.
2026-04-18 19:31:22 +09:00
YeonGyu-Kim 2a99a524ea fix(tmux): drain kill-pane stdout to prevent pipe backpressure hang
closeTmuxPane spawned kill-pane with stdout: "pipe" but never drained the
stream, which could leave the subprocess hanging indefinitely when tmux
wrote anything to stdout (for example under --force-close race conditions).

- send-keys now uses stdout: "ignore" so there is no pipe to drain
- kill-pane keeps the pipe but drains stdout/stderr alongside proc.exited
- switch imports to the new spawn-process helper so the behavior is
  covered by hermetic tests that mock the spawn boundary
2026-04-18 19:31:04 +09:00
Sisyphus e47a6aa7f6 Merge branch 'fix/perf-q10' into fix/perf-omo-in-tree 2026-04-18 14:43:37 +09:00
Sisyphus 38990ec76f Merge branch 'fix/perf-q02' into fix/perf-omo-in-tree 2026-04-18 14:43:37 +09:00
YeonGyu-Kim 6dc2234d89 fix(shared): memoize detectPluginConfigFile per process
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-18 14:12:55 +09:00
YeonGyu-Kim 948343ab66 test(shared): cover detectPluginConfigFile memoization
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-18 14:12:50 +09:00
Sisyphus 79eb6c738f fix(shared/project-discovery-dirs): memoize detectWorktreePath per process 2026-04-18 14:11:24 +09:00
YeonGyu-Kim ac2686ffde fix(shared): memoize loadOpencodePlugins by directory
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-18 14:10:45 +09:00
YeonGyu-Kim 428bae632c test(shared): cover loadOpencodePlugins memoization
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-18 14:09:46 +09:00
Sisyphus 32598bc5e1 test(shared/project-discovery-dirs): cover worktree-path memoization 2026-04-18 14:09:17 +09:00
Sisyphus 49c7d4dbf9 chore(shared): add EXCLUDED_DIRS constant for recursive FS scans
Introduces a frozen Set<string> of directory basenames (node_modules, .git,
dist, build, .next, .sisyphus, .omx, .turbo, coverage, out, .cache,
.vscode-test, target, .local-ignore) that callers performing recursive
filesystem scans should skip.

This is shared infrastructure for upcoming fixes in rules-injector,
command-discovery, and claude-code-command-loader that currently descend
into node_modules and other junk directories, causing slow plugin init
and slow edit loops when the plugin is launched in-tree.
2026-04-18 14:04:45 +09:00
YeonGyu-Kim 1a8f60b89e Merge pull request #3488 from chan1103/fix/explore-allow-lsp-ast-grep
fix(explore): allow LSP and ast-grep tools
2026-04-18 03:28:10 +09:00
YeonGyu-Kim 1b5f3167eb Merge pull request #3492 from code-yeongyu/refactor/legacy-plugin-decoupling
refactor: modernize plugin entry to V1 format and decouple legacy/tightly-coupled code
2026-04-18 03:10:14 +09:00
YeonGyu-Kim 70ddc01e10 refactor: remove AI slop from refactored files
Behavior-preserving cleanup of AI-generated code smells in 5 files authored/moved by this PR:

- src/hooks/model-fallback/fallback-state-controller.ts (-47/+47 net reorganization, redundant defensiveness removed)
- src/shared/model-string-parser.ts (-4 LOC obvious-comment cleanup)
- src/shared/ripgrep-cli.ts (-13 LOC obvious comments + redundant defensive checks)
- src/tools/delegate-task/tool-description.ts (-6 LOC)
- src/tools/look-at/look-at-input-preparer.ts (-6 LOC)

Targets: obvious comments that restate code, over-defensive null checks on guaranteed values, redundant existence checks. No public API signatures changed, no type hints removed, no new abstractions introduced. Full test suite still passes.
2026-04-18 03:01:51 +09:00
YeonGyu-Kim 81b37dd2cc refactor: remove cosmetic OhMyOpenCodePlugin references
Post-V1-migration cleanup of the removed symbol's ghost references:

- src/index.ts: log prefix '[OhMyOpenCodePlugin]' -> '[oh-my-openagent]'
- src/index.test.ts: describe label 'OhMyOpenCodePlugin' -> 'oh-my-openagent plugin module'
- src/index.telemetry.test.ts: describe label 'OhMyOpenCodePlugin telemetry isolation' -> 'oh-my-openagent telemetry isolation'
- src/shared/log-legacy-plugin-startup-warning.ts: log prefix '[OhMyOpenCodePlugin]' -> '[legacy-migration]' (plus matching test assertion)

After these renames 'grep -rn OhMyOpenCodePlugin src/' returns zero matches. Pure cosmetic rename, no behavior change.
2026-04-18 02:52:10 +09:00
YeonGyu-Kim e6f84f713b refactor(tools): break glob->grep sibling-tool coupling
Hoist shared ripgrep CLI resolution helpers (resolveGrepCli, resolveGrepCliWithAutoInstall, GrepBackend, DEFAULT_RG_THREADS, ResolvedCli) out of src/tools/grep/constants.ts into src/shared/ripgrep-cli.ts so they no longer straddle two sibling tool directories.

Before: src/tools/glob/constants.ts re-exported from src/tools/grep/constants.ts, violating the project's "tools should not import from sibling tools" rule enforced by .sisyphus/rules/modular-code-enforcement.md.

After: both src/tools/glob/ and src/tools/grep/ consume the shared helpers from src/shared/ripgrep-cli.ts. src/tools/grep/constants.ts keeps only the grep-specific UI-exposed constants.
2026-04-18 02:38:24 +09:00
YeonGyu-Kim db056346d2 refactor(shared): move parseModelString out of delegate-task to break cross-tool coupling
Move parseModelString into src/shared so callers can depend on a neutral module instead of reaching into delegate-task internals.

Cross-tool coupling violates module boundaries, and this keeps call-omo-agent plus runtime-fallback from importing through a sibling tool.
2026-04-18 01:51:26 +09:00
YeonGyu-Kim 5759a9c503 docs(agents): refresh AGENTS.md hierarchy via /init-deep
Updated root + 14 core subdirectory AGENTS.md files to reflect current
state (commit 2892ca4a on dev). Added 4 new AGENTS.md files for gap
directories: hooks/comment-checker (AI slop blocker), features/claude-
code-plugin-loader (CC compat layer), features/claude-code-mcp-loader
(tier 2 MCP loader), cli/doctor (health diagnostics with 25 check files).
2026-04-18 01:21:20 +09:00
chan1103 73f09fdb37 fix(explore): allow LSP and ast-grep tools 2026-04-17 16:42:43 +09:00
YeonGyu-Kim 5478bab457 refactor(models): preserve legacy claude-opus-4-6 aliases and category mapping
Addresses review feedback on #3486:

1. claude-thinking-legacy-alias now matches both claude-opus-4-6-thinking
   and claude-opus-4-7-thinking, canonicalizing both to claude-opus-4-7.
   The previous diff retargeted the regex to 4-7 only, which dropped
   backward compatibility for users still pinned to the 4-6 thinking
   suffix.

2. MODEL_TO_CATEGORY_MAP keeps the claude-opus-4-6 to unspecified-high
   entry alongside the new 4-7 entry. The map is order-independent from
   MODEL_VERSION_MAP, so preserving the 4-6 key avoids relying on a
   specific migration ordering for legacy agent configs.

3. Fix stale 'Claude Opus 4.6' labels and BDD test comments that the
   sed-based bump missed.
2026-04-17 15:35:11 +09:00
YeonGyu-Kim 4ca4c06698 refactor(migration): auto-upgrade claude-opus-4-5 and 4-6 to claude-opus-4-7
MODEL_VERSION_MAP now chains the legacy claude-opus-4-5 entry straight
to claude-opus-4-7 and adds an explicit claude-opus-4-6 to 4-7 bump
path, letting existing user configs upgrade on next load without an
intermediate 4-6 stop.

MODEL_TO_CATEGORY_MAP picks up claude-opus-4-7 as the canonical
unspecified-high model (prior 4-6 entry is covered by the chained
version map above, so legacy hardcoded configs still resolve).

Migration tests rewritten to reflect the chained 4-5 to 4-7 behavior
and the new 4-6 to 4-7 bump path, including the sidecar-union
scenario.
2026-04-17 14:52:02 +09:00
YeonGyu-Kim def44338ff refactor(models): bump claude-opus-4-6 to claude-opus-4-7 across fallback chains, categories, and hooks
Updates the canonical Anthropic Opus model in every fallback chain
(sisyphus, oracle, prometheus, metis, momus, visual-engineering,
ultrabrain, deep, artistry, unspecified-high), the unspecified-high
category default, the think-mode HIGH_VARIANT_MAP, the Claude Code
alias map, the claude-thinking legacy alias, the context-limit GA
regex, and event.ts fallback strings.

Widens supportsCachedAnthropicLimit to accept both claude-*-4-6 and
claude-*-4-7 so the 1M context cache still applies across the bump.

Regenerates the bundled model-capabilities snapshot from models.dev
and the model-fallback snapshot to match the new source output.
2026-04-17 14:51:52 +09:00
YeonGyu-Kim 7bc170fb86 fix: installer writes hyphenated anthropic IDs, variant=max Anthropic OAuth compat (#3429, #3459) 2026-04-16 14:28:56 +09:00
YeonGyu-Kim 0764526aca fix(posthog): disable exception autocapture to stay within free tier
Error tracking exceeded 100K free tier limit (188K in 5 days).

Top exceptions were mostly noise:
- ProviderModelNotFoundError: 75K (user config issues)
- EPIPE/EOF/stream destroyed: 40K (normal pipe closures)
- ENOSPC: 3K (user disk space issues)

Manual captureException() for critical errors in runner.ts
is preserved. Only automatic unhandled exception capture is
disabled.
2026-04-15 15:30:40 +09:00
YeonGyu-Kim e5d3fe96c4 fix(agents): address all PR #2299 code review findings
Blocking fixes:
- B1: Return empty restrictions for unknown/custom agents instead of
  EXPLORATION_AGENT_DENYLIST, allowing custom agents full tool access
- B2: Use Object.create(null) consistently across all 5 agent-loading
  result objects to prevent prototype pollution
- B3: Add code comment documenting custom agent bash access trust model
- B4: Mock getOpenCodeConfigDir in opencode-config-agents-reader tests
  to prevent global config dir leakage

Non-blocking fixes:
- N1: Use resolveAgentDefinitionPaths with project boundary enforcement
  in opencode-config-agents-reader for path containment
- N2: Add session-scoped 30s TTL cache to resolveCallableAgents to
  avoid redundant SDK IPC calls per tool invocation
- N3: Extract shared parseToolsConfig into src/shared/parse-tools-config.ts
  replacing 4 duplicated local implementations
- N4: Add .min(1) to AgentDefinitionPathSchema rejecting empty paths
- N5: Add resolve-agent-definition-paths.test.ts covering tilde expansion,
  relative paths, boundary enforcement, and null containmentDir
- N6: Validate agent mode against allowed values instead of bare type
  assertion in opencode-config-agents-reader
2026-04-15 10:58:16 +09:00
Brandon Webb fd28f7e668 feat(agents): add agent_definitions schema, eager path resolution, and JSON agent loader
Wave 1 of agent definitions enhancement (PR #2299):

Schema & Configuration:
- Add agent_definitions field to oh-my-opencode config schema
- Support list of file paths to .md or .json agent definition files
- Add to PARTIAL_STRING_ARRAY_KEYS for Set-union merge semantics
- Implement eager path resolution in loadPluginConfig() before merging

Path Resolution:
- Create resolve-agent-definition-paths.ts helper
- User-level paths resolve from ~/.config/opencode/ (no containment)
- Project-level paths resolve from project root (with containment check)
- Homedir expansion, absolute/relative path handling

JSON Agent Loader:
- Create parseJsonAgentFile() for .json/.jsonc agent definitions
- Validate required fields (name, prompt)
- Support tools as string (comma-separated) or array
- Map model via mapClaudeModelToOpenCode()
- Comprehensive test suite (7 test cases, all passing)

Type Extensions:
- Extend AgentScope: add 'definition-file' and 'opencode-config'
- Add AgentJsonDefinition interface for JSON agent schema

All automated checks passing:
- lsp_diagnostics clean on all changed files
- json-agent-loader.test.ts: 7/7 passing
- Full typecheck: zero new errors
- QA evidence saved to .sisyphus/evidence/
2026-04-15 10:57:54 +09:00
Brandon Webb 1e85a88db0 fix(agent-restrictions): restore EXPLORATION_AGENT_DENYLIST as default fallback for unknown agents 2026-04-15 10:57:17 +09:00
YeonGyu-Kim 1d8f8a03ca Merge pull request #3437 from code-yeongyu/fix/bug-batch-2
fix: Git Bash shell detection, legacy agent name resolution, backup spam
2026-04-15 10:53:46 +09:00
YeonGyu-Kim 62c60ae9d8 fix: numeric skill names, ultrawork missing run_in_background, ZWSP agent lookups
- #3354: Coerce data.name to String in loadSkillFromPath/loadSkillFromPathAsync
  to prevent crash when YAML parses numeric skill names (e.g., name: 12306)

- #3416: Add required run_in_background parameter to all task() examples in
  ultrawork prompts (default, gpt, gemini, planner) to match tool schema

- #3379/#3417/#3418/#3337/#3335: Strip ZWSP (U+200B) before agent name
  comparisons in agent-tool-restrictions, sync-prompt-sender, tool-execute-after,
  tool-execute-before, oracle-verification-detector, call-omo-agent,
  recovery-prompt-config, and agent-variant to prevent ZWSP-prefixed display
  names from breaking exact-match lookups
2026-04-15 10:46:41 +09:00
YeonGyu-Kim 0dab3116b7 fix: resolve 3 bugs (#3366, #3272, #3222)
- shell-env: detect Git Bash via MSYSTEM env var when SHELL is unset (#3366)
  On some Git Bash installations SHELL is not set but MSYSTEM (MINGW64/MSYS)
  is always present. Check MSYSTEM before PSModulePath to avoid emitting
  PowerShell syntax in bash shells.

- session-state: resolve legacy agent names in resolveRegisteredAgentName (#3272)
  Historical sessions stored agent names like 'Sisyphus (Ultraworker)' which
  don't match the current registered format. Fall back to getAgentConfigKey
  for legacy/parenthesized name resolution before returning the raw name.

- config-migration: skip backup when file content is unchanged (#3222)
  Compare serialized config with existing file content before creating a
  timestamped .bak file. Only create backup when the on-disk content
  actually differs from the migrated content.
2026-04-15 10:43:44 +09:00
YeonGyu-Kim b525055590 fix(telemetry): guard PostHog init failures 2026-04-14 02:50:50 +09:00
Matan Kushner 569addd3b0 docs(provider): add comments to vercel transform logic 2026-04-13 14:05:29 +09:00
Matan Kushner effae16c14 refactor(provider): replace per-model string replacements with generic regex
Claude version transforms now use a single regex
claude-(\w+)-(\d+)-(\d+) -> claude-$1-$2.$3 instead of one
.replace() per model. New claude models need zero changes.
2026-04-13 14:01:17 +09:00
Matan Kushner 34e334eabe feat(provider): add vercel to all gateway-supported fallback entries
Adds vercel to every fallback entry where the model exists on the
gateway (kimi-k2.5, glm-5, glm-4.6v, minimax-m2.7, grok-code-fast-1,
gpt-5-nano). Adds inferSubProvider mappings for minimax, moonshotai,
and zai. Updates librarian/explore special cases to prefer minimax
via gateway over claude-haiku.
2026-04-13 13:44:50 +09:00
Matan Kushner 542dc890f9 fix(provider): use gateway-specific model IDs for vercel transform
The gateway uses google/gemini-3-flash (no -preview suffix) unlike the
direct Google API. Give the vercel provider its own transform instead
of delegating to sub-provider transforms blindly.
2026-04-13 12:51:56 +09:00
Matan Kushner dac0c99e8c feat: add Vercel AI Gateway as a provider
Adds 'vercel' as a recognized provider throughout the model resolution
system, enabling users with Vercel AI Gateway to use it as a universal
fallback for OpenAI, Anthropic, and Google models.

The gateway transform infers the sub-provider from canonical model names
(claude->anthropic, gpt->openai, gemini->google) and delegates to the
appropriate provider-specific transform, producing model strings like
vercel/anthropic/claude-opus-4.6.
2026-04-13 11:22:22 +09:00
YeonGyu-Kim 051ab840d6 Merge pull request #3348 from code-yeongyu/refactor/ulw-repo-cleanup-20260411
refactor: simplify nullish guards and remove dead no-op paths
2026-04-12 18:04:43 +09:00
YeonGyu-Kim c750781be4 fix(shared): avoid false-positive skill path resolution on npm scoped packages (#2857)
The @scope/path regex incorrectly matched npm scoped package references
like 'require(\"@scope/pkg\")' or '--package=@scope/pkg'. Added context
filtering to exclude matches preceded by npm/import indicators.

🤖 Generated with OhMyOpenCode assistance
https://github.com/code-yeongyu/oh-my-opencode
2026-04-12 02:28:46 +09:00