Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cbb6f98
feat: add in-chat computer use toggle
null-crafter Jul 7, 2026
796088d
feat: enable remote AI chat + MCP tools for computer use
null-crafter Jul 8, 2026
bfe6dd3
feat: Computer Use no longer requires a local model
null-crafter Jul 8, 2026
d9d3e89
chore: bump @iblai/iblai-js to 1.22.5-computer-use-1.12
null-crafter Jul 9, 2026
70712e8
feat: move local llms to llm tab
null-crafter Jul 20, 2026
a826cc0
chore: merge main
null-crafter Jul 22, 2026
9304e57
feat: improve local model handling
null-crafter Jul 22, 2026
a77df3c
feat(mentor): on-device models in the LLM picker — canonical provider…
null-crafter Jul 23, 2026
6a0c9f1
chore: fix deps
null-crafter Jul 23, 2026
6ad3ef4
chore: merge main
null-crafter Jul 23, 2026
cbe8d31
feat: ACP integration
null-crafter Jul 24, 2026
cc09d02
fix: update tests
null-crafter Jul 27, 2026
cfbb189
chore: merge main
null-crafter Jul 27, 2026
e6bcf1b
chore: update lock files
null-crafter Jul 28, 2026
2c85392
chore: merge main
null-crafter Jul 28, 2026
2d5e67d
chore: add translations
null-crafter Jul 28, 2026
be6422f
chore: update lockfile
null-crafter Jul 28, 2026
7a4a318
chore: fix deps
null-crafter Jul 28, 2026
a6e4fdf
chore: fix deps
null-crafter Jul 28, 2026
a3fcee5
chore: fix deps
null-crafter Jul 28, 2026
0bf6639
feat: add ACP implementation
null-crafter Jul 28, 2026
751cc99
chore: merge main
null-crafter Jul 28, 2026
791df32
chore: exclude tauri e2e tests in unit tests
null-crafter Jul 29, 2026
0135366
tests: fix tests
null-crafter Jul 29, 2026
3ef33bd
Merge branch 'main' of https://github.com/iblai/os
null-crafter Jul 29, 2026
a068298
Merge branch 'feat/acp' (ACP / opencode Coding Mode)
null-crafter Jul 30, 2026
27fc2c9
feat: merge in acp support
null-crafter Jul 30, 2026
5e7b97b
feat: cowork mcp updates
null-crafter Jul 30, 2026
41cedd8
chore: merge main
null-crafter Jul 30, 2026
7ed5963
feat: parallel opencode acp for code mode
null-crafter Jul 31, 2026
9bf0133
chore: update pnpm lock
null-crafter Jul 31, 2026
02eaf94
chore: merge main
null-crafter Jul 31, 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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,9 @@ e2e/playwright-report
e2e/test-results.json
playwright/.auth
e2e/playwright/.auth


# harnesses
.omo
.omp
.pi
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Chores

