Skip to content

feat: enrich install completion summary and stream agent file ops#186

Closed
nicknisi wants to merge 1 commit into
nicknisi/port-detectionfrom
nicknisi/install-ux
Closed

feat: enrich install completion summary and stream agent file ops#186
nicknisi wants to merge 1 commit into
nicknisi/port-detectionfrom
nicknisi/install-ux

Conversation

@nicknisi

@nicknisi nicknisi commented Jul 8, 2026

Copy link
Copy Markdown
Member

What

  • Structured completion summary: a new pure buildCompletionData builder assembles the lockfile-aware dev command, app URL with the detected port, changed files (capped at 5), composed next steps, and per-framework docs/dashboard URLs. It runs in a new buildingCompletion state between postInstall and complete, and all three adapters (CLI, Dashboard, Headless) render it via a widened optional complete.completion payload with a graceful static fallback. Previously the success branch of summary-box.ts discarded the rich per-framework summary the installer already built and printed two static lines.
  • Append-only agent progress: removed the 2-second setInterval that clobbered the AI-agent phase text, and subscribed the already-emitted file:write/file:edit events as persistent step lines (CLI) and NDJSON events (headless), with dedupe.
  • Stretch items (approved in the ideation contract): agent:tool event surfaces Bash commands the agent runs (emitted from the post-permission branch of handleSDKMessage); a "add a sign-in link" snippet in the summary — Next.js only, since inventing route copy for unverified frameworks would violate accuracy.

Why

From the friction log: after a successful install the user got two generic lines and no idea what to do next (dev command, URL, what changed), and during the agent phase a spinner overwrote its own progress text every 2 seconds while file-operation events went unconsumed.

Testing

  • New completion-data.spec.ts; strengthened summary-box.spec.ts (replacing the bug-encoding title-only assertion) and adapter specs, including a fake-timers no-clobber case and NDJSON file:write/file:edit assertions that fail on pre-fix code.
  • Full gate: pnpm build && pnpm test && pnpm typecheck (plus oxlint/oxfmt) green.

Stack 2/7 (akshay-friction-log): merge after #185, then rebase this branch onto main (squash-merge stack).

Phase 4 (Install UX) of the Akshay friction-log fixes.

Completion summary:
- Add CompletionData type + pure buildCompletionData builder (lockfile-aware
  dev command via resolveDevCommand, app URL via detectPort, changed files,
  composed next steps, per-framework docs + dashboard URLs).
- New buildingCompletion machine state + actor computes the payload between
  postInstall and complete; errors degrade to the static fallback box.
- renderCompletionSummary renders the structured success box (files capped at
  5, next steps, per-framework docs footer); falls back to the static box when
  no completion data is present. Failure branch unchanged.
- Widen the complete event with an optional completion payload; all three
  adapters (CLI, Dashboard, Headless) render/emit it. Headless spreads the
  fields only when present, preserving the existing NDJSON shape.

Agent progress:
- CLI adapter: remove the 2s spinner-message reset that clobbered phase text;
  persist file:write/file:edit as append-only step lines above the spinner
  (stop -> log -> restart), path-only with consecutive-path dedupe.
- Headless adapter: stream path-only file:write/file:edit NDJSON (never content).
- Stretch: surface Bash commands via a new agent:tool event emitted from the
  post-permission tool_use branch; rendered as step lines (CLI) and NDJSON
  (headless).
- Stretch: optional UIConfig.getSignInSnippet surfaced as a next step; defined
  for Next.js (client-side refreshAuth guidance, per the AuthKit skill).

Validated: pnpm build, pnpm test (2241), pnpm typecheck, oxlint, oxfmt all pass.
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR enriches install completion output and streams agent progress more directly. The main changes are:

  • Added structured completion data with dev command, app URL, changed files, links, and next steps.
  • Routed the completion payload through CLI, dashboard, and headless adapters.
  • Streamed file writes, file edits, and agent tool activity as persistent progress events.
  • Added tests for completion rendering, NDJSON output, dedupe behavior, and spinner updates.

Confidence Score: 4/5

This PR needs a targeted redaction fix before merging.

