Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
aae6420
feat(assistant): coordinate work through a durable inbox bot
xxhZs Oct 15, 2026
712d531
fix(assistant): reconcile durable wakes and canonical outbox retries
xxhZs Oct 15, 2026
b4ce59d
fix(assistant): clarify bot duties and separate discovery configuration
xxhZs Oct 16, 2026
f1a5fcb
refactor(assistant): focus bot instructions on duties and mechanics
xxhZs Oct 16, 2026
d6ff911
refactor(assistant): clarify bot duties and delegation
xxhZs Oct 16, 2026
619d936
fix(memory): share and parallelize source refreshes in index listings
xxhZs Oct 16, 2026
01546d7
feat(assistant): add expandable message summaries and typing indicator
xxhZs Oct 16, 2026
5bb4956
fix(assistant): recover interrupted wakes and resolve review checks
xxhZs Oct 16, 2026
bac660d
fix(browser): allow selected sessions to operate hidden browser panels
xxhZs Oct 16, 2026
788bbb1
fix(ui): render summary markdown and unify expanded body colors
xxhZs Oct 16, 2026
e781f3f
fix(assistant): use Astryx summary button and update SendUser fixtures
xxhZs Oct 16, 2026
af03f0d
Merge remote-tracking branch 'origin/main' into feat/assistant-inbox-bot
xxhZs Oct 17, 2026
9cba5c2
fix(assistant): preserve chat empty states and fence browser and mess…
xxhZs Oct 17, 2026
db11126
fix(desktop): remove unused personal assistant re-exports
xxhZs Oct 17, 2026
f471bce
fix(assistant): address remaining provenance, safety and renderer nits
xxhZs Oct 18, 2026
c59ff5a
fix(bot): retain production connector provenance through turn admission
xxhZs Oct 18, 2026
df549e5
fix(assistant): retry failed initiative binding resolution
xxhZs Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/desktop/renderer-architecture.json
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@
"src/renderer/features/personal-assistant/index.tsx": {
"appShell": [
"MemoryOriginalHost",
"PersonalAssistantChatLayout",
"PersonalAssistantEntry",
"createInternalUriDispatcher"
],
Expand Down Expand Up @@ -743,7 +744,7 @@
"react": 1
},
"importSpecifiers": 43,
"nonTriviaTokens": 7276
"nonTriviaTokens": 7274
},
"src/renderer/use-app-shell-session-list.ts": {
"importDeclarations": 3,
Expand Down Expand Up @@ -919,6 +920,7 @@
"./application/contracts/session-inspector/latest-request-usage.js": 1,
"./composer-mentions.js": 1,
"./features/conversation/index.js": 1,
"./features/personal-assistant/index.js": 1,
"@maka/ui": 1,
"react": 1
}
Expand All @@ -938,6 +940,7 @@
"./application/contracts/session-catalog/use-external-store-selector.js": 1,
"./chat-recovery-notice": 1,
"./features/conversation/index.js": 1,
"./features/personal-assistant/index.js": 1,
"./locales/shell-copy": 1,
"./onboarding-hero": 1,
"@maka/ui": 1,
Expand Down
51 changes: 40 additions & 11 deletions apps/desktop/src/main/__tests__/browser-ipc-main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/

