Commit Graph

1011 Commits

Author SHA1 Message Date
bellman 779e2d2f10 fix(task): capture late-arriving sessionId so TUI subagent entry is clickable (#4252)
The OpenCode TUI renders background subagent session entries using
`props.metadata.sessionId` as the navigation target. When the wait-loop
in delegate-task and background-task tools exits before the session is
assigned, but the session is created moments later (before metadata
publish), the published metadata had `sessionId: undefined`, leaving
the TUI entry stuck spinning with no clickable target.

Add a single late-fallback `manager.getTask(task.id)?.sessionId` check
between the wait-loop exit and metadata publish in both paths. This
closes the narrow race window that produced the symptom in #4252.

Regression test: `late-session-id-capture.test.ts` mocks the exact race
(launch returns no sessionId; getTask returns it after the wait loop).

Verified:
- npm run build: PASS
- npm test: exit code 0 (one unrelated pre-existing failure in
  sisyphus-task > browserProvider propagation re: agent-browser skill)
2026-05-24 02:04:38 +09:00
YeonGyu-Kim 01d2196220 Merge pull request #4238 from islee23520/fix/look-at-status-map-hang
Avoid look_at status map wait hang
2026-05-23 01:55:07 +09:00
YeonGyu-Kim d17b2127f2 fix(tools/grep, tools/glob): use Node-safe subprocess streaming (#3919)
OpenCode Desktop v1.14.41+ runs OMO inside a Node.js utility process.
The previous `new Response(proc.stdout).text()` call is Bun-/Web-API-specific
and crashed the Desktop sidecar on Windows when grep/glob were invoked.

Switch glob/grep cli to the new process-stream-reader + search-process-output
helpers. Behavior on Bun and CLI/Linux/macOS is unchanged. ripgrep auto-download,
PowerShell fallback, and rgSemaphore are preserved.

Fixes #3919

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-22 20:40:06 +09:00
YeonGyu-Kim 4ea7562f50 feat(shared): add Node-safe process stream reader and search output collector
Introduces:
- src/shared/process-stream-reader.ts: Buffer-concat stream reader compatible with both Bun and Node ChildProcess stdout (replaces Web Response API usage)
- src/tools/shared/search-process-output.ts: structured subprocess output collector with timeout, kill, and rejection cleanup
- bun-spawn-shim hardened: Node path forces windowsHide: true; spawn errors no longer escape as unhandledRejection

Foundation for #3919 fix.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-22 20:39:52 +09:00
YeonGyu-Kim 11c3da752c fix(default-mode,multimodal-looker,delegate-task): preserve user-expected behavior
default-mode (system-transform):
- e5463e2db introduced auto-activation of ultrawork+ralph-loop, and
  dc2e082ac then skipped the ultrawork system prompt whenever ralph_loop
  was also enabled. Net effect: the keyword-detector still showed
  'Default ultrawork mode enabled' to the user, but the first turn had
  none of the ultrawork behavior. Loop continuation kept the ultrawork
  prefix, so the contract was honored only on later iterations.
- Drop the skip so the initial turn matches what the toast advertises.
  New matrix test pins all four (ultrawork, ralph_loop) combinations.

multimodal-looker:
- Prompt claimed 'read' and 'call_omo_agent' were available, but the
  look_at invocation runtime explicitly disables both via READ_ENABLED
  and createAgentToolAllowlist([]). Small VL models trusted the prompt
  and looped on rejected tool calls (#4116).
- Rewrite the agent prompt to describe direct-attachment analysis and
  forbid tool/agent calls. Add a consistency test that extracts the
  prompt's 'available tools' claim and compares it against the
  configured allowlist.

delegate-task (skill-resolver):
- 088693697 filtered per-agent restricted skills at the skill tool and
  builtin agent prompt layers, but delegate-task itself happily injected
  whatever skill name a caller passed. A target agent could be force-fed
  a skill marked agent: oracle just by listing it in load_skills.
- Thread the target agent through resolveSkills and silently filter
  skills whose definition.agent does not include it. Public skills with
  no restriction are unaffected. Regression test pins the bypass.
2026-05-22 00:07:46 +09:00
YeonGyu-Kim bc0da0fad3 test: fix prometheus-prompt syntax + sync display name casing to lowercase
- prometheus-prompt.test.ts: close missing }) on the OpenSpec expanded
  commands describe block (introduced by d66b6bcbf, parse error).
- agent-sort-shim/agent-config-integration/continuation-injection/
  unstable-agent-babysitter/subagent-resolver/sync-executor/
  resolve-caller-team-lead tests: expect 'Sisyphus - ultraworker'
  (lowercase) to match production after cd39f8858, which lowercased the
  display name to dodge a TUI ZWSP rendering glitch. Legacy uppercase
  inputs that exercise the normalization path are preserved.
- sync-executor.ts + resolve-caller-team-lead.ts: route legacy display
  name inputs through normalizeAgentForPrompt so prompt agent names and
  caller team lead lookups produce the canonical lowercase form.
2026-05-22 00:07:46 +09:00
YeonGyu-Kim 4ea76365cd refactor(packages): extract hashline-core package
Move the hash-anchored edit core (hash computation, validation, edit operations, text normalization, chunk formatter, diff utilities, and a runtime-aware xxHash32 binding) into a new @oh-my-opencode/hashline-core workspace package.

The src/tools/hashline-edit/ surface becomes a set of thin re-export shims, so existing import paths in the plugin keep working while the pure logic lives behind a stable package boundary that has no opencode runtime dependencies.

Tests: bun test packages/hashline-core src/tools/hashline-edit
2026-05-21 16:19:38 +09:00
ilseob lee c4a51bee23 Cover look_at permanently absent session output 2026-05-21 16:03:35 +09:00
YeonGyu-Kim 6d15ab86ab fix(background-agent): fail cancellation when abort fails 2026-05-21 15:50:28 +09:00
ilseob lee b3a195d662 Avoid look_at status map wait hang 2026-05-21 15:48:03 +09:00
YeonGyu-Kim 6e1e01ebe0 Merge pull request #4219 from sjawhar/fix/skill-discovery-opencode-config
fix(skill-discovery): load native OpenCode skills in task delegation
2026-05-21 15:07:39 +09:00
YeonGyu-Kim b2971a6ac3 Merge pull request #2827 from z-traveler/feat/per-agent-skill-filtering
feat: filter agent-restricted skills from prompts and tool description
2026-05-21 13:07:30 +09:00
YeonGyu-Kim 3d40264456 Merge pull request #4228 from code-yeongyu/fix/delegate-stale-activity
Fix delegate stale timeout activity checks
2026-05-21 12:59:11 +09:00
YeonGyu-Kim bd55396266 fix(look-at): avoid empty stable idle completion 2026-05-21 12:54:56 +09:00
YeonGyu-Kim 036e04c15b fix(look-at): allow stable idle after sync prompt 2026-05-21 12:32:55 +09:00
ZeyuFu 860c663c80 fix-up(#4027): narrow coordinator guard to registry hard-reject set
Maintainer feedback (#4071 review): the original guard rejected
sisyphus and atlas as subagent targets even from team-mode where
resolveMember() intentionally calls resolveSubagentExecution with
allowPrimaryAgentDelegation: true. Per AGENT_ELIGIBILITY_REGISTRY
(src/features/team-mode/types.ts), only prometheus is hard-reject;
sisyphus and atlas are explicitly verdict: 'eligible' for team
membership.

Shrink COORDINATOR_AGENT_NAMES to ['prometheus'] so the guard
aligns with the registry's authoritative classification, document
the scoping rule in a comment, and add regression tests covering:

- sisyphus is NOT blocked by the coordinator guard (registry eligible)
- atlas is NOT blocked by the coordinator guard (registry eligible)
- prometheus IS blocked even when allowPrimaryAgentDelegation: true
  (registry hard-reject is authoritative)

Fixes the 5 zauc-mocks resolver tests that were locking in the
wrong rejection set (including 'allows delegating to a primary
agent when allowPrimaryAgentDelegation is enabled'). The one test
asserting the literal primary-agent error string for Prometheus
display-name was loosened to a regex that accepts either guard's
message, since prometheus is now caught by the coordinator path
which fires before the primary-agent lookup.
2026-05-20 22:05:47 -04:00
ZeyuFu 7af3007e67 fix(team-mode): reject coordinator agents as subagent targets (#4027)
Agents could select coordinator/meta agents (Prometheus, Atlas,
Sisyphus/Ultraworker) as subagent targets via task() / delegation,
producing duplicate orchestration loops and conflicting team state.
This is the inverse of #3987 / #4065 — symmetric guard on the
delegation TARGET side, using the same AGENT_ELIGIBILITY_REGISTRY
classification.

Add a runtime guard at the delegation entry point that rejects
task() calls whose subagent_type resolves to an agent marked as
hard-reject / coordinator-only in the eligibility registry, with
an actionable error naming the agent. Regression test asserts a
prometheus-targeted delegation is rejected before any subagent
session spawns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 22:05:47 -04:00
Sami Jawhar 9c4ae26945 fix(skill-discovery): load native OpenCode skills in task delegation 2026-05-20 16:22:01 +00:00
YeonGyu-Kim abcc46f44a fix: update pollSessionUntilIdle -> waitForLookAtSessionResult import 2026-05-21 00:57:36 +09:00
YeonGyu-Kim d0719ff7b7 Merge pull request #3875 from jollyxenon/fix/3846-opencode-config-dir-additive
fix(config): align OPENCODE_CONFIG_DIR with additive OpenCode semantics
2026-05-21 00:48:00 +09:00
YeonGyu-Kim 85d03298b9 Merge pull request #4098 from sjawhar/feat/look-at-async
fix(look-at): rework session poller to wait for assistant response, not just idle
2026-05-21 00:15:22 +09:00
YeonGyu-Kim aa5eeaf892 Merge pull request #4102 from sjawhar/fix/skill-directory-param
fix(skill): pass directory to getAllSkills + fix async test timing
2026-05-21 00:08:40 +09:00
YeonGyu-Kim c6d754d389 Merge pull request #4115 from PeterPonyu/fix/3726-glob-grep-broken-symlinks
fix(glob,grep): tolerate broken symlinks and non-fatal I/O warnings (#3726)
2026-05-21 00:03:53 +09:00
YeonGyu-Kim 8276eb6c53 Merge pull request #4121 from mguttmann/fix-4119
fix(delegate-task): default run_in_background and load_skills instead of throwing (fixes #4119)
2026-05-21 00:01:12 +09:00
YeonGyu-Kim 3e9c3f8ada Merge pull request #4146 from LYY/fix/skill-shortname-fallback
fix: support unambiguous short skill names in delegated skill loading
2026-05-20 23:42:44 +09:00
YeonGyu-Kim 791825fc20 Merge pull request #4153 from MoerAI/fix/fallback-model-string-guard
fix(shared,delegate-task,claude-code-agent-loader): guard model parsers against non-string input (fixes #4145)
2026-05-20 23:40:33 +09:00
YeonGyu-Kim d3e218f912 fix(prompt): treat post-dispatch failures as accepted 2026-05-20 11:42:32 +09:00
Sami Jawhar 33c8bcd850 fix(look-at): address Oracle review findings on async session poller 2026-05-19 12:24:59 +00:00
Sami Jawhar d2d1541377 fix(skill): pass directory to getAllSkills and fix async test timing 2026-05-19 12:24:59 +00:00
YeonGyu-Kim bcea4a9d28 fix(prompt-gate): harden sync and team prompt dispatch 2026-05-19 19:18:13 +09:00
YeonGyu-Kim 1492bffd20 fix(prompt-gate): harden internal prompt dispatch 2026-05-19 19:18:13 +09:00
Claude Agent 9b151a2551 fix(delegate-task): address Oracle review on PR #4121 — preserve explicit-null reject + rewrite continuation test
Two blockers from the maintainer's Oracle review on PR #4121:

Blocker 1 — load_skills=null should still throw, omitted should default

The previous PR collapsed both `loadSkills === undefined` and
`loadSkills === null` into a silent default of `[]`. The closing
rationale of PR #1663 (which reverted PR #1493) and the maintainer's
review both call out the importance of preserving the distinct
"omitted -> default, explicit invalid -> throw" contract. `null`
strongly signals "I tried to pass something and it was wrong";
silently coercing it hides bugs upstream.

Restored the split: `undefined` -> default `[]` + log,
`null` -> throw with the historical error string.

Blocker 2 — task_id continuation test rewritten, not deleted

The original PR removed the `task_id without run_in_background ->
throws` test entirely. The behavior IS preserved (default false ->
`isExplicitSyncRun` true -> `executeSyncContinuation`), but with the
test gone the new contract was unprotected.

Added a regression test that asserts the new contract: when
`task_id` is present and `run_in_background` is omitted,
`tool.execute` must route through sync continuation without throwing
the legacy required-parameter error. Mocks include `session.abort`
because the sync poller calls it during shutdown.

Also flipped the existing `load_skills=null` regression test from
"normalizes to []" back to "throws with the legacy error string" to
match the restored contract.

Tests:
- bun test src/tools/delegate-task/tools.test.ts -> 132/132 pass
- bun test src/tools/delegate-task/ -> 406/406 pass
- bun run typecheck -> clean
2026-05-19 09:13:08 +02:00
Claude Agent 2f16a7da9f fix(delegate-task): default run_in_background and load_skills instead of throwing (fixes #4119)
Sisyphus and other delegators occasionally invoke the task() tool without
an explicit run_in_background or load_skills argument. The runtime
validators in tool-argument-preparation.ts threw a hard Error in that
case, which short-circuited tool.execute() entirely. Because OpenCode's
tool.execute.after hook only runs on returned results, the
delegate-task-retry hook never had a chance to attach corrective
guidance — so the model saw a raw failure and either burned several
retries or fell back to a synchronous Explore call, silently losing
parallel execution.

Behavior change:
- run_in_background omitted -> defaults to false (sync delegation), with
  a log entry for observability.
- load_skills omitted or null -> normalized to [] with a log entry on
  the explicit-null path.
- The Zod schema entries are now .optional() and their .describe()
  strings declare the defaults honestly; the markdown tool description
  was updated to match (no more 'REQUIRED' lie).

The orthogonal validation 'Must provide either category or
subagent_type.' is unchanged and still surfaces as a returned error.

Tests:
- The five throw-on-missing tests in tools.test.ts are rewritten to
  assert the new default-and-proceed contract.
- The 'no category, no subagent_type' test now asserts the
  missing-target error remains intact.

Refs the workaround the reporter validated in the original issue body;
matches the design from PR #2375 which was previously reverted by
566031f4.
2026-05-19 09:13:08 +02:00
YeonGyu-Kim e57bac3b6b fix(prompt-retry): preserve async holds without blocking validation fallbacks 2026-05-19 13:48:33 +09:00
YeonGyu-Kim d8f6d59dc2 docs: update rules and MCP inventories
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 21:28:12 +09:00
YeonGyu-Kim a86cc6af18 refactor(tools): remove native ast-grep tool
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 21:27:36 +09:00
MoerAI ae0c106ed4 fix(shared,delegate-task,claude-code-agent-loader): guard model parsers against non-string input (fixes #4145)
After the 4.2.0 unified-dispatch refactor (a42f894f / df198d8b / fee515c5 / 989ab717 / dd3fecaf / 1bbe065c / 12bd6580), at least one caller in the new prompt-async-gate path forwards a FallbackModelObject (or some other non-string shape) into parsers that statically claim 'model: string'. The downstream .trim() call then throws 'model.trim is not a function', which rejects the session.processor promise and surfaces as 'Aborted process' + UI 'interrupted'. The issue (#4145) reports this aborts 90% of subagent dispatches across every provider on 4.2.0 + opencode 1.15.4.

This patch adds a 'typeof x !== "string"' runtime guard at the four parser entrypoints called from the dispatch path:

- src/shared/fallback-chain-from-models.ts :: parseVariantFromModel, parseFallbackModelEntry

- src/tools/delegate-task/model-string-parser.ts :: parseVariantFromModelID, parseModelString

- src/shared/model-string-parser.ts (duplicate file with same API) :: parseVariantFromModelID, parseModelString

- src/features/claude-code-agent-loader/claude-model-mapper.ts :: mapClaudeModelString

Each parser now returns undefined / { modelID: "" } for non-string input instead of throwing. This unblocks subagent dispatch and leaves the underlying caller bug for a follow-up.

Regression coverage: three new tests in src/shared/fallback-chain-from-models.test.ts pin the non-string behavior (object, null/undefined, number). Existing 38 tests still pass. Total: 41/41 green, typecheck clean.
2026-05-18 19:22:33 +09:00
LYY 47fced7473 fix: address review findings - git-master identity check, test fixtures, regression strength 2026-05-18 17:17:31 +08:00
LYY 77997d8e74 fix(skill-loader): support unambiguous short skill names 2026-05-18 17:17:31 +08:00
Z 088693697a feat: filter agent-restricted skills from prompts and tool description
Skills with an `agent` frontmatter field are intended for a specific
agent. Previously they still appeared in:
- every agent's system prompt (via `buildAvailableSkills`)
- the `skill` tool's `<available_items>` description visible to all agents

This wasted tokens and could mislead agents into attempting calls that
would be rejected at execution time.

Changes:
- `buildAvailableSkills`: new optional `agentName` parameter; when
  provided, skills whose `definition.agent` does not match are excluded
- `builtin-agents.ts`: pass per-agent name to `buildAvailableSkills`
  for sisyphus, hephaestus, and atlas, so each agent's prompt only
  lists the skills it is allowed to use
- `createSkillTool` (`tools.ts`): exclude agent-restricted skills from
  both the eager and lazy description builds, keeping the shared tool
  description free of skills the current agent cannot access

Execution-time enforcement (throwing on mismatch) is unchanged; this
change adds the earlier, description-level visibility gate.

Tests: new `available-skills.test.ts` (5 cases) + 3 new cases in
`tools.test.ts` covering the description-filter and execute paths.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-18 17:02:37 +08:00
YeonGyu-Kim 7187db6ede chore(mcp): move lsp submodule under packages 2026-05-18 16:00:40 +09:00
YeonGyu-Kim fdf7ba2e6f Merge pull request #4129 from code-yeongyu/feature/stage-c-lsp-mcp
Extract LSP tools into Tier-1 MCP server via lsp-tools-mcp submodule
2026-05-18 13:39:17 +09:00
YeonGyu-Kim 00e6c82409 Merge pull request #4117 from ririnto/fix/plan-subagent-hidden-registry
fix(delegate-task): restore hidden plan subagent delegation
2026-05-18 13:30:35 +09:00
YeonGyu-Kim 9560e93235 docs: correct tool directory counts and lsp alias wording
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-18 13:21:25 +09:00
YeonGyu-Kim b149ce5331 docs: update AGENTS docs for MCP-backed LSP architecture 2026-05-18 12:24:44 +09:00
YeonGyu-Kim ca51f613ca refactor(tools): remove native LSP tool registry wiring 2026-05-18 12:22:54 +09:00
YeonGyu-Kim d95a45c872 chore: add lsp-tools-mcp submodule at vendor/lsp-tools-mcp 2026-05-18 12:22:54 +09:00
YeonGyu-Kim 34e6af1ae6 docs: update AGENTS guidance 2026-05-18 11:49:56 +09:00
ririnto a163068507 fix(delegate-task): restore hidden plan delegation 2026-05-18 00:53:40 +09:00
ZeyuFu 8bba7357b1 fix(glob,grep): keep exit-code gate at >1 — --no-messages alone is enough
Addresses cubic-dev-ai P1 + P2 findings on #4115.

The original PR relaxed `exitCode > 1` to `> 2` based on the (wrong)
claim that ripgrep exits 2 only on non-fatal I/O issues. ripgrep
actually uses exit code 2 for BOTH fatal errors (pattern syntax,
invalid args) AND non-fatal I/O issues; GNU grep (the fallback backend
in grep/cli.ts) likewise uses 2 for fatal errors. So `> 2` would
silently suppress fatal errors.

The correct fix is just `--no-messages`, which suppresses ripgrep's
stderr only for soft I/O issues (broken symlinks, permission denied)
while leaving fatal-error messages intact. With the gate kept at
`exitCode > 1 && stderr.trim()`:

- Broken symlink: ripgrep exits 2, stderr is empty (suppressed) →
  `stderr.trim()` is falsy → gate fails → partial results survive.
- Fatal error: ripgrep exits 2, stderr has the real error message
  (not suppressed by --no-messages) → gate triggers → error returned.

Reverting both `exitCode > 1` → `> 2` changes; keeping the
`--no-messages` flag additions and the regression test (test comment
updated to describe the cleaner architecture).

Verification: bun test src/tools/glob/ src/tools/grep/ → 30 pass / 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:21:28 -04:00