The structured completion flow and adapter wiring are covered, but the new command progress stream can expose sensitive command values.

src/lib/agent-interface.ts, src/lib/adapters/cli-adapter.ts, and src/lib/adapters/headless-adapter.ts need attention.

T-Rex T-Rex Logs

What T-Rex did

  • Reproduced a focused harness that starts the HeadlessAdapter and emits an agent:tool command containing inline secrets.
  • Captured the NDJSON progress output that exposed WORKOS_API_KEY=[REDACTED] and --password hunter2 in the agent:tool detail.
  • Validated the focused Vitest rerun completed with EXIT_CODE: 0 and confirmed the typecheck step also finished with EXIT_CODE: 0, noting the earlier shell wrapper failure was superseded by the clean rerun artifact.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/lib/agent-interface.ts Emits allowed Bash commands as agent tool events, but currently forwards raw command text that can contain sensitive values.
src/lib/adapters/cli-adapter.ts Streams file ops and agent tool commands above the spinner and renders structured completion; raw command display needs redaction.
src/lib/adapters/headless-adapter.ts Streams path-only file-operation NDJSON and structured completion fields; agent command detail is passed through unredacted.
src/lib/completion-data.ts Introduces the structured completion payload builder for dev command, URL, changed files, links, and next steps.
src/lib/installer-core.ts Adds a buildingCompletion state that computes optional completion data before the final complete event.
src/lib/run-with-core.ts Wires completion-data construction into the runtime machine with fallback on builder errors.
src/utils/summary-box.ts Renders structured success summaries with changed files, next steps, and docs footer, falling back when absent.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Agent as Agent SDK
participant Core as Installer Core
participant Builder as buildCompletionData
participant CLI as CLI Adapter
participant Headless as Headless Adapter
participant Summary as summary-box

Agent->>Core: file:write / file:edit / agent:tool
Core->>CLI: stream progress events
Core->>Headless: stream NDJSON events
Core->>Builder: buildingCompletion invoke
Builder-->>Core: CompletionData or undefined
Core->>CLI: complete(success, summary, completion)
Core->>Headless: complete(success, summary, completion)
CLI->>Summary: renderCompletionSummary(...)
Summary-->>CLI: structured or fallback success box
Headless-->>Headless: spread structured fields into NDJSON
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Agent as Agent SDK
participant Core as Installer Core
participant Builder as buildCompletionData
participant CLI as CLI Adapter
participant Headless as Headless Adapter
participant Summary as summary-box

Agent->>Core: file:write / file:edit / agent:tool
Core->>CLI: stream progress events
Core->>Headless: stream NDJSON events
Core->>Builder: buildingCompletion invoke
Builder-->>Core: CompletionData or undefined
Core->>CLI: complete(success, summary, completion)
Core->>Headless: complete(success, summary, completion)
CLI->>Summary: renderCompletionSummary(...)
Summary-->>CLI: structured or fallback success box
Headless-->>Headless: spread structured fields into NDJSON
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/lib/agent-interface.ts:846-849
**Redact surfaced commands**
`agent:tool` emits the full Bash command into CLI/headless progress streams. When an allowed command contains an inline credential such as `WORKOS_API_KEY=... pnpm ...` or `--password ...`, that secret is logged verbatim by the adapters. Redact known sensitive assignment and flag values before emitting this event.

Reviews (1): Last reviewed commit: "feat: enrich install completion summary ..." | Re-trigger Greptile