* **deps:** bump @iblai/iblai-js to 2.2.7 ([462b98f](https://github.com/iblai/os/commit/462b98fa48202ea7dd1e778382815523037014db))
- **deps:** bump @iblai/iblai-js to 2.2.7 ([462b98f](https://github.com/iblai/os/commit/462b98fa48202ea7dd1e778382815523037014db))

## [0.108.11](https://github.com/iblai/os/compare/v0.108.10...v0.108.11) (2026-07-30)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export function ChatRowItem({
isPinned,
isLoading,
canExport = true,
awaitingPermission = false,
onPinToggle,
onExport,
onDelete,
Expand All @@ -114,6 +115,8 @@ export function ChatRowItem({
isPinned: boolean;
isLoading: boolean;
canExport?: boolean;
/** Code is blocked on a permission answer in this chat. */
awaitingPermission?: boolean;
onPinToggle: () => void;
onExport: () => void;
onDelete: () => void;
Expand All @@ -137,6 +140,19 @@ export function ChatRowItem({
: 'text-[#4a5568] hover:bg-[#f4f4f4]',
)}
>
{/* Code's prompts only render in the chat that raised them, so a background
chat would otherwise wait unseen and time out as denied after 180s. This is
the only signal telling the user where to look — deliberately in the row's
own muted grey rather than an accent colour, so it reads as a hint you can
find when you look for it, not an alert competing with the chat title. */}
{awaitingPermission && (
<span
data-testid="chat-awaiting-permission"
title={t('awaitingPermission')}
aria-label={t('awaitingPermission')}
className="size-1.5 shrink-0 rounded-full bg-[#9ca3af]"
/>
)}
<span className="line-clamp-1 min-w-0 flex-1 overflow-hidden">
{chatRowLabel(row, t('noContent'))}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {

import { ChatRow, chatRowLabel } from './chat-row-label';
import { ChatRowItem } from './chat-row';
import { useCodePermissionSessions } from '@/components/chat/code-permission-card';
import { useRecentChats } from './use-recent-chats';

const NAV_MUTED = '#5f5f61';
Expand Down Expand Up @@ -76,6 +77,10 @@ export function SidebarChatsSection({
onAfterNav,
});

// Chats where Code is blocked on an answer. Its prompts render only in the chat that
// raised them, so without this a background chat waits unseen until it times out.
const awaitingPermission = useCodePermissionSessions();

// Render helpers -----------------------------------------------------

const renderRow = (row: ChatRow, kind: 'pinned' | 'recent') => (
Expand All @@ -87,6 +92,7 @@ export function SidebarChatsSection({
isPinned={kind === 'pinned'}
isLoading={actingSessionId === row.session_id}
canExport={canExport}
awaitingPermission={awaitingPermission.has(row.session_id)}
onPinToggle={() =>
kind === 'pinned' ? handleUnpin(row) : handlePin(row)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,34 @@ vi.mock('@/components/modals/auth-modal', () => ({
}));

let lastCreditBalanceProps: any = null;
// On-device selection, driven per-test; default OFF so existing tests see the
// pre-badge nav-bar. One catalog model lets the badge resolve a display name.
let mockLocalEnabled = false;
let mockLocalModelId: string | null = null;
vi.mock('@iblai/iblai-js/web-containers', () => ({
// The toggle now ships from the SDK; tests don't exercise its internals,
// so a marker-only stub is enough — it also blocks the SDK's transitive
// axios chain from being pulled into the test's module graph. The marker
// lets path-gating tests assert whether the chip is mounted at all.

ChatPrivacyToggle: () => <div data-testid="chat-privacy-toggle" />,
// On-device model helpers read by `useSelectedLocalModel` (the top-left
// badge). Default to local mode OFF so the nav-bar renders exactly as it did
// before the badge existed (cloud selector shown, no badge).
isLocalLLMEnabled: () => mockLocalEnabled,
getLocalLLMModel: () => mockLocalModelId,
// The hook reconciles the tool-support cache against the catalog on read.
getLocalLLMToolSupport: () => true,
setLocalLLMToolSupport: () => {},
LOCAL_MODELS: [
{
name: 'Llama 3.2',
provider: 'Meta',
id: 'llama3.2',
size: '2.0 GB',
tool_support: true,
},
],
NotificationDropdown: () => (
<div data-testid="notification-dropdown">Notifications</div>
),
Expand Down Expand Up @@ -583,6 +605,8 @@ describe('NavBar', () => {
};
mockAllTenants = [{ key: 'test-tenant' }];
lastCreditBalanceProps = null;
mockLocalEnabled = false;
mockLocalModelId = null;
emitMock.mockClear();
navigateToHomeMock.mockClear();
// Suppress console.log during tests
Expand Down Expand Up @@ -664,6 +688,51 @@ describe('NavBar', () => {
expect(screen.getByLabelText('LLM Model Selector')).toBeInTheDocument();
});

it('shows the on-device model badge and hides the cloud selector when local mode is on', () => {
mockIsAdmin = true;
mockUserIsStudent = false;
mockPathname = '/platform/tenant123/mentor456';
mockLocalEnabled = true;
mockLocalModelId = 'llama3.2';
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

const badge = screen.getByTestId('local-model-indicator');
expect(badge).toHaveTextContent('Llama 3.2');
expect(badge).toHaveTextContent('On-device');
// The on-device badge replaces the cloud model selector while local is on.
expect(
screen.queryByLabelText('LLM Model Selector'),
).not.toBeInTheDocument();
});

it('opens the model picker when the on-device badge is clicked, so an admin can still choose an LLM while local is on', () => {
mockIsAdmin = true;
mockUserIsStudent = false;
mockPathname = '/platform/tenant123/mentor456';
mockLocalEnabled = true;
mockLocalModelId = 'llama3.2';
const store = createTestStore();

render(
<Provider store={store}>
<NavBar />
</Provider>,
);

// Picker is closed until the badge is clicked.
expect(
screen.queryByTestId('llm-provider-modal'),
).not.toBeInTheDocument();
fireEvent.click(screen.getByTestId('local-model-indicator'));
expect(screen.getByTestId('llm-provider-modal')).toBeInTheDocument();
});

it('hides the LLM model selector on the tenant-scoped projects index page', () => {
mockIsAdmin = true;
mockUserIsStudent = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,19 @@ vi.mock('@iblai/iblai-js/web-utils', () => ({
},
}));

// Mock @iblai/iblai-api to provide the enum
// Mock @iblai/iblai-api to provide the enums. A factory mock replaces the WHOLE
// module, so every enum reached transitively from the nav-bar import graph must
// be listed here — `PromptVisibilityEnum` (pulled in via the prompt modals) is
// read at module scope, so omitting it fails the file at import time.
vi.mock('@iblai/iblai-api', () => ({
MentorVisibilityEnum: {
VIEWABLE_BY_ANYONE: 'VIEWABLE_BY_ANYONE',
},
PromptVisibilityEnum: {
VIEWABLE_BY_TENANT_ADMINS: 'viewable_by_tenant_admins',
VIEWABLE_BY_TENANT_STUDENTS: 'viewable_by_tenant_students',
VIEWABLE_BY_ANYONE: 'viewable_by_anyone',
},
UserApp: {},
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,19 @@ vi.mock('@iblai/iblai-js/web-utils', () => ({
},
}));

// A factory mock replaces the WHOLE module, so every enum reached transitively
// from the nav-bar import graph must be listed here — `PromptVisibilityEnum`
// (pulled in via the prompt modals) is read at module scope, so omitting it
// fails the file at import time.
vi.mock('@iblai/iblai-api', () => ({
MentorVisibilityEnum: {
VIEWABLE_BY_ANYONE: 'VIEWABLE_BY_ANYONE',
},
PromptVisibilityEnum: {
VIEWABLE_BY_TENANT_ADMINS: 'viewable_by_tenant_admins',
VIEWABLE_BY_TENANT_STUDENTS: 'viewable_by_tenant_students',
VIEWABLE_BY_ANYONE: 'viewable_by_anyone',
},
UserApp: {},
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,19 @@ vi.mock('@iblai/iblai-js/web-utils', () => ({
},
}));

// Mock @iblai/iblai-api to provide the enum
// Mock @iblai/iblai-api to provide the enums. A factory mock replaces the WHOLE
// module, so every enum reached transitively from the nav-bar import graph must
// be listed here — `PromptVisibilityEnum` (pulled in via the prompt modals) is
// read at module scope, so omitting it fails the file at import time.
vi.mock('@iblai/iblai-api', () => ({
MentorVisibilityEnum: {
VIEWABLE_BY_ANYONE: 'VIEWABLE_BY_ANYONE',
},
PromptVisibilityEnum: {
VIEWABLE_BY_TENANT_ADMINS: 'viewable_by_tenant_admins',
VIEWABLE_BY_TENANT_STUDENTS: 'viewable_by_tenant_students',
VIEWABLE_BY_ANYONE: 'viewable_by_anyone',
},
UserApp: {},
}));

Expand Down
Loading
Loading