Skip to content

Commit 18aa544

Browse files
authored
Merge pull request #3277 from GCWing/gcwing/dev
fix(btw): discard temporary side threads and save durable forks
2 parents d82a63e + 66ff23f commit 18aa544

37 files changed

Lines changed: 1439 additions & 208 deletions

‎design-system/packages/ui/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ Compact tabs use `size="sm"` (30px, 14px icons, 4px icon gap); standard tabs ret
625625

626626
Dialog titles use 24px bold type with their own 29px line box and normal tracking. `DialogHeader` and `DialogFooter` omit separators by default; pass `separator` for a deliberate divider. A direct `DialogBody` sibling of `DialogFooter appearance="floating"` owns the trailing scroll inset automatically. The floating footer provides the 68px centered action area and a masked blur/gradient using the current theme surface; reduced transparency and forced colors use an opaque fallback. Keep scrollable form content inside `DialogBody` instead of adding a second viewport with independent footer spacing.
627627

628+
`ConfirmDialog secondaryActionPlacement="start"` presents an alternative action as a text button at the start of the footer, with cancel and confirm grouped at the end. The groups wrap when space is limited, keeping cancel and confirm together. The default `inline` placement retains the standard action order and outline secondary button.
629+
628630
Extra-large (`xl`) dialogs have an 800px maximum width and continue shrinking within the viewport gutter. Provider editing uses the floating footer; small workspace creation retains its attached footer and existing button/input sizes. The Lab workspace pattern uses local sample paths and callbacks only.
629631

630632
Keep `Dialog` and `Sheet` mounted and set `open={false}` to close them. They retain the last committed children during the exit animation, with interaction disabled, so clearing an owner selection does not collapse the surface. Reopening uses the latest children and cancels the pending exit. Owners that conditionally mount an editor can remove it in `onExitComplete`, which runs once after the surface unmounts.

‎design-system/packages/ui/src/components/ConfirmDialog/ConfirmDialog.meta.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const confirmDialogMeta = {
1212
{ defaultValue: "warning", name: "type", type: "info | warning | error | success" },
1313
{ name: "confirmText", type: "ReactNode" },
1414
{ name: "secondaryText", type: "ReactNode" },
15+
{ defaultValue: "inline", name: "secondaryActionPlacement", type: "inline | start" },
1516
{ name: "cancelText", type: "ReactNode" },
1617
{ name: "preview", type: "ReactNode" },
1718
{ defaultValue: "false", name: "confirmDanger", type: "boolean" },
@@ -42,5 +43,8 @@ export const confirmDialogMeta = {
4243
"layout.confirmDialog.previewRadius",
4344
"type.body.md.fontSize",
4445
"type.code.sm.fontSize",
46+
"space.2",
47+
"space.3",
48+
"space.4",
4549
],
4650
} as const satisfies ComponentMeta;

‎design-system/packages/ui/src/components/ConfirmDialog/ConfirmDialog.module.css‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
.messageRow,
44
.icon,
55
.message,
6-
.preview {
6+
.preview,
7+
.splitActions,
8+
.actionGroup {
79
box-sizing: border-box;
810
}
911

@@ -93,6 +95,25 @@
9395
overflow-wrap: anywhere;
9496
}
9597