Comment on lines +846 to +849
if (toolName === 'Bash' && input) {
const command = input.command as string;
if (command) {
emitter?.emit('agent:tool', { kind: 'command', detail: command });

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 Redact surfaced commands
agent:tool emits the full Bash command into CLI/headless progress streams. When an allowed command contains an inline credential such as WORKOS_API_KEY=... pnpm ... or --password ..., that secret is logged verbatim by the adapters. Redact known sensitive assignment and flag values before emitting this event.

Rule Used: Do not log sensitive fields like access_token, ref... (source)

Artifacts

Repro: focused headless adapter harness

  • Contains supporting evidence from the run (text/typescript; charset=utf-8).

Repro: command output showing verbatim secret exposure

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/agent-interface.ts
Line: 846-849

Comment:
**Redact surfaced commands**
`agent:tool` emits the full Bash command into CLI/headless progress streams. When an allowed command contains an inline credential such as `WORKOS_API_KEY=... pnpm ...` or `--password ...`, that secret is logged verbatim by the adapters. Redact known sensitive assignment and flag values before emitting this event.

**Rule Used:** Do not log sensitive fields like access_token, ref... ([source](https://app.greptile.com/workos/-/custom-context?memory=395edb09-f95b-4759-834f-94cb847721f5))

How can I resolve this? If you propose a fix, please make it concise.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 6 potential issues.

Open in Devin Review

Comment on lines +413 to +416
private handleAgentTool = ({ detail }: InstallerEvents['agent:tool']): void => {
const cmd = detail.length > 80 ? `${detail.slice(0, 77)}…` : detail;
this.logAboveSpinner(() => clack.log.step(`Running ${chalk.dim(cmd)}`));
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 File operation notifications are silently suppressed when a tool call intervenes between two operations on the same file

The deduplication of file-operation notifications does not reset when a tool-call event fires (handleAgentTool at src/lib/adapters/cli-adapter.ts:413), so a write-to-A → tool-call → edit-of-A sequence suppresses the edit notification even though the operations are not truly consecutive.

Impact: Users miss that a file was edited after an intervening command, making the install log incomplete.

Mechanism: shared lastFileOp is never cleared by tool-call handlers

lastFileOp is checked in both handleFileWrite (src/lib/adapters/cli-adapter.ts:400) and handleFileEdit (src/lib/adapters/cli-adapter.ts:407), but handleAgentTool (src/lib/adapters/cli-adapter.ts:413) does not reset it. The same pattern exists in the headless adapter (src/lib/adapters/headless-adapter.ts:302). So the sequence:

  1. file:write { path: 'src/auth.ts' } → logged, lastFileOp = 'src/auth.ts'
  2. agent:tool { detail: 'pnpm add ...' } → logged, but lastFileOp unchanged
  3. file:edit { path: 'src/auth.ts' }silently dropped because path === lastFileOp

The comment at line 400 says "dedupe consecutive same-path ops", but the tool call breaks consecutiveness without resetting the dedup state.

Suggested change
private handleAgentTool = ({ detail }: InstallerEvents['agent:tool']): void => {
const cmd = detail.length > 80 ? `${detail.slice(0, 77)}…` : detail;
this.logAboveSpinner(() => clack.log.step(`Running ${chalk.dim(cmd)}`));
};
private handleAgentTool = ({ detail }: InstallerEvents['agent:tool']): void => {
this.lastFileOp = null; // tool call breaks the consecutive-file-op chain
const cmd = detail.length > 80 ? `${detail.slice(0, 77)}…` : detail;
this.logAboveSpinner(() => clack.log.step(`Running ${chalk.dim(cmd)}`));
};
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +302 to +304
private handleAgentTool = ({ kind, detail }: InstallerEvents['agent:tool']): void => {
writeNDJSON({ type: 'agent:tool', kind, detail });
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Headless adapter also fails to reset file-operation dedup state on tool calls

The headless adapter's handleAgentTool (src/lib/adapters/headless-adapter.ts:302) does not reset lastFileOp, so a file:write → agent:tool → file:edit sequence on the same path silently drops the NDJSON event for the edit.

Impact: CI/CD consumers of the NDJSON stream miss file-edit events that follow a tool call on the same path.

Same root cause as the CLI adapter

lastFileOp at src/lib/adapters/headless-adapter.ts:33 is shared across handleFileWrite (line 290) and handleFileEdit (line 296), but handleAgentTool (line 302) never resets it. An intervening tool call should break the dedup chain.

Suggested change
private handleAgentTool = ({ kind, detail }: InstallerEvents['agent:tool']): void => {
writeNDJSON({ type: 'agent:tool', kind, detail });
};
private handleAgentTool = ({ kind, detail }: InstallerEvents['agent:tool']): void => {
this.lastFileOp = null; // tool call breaks the consecutive-file-op chain
writeNDJSON({ type: 'agent:tool', kind, detail });
};
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +388 to +397
private logAboveSpinner(render: () => void): void {
const wasRunning = this.spinner !== null;
this.spinner?.stop();
this.spinner = null;
render();
if (wasRunning) {
this.spinner = clack.spinner();
this.spinner.start(this.lastAgentMessage);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Spinner stopped without a message — inconsistent with all other call sites

Every other call to spinner.stop() in the CLI adapter passes an explicit message string (e.g. stopSpinner('Agent completed'), this.spinner.stop('Cancelled'), this.spinner.stop('Authenticated')). The new logAboveSpinner method at src/lib/adapters/cli-adapter.ts:390 calls this.spinner?.stop() without a message. Depending on clack's internal behavior, this may print the spinner's last message as a "completed" line (with a checkmark), creating a misleading visual artifact where the current phase (e.g. "Configuring middleware") appears completed before the spinner immediately restarts with the same message. The test mocks stop as a no-op so this visual behavior is untested. Consider passing an empty string or a neutral message to stop() to control the output.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/lib/run-with-core.ts
Comment on lines +373 to +374
frameworkNextSteps: cfg?.ui.getOutroNextSteps?.({}) ?? [],
signInSnippet: cfg?.ui.getSignInSnippet?.({}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Framework config functions called with empty context object

In src/lib/run-with-core.ts:373-374, getOutroNextSteps and getSignInSnippet are called with {} as the context argument. For the current Next.js integration this is safe because both functions ignore their parameter (() => [...] and () => '...'). However, other framework configs (e.g. Next.js's getOutroChanges) do use the context to extract router type via context.router. If a future framework's getOutroNextSteps or getSignInSnippet depends on context (e.g. to vary copy by router or platform), the empty object would silently produce incorrect output. The gatherContext result from the state machine is available in the machine context but is not threaded through to buildCompletion.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +399 to +411
private handleFileWrite = ({ path }: InstallerEvents['file:write']): void => {
if (path === this.lastFileOp) return; // dedupe consecutive same-path ops
this.lastFileOp = path;
const rel = relative(process.cwd(), path);
this.logAboveSpinner(() => clack.log.step(`Creating ${chalk.dim(rel)}`));
};

private handleFileEdit = ({ path }: InstallerEvents['file:edit']): void => {
if (path === this.lastFileOp) return;
this.lastFileOp = path;
const rel = relative(process.cwd(), path);
this.logAboveSpinner(() => clack.log.step(`Editing ${chalk.dim(rel)}`));
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Cross-type file dedup: a write followed by an edit to the same path is suppressed

The lastFileOp field in both CLI (src/lib/adapters/cli-adapter.ts:45) and headless (src/lib/adapters/headless-adapter.ts:33) adapters is shared between handleFileWrite and handleFileEdit. This means a file:write to path A immediately followed by a file:edit to path A will suppress the edit notification. This is a plausible agent pattern (create a file, then immediately refine it). The tests only verify same-type dedup (edit→edit). Whether this cross-type suppression is intentional or an oversight depends on the desired UX — it reduces noise but hides a meaningful operation change (create vs. edit).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +398 to +406
...(completion
? {
integration: completion.integration,
devCommand: completion.devCommand,
url: completion.url,
files: completion.files,
nextSteps: completion.nextSteps,
}
: {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 Headless complete event spreads completion fields but omits docsUrl and dashboardUrl

In src/lib/adapters/headless-adapter.ts:398-406, the structured completion data is spread into the NDJSON complete event, but only integration, devCommand, url, files, and nextSteps are included. The docsUrl and dashboardUrl fields from CompletionData are omitted. The test at src/lib/adapters/headless-adapter.spec.ts:416-426 uses expect.objectContaining which doesn't catch missing fields. If NDJSON consumers need docs/dashboard URLs, they won't find them in the event. This may be intentional (keeping the NDJSON payload lean) but is worth confirming.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@nicknisi

nicknisi commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Closing in favor of a single combined PR from nicknisi/akshay.

@nicknisi nicknisi closed this Jul 8, 2026
@nicknisi nicknisi deleted the nicknisi/install-ux branch July 8, 2026 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant