Computer Use Updates
Rolling release / release (push) Failing after 1m46s

This commit is contained in:
2026-09-13 19:30:17 -04:00
parent c5ccaa490b
commit 599bfe440d
34 changed files with 1033 additions and 573 deletions
@@ -118,7 +118,6 @@ export default class JarvisExtension extends Extension {
view.onSuggestion = (id, payload) => this._call('Ask', '(s)', [payload?.text || payload?.prompt || id]);
view.onConfirm = (jobId, toolCallId, decision) => this._call('Confirm', '(sss)', [jobId, toolCallId, decision]);
view.onConfirmShown = () => this._openPopup();
view.onExpand = () => this.session.show(true);
view.onSettings = () => this._openSettings();
view.onMode = (mode) => this._call('SetMode', '(s)', [mode]);
}
@@ -134,18 +133,6 @@ export default class JarvisExtension extends Extension {
menu.box.add_child(this.popup.root);
}
menu.actor?.add_style_class_name?.('jarvis-menu');
try {
menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
const grantItem = new PopupMenu.PopupMenuItem('Grant desktop');
grantItem.connect('activate', () => this._call('ComputerGrant', '(b)', [true]));
menu.addMenuItem(grantItem);
const revokeItem = new PopupMenu.PopupMenuItem('Revoke desktop');
revokeItem.connect('activate', () => this._call('ComputerRevoke'));
menu.addMenuItem(revokeItem);
const settingsItem = new PopupMenu.PopupMenuItem('Settings');
settingsItem.connect('activate', () => this._openSettings());
menu.addMenuItem(settingsItem);
} catch {}
this._menuState = menu.connect('open-state-changed', (_menu, open) => {
if (!open) this.popup.endTalk();
else this.popup.followActive();
@@ -133,8 +133,8 @@ function grantRow() {
});
};
allow.connect('clicked', () => {
callDaemon('ComputerGrant', '(b)', [true], (error) => {
row.subtitle = error ? `Could not grant: ${error.message}` : 'Grant requested. A portal prompt may appear.';
callDaemon('ComputerGrant', '(b)', [false], (error) => {
row.subtitle = error ? `Could not grant: ${error.message}` : 'Grant requested. Choose a screen in the GNOME prompt.';
refresh();
});
});
@@ -5,7 +5,7 @@ import Shell from 'gi://Shell';
const BUS = 'io.qvac.Jarvis.Shell';
const PATH = '/io/qvac/Jarvis/Shell';
const XML = `<node><interface name="io.qvac.Jarvis.Shell"><method name="ListWindows"><arg type="s" direction="out"/></method><method name="FocusedWindow"><arg type="s" direction="out"/></method><method name="FocusWindow"><arg type="t" direction="in"/></method><method name="Screenshot"><arg type="s" direction="in"/><arg type="s" direction="out"/></method></interface></node>`;
const describe = (window) => ({ id: Number(window.get_id?.() || 0), title: window.get_title?.() || '', wm_class: window.get_wm_class?.() || '', pid: Number(window.get_pid?.() || 0), rect: (() => { const r = window.get_frame_rect?.(); return r ? [r.x, r.y, r.width, r.height] : null; })(), focused: window === global.display.get_focus_window?.() });
const describe = (window) => ({ id: Number(window.get_id?.() || 0), title: window.get_title?.() || '', wm_class: window.get_wm_class?.() || '', pid: Number(window.get_pid?.() || 0), rect: (() => { const r = window.get_frame_rect?.(); return r ? [r.x, r.y, r.width, r.height] : null; })(), buffer_rect: (() => { const r = window.get_buffer_rect?.(); return r ? [r.x, r.y, r.width, r.height] : null; })(), focused: window === global.display.get_focus_window?.() });
export function installShellService() {
const implementation = {
@@ -22,18 +22,18 @@
.jarvis-tab { padding: 4px 10px; border-radius: 999px; background-color: transparent; color: #D7DDE8; font-size: 12px; }
.jarvis-tab:hover, .jarvis-tab:focus { color: #F6F7FB; background-color: rgba(255, 255, 255, .10); }
.jarvis-tab-active { background-color: #F4B942; color: #16191F; font-weight: bold; }
.jarvis-thinking { color: #D7DDE8; font-size: 12px; padding: 8px 10px; border-left: 2px solid #4FD2FF; background-color: rgba(79, 210, 255, .08); border-radius: 8px; min-height: 40px; }
.jarvis-thinking { color: #D7DDE8; font-size: 12px; padding: 8px 10px; border-left: 2px solid #4FD2FF; background-color: rgba(79, 210, 255, .08); border-radius: 8px; }
.jarvis-notice { color: #F4B942; font-size: 12px; }
.jarvis-confirm { spacing: 8px; padding: 4px 0; }
.jarvis-confirm-actions { spacing: 6px; }
.jarvis-confirm-label { color: #F6F7FB; font-size: 13px; }
.jarvis-popup-scroll { height: 180px; }
.jarvis-thinking-pane { height: 160px; max-height: 160px; }
.jarvis-thinking-scroll { height: 160px; max-height: 160px; }
.jarvis-thinking-pane { height: 228px; }
.jarvis-thinking-scroll { height: 228px; }
.jarvis-thinking-box { spacing: 0; }
.jarvis-session-scroll { height: 220px; }
.jarvis-session-thinking-pane { height: 200px; max-height: 200px; }
.jarvis-session-thinking { height: 200px; max-height: 200px; }
.jarvis-session-thinking-pane { height: 268px; }
.jarvis-session-thinking { height: 268px; }
.jarvis-thinking-scroll StScrollBar, .jarvis-session-thinking StScrollBar { min-width: 10px; padding: 0 1px; }
.jarvis-transcript { spacing: 6px; }
.jarvis-row { padding: 6px 10px; border-radius: 8px; font-size: 13px; color: #F6F7FB; }
+61 -28
View File
@@ -66,6 +66,7 @@ export const prettyToolName = (value) => safeText(value || 'tool').replace(/_/g,
function wrapLabel(label) {
if (label.clutter_text) {
try { label.clutter_text.single_line_mode = false; } catch {}
label.clutter_text.line_wrap = true;
label.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
@@ -73,6 +74,22 @@ function wrapLabel(label) {
return label;
}
function actorWidth(actor) {
if (!actor) return 0;
const box = actor.get_allocation_box?.() || actor.allocation;
if (box) {
if (typeof box.get_width === 'function') {
const width = Number(box.get_width()) || 0;
if (width) return width;
}
const x1 = Number(box.x1) || 0;
const x2 = Number(box.x2) || 0;
if (x2 > x1) return x2 - x1;
if (Number(box.width)) return Number(box.width);
}
return Number(actor.width) || 0;
}
export class ConversationView {
constructor({ compact = true, maxRows = compact ? POPUP_ROWS : 40, brandDir = '' } = {}) {
this.compact = compact;
@@ -122,19 +139,19 @@ export class ConversationView {
this.transcript = new St.BoxLayout({ style_class: 'jarvis-transcript', vertical: true, x_expand: true });
this.transcript.accessible_name = 'Conversation transcript';
if (typeof this.scroll.set_child === 'function') this.scroll.set_child(this.transcript); else this.scroll.add_child(this.transcript);
const thinkHeight = compact ? 160 : 200;
const thinkHeight = compact ? 228 : 268;
this.thinkingPane = new St.BoxLayout({
style_class: compact ? 'jarvis-thinking-pane' : 'jarvis-session-thinking-pane',
vertical: true,
x_expand: true,
y_expand: false,
y_expand: true,
visible: false,
reactive: true,
clip_to_allocation: true,
height: thinkHeight,
});
try { this.thinkingPane.set_height(thinkHeight); } catch {}
try { this.thinkingPane.set_style?.(`height: ${thinkHeight}px; max-height: ${thinkHeight}px;`); } catch {}
try { this.thinkingPane.set_style?.(`height: ${thinkHeight}px;`); } catch {}
try { this.thinkingPane.clip_to_allocation = true; } catch {}
this.thinkingScroll = new St.ScrollView({
style_class: compact ? 'jarvis-thinking-scroll' : 'jarvis-session-thinking',
@@ -146,15 +163,25 @@ export class ConversationView {
enable_mouse_scrolling: true,
clip_to_allocation: true,
});
try { this.thinkingScroll.hscrollbar_policy = St.PolicyType.NEVER; this.thinkingScroll.vscrollbar_policy = St.PolicyType.ALWAYS; } catch {}
try { this.thinkingScroll.hscrollbar_policy = St.PolicyType.NEVER; this.thinkingScroll.vscrollbar_policy = St.PolicyType.AUTOMATIC; } catch {}
try { this.thinkingScroll.overlay_scrollbars = false; } catch {}
try { this.thinkingScroll.set_height(thinkHeight); } catch {}
try { this.thinkingScroll.clip_to_allocation = true; } catch {}
this.thinkingBox = new St.BoxLayout({ style_class: 'jarvis-thinking-box', vertical: true, x_expand: true, y_expand: false });
this.thinking = wrapLabel(new St.Label({ text: '', style_class: 'jarvis-thinking', x_expand: true, y_align: Clutter.ActorAlign.START, reactive: true, can_focus: true }));
this.thinking = wrapLabel(new St.Label({ text: '', style_class: 'jarvis-thinking', x_expand: true, y_expand: false, y_align: Clutter.ActorAlign.START, reactive: true, can_focus: true }));
this.thinking.accessible_name = 'Jarvis thinking';
this.thinkingBox.add_child(this.thinking);
if (typeof this.thinkingScroll.set_child === 'function') this.thinkingScroll.set_child(this.thinkingBox); else this.thinkingScroll.add_child(this.thinkingBox);
this.thinkingPane.add_child(this.thinkingScroll);
try {
if (Clutter.BindConstraint && Clutter.BindCoordinate) {
this.thinkingBox.add_constraint(new Clutter.BindConstraint({
source: this.thinkingPane,
coordinate: Clutter.BindCoordinate.WIDTH,
offset: -8,
}));
}
} catch {}
this.chipScroll = new St.ScrollView({ style_class: 'jarvis-chip-scroll', overlay_scrollbars: true, x_expand: true, visible: false });
try { this.chipScroll.vscrollbar_policy = St.PolicyType.NEVER; this.chipScroll.hscrollbar_policy = St.PolicyType.AUTOMATIC; } catch {}
this.chips = new St.BoxLayout({ style_class: 'jarvis-chips' });
@@ -177,12 +204,7 @@ export class ConversationView {
this._bindChip(this.reset, () => this.onReset?.());
this.controls.add_child(this.stop);
this.controls.add_child(this.reset);
if (compact) {
this.expand = new St.Button({ label: 'Open', style_class: 'jarvis-chip jarvis-chip-quiet', reactive: true, can_focus: true });
this.expand.accessible_name = 'Open conversation';
this._bindChip(this.expand, () => this.onExpand?.());
this.controls.add_child(this.expand);
} else {
if (!compact) {
this.close = new St.Button({ label: 'Close', style_class: 'jarvis-chip jarvis-chip-quiet', reactive: true, can_focus: true });
this._bindChip(this.close, () => this.hide());
this.controls.add_child(this.close);
@@ -274,30 +296,40 @@ export class ConversationView {
try { child?.get_first_child?.()?.queue_relayout?.(); } catch {}
}
_viewportWidth(scroll) {
const read = (box) => {
if (!box) return 0;
if (typeof box.get_width === 'function') return Number(box.get_width()) || 0;
const x1 = Number(box.x1) || 0;
const x2 = Number(box.x2) || 0;
return x2 > x1 ? x2 - x1 : Number(box.width) || 0;
};
try {
const width = read(scroll?.get_allocation_box?.() || scroll?.allocation);
if (width >= 200) return width;
} catch {}
return Number(scroll?.width) || Number(this.root?.width) || (this.compact ? 292 : 388);
const fallback = this.compact ? 292 : 388;
const widths = [
actorWidth(scroll),
actorWidth(this.thinkingPane),
actorWidth(this.root),
];
return widths.find((width) => width >= 160) || fallback;
}
_fitThinking() {
if (this._destroyed) return;
const label = this.thinking;
const text = label?.clutter_text;
if (!label || !text) return;
const width = Math.max(200, this._viewportWidth(this.thinkingScroll) - 28);
const width = Math.max(this.compact ? 260 : 360, this._viewportWidth(this.thinkingScroll) - 18);
try { text.single_line_mode = false; } catch {}
try { text.set_single_line_mode?.(false); } catch {}
try { text.line_wrap = true; } catch {}
try { text.line_wrap_mode = Pango.WrapMode.WORD_CHAR; } catch {}
try { text.ellipsize = Pango.EllipsizeMode.NONE; } catch {}
try { text.width = width; } catch {}
try { text.set_line_wrap?.(true); } catch {}
try { text.set_line_wrap_mode?.(Pango.WrapMode.WORD_CHAR); } catch {}
try { text.set_ellipsize?.(Pango.EllipsizeMode.NONE); } catch {}
for (const actor of [this.thinkingBox, label, text]) {
try { actor.set_width?.(width); } catch {}
try { actor.width = width; } catch {}
}
try {
const layout = text.get_layout?.();
if (layout?.set_width) {
layout.set_width(width * (Pango.SCALE || 1024));
layout.set_wrap?.(Pango.WrapMode.WORD_CHAR);
layout.set_ellipsize?.(Pango.EllipsizeMode.NONE);
}
} catch {}
try { label.queue_relayout?.(); } catch {}
try { this.thinkingBox?.queue_relayout?.(); } catch {}
}
@@ -703,10 +735,11 @@ export class SessionPanel {
const width = Math.min(SESSION_WIDTH, monitor.width - 48);
this.root.set_width(width);
this.root.set_position(monitor.x + Math.max(24, monitor.width - width - 24), monitor.y + 40);
this.view.scroll.set_height(Math.max(120, Math.min(280, monitor.height - 360)));
const thinkHeight = Math.max(120, Math.min(240, monitor.height - 400));
const chatHeight = Math.max(120, Math.min(280, monitor.height - 360));
this.view.scroll.set_height(chatHeight);
const thinkHeight = chatHeight + 48;
this.view.thinkingPane?.set_height?.(thinkHeight);
this.view.thinkingPane?.set_style?.(`height: ${thinkHeight}px; max-height: ${thinkHeight}px;`);
this.view.thinkingPane?.set_style?.(`height: ${thinkHeight}px;`);
this.view.thinkingScroll.set_height(thinkHeight);
}
this.root.visible = true;