Skip to content

fix(sessions): fold Cursor Tasks under Agent Sub-Sessions like Codex#262

Merged
wxtsky merged 3 commits into
wxtsky:mainfrom
zephyr110:fix/cursor-ide-subagent-session-fold
Jul 23, 2026
Merged

fix(sessions): fold Cursor Tasks under Agent Sub-Sessions like Codex#262
wxtsky merged 3 commits into
wxtsky:mainfrom
zephyr110:fix/cursor-ide-subagent-session-fold

Conversation

@zephyr110

@zephyr110 zephyr110 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Routing: Honor Agent Sub-Sessions (separate / merge / hide) for Cursor cursor / cursor-cli hooks whose transcript_path is under …/agent-transcripts/<parentId>/… while session_id is a child. Merge rewrites to the parent + agent_id; hide drops the child; separate (default) keeps a separate card — no forced merge.
  • Lifecycle: After Stop, record closed Task ids and drop idle orphan cards so a still-open IDE process cannot revive them. Keep the parent active while merged Tasks are still running. Deny/drop late Permission / Question / AskUserQuestion for closed Tasks. Relaunch on UserPromptSubmit / beforeSubmitPrompt. When splitting to separate, do not tail the parent transcript twice.
  • Hardening: Denying or answering a merged Task must not idle the parent card. An idle orphan must not overwrite a live merged Task. Only Task cards (transcript parent ≠ session id) self-close on Stop; parent chats keep cliPid. Persist closedSubagentIds. Settings copy mentions Cursor Task/subagent.
  • AppState teardown: Allow AppState deinit off the main actor (async XCTest / ARC). FSEvents callbacks hold a weak-boxed watcher so queued main-queue deliveries stay safe; avoids MainActor.assumeIsolated traps.
  • Tests: CursorSessionFoldingTests, AppStateCursorSubsessionTests, SessionPersistenceTests, AppStateDeinitTests.

中文说明

  • 路由: Cursor(cursor / cursor-cli)在「子 session_id + transcript 仍指向父对话」时,走同一套 Agent 子会话设置。合并 → 父 session + agent_id;隐藏 → 丢弃;默认独立显示,不强制合并。
  • 生命周期: Stop 后写入 closed,并丢掉空闲孤儿卡,避免 IDE 进程仍在时被拉活;父 turn 结束时若仍有合并中的 Task,父卡保持活跃;closed 后的晚到 Permission/Question/AskUserQuestion 直接拒绝或丢弃;新 prompt 可重新打开;拆成独立卡时不重复 tail 父 transcript。
  • 加固: 合并态 deny/回答不得把父卡打成 idle;空闲孤儿卡不得覆盖正在跑的合并 Task;仅 Task 卡(transcript 父 id ≠ 本卡 id)在 Stop 时自记录 closed,父会话保留 cliPid;持久化 closedSubagentIds;设置文案补充 Cursor Task/subagent。
  • AppState 销毁: 允许非主线程 deinit(异步 XCTest / ARC)。FSEvents 经弱引用 box 回调,避免 MainActor.assumeIsolated 崩溃。
  • 测试: 同上,另含 AppStateDeinitTests

Test Plan

  • DEVELOPER_DIR=/Applications/Xcode.app/Contents/DeveloperCursorSessionFoldingTests (24), AppStateCursorSubsessionTests (26), SessionPersistenceTests (6), AppStateDeinitTests (1) — all passed
  • Settings → Behavior → Agent Sub-Sessions: copy mentions Cursor; default Show separately / 独立显示 (verified: L10n + SettingsDefaults.pluginSessionMode == "separate")
  • Manual separate: Cursor Task → separate card; Stop is not revived while IDE stays open (unit coverage: separate leave/split + Stop self-tombstone + no IDE-ppid revive)
  • Manual merge: Task appears under the parent; parent stays active if Task still runs after parent AfterAgentResponse / Stop (unit coverage: fold + parent keeps active)
  • Manual hide: Task does not appear as a card (unit coverage: hide removes/clears cards)
  • Manual: after merged Task Stop, late Permission / Question / AskUserQuestion are denied/dropped; a new prompt relaunches cleanly (unit coverage: closed-agent deny/drop + UserPromptSubmit reopen)
  • Manual: deny a merged Task Permission — parent stays .running with tool Agent, not .idle (unit coverage: testMergedPermissionMarksSubagentWaitingApproval)
  • Confirm async XCTest suites that construct AppState no longer SIGTRAP on teardown (AppStateDeinitTests passed)

