qwen-code/packages/web-shell/client/components/ChatPane.module.css
ytahdn b04db26db8
feat(web-shell): approval and ask-user dialogs as in-flow sheets; fix background-agent false failure (#9351)
* feat(web-shell): approval and ask-user dialogs as in-flow sheets without covering messages

When a tool approval or ask-user-question dialog is pending, the composer and
the pane status area are hidden and the dialog occupies the composer slot in
flow, so the message list shrinks above it instead of being covered by a
floating overlay. The dialog width now matches the chat content width.

Also, while an agent's launch approval is pending, its row stays compact and
is not openable (there is nothing to show yet), and the launch dialog defaults
its focus to the one-shot allow instead of the reject button.

* fix(web-shell): don't fail background agents while their launch approval is pending

The background-agent reconciliation probes /subagents/:callId for every active
background agent. While the agent's launch approval is unanswered its subagent
session cannot exist yet, so the probe 404s and, after the two-miss grace, the
reconciliation marked the agent failed — painting a red error on the row while
the permission dialog was still up.

Skip reconciliation for callIds with an unresolved permission request, and
resume probing once the permission resolves.

* style(web-shell): drop notification bubble row margin

* fix(web-shell): stabilize reconciliation dependency key; gate parallel agent rows under launch approval

Address review findings on the approval-dialog and background-agent changes:

- R1-1 (Critical): the pending-permission effect dependency was a fresh Set on
  every transcript delta, so the reconciliation effect re-ran on each streamed
  update, bypassing the retry backoff and re-introducing the false-failure the
  change set out to fix. Depend on a stable sorted-key string instead and
  rebuild the membership Set inside the effect.
- R1-2: add a two-agent reconciliation test pinning that exclusion is per
  callId (the healthy sibling keeps probing while one agent is under approval).
- R1-3: apply the non-openable-while-pending guard to ParallelAgentsGroup rows,
  which previously bypassed it when agents rendered grouped.
- R1-4: add a test that an accumulated missing-agent miss is fully reset when a
  launch approval engages, so the post-approval grace restarts fresh.

* fix(web-shell): address round-2 review findings

- useMessages: pace the missing-agent grace by wall clock so a re-probe
  triggered by an unrelated permission change cannot collapse the retry
  ladder into two immediate misses, and never count misses or retry-budget
  errors for agents excluded by a pending permission (an in-flight round
  settles after the exclusion cleanup ran and would otherwise re-add them)
- App.module.css: keep the approval sheet above portal-hosted DialogShell
  modals by restoring its z-index while preserving the in-flow layout
- ParallelAgentsGroup: the pending-approval row keeps its status dot, shows
  a pending label instead of running, and is inert (no pointer, no hover
  highlight, no rowActive sweep); add a regression test for the inert row

* fix(web-shell): pace background agent reconciliation

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
2026-08-19 02:49:39 +00:00

234 lines
5.2 KiB
CSS

.pane {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
height: 100%;
background: var(--background);
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
}
.paneEmbedded {
border: 0;
border-radius: 0;
}
.paneEmbedded .footer {
padding-bottom: 0;
}
.header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 10px;
/* On a multi-workspace pane, `--ws-accent` is set by a shared accent class
(workspaceAccent.module.css) and colorizes this divider into a per-workspace
strip; it falls back to the neutral border everywhere else, so single-
workspace panes are unchanged. */
border-bottom: 1px solid var(--ws-accent, var(--border));
background: color-mix(in srgb, var(--foreground) 3%, transparent);
flex: 0 0 auto;
}
/* The `--ws-accent` variable that colorizes the divider above and the tag below
is set by a shared accent class from workspaceAccent.module.css (also used by
the composer chip), applied to `.header` when the pane has a workspace. */
/* Which workspace this pane's session lives in — a compact colored chip at the
start of the header, shown only on a multi-workspace daemon. The dot never
shrinks, so panes stay distinguishable by color even when the name and the
session title both ellipsize on a narrow split; the full cwd is in the
native title tooltip. */
.workspaceTag {
display: inline-flex;
align-items: center;
gap: 5px;
flex: 0 1 auto;
min-width: 0;
max-width: 50%;
padding: 2px 8px 2px 6px;
border-radius: 999px;
background: color-mix(in srgb, var(--ws-accent) 14%, transparent);
color: color-mix(in srgb, var(--ws-accent) 70%, var(--foreground));
font-size: 11px;
font-weight: 600;
line-height: 1.4;
}
.workspaceTagDot {
width: 7px;
height: 7px;
flex: 0 0 7px;
border-radius: 999px;
background: var(--ws-accent);
}
.workspaceTagText {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Per-pane connection-loss indicator: each pane has its own daemon connection,
so a drop on one shouldn't silently leave stale messages with no signal. */
.connectionError {
flex: 0 0 auto;
padding: 6px 10px;
background: var(--warning-bg);
border-bottom: 1px solid var(--warning-border);
color: var(--warning-color);
font-size: 12px;
}
.connectionErrorText {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.title {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
font-weight: 600;
color: var(--foreground);
}
.headerActions {
position: relative;
flex: 0 0 auto;
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 0;
}
.headerActionsInline {
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 0;
}
.headerActionSlot {
display: inline-flex;
align-items: center;
}
/* Keep host actions mounted while collapsed so stateful actions survive
resize. Absolutely positioned so they do not affect flex layout. */
.headerActionsHostHidden {
position: absolute;
visibility: hidden;
pointer-events: none;
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
/* The built-in maximize/close controls used to be direct children of `.header`
(gap 8px); keep that spacing now that they are grouped here. */
.headerTrailing {
display: inline-flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
}
.headerOverflowPanel {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 4px;
padding: 2px;
}
.headerActionButton,
.closeButton,
.maximizeButton {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px;
height: 26px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--muted-foreground);
cursor: pointer;
}
.headerActionButton:hover,
.closeButton:hover,
.maximizeButton:hover {
color: var(--foreground);
background: color-mix(in srgb, var(--foreground) 8%, transparent);
}
/* The maximized pane keeps its toggle visually "active" so it's clear which
control returns to the tiled layout. */
.maximizeButton[aria-pressed='true'] {
color: var(--foreground);
background: color-mix(in srgb, var(--foreground) 10%, transparent);
}
/* The transcript takes the remaining height and scrolls independently per pane. */
.body {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.footer {
flex: 0 0 auto;
padding: 8px;
position: relative;
}
.approval {
margin-bottom: 8px;
}
.composerHidden {
display: none;
}
.admissionUnknown {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 8px;
padding: 7px 9px;
border: 1px solid var(--warning-border);
border-radius: 8px;
background: var(--warning-bg);
color: var(--warning-color);
font-size: 12px;
}
.admissionUnknownActions {
display: inline-flex;
flex: 0 0 auto;
gap: 6px;
}
.admissionUnknownActions button {
border: 1px solid var(--warning-border);
border-radius: 6px;
padding: 3px 7px;
background: var(--background);
color: inherit;
cursor: pointer;
}