Skip to content

feat(tasks): live view-mode switching for subagent conversations (v2) - #1313

Merged
griffinwork40 merged 4 commits into
mainfrom
afk/tasks-v2-live-view
Aug 28, 2026
Merged

feat(tasks): live view-mode switching for subagent conversations (v2)#1313
griffinwork40 merged 4 commits into
mainfrom
afk/tasks-v2-live-view

Conversation

@griffinwork40

Copy link
Copy Markdown
Owner

Summary

Implements v2 live view-mode switching for the /tasks feature, stacking on #1312 (the v1 always-on JSONL logging + page-and-return viewer).

What's new in v2

1. viewingTaskId on InteractiveCtx

Added viewingTaskId?: string to InteractiveCtx (shared.ts). When set, the REPL is in task-view mode tracking the subagent being viewed.

2. Live view-mode switching (task-view-mode.ts — new file)

  • enterTaskViewMode() — clears screen, renders conversation history, starts live tailing for running subagents, wires Esc to exit
  • exitTaskViewMode() — clears viewingTaskId, repaint status line, emits return notice
  • renderTaskViewHeader() — builds status header with ID, type, and colored status badge
  • buildTaskFooterLine() — footer message indicating running vs completed state + Esc hint

3. Live tailing

For running subagents, enterTaskViewMode subscribes to handle.session.getOutputStream() (an AsyncIterable<OutputEvent>) and appends new events in real-time via formatOutputEvent. Stops when the stream is exhausted or Esc is pressed.

4. Esc to return

Wired via ctx.setSoftStopHandler — the existing REPL surface seam. Esc during any view (running or completed) calls exitTaskViewMode and clears the handler.

5. Arrow-key navigation in /tasks list

/tasks now shows a cursor-navigable list:

  • ↑/↓ arrows move the cursor
  • Enter opens the selected task's view via enterTaskViewMode
  • Esc returns to the main prompt
  • Falls back to the plain v1 list when setSoftStopHandler is not available (non-TTY surfaces)

Files changed

File Change
src/cli/commands/interactive/shared.ts Added viewingTaskId?: string to InteractiveCtx
src/cli/commands/interactive/task-view-mode.ts New — live view-mode logic (142 code lines)
src/cli/commands/interactive/task-view.test.ts New — 17 tests for view-mode functions
src/cli/slash/commands/tasks.ts Updated /tasks (cursor nav) and /tasks:view (live mode)

Gates

  • pnpm lint
  • pnpm audit:filesize:check ✓ (all files < 350 code lines)
  • pnpm scan:env:check
  • pnpm exec vitest run src/cli/slash/commands/tasks.test.ts src/cli/commands/interactive/task-view.test.ts ✓ (30 tests passing)

Stack

Stacks on #1312 (v1: always-on JSONL logging, page-and-return /tasks:view).

@vercel

vercel Bot commented Aug 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-afk-docs Ready Ready Preview Aug 28, 2026 12:14am

@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: 24b742d9c7

ℹ️ 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".

});

try {
await tailOutputStream(handle.session.getOutputStream(), ctx, signal);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Tail running tasks through a supported stream

When the selected subagent is running, this calls AgentSession.getOutputStream(), whose implementation in src/agent/session/agent-session.ts:1248-1251 always throws because callers must use sendMessageStream() instead. The broad catch suppresses that error and the finally block prints “Task complete,” so every real running-task view exits immediately and falsely reports completion rather than tailing output.

Useful? React with 👍 / 👎.

resolve();
});

process.stdin.on('data', onKeypress);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Take exclusive ownership of terminal keypresses

In the persistent-compositor REPL, stdin already has a keypress listener, so adding this independent data listener makes both consumers process every navigation key. For example, pressing Up in this menu also makes the compositor recall the previous user prompt; pressing Enter then queues that hidden prompt before this handler opens the selected task, causing it to be automatically resubmitted after returning. Suspend or replace the existing input surface while the menu owns navigation instead of attaching a competing stdin listener.

Useful? React with 👍 / 👎.