Cursor IDE Tasks use a child session_id while transcript_path still points at
the parent chat, which created duplicate cards. Honor separate/merge/hide,
keep closed subagent ids across Stop (and idle-orphan merge drops), deny late
Permission/AskUserQuestion/Question for closed agent ids, drop idle orphans
without resuscitating via a shared IDE pid, fill missing parent identity from
merge-first Task hooks, reopen Cursor Tasks via beforeSubmitPrompt after Stop,
keep the parent active while folded Tasks run, and avoid dual-tailing the
parent transcript when splitting to separate cards.
Keep folded deny/answer from idling the parent, drop idle orphans without
overwriting live Tasks, and only self-tombstone foldable separate Task cards.
Async XCTest teardown released AppState off MainActor and trapped in
assumeIsolated; use weak-boxed FSEvents teardown so discovery stays safe.
nguyenvanduocit pushed a commit to nguyenvanduocit/CodeIsland that referenced this pull request Jul 15, 2026
…atch added to T-073

No new commits to wxtsky/CodeIsland since v1.0.30 (Jul 10). One new open
PR wxtsky#262 (Jul 14): fix(appstate) ProjectsWatcherBox for FSEvents teardown
safety — noted as prerequisite for T-073 implementation. vibe-island
unchanged. No new GitHub issues created.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X1rG9TcKyFd2mpEVXb9n6k
nguyenvanduocit pushed a commit to nguyenvanduocit/CodeIsland that referenced this pull request Jul 16, 2026
Upstream quiet since v1.0.30 (Jul 10). One new watch item:
- T-076: panel intermittently shifts from notch to upper-left corner
  on macOS 27.0 beta (issue wxtsky#263, M5 Pro, no upstream fix yet)

Skip: PR wxtsky#264 (OMP), PR wxtsky#262 (Cursor Tasks), issue wxtsky#265 (Cursor) —
all non-Claude CLIs or Cursor-specific.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5RZPP2LLwKBjbXuqKxDjf
@wxtsky
wxtsky merged commit d041a14 into wxtsky:main Jul 23, 2026
@wxtsky

wxtsky commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Merged (d041a14) — impressive test coverage, and the merge-tree analysis confirmed it composes cleanly with #274. Review left three LOW-severity follow-up notes (none blocking, recorded here for visibility): (a) the new deinit path can invalidate rotationTimer/cleanupTimer/saveTimer off-main — moving those into the main-synced teardown closure would make the teardown uniformly main-thread; (b) closedSubagentIds has no eviction cap and is persisted — a keep-last-N bound would future-proof very long sessions; (c) the mayNeedCursor byte probe can trigger extra JSON parses on non-Cursor payloads containing both substrings (benign, perf-only). Happy to take follow-up PRs for any of these.

wxtsky added a commit that referenced this pull request Jul 23, 2026
Cursor's AskQuestion tool has no hook channel: the question is asked and
answered entirely inside Cursor's own UI while the registered hooks go
silent, so the session card sat on "thinking" forever and the question
never appeared. There is also no channel to answer from CodeIsland, so
this is a display-only wait derived from the agent transcript.

- JSONLTailer: recognize Cursor's role-keyed transcript lines
  ({"role":...,"message":{"content":[...]}}) via an O(1) role-prefix
  probe and derive a CursorQuestionSignal — pending when the trailing
  assistant entry carries an unanswered AskQuestion tool_use (args
  {title, questions[].prompt, options[].label}, snake_case or camelCase,
  run_async questions excluded), cleared when any newer entry supersedes
  it. Chat text is deliberately not extracted from this shape; hooks
  already stream it and the transcript copies differ cosmetically.
- AppState: apply the signal to main-agent Cursor sessions only
  (transcript parent == session id, so #262 folded Task cards are never
  touched): flip processing/running into .waitingQuestion with the
  question text, resume as .processing once the transcript moves on;
  hook activity resumes via the existing wasWaiting drain. Attach-time
  backfill recovers an already-stuck question after app restart or
  discovery (recency-gated to 30 min).
- SessionCard: show the question plus a localized "answer in Cursor"
  hint (7 languages) instead of the endless thinking indicator; play the
  approval chime on a fresh wait; seed the multi preview scenario.

Fixes #265

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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