Skip to content

fix(server): track OhMyPi child agent tasks from native tool updates - #18

Merged
Fryuni merged 3 commits into
mainfrom
t3code/fix-ohmypi-subagent-tracking
Sep 18, 2026
Merged

Fryuni merged 3 commits into
mainfrom
t3code/fix-ohmypi-subagent-tracking

Conversation

@Fryuni

@Fryuni Fryuni commented Sep 18, 2026

Copy link
Copy Markdown
Owner

What Changed

OhMyPi subagent work now surfaces in the UI even when it flows through the provider's native TaskTool payloads rather than the agent-hub API. The adapter inspects tool_call / tool_call_update output for task-shaped progress and result snapshots, folds them into stable child-task lifecycles (task.started / task.progress / task.completed), and links them to the originating tool call.

  • New OhMyPiAdapter logic: defensive parsing of progress entries, statusEvents, and job summaries; status mapping to completed/failed/stopped; fingerprint-based dedup so repeated updates don't re-emit; and result enrichment of a terminal snapshot's final output/usage.
  • Extended acp-mock-agent.ts with a T3_ACP_EMIT_OH_MY_PI_TASK_UPDATES mode reproducing the native batch-task update shapes.
  • Added a driver test that runs the mock through the real adapter and asserts the emitted lifecycle events, including foreground-eval and hub-wait shapes, and that ordinary tools are not promoted into tasks.

Why

OhMyPi child agents were invisible in the agent panel whenever the CLI reported them only via native tool updates, leaving users with no visibility into delegated work. Parsing this evidence at the adapter boundary (rather than trusting tool-call titles) keeps orchestration and the client side unchanged, and the strict entry parser plus fingerprint dedup avoids false positives and duplicate events.

UI Changes

Not applicable — no UI code changed. User-visible effect: OhMyPi child agents appear correctly in the existing agent panel for providers that only emit native task updates.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Generated with GLM (glm-5.3-flash) via opencode.

Copilot AI lite review requested due to automatic review settings September 18, 2026 01:30
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-18T02:45:59.629463Z a6c4c4d New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved moderate issues affect live progress, terminal results, and task completion.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds native OhMyPi task-update parsing so delegated child agents appear in existing lifecycle and agent-panel flows.

Changes:

  • Parses progress, status, result, and job snapshots.
  • Emits deduplicated child-task lifecycle events.
  • Adds mock-agent fixtures and driver coverage.
File summaries
File Summary Review findings
apps/server/src/provider/Layers/OhMyPiAdapter.ts Parses snapshots and emits child-task events. Moderate (1 vote): nested task updates may be suppressed by ACP coalescing. Moderate (2 votes): terminal result summaries may be discarded. Moderate (1 vote): several terminal status variants are not recognized. Nit (1 vote): tool changes are missing from deduplication.
apps/server/src/provider/Drivers/OhMyPiDriver.test.ts Tests lifecycle mapping and panel integration. No final comments.
apps/server/scripts/acp-mock-agent.ts Simulates native OhMyPi task updates. No final comments.
Review details

Suppressed comments (3)

apps/server/src/provider/Layers/OhMyPiAdapter.ts:961

  • This hook only receives ToolCallUpdated events that survive ACP's generic tool-update coalescer. That coalescer measures only top-level text fields, while these task snapshots live under rawOutput.details.progress/results/statusEvents/jobs, so repeated in-progress snapshots with unchanged tool detail are suppressed (up to the coalescing limit) and child status changes can remain invisible until the tool completes. Please route these native task updates through a snapshot-aware path or make the coalescer account for the nested task payload before relying on this hook for live progress.
                    yield* emitOhMyPiChildTaskEvents(ctx, event.toolCall);

apps/server/src/provider/Layers/OhMyPiAdapter.ts:257

  • childTaskStatus only recognizes completed, failed, aborted, and cancelled. Native task snapshots can also report terminal values such as success/succeeded, error, stopped, or killed (the analogous task mapper handles these), and those currently fall through to running, so the adapter emits task.progress forever and never completes/stops the child. Normalize the status before mapping and cover these terminal variants in the driver test.