Comment on lines +203 to +205
if (!isRunning) {
if (ictx) ictx.viewingTaskId = undefined;
return;

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 Keep completed task views open until dismissal

For every completed in-memory subagent, this branch returns immediately after rendering a footer that says to press Esc. The slash handler therefore resumes the main REPL prompt at once, and the installed soft-stop callback no longer owns a blocking view; moreover, idle-mode Esc is handled through the input surface's idle-escape path rather than this soft-stop hook. As a result, completed conversations never actually enter the advertised view mode and cannot be dismissed with Esc as instructed.

Useful? React with 👍 / 👎.

Base automatically changed from afk/research-subagent-chat-mode to main August 27, 2026 20:22
…logging

Implements the foundation for viewing subagent conversations in the REPL,
analogous to Claude Code's /tasks feature.

- `src/agent/subagent/log.ts` — SubagentLogWriter + SubagentLogReader.
  Always-on JSONL per subagent under state/subagent-logs/<session>/<id>.jsonl.
  Opt-out via AFK_SUBAGENT_LOG=0.
- `src/agent/subagent/completed-cache.ts` — Bounded LRU of recently-completed
  subagent handles for memory-first /tasks:view.
- `src/agent/subagent/handle.streaming.ts` — Extracted streaming methods from
  handle.ts (streamToFinalMessage, runToResult, runInBackground,
  dispatchStopAndRelease). Drops handle.ts from 417 to ~203 code lines.
- `src/cli/output-event-format.ts` — Shared OutputEvent→text formatter,
  extracted from bgsub.ts and enriched with tool args preview.
- `src/cli/commands/interactive/task-view.ts` — Replay renderer for subagent
  conversations (memory Message[] path + disk OutputEvent path).
- `src/cli/slash/commands/tasks.ts` — /tasks (list), /tasks:view <id> (view
  conversation), /tasks:cancel <id> (cancel running).

- `session-types.ts` — Added getHistory() to IAgentSession interface.
- `subagent.ts` — Wired SubagentLogWriter + CompletedCache into forkSubagent.
- `handle.ts` — Delegates to handle.streaming.ts, added _logWriter field.
- `bgsub.ts` — Imports formatDiskEvent from shared module.
- `slash/index.ts` — Registers tasksCommands.
- `env.ts` — Registers AFK_SUBAGENT_LOG env var.
- `paths.ts` — Added subagent-logs path helpers.
…(v2)

- Add viewingTaskId?: string to InteractiveCtx (shared.ts) — tracks
  when REPL is in task-view mode
- New task-view-mode.ts: enterTaskViewMode(), exitTaskViewMode(),
  renderTaskViewHeader(), buildTaskFooterLine() — manages live view UX
- Live tailing: streams OutputEvent from handle.session.getOutputStream()
  for running subagents, updating output in real-time
- Esc to return: wires setSoftStopHandler to exitTaskViewMode, restores
  status line and repaint
- Arrow key navigation in /tasks list: ↑/↓ to move cursor, Enter to
  open task view, Esc to return to main prompt
- /tasks:view <id> now enters live view mode via enterTaskViewMode()
  (replaces page-and-return v1 UX with proper mode switching)
- 30 tests passing across tasks.test.ts (13) and task-view.test.ts (17)
@griffinwork40

Copy link
Copy Markdown
Owner Author

Shadow-verify is complete. Let me compile the final review.


PR #1313 Review — feat(tasks): live view-mode switching for subagent conversations (v2)

Target: afk/tasks-v2-live-viewmain | Ref: db5fe9c0 | Regime: full (+1009/−124, 5 files) | Change type: feature


Findings

S-1 · high · blocking:true · confidence:high · security · tasks.ts:290 · ref:db5fe9c0 · file-state · shadow-verified: CONFIRMED

Stdin dual-consumer — process.stdin.on('data') conflicts with TerminalCompositor's stdin claim, producing phantom turns

The /tasks interactive handler registers process.stdin.on('data', onKeypress) without acquiring a StdinClaimHandle. The compositor already holds the claim (lifecycle.ts:159) and its 'keypress' listener stays active — loop-iteration.ts never calls suspendInput() before dispatchSlash. Both listeners receive every keystroke. The compositor's input buffer accumulates arrow/Enter keystrokes typed during navigation, and those flush as unsolicited user turns when the handler resolves — the exact #511 phantom-turn bug class documented in stdin-claim.ts:1–9.

// tasks.ts:290 — no stdin claim, no suspendInput
process.stdin.on('data', onKeypress);

Suggestion: Call compositor.suspendInput() before the navigation await and resumeInput() on all resolution paths (matching editor-spawn.ts/transcript.ts), or acquire a StdinClaimHandle via withStdinClaim.


F2 · high · blocking:true · confidence:high · correctness · task-view-mode.ts:225-233 · ref:db5fe9c0 · diff-context · shadow-verified: CONFIRMED

Natural completion of live-tail never calls exitTaskViewMode — status line unreset, return banner never shown

When the stream exhausts naturally (subagent finishes, Esc not pressed), the finally block emits FOOTER_COMPLETE, nulls the handler, and clears viewingTaskId — but never calls exitTaskViewMode(). repaintStatusLine() (line 107) and FOOTER_RETURN (line 109) exist only inside exitTaskViewMode. The user is left staring at the task view with no visual indication they've returned to the main prompt.

// finally block — line 227-232: no exitTaskViewMode call
if (!signal.aborted) {
  ctx.out.line('');
  ctx.out.line(FOOTER_COMPLETE);
  ctx.setSoftStopHandler?.(null);
  if (ictx) ictx.viewingTaskId = undefined;
}

Suggestion: Replace the manual cleanup in finally with exitTaskViewMode(entry) (when !signal.aborted), ensuring the single cleanup function handles all teardown.


S-2 · medium · blocking:true · confidence:high · security · task-view-mode.ts:170-181 · ref:db5fe9c0 · file-state

In-memory history rendered with JSON.stringify(raw) — no truncation, no redaction of tool arguments

The memory-path rendering in enterTaskViewMode serializes message content via JSON.stringify(raw) with no length cap. task-view.ts already has CONTENT_PREVIEW_CHARS = 200 truncation for tool_use/tool_result blocks, but task-view-mode.ts bypasses it entirely with its own inline loop.

// task-view-mode.ts:174 — unbounded, unredacted
const text = typeof raw === 'string' ? raw : JSON.stringify(raw);

Suggestion: Route the memory path through renderMessagesView() from task-view.ts, which already truncates tool inputs/results.


F4 · medium · blocking:true · confidence:high · correctness · tasks.ts:269 · ref:db5fe9c0 · diff-context · shadow-verified: PARTIALLY CONFIRMED

void enterTaskViewMode(entry).then(resolve) — no .catch() leaves REPL hung if enterTaskViewMode rejects

The void discard with no .catch() means any rejection from enterTaskViewMode leaves the outer Promise<void> permanently pending and the REPL frozen. Shadow-verify confirmed the bug pattern but disproved the stated trigger: getOutputStream() throws inside a try/catch {} block (line 221-224) that swallows the error, so the specific AgentSession throw does NOT propagate. The live-tail path silently no-ops instead. The missing .catch() remains a latent hang for any future rejection path.

// tasks.ts:269
void enterTaskViewMode(entry).then(resolve);

Suggestion: .then(resolve, resolve) — a rejection should resolve the navigation promise and return to the prompt, not hang.


F5 · medium · blocking:false · confidence:high · spec-compliance · task-view.ts (entire file) · waived: bounded dead code, non-data-affecting; reasonable to wire in as the S-2 fix

renderMessagesView and renderEventsView have zero production callers — 278 LOC of dead code

Grep confirms zero import sites outside the file's own definitions. The file duplicates rendering logic already inline in task-view-mode.ts but with richer formatting (markdown rendering, tool truncation). The S-2 fix naturally wires this module.


F6 · medium · blocking:false · confidence:high · test-coverage · task-view.test.ts · waived: bounded, non-data-affecting

No test covers the running/live-tail path of enterTaskViewMode

All 17 tests use status='succeeded' or disk-fallback. Zero tests for: running handle entering the tail loop, Esc abort, natural stream completion, or setSoftStopHandler override. Grep confirmed: zero matches for running, abort, tail in test assertions.


F7 · low · blocking:false · confidence:high · perf-observability · task-view-mode.ts:260-267 · ref:db5fe9c0 · diff-context

No backpressure on live-tail rendering — per-event ctx.out.line() with no batching


F3 · low · blocking:false · confidence:high · correctness · tasks.ts:290 · ref:db5fe9c0 · diff-context

'data' event instead of 'keypress' — functional under compositor raw mode, but inconsistent with selectors.ts pattern


F1 · low · blocking:false · confidence:high · correctness · task-view-mode.ts:200-201 · ref:db5fe9c0 · diff-context

Dead wireEscapeToExit call immediately overwritten for running path — no behavioral impact


A-1 · nit · blocking:false · api-compatformatHandleLine signature change, module-private.
A-2 · nit · blocking:false · api-compatviewingTaskId additive optional field.


Decision: DO NOT MERGE — 2 high blocking (1 security, 1 correctness), 2 medium blocking (1 security, 1 correctness); 2 medium waived, 3 low, 2 nit.

The four blocking findings are all fixable without architectural changes:

  • S-1: add suspendInput()/resumeInput() around the navigation await
  • F2: call exitTaskViewMode(entry) in the finally block
  • S-2: wire memory path through renderMessagesView() (which also addresses F5)
  • F4: add .catch() on the enterTaskViewMode promise chain

What Was Not Checked

  • Citations verified inline against branch HEAD db5fe9c0.
  • Stated intent: PR feat(tasks): live view-mode switching for subagent conversations (v2) #1313 title+body — spec-compliance assessed.
  • Not checked: Telegram/daemon surface paths into enterTaskViewMode; compositor re-arm state after task-view exit; getOutputStream() override status for session types other than AgentSession; whether renderMessagesView changes rendering semantics for non-tool messages.

Done

  • What was done: Full-regime review of PR feat(tasks): live view-mode switching for subagent conversations (v2) #1313 with 2 parallel Wave 1 agents, inline Wave 1.5 citation/absence verification, Wave 2 synthesis, and shadow-verification of 3 blocking claims.
  • Evidence: Review output above; compose artifacts at ~/.afk/state/sessions/ca4863b3-*/compose/.
  • What changed: Nothing — review is read-only.
  • Deferred: None.

🤖 Posted by agent-afk /review --post github

S-1 (high/security): Suspend TerminalCompositor before registering
process.stdin 'data' listener in /tasks interactive mode. The
compositor holds the stdin claim during slash dispatch; a second
listener creates the dual-consumer phantom-turn bug (#511 class).
Added suspendInput()/resumeInput() around the navigation promise
with a cleanup() helper on all exit paths.

F2 (high/correctness): Call exitTaskViewMode() in the finally block
on natural stream completion. Previously, repaintStatusLine() and
FOOTER_RETURN were only emitted on the Esc path, leaving the REPL
visually stuck in task-view mode when a subagent finished naturally.

S-2 (medium/security): Replace inline JSON.stringify rendering of
in-memory history with renderMessagesView() from task-view.ts, which
applies CONTENT_PREVIEW_CHARS=200 truncation on tool_use/tool_result
content. This also wires the previously dead-code task-view.ts module
(resolves finding F5).

F4 (medium/correctness): Add .catch(() => resolve()) on the
enterTaskViewMode promise chain so a rejection returns to the prompt
instead of hanging the REPL indefinitely.

@griffinwork40 griffinwork40 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🤖 Automated review (scheduled sweep), generated by the /review tool — a maintainer will follow up.


Prior Automated Round — Finding Dispositions

1 prior automated review (on commit db5fe9c0, 2026-08-27). Current HEAD is a0074940 which explicitly addresses 4 prior blocking findings.

Prior finding Disposition
S-1 (high): stdin dual-consumer — process.stdin.on('data') conflicts with compositor stdin claim FIXED — commit a0074940 adds compositor?.suspendInput() before the listener and compositor?.resumeInput() in cleanup() on all exit paths.
F2 (high): natural stream completion never calls exitTaskViewMode — status line unreset FIXED — commit a0074940 adds exitTaskViewMode(entry) in the finally block behind if (!signal.aborted). Wave 1.5 verified the logic is correct (Esc path calls exitTaskViewMode directly, then signal.aborted is true in finally so the block is skipped — no double-call).
S-2 (medium): in-memory history rendered with JSON.stringify — no truncation FIXED — commit a0074940 imports and calls renderMessagesView() from task-view.ts which applies CONTENT_PREVIEW_CHARS=200 truncation. This also wires F5 (dead code).
F4 (medium): no .catch() on enterTaskViewMode promise chain — REPL hangs on rejection FIXED — commit a0074940 adds .catch(() => resolve()).
F5 (medium, waived): renderMessagesView had zero callers (dead code) FIXED as a side-effect of the S-2 fix.
F6 (medium, waived): no tests for running path STILL PRESENT — zero tests for the live tail loop, natural stream completion, or Esc during running. Advisory.
F7 (low): no backpressure on live-tail rendering STILL PRESENT — per-event ctx.out.line() with no batching. Advisory.
F3 (low): 'data' event vs 'keypress' inconsistency STILL PRESENT — advisory, no behavioral impact.
F1 (low): dead wireEscapeToExit call immediately overwritten for running path STILL PRESENT in a different form — see new finding N2 below. Advisory.

Decision

✅ MERGE (with follow-ups)

Severity arithmetic: 0 critical, 0 high → MERGE (with follow-ups); 2 medium advisory (non-blocking per sweep policy — only critical/high block), 2 low advisory, 1 nit advisory.

Per sweep policy: medium and below are non-blocking. They are listed as follow-ups below with suggested issue rather than fix commits.


Review Summary

PR: feat(tasks): live view-mode switching for subagent conversations (v2)
Reviewed ref: a0074940f98b0feb5ebfce920a33cdca309edb62
Regime: full (+1009/−124, 5 files: 2 new production, 1 new test, 2 modified)
CI: Lint & Build ✅, Test (ubuntu-latest) ✅, Test (macos-latest) ✅, PTY scrollback ✅, Docs ✅, Publish Bundle ✅. All green.

Wave 1.5 citation verification: 3 of 7 high-severity security findings were FABRICATED (premises refuted against a0074940):

  • Security finding 1 (manager.get returns snapshot with no session): FABRICATEDSubagentManager.get() returns SubagentHandle | undefined from active.get() ?? completed.get()?.handle; SubagentHandleImpl carries readonly session: IAgentSession. No crash.
  • Security findings 2/3/4 (compositor not resumed, viewingTaskId not cleared on abort): FABRICATEDcleanup() IS called before enterTaskViewMode dispatch (line 272); Esc handler explicitly calls exitTaskViewMode(entry) which clears viewingTaskId; the "double-wire window" is synchronous code with no await — events cannot interleave.
  • Security finding 5 (dual data listeners): DROPPED — insufficient evidence that readline's internal listener survives suspendInput; the design comment acknowledges the dual-consumer risk and correctly applies the fix.

The 4 prior blocking findings are verifiably fixed. The remaining findings are follow-up hygiene.


🟡 Advisory Findings (non-blocking — suggested as follow-up issues)

N1 · medium · advisory · correctness · task-view-mode.ts:185–188 · ref:a0074940

Disk path installs wireEscapeToExit handler then returns without clearing it.

When the disk fallback path completes (handle is null and disk replay returns), wireEscapeToExit(entry) is called then the function returns early. If the user returns to the task list without pressing Esc, the soft-stop handler installed by wireEscapeToExit remains live. On the next soft-stop event from any context, exitTaskViewMode(entry) fires against the already-exited view, emitting FOOTER_RETURN and calling repaintStatusLine() into whatever screen state is current.

wireEscapeToExit(entry);
if (ictx) ictx.viewingTaskId = undefined;
return;  // handler not cleared — stale soft-stop handler survives

Suggested fix (follow-up): Before return, add entry.ctx.setSoftStopHandler?.(null).


N2 · medium · advisory · correctness · tasks.ts:274–280 · ref:a0074940

ictx absent from TaskViewEntry construction in /tasks interactive mode — viewingTaskId tracking inoperative.

The PR's stated new feature ("viewingTaskId tracking") is not wired at the primary call site. Both TaskViewEntry constructions in tasks.ts omit ictx, so enterTaskViewMode always sees ictx === undefined and the if (ictx) ictx.viewingTaskId = id guard never fires. The REPL loop cannot know the user is in task-view mode.

const entry: TaskViewEntry = {
  id: selected.id, manager, sessionLabel, ctx,
  // ictx omitted — viewingTaskId tracking never activates
};

Suggested fix (follow-up): Thread InteractiveCtx into the /tasks and /tasks:view handlers (e.g. via an optional ictx on SlashContext or a module-scope set at bootstrap) and include it in the entry.


N3 · low · advisory · test-coverage · task-view.test.ts · ref:a0074940

Zero tests for the live tail loop (running path), natural stream completion, and Esc-during-running.

The 17 tests cover completed-handle memory path and disk fallback. The F2 fix (finally block on natural completion) is the explicitly stated fix from this PR round but has no test. A stated fix with no test is unverified at the test level (though Wave 1.5 verified the logic is correct).

Suggested fix (follow-up): Add tests: (a) tailOutputStream resolves naturally → assert exitTaskViewMode called, FOOTER_COMPLETE emitted; (b) abort fires → assert exitTaskViewMode NOT called from finally.


N4 · low · advisory · correctness · task-view.ts · ref:a0074940

renderEventsView exported but has zero callers.

task-view.ts exports renderEventsView(header, events, options) but nothing in this PR imports it. It is dead code. The memory path now uses renderMessagesView (S-2 fix), but renderEventsView was presumably intended as the disk-event counterpart and was never wired.

Suggested fix (follow-up): Either wire renderEventsView in replayDiskEvents (replacing the inline loop) or remove it until it has a defined call site.


N5 · nit · advisory · correctness · tasks.ts:280 · ref:a0074940

Blank .catch(() => resolve()) discards programming errors silently.

The F4 fix prevents hangs but makes any unexpected throw from enterTaskViewMode indistinguishable from success. A brief console/ctx error log before resolving would surface bugs during development.

void enterTaskViewMode(entry).then(resolve).catch(() => resolve());
// All errors silently resolve — no diagnostic signal

Suggested fix: .catch((e) => { ctx.out.error?.(task view error: ${String(e)}); resolve(); })


Spec-Compliance

Stated intent: PR #1313 title+body — spec-compliance assessed.

Requirement Status
viewingTaskId?: string on InteractiveCtx ✅ Field added, optional, additive
enterTaskViewMode / exitTaskViewMode — new file ✅ Implemented in task-view-mode.ts
Live tailing via handle.session.getOutputStream() tailOutputStream streams AsyncIterable<OutputEvent>
Esc to return via ctx.setSoftStopHandler ✅ Wired in running-path override
Arrow-key navigation in /tasks list \x1b[A/\x1b[B + cursor state
S-1 fix: suspendInput/resumeInput around stdin listener ✅ Confirmed at ref
F2 fix: exitTaskViewMode in finally block on natural completion ✅ Confirmed at ref
S-2 fix: memory path uses renderMessagesView (truncated) ✅ Confirmed at ref
F4 fix: .catch() on enterTaskViewMode dispatch ✅ Confirmed at ref
viewingTaskId tracking at /tasks call site ⚠️ Not wired — ictx absent from entry (N2, advisory)
30 tests passing ✅ CI green

Dimensions With No Issues

  • Security — after Wave 1.5 drops fabricated findings: compositor suspend/resume is correctly symmetric; no new trust boundaries or injection surfaces; ctx.out.line() renders text, no injection risk.
  • API-compatviewingTaskId?: string is additive optional; formatHandleLine signature change is module-private (zero external importers confirmed); new exports in task-view-mode.ts and task-view.ts are net-new with no breaking changes.
  • Perf-observabilityrenderList() only redraws on recognized arrow keys (if/else-if chain), not on every keystroke; clearScreen + redraw on arrow key is standard TUI practice for short lists.

What Was Not Checked

  • Citations verified against branch HEAD a0074940f98b0feb5ebfce920a33cdca309edb62 via GitHub API file reads and Wave 1.5 inline verification. 3 of 7 proposed high-severity security findings were refuted and dropped.
  • Stated intent: PR #1313 title+body — spec-compliance assessed.
  • Not checked: runtime test execution — static review only. CI shows all checks passing.
  • Not checked: Telegram/daemon surface paths into enterTaskViewModesetSoftStopHandler is absent on those surfaces so the Esc path is a no-op; the live-tail loop runs to natural completion only.
  • Not checked: whether suspendInput() in TerminalCompositor pauses the readline Interface's internal 'data' listener (insufficient evidence to assert either way).
  • Not checked: compositor re-arm state after task-view exit (pre-existing surface concern, not introduced by this PR).

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.

1 participant