98+
.splitActions {
99+
display: flex;
100+
min-inline-size: 0;
101+
inline-size: 100%;
102+
flex-wrap: wrap;
103+
align-items: center;
104+
justify-content: space-between;
105+
column-gap: var(--openbitfun-space-4);
106+
row-gap: var(--openbitfun-space-3);
107+
}
108+
109+
.actionGroup {
110+
display: flex;
111+
flex: 0 0 auto;
112+
align-items: center;
113+
gap: var(--openbitfun-space-2);
114+
margin-inline-start: auto;
115+
}
116+
96117
@media (forced-colors: active) {
97118
.icon {
98119
color: CanvasText;

‎design-system/packages/ui/src/components/ConfirmDialog/ConfirmDialog.tsx‎

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export interface ConfirmDialogProps {
4444
open: boolean;
4545
pendingAction?: "confirm" | "secondary" | null;
4646
preview?: ReactNode;
47+
/** Place an alternative action at the start, separate from cancel and confirm. */
48+
secondaryActionPlacement?: "inline" | "start";
4749
secondaryText?: ReactNode;
4850
showCancel?: boolean;
4951
showCloseButton?: boolean;
@@ -75,6 +77,7 @@ export const ConfirmDialog = forwardRef<HTMLDivElement, ConfirmDialogProps>(
7577
open,
7678
pendingAction: controlledPendingAction,
7779
preview,
80+
secondaryActionPlacement = "inline",
7881
secondaryText,
7982
showCancel = true,
8083
showCloseButton = false,
@@ -91,6 +94,8 @@ export const ConfirmDialog = forwardRef<HTMLDivElement, ConfirmDialogProps>(
9194
const resolvedIcon = icon === false ? null : icon ?? defaultIcons[type];
9295
const hasMessage = message !== undefined && message !== null && message !== "";
9396
const hasPreview = preview !== undefined && preview !== null && preview !== "";
97+
const hasSecondary = secondaryText !== undefined && secondaryText !== null;
98+
const secondaryAtStart = hasSecondary && secondaryActionPlacement === "start";
9499
const resolvedCancelText = cancelText ?? designSystem.messages.confirmCancel;
95100
const resolvedConfirmText = confirmText ?? designSystem.messages.confirmAction;
96101

@@ -125,6 +130,35 @@ export const ConfirmDialog = forwardRef<HTMLDivElement, ConfirmDialogProps>(
125130
onOpenChange(false, reason);
126131
}, [busy, onOpenChange]);
127132

133+
const cancelButton = showCancel ? (
134+
<Button disabled={busy} onClick={() => requestClose("cancel-button")} variant="fill">
135+
{resolvedCancelText}
136+
</Button>
137+
) : null;
138+
const secondaryButton = hasSecondary ? (
139+
<Button
140+
disabled={busy}
141+
labelBehavior={secondaryAtStart ? "static" : undefined}
142+
loading={pendingAction === "secondary"}
143+
onClick={() => void runAction("secondary", onSecondary)}
144+
variant={secondaryAtStart ? "text" : "outline"}
145+
>
146+
{secondaryText}
147+
</Button>
148+
) : null;
149+
const confirmButton = (
150+
<Button
151+
disabled={busy}
152+
loading={pendingAction === "confirm"}
153+
onClick={() => void runAction("confirm", onConfirm)}
154+
ref={confirmButtonRef}
155+
tone={confirmDanger || type === "error" ? "danger" : "neutral"}
156+
variant="primary"
157+
>
158+
{resolvedConfirmText}
159+
</Button>
160+
);
161+
128162
return (
129163
<Dialog
130164
closeOnEscape={!busy && closeOnEscape}
@@ -186,31 +220,29 @@ export const ConfirmDialog = forwardRef<HTMLDivElement, ConfirmDialogProps>(
186220
</DialogBody>
187221
) : null}
188222
<DialogFooter>
189-
{showCancel ? (
190-
<Button disabled={busy} onClick={() => requestClose("cancel-button")} variant="fill">
191-
{resolvedCancelText}
192-
</Button>
193-
) : null}
194-
{secondaryText !== undefined && secondaryText !== null ? (
195-
<Button
196-
disabled={busy}
197-
loading={pendingAction === "secondary"}
198-
onClick={() => void runAction("secondary", onSecondary)}
199-
variant="outline"
223+
{secondaryAtStart ? (
224+
<div
225+
className={styles.splitActions}
226+
data-openbitfun-component="confirm-dialog"
227+
data-openbitfun-part="actions"
200228
>
201-
{secondaryText}
202-
</Button>
203-
) : null}
204-
<Button
205-
disabled={busy}
206-
loading={pendingAction === "confirm"}
207-
onClick={() => void runAction("confirm", onConfirm)}
208-
ref={confirmButtonRef}
209-
tone={confirmDanger || type === "error" ? "danger" : "neutral"}
210-
variant="primary"
211-
>
212-
{resolvedConfirmText}
213-
</Button>
229+
{secondaryButton}
230+
<div
231+
className={styles.actionGroup}
232+
data-openbitfun-component="confirm-dialog"
233+
data-openbitfun-part="actionGroup"
234+
>
235+
{cancelButton}
236+
{confirmButton}
237+
</div>
238+
</div>
239+
) : (
240+
<>
241+
{cancelButton}
242+
{secondaryButton}
243+
{confirmButton}
244+
</>
245+
)}
214246
</DialogFooter>
215247
</Dialog>
216248
);

‎src/crates/assembly/core/src/agentic/coordination/coordinator.rs‎

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11311,7 +11311,7 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet
1131111311
snapshot.parent_agent_type.clone(),
1131211312
snapshot.build_child_session_config(None),
1131311313
Some(format!("session-{}", snapshot.parent_session_id)),
11314-
SessionKind::Standard,
11314+
SessionKind::EphemeralChild,
1131511315
)
1131611316
.await?;
1131711317
self.session_manager
@@ -21722,7 +21722,7 @@ mod tests {
2172221722
}
2172321723

2172421724
#[tokio::test]
21725-
async fn btw_session_persists_relationship_and_seeds_forked_listing_baselines() {
21725+
async fn btw_session_is_ephemeral_and_seeds_forked_listing_baselines() {
2172621726
let (coordinator, session_manager) = test_persistent_coordinator();
2172721727
// The parent lives in a registered remote workspace; the child must
2172821728
// inherit that record's SSH facts rather than transport hints.
@@ -21811,7 +21811,7 @@ mod tests {
2181121811

2181221812
assert_eq!(
2181321813
child_session.kind,
21814-
crate::agentic::core::SessionKind::Standard
21814+
crate::agentic::core::SessionKind::EphemeralChild
2181521815
);
2181621816
assert_eq!(
2181721817
child_session.last_user_dialog_agent_type.as_deref(),
@@ -21871,26 +21871,27 @@ mod tests {
2187121871
let metadata = session_manager
2187221872
.load_session_metadata(&session_storage_path, &child_session.session_id)
2187321873
.await
21874-
.expect("BTW metadata should load")
21875-
.expect("BTW metadata should exist");
21876-
let relationship = metadata
21877-
.relationship
21878-
.expect("BTW relationship should persist");
21879-
assert_eq!(relationship.kind, Some(SessionRelationshipKind::Btw));
21880-
assert_eq!(
21881-
relationship.parent_session_id.as_deref(),
21882-
Some(parent_session.session_id.as_str())
21883-
);
21884-
assert_eq!(
21885-
relationship.parent_request_id.as_deref(),
21886-
Some("btw-request")
21887-
);
21888-
assert_eq!(
21889-
relationship.parent_dialog_turn_id.as_deref(),
21890-
Some("parent-turn")
21891-
);
21892-
assert_eq!(relationship.parent_turn_index, Some(2));
21893-
assert_eq!(metadata.memory_mode, SessionMemoryMode::Disabled);
21874+
.expect("BTW metadata lookup should succeed");
21875+
assert!(metadata.is_none(), "temporary BTW must not write history");
21876+
assert!(!session_manager.should_persist_session_id(&child_session.session_id));
21877+
21878+
let reused = coordinator
21879+
.ensure_btw_session(
21880+
&parent_session.session_id,
21881+
&child_session.session_id,
21882+
None,
21883+
"btw-follow-up",
21884+
Some("parent-turn"),
21885+
Some(2),
21886+
)
21887+
.await
21888+
.expect("temporary BTW should accept follow-up questions");
21889+
assert_eq!(reused.kind, SessionKind::EphemeralChild);
21890+
assert!(session_manager
21891+
.load_session_metadata(&session_storage_path, &child_session.session_id)
21892+
.await
21893+
.expect("BTW metadata lookup after reuse should succeed")
21894+
.is_none());
2189421895
}
2189521896

2189621897
#[test]

‎src/crates/assembly/core/src/agentic/persistence/manager.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1124,7 +1124,7 @@ impl PersistenceManager {
11241124
}
11251125
}
11261126

1127-
async fn build_session_metadata(
1127+
pub(super) async fn build_session_metadata(
11281128
&self,
11291129
workspace_path: &Path,
11301130
session: &Session,

0 commit comments

Comments
 (0)