function childTaskStatus(
  entry: OhMyPiTaskEntry,
): "pending" | "running" | "completed" | "failed" | "stopped" {
  if (entry.aborted || entry.status === "aborted" || entry.status === "cancelled") return "stopped";
  if (entry.status === "failed" || (entry.exitCode !== undefined && entry.exitCode !== 0))
    return "failed";
  if (entry.status === "completed" || entry.exitCode === 0) return "completed";
  return entry.status === "pending" ? "pending" : "running";

apps/server/src/provider/Layers/OhMyPiAdapter.ts:510

  • currentTool is omitted from the dedup fingerprint even though it is emitted as lastToolName below. Because the summary prefers lastIntent, a task can switch tools while status, intent, and usage stay unchanged; the identical fingerprint then suppresses the update and leaves the agent panel showing a stale last tool.
          entry.tokens ?? "",
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/server/src/provider/Layers/OhMyPiAdapter.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f8e7ab4bc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/provider/Layers/OhMyPiAdapter.ts Outdated
Comment thread apps/server/src/provider/Layers/OhMyPiAdapter.ts Outdated
Copilot AI review requested due to automatic review settings September 18, 2026 02:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved moderate issues affect lifecycle cleanup, validation, status mapping, false-positive prevention, and event payload size.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (6)

Previously missed (1) — in code that hasn't changed since the last review.

apps/server/src/provider/Layers/OhMyPiAdapter.ts:148

  • RuntimeTaskUsage is validated downstream with NonNegativeInt, but this helper accepts fractional values. A malformed native snapshot such as tokens: 1.5 or durationMs: 2.5 will therefore produce an invalid typedUsage payload and can fail when the runtime event is decoded or persisted, despite the parser being intended to be defensive. Restrict these numeric fields to finite integers before emitting them.

apps/server/src/provider/Layers/OhMyPiAdapter.ts:463

  • This map retains one mutable state object for every child task ID and is never pruned until the entire provider session stops. Long-lived OhMyPi sessions that delegate repeatedly will accumulate titles, fingerprints, and terminal states indefinitely. Keep active tasks separate from a bounded terminal dedup/enrichment cache, or otherwise evict completed entries.
        const existing = ctx.childTasks.get(stateKey);
        const state: OhMyPiChildTaskState = existing ?? {
          taskId,
          turnId: ctx.activeTurnId,
          toolUseId: toolCall.toolCallId,
          title: candidateTitle,
          role: candidateRole,
          started: false,
          terminal: false,
          fingerprint: undefined,
        };
        ctx.childTasks.set(stateKey, state);

apps/server/src/provider/Layers/OhMyPiAdapter.ts:271

  • This mapper treats every unrecognized status as running, so native values such as success/succeeded, error, stopped, or killed never reach the required terminal completed/failed/stopped states. The existing task lifecycle mapper handles these aliases (see apps/server/src/provider/acp/XAiBackgroundTasks.ts:35-56); normalize the OhMyPi status before falling back to running, otherwise a finished child can remain active in the panel.
function childTaskStatus(
  entry: OhMyPiTaskEntry,
): "pending" | "running" | "completed" | "failed" | "stopped" {
  if (entry.aborted || entry.status === "aborted" || entry.status === "cancelled") return "stopped";
  if (entry.status === "failed" || (entry.exitCode !== undefined && entry.exitCode !== 0))
    return "failed";
  if (entry.status === "completed" || entry.exitCode === 0) return "completed";
  return entry.status === "pending" ? "pending" : "running";

apps/server/src/provider/Layers/OhMyPiAdapter.ts:223

  • This promotes any tool output containing details.jobs with a type: "task" entry, even when the enclosing tool is not OhMyPi's hub-wait envelope. An ordinary tool can therefore create task.started/task.completed events, contrary to the false-positive guard covered by this PR; gate this branch on the hub-wait discriminator (for example details.op === "wait") or another equivalent native marker.
  const jobValues = Array.isArray(details.jobs)
    ? details.jobs
        .filter((value) => isUnknownRecord(value) && value.type === "task")
        .map((value) => ({
          ...value,
          id: nonEmptyString(value.agentUrlId) ?? value.id,
          agent: nonEmptyString(value.type) ?? "task",
          task: nonEmptyString(value.label),
          output: nonEmptyString(value.resultText),
          error: nonEmptyString(value.errorText),
          status: value.status === "cancelled" ? "aborted" : value.status,
        }))
    : [];

apps/server/src/provider/Layers/OhMyPiAdapter.ts:736

  • The callback forces every matching native update through the ACP event queue, but these snapshots keep child output/stderr and progress arrays nested under rawOutput.details. The ACP raw-output bounding only limits top-level fields (content, stdout, stderr, output), so a busy batch can repeatedly send and persist large cumulative nested snapshots; bound/strip those nested fields before emission or keep a separate lightweight task-update path.
            shouldEmitToolCallUpdate: (toolCall) =>
              parseOhMyPiTaskSnapshot(toolCall.data.rawOutput) !== undefined,

apps/server/src/provider/Layers/OhMyPiAdapter.ts:883

  • The new child-task map is never drained when stopSessionInternal closes the ACP session (lines 661-678), so children that were running when the provider is stopped receive no task.completed with status: "stopped". This leaves persisted agent rows without a terminal lifecycle and can make them reappear as running after the session is resumed; the Claude adapter explicitly completes its live tasks during shutdown (apps/server/src/provider/Layers/ClaudeAdapter.ts:4099-4117). Emit stopped completions for non-terminal entries before removing the session.
            childTasks: new Map(),
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread apps/server/src/provider/Layers/OhMyPiAdapter.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c7c21c9605

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/provider/Layers/OhMyPiAdapter.ts Outdated
Copilot AI review requested due to automatic review settings September 18, 2026 02:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

apps/server/src/provider/Layers/OhMyPiAdapter.ts:301

  • childTaskStatus treats every status outside pending/completed/failed/aborted/cancelled as running, so native terminal values such as success/succeeded, error, stopped, or killed never emit task.completed when no exit code is present. Normalize the provider's terminal aliases here (as the other native task mapper does) before falling back to running.
    apps/server/src/provider/Layers/OhMyPiAdapter.ts:492
  • ctx.childTasks retains every completed child forever; only state.terminal changes and no entry is ever removed or bounded. Long-lived sessions that delegate repeatedly will therefore accumulate one state and fingerprint per child, so please add a bounded/pruned terminal cache (while retaining enough state to deduplicate late result snapshots).
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@Fryuni
Fryuni enabled auto-merge September 18, 2026 02:26

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7c25ce644

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

continue;
}
const terminal = status === "completed" || status === "failed" || status === "stopped";
const terminalSummary = entry.abortReason ?? entry.error ?? entry.output ?? entry.stderr;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prefer failure diagnostics over successful output

When a failed or stopped child result contains both output and stderr—for example, partial stdout followed by a nonzero exit and a diagnostic on stderr—this ordering selects output as the terminal summary. The client treats a failed completion's summary as its error, so the agent panel displays the partial output and hides the actual failure diagnostic. Select error/stderr ahead of output for failed and stopped statuses while retaining output precedence for successful completions.

Useful? React with 👍 / 👎.

- Parse TaskTool progress/results/statusEvents/jobs from ACP tool calls into task.started/progress/completed runtime events
- Deduplicate updates via status fingerprints and prevent non-terminal regressions after completion
- Add mock agent scenario and driver test covering lifecycles and panel model
Copilot AI review requested due to automatic review settings September 18, 2026 02:42
@Fryuni
Fryuni force-pushed the t3code/fix-ohmypi-subagent-tracking branch from e7c25ce to a6c4c4d Compare September 18, 2026 02:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved issues remain with unbounded child-task retention and incomplete terminal-status normalization.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

apps/server/src/provider/Layers/OhMyPiAdapter.ts:492

  • childTasks retains every observed child forever and is only discarded when the whole provider session stops. A long-lived session that repeatedly delegates work will therefore grow this map without bound even though terminal entries are no longer needed for shutdown; keep active tasks separate from bounded terminal/enrichment state, or otherwise prune completed IDs once late result enrichment is no longer possible.
        ctx.childTasks.set(stateKey, state);
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +297 to +301
if (entry.aborted || entry.status === "aborted" || entry.status === "cancelled") return "stopped";
if (entry.status === "failed" || (entry.exitCode !== undefined && entry.exitCode !== 0))
return "failed";
if (entry.status === "completed" || entry.exitCode === 0) return "completed";
return entry.status === "pending" ? "pending" : "running";
@Fryuni
Fryuni merged commit 071ec5b into main Sep 18, 2026
9 checks passed
@Fryuni
Fryuni deleted the t3code/fix-ohmypi-subagent-tracking branch September 18, 2026 02:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants