Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
71e4091
docs(status): pause at 92 percent - wake queue: userspace re-land, th…
jaylfc Jun 12, 2026
a7a344c
docs(status): wind-down state for the weekly limit; no auto-resume
jaylfc Jun 12, 2026
0d2fe12
docs(status): freshness sweep 2026-06-13 -- master 99cf786e, 8 open PRs
jaylfc Jun 13, 2026
c00326c
docs(status): fix stale dev tip (a7a344c3 -> 0d2fe121)
jaylfc Jun 13, 2026
f3243db
docs(status): freshness sweep 2026-06-13 -- fix dev tip 0d2fe121 -> c…
jaylfc Jun 13, 2026
5ebb699
docs(status): freshness sweep 2026-06-13 -- fix dev tip c00326c9 -> f…
jaylfc Jun 13, 2026
f215668
docs(status): freshness sweep cycle 3 -- fix dev tip f3243dbf -> 5ebb…
jaylfc Jun 13, 2026
26c1794
docs(status): freshness sweep cycle 4 -- fix dev tip 5ebb6995 -> f215…
jaylfc Jun 13, 2026
19295d0
chore: stop tracking docs/AGENT_HANDOFF.md (already in .gitignore)
jaylfc Jun 13, 2026
46b3549
feat(messages): full markdown rendering in chat (#826)
jaylfc Jun 13, 2026
a9fcaa2
feat(messages): per-channel draft persistence (#829)
jaylfc Jun 13, 2026
22dffe5
feat(messages): surface the Threads view (#830)
jaylfc Jun 13, 2026
385ae32
feat(messages): full emoji picker for reactions (#827)
jaylfc Jun 13, 2026
18dd149
feat(messages): unread divider (#831)
jaylfc Jun 13, 2026
cdf32cc
feat(messages): date separators in the message list (#828)
jaylfc Jun 13, 2026
bebafac
feat(messages): chat search UI (#832)
jaylfc Jun 13, 2026
3a14601
fix(messages): address CodeRabbit review on search
jaylfc Jun 13, 2026
3480940
docs(status): Messages train landed (#833), #783 verified, monitors a…
jaylfc Jun 13, 2026
c55f929
fix(activity): dedupe local node in scheduler + detect ARM SoC for CP…
jaylfc Jun 13, 2026
a1baa5c
feat(desktop): deep-navigation API to open apps by url param or event
jaylfc Jun 13, 2026
9033276
refactor(desktop): extract deep-navigation into a tested useDeepNavig…
jaylfc Jun 13, 2026
ec86354
feat(settings): toggle for the anonymous update ping (job 17)
jaylfc Jun 13, 2026
c2b07c7
test(desktop): CodeBlock component coverage (job 16)
jaylfc Jun 13, 2026
9153464
docs(status): freshness sweep -- fix dev tip a1baa5ca -> c2b07c79
jaylfc Jun 13, 2026
409b015
feat(messages): quick channel switcher (job 8)
jaylfc Jun 13, 2026
0cf41bc
docs(themes): token inventory for the theme engine (#837)
jaylfc Jun 13, 2026
960d546
docs(status): sync to current dev (jobs 8/12/16/17, deep-nav, activit…
jaylfc Jun 13, 2026
19f37a0
docs(status): freshness sweep -- fix dev tip 0cf41bc6 -> 960d5464, 27…
jaylfc Jun 13, 2026
29c3292
docs(status): record open PRs #838/#839 + website preview + merge-wor…
jaylfc Jun 13, 2026
355bb5e
ci: parallelize the test suite with pytest-xdist (-n auto) (#839)
jaylfc Jun 13, 2026
6cb8e2f
docs(status): sync dev tip + usage; record #839 merged, website live,…
jaylfc Jun 13, 2026
fe8b6cb
docs(status): all 26 agent jobs done; #838/#842 open, blocked on Code…
jaylfc Jun 13, 2026
c6b0754
feat(messages): polish batch - empty states, scroll-to-bottom, sideba…
jaylfc Jun 13, 2026
bf37698
docs(agent): more answer templates in the manual (job 14) (#842)
jaylfc Jun 13, 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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ jobs:
# The real build is exercised in the spa-build job below.

- name: Run tests
run: pytest tests/ -v --tb=short --ignore=tests/e2e
# Run across all runner cores. The serial suite (4845 tests) took
# ~22 min; -n auto cuts it to a few minutes so "merge on green" is
# practical. Dropped -v so xdist worker output stays readable.
run: pytest tests/ --tb=short --ignore=tests/e2e -n auto

- name: Verify app starts
run: python -c "from tinyagentos.app import create_app; print('OK')"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,4 @@ desktop/tsconfig.tsbuildinfo
docs/AGENT_HANDOFF.md
docs/audit/
.understand-anything/
docs/agent-jobs/
16 changes: 15 additions & 1 deletion desktop/src/apps/ActivityApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -824,14 +824,28 @@ export function ActivityApp({ windowId: _windowId }: { windowId: string }) {
const cpuLabel = hardwareLabel("cpu", hw);
if (cpuLabel) cards.push({ kind: "cpu", label: cpuLabel, tierIdx: 2 });

// The controller self-registers as a worker named "local"
// (loopback url). Its NPU/CPU are already shown above as the
// controller's own scheduler resources, so for the local node
// only surface devices the controller does not already list
// (e.g. an integrated GPU it does not schedule on). This
// avoids the same physical hardware appearing twice.
const isSelf =
w.name === "local" ||
/\/\/(127\.0\.0\.1|localhost|\[::1\])(:|\/|$)/.test(w.url || "");
const controllerTiers = new Set(schedulerStats.resources.map((res) => res.tier));
const visibleCards = isSelf
? cards.filter((c) => !controllerTiers.has(c.tierIdx))
: cards;

const tierLabels = ["GPU", "NPU", "CPU"];
const tierColors = [
"text-emerald-400 bg-emerald-500/10",
"text-violet-400 bg-violet-500/10",
"text-sky-400 bg-sky-500/10",
];

return cards.map((card) => (
return visibleCards.map((card) => (
<div
key={`${w.name}-${card.kind}`}
className={`p-2 rounded-lg border ${online ? "bg-white/[0.02] border-white/5" : "bg-white/[0.01] border-white/5 opacity-60"}`}
Expand Down
694 changes: 623 additions & 71 deletions desktop/src/apps/MessagesApp.tsx

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions desktop/src/apps/SettingsApp/UpdatesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface UpdateInfo {

interface AutoUpdatePrefs {
check_enabled?: boolean;
update_ping_enabled?: boolean;
}

interface UpdateStatus {
Expand Down Expand Up @@ -64,6 +65,7 @@ export function UpdatesPanel() {
if (data && typeof data === "object") {
setPrefs({
check_enabled: data.check_enabled ?? true,
update_ping_enabled: data.update_ping_enabled ?? true,
});
}
}
Expand Down Expand Up @@ -301,6 +303,20 @@ export function UpdatesPanel() {
/>
</div>

<div className="flex items-center justify-between gap-3">
<div className="flex-1 min-w-0">
<Label className="text-sm">Anonymous install ping</Label>
<p className="text-[11px] text-shell-text-tertiary mt-0.5">
Reports an anonymous install count with the update check. No personal data, no identifiers beyond a random id. Updates work either way.
</p>
</div>
<Switch
checked={prefs.update_ping_enabled ?? true}
onCheckedChange={(v) => savePrefs({ ...prefs, update_ping_enabled: v })}
aria-label="Anonymous install ping"
/>
</div>

</div>

{/* Advanced disclosure */}
Expand Down
1 change: 1 addition & 0 deletions desktop/src/apps/chat/AllThreadsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export function AllThreadsList({

return (
<aside
id="all-threads-panel"
role="complementary"
aria-label="All threads"
className="fixed top-0 right-0 h-full w-[360px] bg-shell-surface border-l border-white/10 shadow-xl flex flex-col z-40"
Expand Down
8 changes: 7 additions & 1 deletion desktop/src/apps/chat/AttachmentsBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export type PendingAttachment = {
record?: AttachmentRecord; // set once upload completes
error?: string;
uploading?: boolean;
file?: File; // original file kept so a failed upload can be retried
retries?: number; // retry attempts so far (capped at 3)
};

export function AttachmentsBar({
Expand All @@ -31,7 +33,11 @@ export function AttachmentsBar({
<span className="opacity-50">{Math.max(1, Math.round(it.size / 1024))} KB</span>
{it.uploading && <span className="opacity-70">…</span>}
{it.error && (
<button aria-label="Retry upload" onClick={() => onRetry(it.id)} className="text-red-300">retry</button>
(it.retries ?? 0) < 3 && it.file ? (
<button aria-label="Retry upload" onClick={() => onRetry(it.id)} className="text-red-300">retry</button>
) : (
<span title="Upload failed" className="text-red-400/70">failed</span>
)
)}
<button aria-label={`Remove ${it.filename}`} onClick={() => onRemove(it.id)} className="opacity-70 hover:opacity-100">×</button>
</div>
Expand Down
112 changes: 112 additions & 0 deletions desktop/src/apps/chat/ChannelSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { useEffect, useRef, useState } from "react";

interface SwitcherChannel {
id: string;
name: string;
type?: string;
}

/**
* Slack-style quick channel switcher. Cmd/Ctrl+K opens it from MessagesApp;
* type to filter, Up/Down to move, Enter to select, Escape or backdrop to close.
*/
export function ChannelSwitcher({
channels,
onSelect,
onClose,
}: {
channels: SwitcherChannel[];
onSelect: (id: string) => void;
onClose: () => void;
}) {
const [query, setQuery] = useState("");
const [highlightIndex, setHighlightIndex] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
inputRef.current?.focus();
}, []);

const q = query.trim().toLowerCase();
const results = channels
.filter((c) => (q ? c.name.toLowerCase().includes(q) : true))
.slice(0, 8);

// Keep the highlight within the current result list as it shrinks/grows.
useEffect(() => {
setHighlightIndex((i) => Math.min(i, Math.max(0, results.length - 1)));
}, [results.length]);

const choose = (idx: number) => {
const hit = results[idx];
if (hit) {
onSelect(hit.id);
onClose();
}
};

const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault();
onClose();
} else if (e.key === "ArrowDown") {
e.preventDefault();
setHighlightIndex((i) => Math.min(i + 1, Math.max(0, results.length - 1)));
} else if (e.key === "ArrowUp") {
e.preventDefault();
setHighlightIndex((i) => Math.max(i - 1, 0));
} else if (e.key === "Enter") {
e.preventDefault();
choose(highlightIndex);
}
};

return (
<div
className="fixed inset-0 z-[10010] flex items-start justify-center bg-black/50 pt-[18vh]"
onClick={onClose}
role="dialog"
aria-modal="true"
aria-label="Switch channel"
>
<div
className="w-full max-w-[420px] mx-4 bg-zinc-900 border border-white/10 rounded-xl shadow-2xl overflow-hidden"
onClick={(e) => e.stopPropagation()}
onKeyDown={onKeyDown}
>
<input
ref={inputRef}
value={query}
onChange={(e) => {
setQuery(e.target.value);
setHighlightIndex(0);
}}
placeholder="Jump to channel…"
aria-label="Search channels"
className="w-full bg-transparent px-4 py-3 text-sm text-white outline-none border-b border-white/10 placeholder:text-white/40"
/>
<ul className="max-h-[320px] overflow-y-auto py-1" role="listbox" aria-label="Channels">
{results.length === 0 ? (
<li className="px-4 py-3 text-xs text-white/40">No channels match.</li>
) : (
results.map((c, i) => (
<li key={c.id} role="option" aria-selected={i === highlightIndex}>
<button
type="button"
onClick={() => choose(i)}
onMouseEnter={() => setHighlightIndex(i)}
className={`w-full text-left px-4 py-2 text-sm flex items-center gap-2 ${
i === highlightIndex ? "bg-blue-500/20 text-white" : "text-white/80 hover:bg-white/5"
}`}
>
<span className="text-white/40">#</span>
<span className="truncate">{c.name}</span>
</button>
</li>
))
)}
</ul>
</div>
</div>
);
}
Loading
Loading