import assert from 'node:assert/strict';
import { createBrowserSelectionCoordinator } from '../../preload/browser-selection.js';
import { EventEmitter } from 'node:events';
import { registerHooks } from 'node:module';
import test from 'node:test';
Expand Down Expand Up @@ -220,42 +221,43 @@ test('browser IPC isolates owned renderer documents and their native parents', a
assert.deepEqual(mainController.navigations, ['https://main.example/']);
assert.deepEqual(workHubController.navigations, ['https://workhub.example/']);

// An ordinary session remains foreground-only even in an auxiliary renderer.
// Hiding also revokes a read already waiting on the page.
// An ordinary selected Session can operate with its browser hidden.
// Hiding does not revoke a read already waiting on the page.
setBridgeFactoryForTest(() => ({
connect: async () => ({ getCurrentUrl: async () => 'https://workhub.example/' }) as never,
close: async () => undefined,
send: async () => undefined,
waitForEvent: async () => undefined,
}));
let finishReadOrdinary!: () => void;
let reading!: () => void;
const started = new Promise<void>((resolve) => { reading = resolve; });
const pendingRead = withBrowserPage(workHubKey, 'snapshot', async () => {
reading();
return new Promise<never>(() => {});
return new Promise<void>((resolve) => { finishReadOrdinary = resolve; });
});
const revoked = assert.rejects(pendingRead, BrowserActionRevokedError);
await started;
workHubVisible = false;
browserIpc.refreshVisibility();
await revoked;
finishReadOrdinary();
await pendingRead;
for (const kind of ['observe', 'navigate', 'mutate'] as const) {
assert.equal(await browserViewHost().canDrive(workHubKey, kind), false);
assert.equal(await browserViewHost().canDrive(workHubKey, kind), true);
}
assert.equal(await browserViewHost().canDrive(mainKey, 'observe'), true);
workHubVisible = true;
assert.equal(await browserViewHost().canDrive(workHubKey, 'observe'), true);
// Reparenting follows the current native window, not the initial dock.
windows.set(workHub, floatingWindow);
floatingWindow.visible = false;
assert.equal(await browserViewHost().canDrive(workHubKey, 'navigate'), false);
assert.equal(await browserViewHost().canDrive(workHubKey, 'navigate'), true);
floatingWindow.visible = true;
floatingWindow.minimized = true;
assert.equal(await browserViewHost().canDrive(workHubKey, 'observe'), false);
assert.equal(await browserViewHost().canDrive(workHubKey, 'observe'), true);
floatingWindow.minimized = false;
assert.equal(await browserViewHost().canDrive(workHubKey, 'navigate'), true);

// The reserved coordination identity alone gets background access, still
// Background access still requires a selected Session and is
// fenced by Host epoch, live selection, and renderer lifetime.
const coordinationKey = desktopSessionResourceKey({ ...scope, sessionId: WORKHUB_COORDINATION_SESSION_ID });
workHubVisible = false;
Expand Down Expand Up @@ -358,8 +360,35 @@ test('browser IPC isolates owned renderer documents and their native parents', a

// Closing Main destroys its WebContents before selection cleanup runs.
// Electron rejects fromWebContents on that destroyed native object.
emit('browser:active-session', main, scope, 'main-session', 'main-document', 5);
emit('browser:setViewport', main, scope, { sessionId: 'main-session', rect: mainRect }, 'main-document', 5);
emit('browser:active-session', main, scope, 'main-session', 'main-document', 6);
emit('browser:setViewport', main, scope, { sessionId: 'main-session', rect: mainRect }, 'main-document', 6);
emit('browser:setViewport', main, scope, { sessionId: 'main-session', rect: null }, 'main-document', 6);
assert.equal(mainController.viewports.at(-1), null);
assert.equal(await browserViewHost().canDrive(mainKey, 'mutate'), true);
const hiddenLease = browserViewHost().beginAction(mainKey);
assert.ok(hiddenLease, 'collapsed ordinary browser gets a background rendering lease');
await hiddenLease.ready;
await hiddenLease.release();
assert.equal(mainController.viewports.at(-1), null, 'automation does not open the panel');
// Exercise preload + IPC together: an unresolved replacement must not
// leave the old hidden Session authorized.
let resolveNext!: (value: { scope: typeof scope; sessionId: string }) => void;
const next = new Promise<{ scope: typeof scope; sessionId: string }>((resolve) => { resolveNext = resolve; });
const selection = createBrowserSelectionCoordinator(() => next, {
show: (doc, generation, session) => emit('browser:active-session', main, session.scope, session.sessionId, doc, generation + 10),
hide: (doc, generation) => emit('browser:hide-active-session', main, doc, generation + 10),
capturePage: async () => undefined,
setViewport: () => {},
}, 'main-document');
selection.setActiveSession('replacement');
for (const kind of ['observe', 'navigate', 'mutate'] as const)
assert.equal(await browserViewHost().canDrive(mainKey, kind), false);
let clicked = false;
await assert.rejects(withBrowserPage(mainKey, 'click', async () => { clicked = true; }));
assert.equal(clicked, false);
resolveNext({ scope, sessionId: 'replacement' });
await new Promise<void>((resolve) => setImmediate(resolve));
assert.equal(await browserViewHost().canDrive(mainKey, 'observe'), false);
assert.doesNotThrow(() => main.destroy());
await new Promise<void>((resolve) => setImmediate(resolve));
assert.equal(mainController.disposed, true, 'closing Main still releases its owned browser page');
Expand Down
28 changes: 26 additions & 2 deletions apps/desktop/src/main/__tests__/browser-selection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('browser selection ordering', () => {
pending.resolve({ scope: 'host-a', sessionId: 'runtime-a' });
await flush();

assert.deepEqual(events, ['hide:2']);
assert.deepEqual(events, ['hide:1', 'hide:3']);
});

it('binds capture and viewport delivery to the newest resolved selection generation', async () => {
Expand Down Expand Up @@ -75,6 +75,30 @@ describe('browser selection ordering', () => {

assert.equal(await staleCapture, undefined);
assert.equal(await currentCapture, 'backdrop');
assert.deepEqual(events, ['show:2:runtime-b', 'capture:runtime-b', 'viewport:2:runtime-b']);
assert.deepEqual(events, ['hide:1', 'hide:3', 'show:4:runtime-b', 'capture:runtime-b', 'viewport:4:runtime-b']);
});
});

it('revokes the previous selection before resolving the replacement and stays revoked on failure', async () => {
const pending = deferred<{ scope: string; sessionId: string }>();
let selected: string | undefined;
const events: number[] = [];
const coordinator = createBrowserSelectionCoordinator(
(id) => id === 'a' ? Promise.resolve({ scope: 'host', sessionId: 'a' }) : pending.promise,
{
show: (_, generation, session) => { events.push(generation); selected = session.sessionId; },
hide: (_, generation) => { events.push(generation); selected = undefined; },
capturePage: async () => undefined,
setViewport: () => {},
},
);
coordinator.setActiveSession('a');
await flush();
assert.equal(selected, 'a');
coordinator.setActiveSession('b');
assert.equal(selected, undefined, 'A must lose access while B is unresolved');
pending.reject(Error('Host unavailable'));
await flush();
assert.equal(selected, undefined);
assert.deepEqual(events, [1, 2, 3, 4]);
});
47 changes: 47 additions & 0 deletions apps/desktop/src/main/__tests__/session-local.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,53 @@ test('a Message dispatched in a released epoch is settled from the Host, never r
assert.equal(store.get('authority', 'message-1')?.result?.disposition, 'turn_started');
});

test('an assistant Inbox receipt survives a released epoch without replaying or inventing a Turn', async (t) => {
const { store, beforeClose } = await database(t);
const submissions: TurnMessageSubmitInput[] = [];
const queried: string[][] = [];
const target: DesktopSessionLocalTarget = {
partition: 'authority',
profileId: 'profile',
scope: { hostId: 'root', targetEpoch: 'target-1' },
client: {
...client('epoch-2'),
async queryMessageExecutions(input) {
queried.push([...input.messageIds]);
return {
resolutions: [
{ messageId: 'message-1', state: 'inbox' },
],
};
},
},
submit: async (input) => {
submissions.push(input);
return accepted;
},
};
const service = new DesktopSessionLocalService(store, {
targets: () => [target],
changed() {},
onError: (error) => assert.fail(String(error)),
});
beforeClose.push(() => service.close());
// Dispatch once in the previous epoch, then lose the answer the way a Host
// restart does: the local copy is left `unknown` with a dead dispatch epoch.
const dispatched = store.enqueue('authority', intent());
store.update({
...dispatched,
state: 'unknown',
intent: { ...dispatched.intent, originHostEpoch: 'epoch-1' },
});
service.wake();
await waitFor(() => store.get('authority', 'message-1')?.state === 'accepted');
assert.deepEqual(queried, [['message-1']]);
// The old epoch's submit is never replayed: that answer can only be
// `outcome_unknown`, which would freeze the copy forever.
assert.deepEqual(submissions, []);
assert.equal(store.get('authority', 'message-1')?.result?.disposition, 'inbox');
});

test('a stale-epoch Message the Host never admitted is retired so later sends proceed', async (t) => {
const { store, beforeClose } = await database(t);
const submissions: string[] = [];
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/browser-ipc-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function registerBrowserIpc(deps: BrowserIpcDeps): BrowserIpcController {
const owner = ownerForSession(sessionId);
if (!owner || !browserParentForRenderer(owner)) return false;
const ref = parseDesktopSessionResourceKey(sessionId);
return isWorkHubCoordinationSessionId(ref.sessionId) && deps.isHostActive(ref);
return deps.isHostActive(ref);

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.

[P1] Preserve the ordinary-Session browser visibility gate

Removing the coordination-Session predicate grants every selected ordinary Session a background action lease. A renderer can remain selected while its parent view or native window is hidden/minimized. createBrowserViewHost.canDrive then returns true before checking visibility/viewport, and revokeHiddenActions no longer severs its running action. An agent can therefore read or mutate an authenticated page that the user cannot see, contrary to the explicit security policy in browser/logic.ts:99–107.

Using the registered IPC and actual withBrowserPage path with the existing native/CDP fixture, I observed all three action callbacks execute with the ordinary owner hidden, its window hidden, and its window minimized. Loading only the actual d6ff911 IPC module blocks all nine with BrowserActionBlockedError. Collapsing the panel also newly admits mutation without a viewport. The changed test now expects these hidden actions to succeed and removes the revocation assertion.

Keep background access restricted to WorkHub coordination as before, or explicitly revoke ordinary-Session access when the view/window is hidden, minimized or the panel is collapsed. Retain negative tests for hidden reads, mutations and in-flight revocation; selection/Host identity alone is not an equivalent security boundary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The feature owner has explicitly confirmed that background browser operation includes a hidden or minimized Maka window, as well as a collapsed browser panel. This does not exceed the requested product scope; the changed admission expectations are intentional. We will retain this capability rather than restore visibility-based denial. This does not authorize arbitrary unselected sessions: selected-session ownership, renderer lifetime, active Host and epoch checks still apply. The old visibility-only policy comment needs to be reconciled with this intentional policy change; it should not be treated as evidence that the requested behavior itself is accidental.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@Astro-Han Browser operation while the Maka window is hidden or minimized, or the browser panel is collapsed, is intended product behavior. Please re-review the browser finding on that basis, including whether the remaining selected-session ownership, renderer lifetime, active Host and epoch checks are sufficient for this intended capability. This clarification does not claim that the separate Inbox admission race or architecture-check failures have been fixed.

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.

P2: With hidden ordinary Sessions now allowed to act, the previous Session keeps this access until the renderer's next browser:active-session arrives. browser-selection.ts:63-84 sends that only after the new Session's Host identity resolves, and sends nothing to revoke the old selection first. Reproduced: while B's resolution was pending, a hidden and collapsed A still executed browser_click. Revoke the old selection immediately on change, then show the new one when it resolves.

};
const revokeHiddenActions = (): void => {
for (const sessionId of views.sessionIds()) views.get(sessionId)?.refreshRendering();
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/main/browser/automation-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import type { BrowserViewManager } from './view-manager.js';
* with views owned by conversations, session → endpoint resolution is the
* identity mapping — ensure the conversation's view and attach the CDP bridge to
* it. An action always lands in its OWN session's view, but that view may be
* hidden (the user switched conversations), so canDrive enforces the visible
* lease for ordinary sessions. WorkHub coordination can hold an explicit
* background action lease instead. The
* hidden (the browser panel is collapsed), so every renderer-owned Session
* on a live Host can hold a background action lease. Unselected Sessions
* remain gated by their presentation ownership. The
* endpoint and its secret stay same-process values, never crossing renderer IPC
* or preload.
*
Expand Down
6 changes: 3 additions & 3 deletions apps/desktop/src/main/browser/browser-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ export interface BrowserViewHost {
openOriginLease(sessionId: string, approvedUrl: string, kind: BrowserActionKind): BrowserOriginLease;
/**
* The Host action gate: may `sessionId` run a
* `kind` action right now? Ordinary Sessions require a visible conversation
* (mutation also needs a viewport). The Host-bound WorkHub coordination
* Session alone may operate in the background. Checked before resolveEndpoint so a blocked action
* `kind` action right now? Renderer-owned Sessions on a live Host may operate with a hidden
* browser panel, using a background action lease for rendering and input.
* Session selection and renderer ownership still apply. Checked before resolveEndpoint so a blocked action
* creates no view.
*
* Resolves async for one case: a mutate on the conversation that IS on screen
Expand Down
17 changes: 8 additions & 9 deletions apps/desktop/src/main/browser/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,14 @@ export function viewportBoundsAtScale(
export type BrowserActionKind = 'observe' | 'mutate' | 'navigate';

/**
* The ordinary-session visible-lease policy. The agent runs in a conversation's runtime, which may
* NOT be the conversation on screen; without this gate it could drive a hidden,
* zero-bounds view after the user switches away. EVERY action — including a
* read (observe) — must happen in the conversation the user is looking at:
* observing a logged-in page off screen would let a backgrounded conversation
* exfiltrate its content the user can't see. `mutate` (click/type) additionally
* needs real on-screen bounds, because opencli's native CDP click hit-tests a
* composited frame a hidden view lacks; observe/navigate need only that the
* session is shown (reading and goto don't require a painted frame).
* Fallback visible-lease policy for callers without background authorization.
* The desktop can separately authorize a selected Session on an active Host to
* operate with its browser panel collapsed or its window hidden/minimized.
* That authorization is fenced by selection generation, Host identity/epoch and
* live renderer ownership. Switching Sessions must revoke the old selection
* immediately, before resolving the new owner. Unselected Sessions get no
* background lease. Callers without that lease need visibility, and mutations
* additionally need a viewport.
*/
export function browserActionAllowed(
kind: BrowserActionKind,
Expand Down
3 changes: 1 addition & 2 deletions apps/desktop/src/main/browser/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,7 @@ export async function withBrowserPage<T>(
): Promise<T> {
if (opts?.abort?.aborted) throw new BrowserActionCanceledError(label);
const kind: TakeoverMode = opts?.takeover ?? 'observe';
// The Host admits visible ordinary Sessions and the background-authorized
// WorkHub coordination Session. Runs BEFORE acquire, so a vetoed
// The Host admits renderer-owned Sessions even with hidden browser panels. Runs BEFORE acquire, so a vetoed
// background action creates no view and opens no connection. For a mutate whose
// viewport is briefly absent (a permission modal just closed), canDrive waits
// out the renderer's strip restore so the first approved click/type lands;
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/main/session-local-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,8 @@ export class DesktopSessionLocalService {
},
error: undefined,
});
} else if (resolution?.state === 'inbox') {
this.store.update({ ...current, state: 'accepted', result: { disposition: 'inbox', skillInvocation: EMPTY_SKILL_INVOCATION }, error: undefined });
} else if (resolution?.state === 'pending') {
// The Host durably holds a queued admission for it. Delivery succeeded;
// the Host queue owns the ordering, so this local copy yields.
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/preload/bridge-contract.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1282,7 +1282,7 @@ export interface MakaBridge {
): Promise<
| {
ok: true;
disposition: 'turn_started' | 'steering' | 'followup' | 'locally_saved';
disposition: 'turn_started' | 'steering' | 'followup' | 'locally_saved' | 'inbox';
turnId?: string;
attachments: import('@maka/core/events').AttachmentRef[];
inlineReferences: import('@maka/core/events').InlineReference[];
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src/preload/browser-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@ export function createBrowserSelectionCoordinator(
return {
setActiveSession(sessionId: string | null): void {
generation += 1;
// Revoke the previous owner before asynchronous Host identity resolution.
current = undefined;
sink.hide(documentId, generation);
if (!sessionId) {
current = undefined;
sink.hide(documentId, generation);
return;
}

// Main admits only strictly newer generations; show follows the revoke.
generation += 1;
const selection = {
generation,
sessionId,
Expand Down
11 changes: 5 additions & 6 deletions apps/desktop/src/renderer/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { MemoryOriginalHost, PersonalAssistantEntry, createInternalUriDispatcher } from './features/personal-assistant/index.js';
import { PersonalAssistantChatLayout, MemoryOriginalHost, PersonalAssistantEntry, createInternalUriDispatcher } from './features/personal-assistant/index.js';
import { WorkHubControlOverlay, WorkHubDock, WorkHubMainNavigation } from './features/workhub';
import { WorkHubEnablementWatch } from './application/contracts/workhub-workspace/workhub-enablement.js';
import { RuntimeHostHandoffOverlay } from './features/runtime-host-management/index.js';
Expand All @@ -39,7 +39,6 @@ import { isLinkedSubagentSession } from '@maka/core/session';
import { resolveUiLocale } from '@maka/core/ui-locale';
import { hasSettledInitialOnboarding } from '@maka/core/onboarding-milestone';
import {
ChatSurfaceLayout,
type ComposerHandle,
MakaUriContext,
AstryxLocaleProvider,
Expand Down Expand Up @@ -1305,13 +1304,13 @@ function AppShellContent({
onOpenUsage={() => commands.toggleTool('inspector')} onToggleWorkbar={commands.toggleRightPanel}
onOpenWorkHub={openWorkHub} onOpenSession={(sessionId) => { closeSettings(); openSession(sessionId); }} />
<WorkHubDock workbar={selectors} workbarTogglePosition={workbarTogglePosition} visible={workHubActive && sessionsSelected && !shellObscured} />
<ChatSurfaceLayout
data-session-history-surface="true"
<PersonalAssistantChatLayout
session={activeSessionForView}
// ChatView positions this transcript: switching conversations,
// following the tail and the moves the reader asks for are one
// authority there, and the composer never remounts for any of
// them — its contenteditable DOM carries the live draft.
data-maka-onboarding={showOnboardingHero ? 'true' : undefined}
onboarding={showOnboardingHero}
scrollToBottomLabel={
desktopConversationCopy.actions.scrollMainToBottom
}
Expand Down Expand Up @@ -1475,7 +1474,7 @@ function AppShellContent({
/>

) : null}
</ChatSurfaceLayout>
</PersonalAssistantChatLayout>
</Conversation.ConversationHomeSurface>
{/* Collapse hides the Workbar surface without unmounting its tools. */}
<WorkbarHost togglePosition={workbarTogglePosition} />
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/renderer/chat-composer-region.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import {
Button,
ClientCapabilityPrompt,
clearUserQuestionWizardState,
Composer,
type ComposerInteraction,
ComposerGoalProjectionConsumer,
FormInteractionPrompt,
SandboxBoundaryPrompt,
UserQuestionPrompt,
} from '@maka/ui';
import { PersonalAssistantComposer as Composer } from './features/personal-assistant/index.js';
import { StagedComposer, type ComposerStagingProp } from './features/conversation/index.js';
import type { ComposerHandle } from '@maka/ui';
import {
Expand Down
Loading