Skip to content

Commit 68490ab

Browse files
authored
Merge branch 'main' into bugfix/slack-tls-fix
2 parents a6ce6fa + 7fdb768 commit 68490ab

File tree

22 files changed

+1982
-169
lines changed

22 files changed

+1982
-169
lines changed

.githooks/pre-commit

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
#!/bin/bash
1+
#!/bin/sh
22

3-
set -euo pipefail
3+
set -eu
44

5-
mapfile -t staged_rust_files < <(git diff --cached --name-only --diff-filter=ACMR -- '*.rs')
5+
staged_rust_files=$(git diff --cached --name-only --diff-filter=ACMR -- '*.rs')
66

7-
if [ "${#staged_rust_files[@]}" -eq 0 ]; then
7+
if [ -z "$staged_rust_files" ]; then
88
exit 0
99
fi
1010

11-
if ! git diff --quiet -- "${staged_rust_files[@]}"; then
11+
if ! echo "$staged_rust_files" | xargs git diff --quiet --; then
1212
echo "pre-commit: staged Rust files have unstaged changes." >&2
1313
echo "Please stage or stash those changes, then commit again." >&2
1414
exit 1
@@ -17,4 +17,4 @@ fi
1717
echo "pre-commit: running cargo fmt --all"
1818
cargo fmt --all
1919

20-
git add -- "${staged_rust_files[@]}"
20+
echo "$staged_rust_files" | xargs git add --

docs/design-docs/workers-tab.md

Lines changed: 509 additions & 0 deletions
Large diffs are not rendered by default.

interface/src/api/client.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ export interface CronExecutionsParams {
661661
export interface ProviderStatus {
662662
anthropic: boolean;
663663
openai: boolean;
664+
openai_chatgpt: boolean;
664665
openrouter: boolean;
665666
zhipu: boolean;
666667
groq: boolean;
@@ -669,10 +670,12 @@ export interface ProviderStatus {
669670
deepseek: boolean;
670671
xai: boolean;
671672
mistral: boolean;
673+
gemini: boolean;
672674
ollama: boolean;
673675
opencode_zen: boolean;
674676
nvidia: boolean;
675677
minimax: boolean;
678+
minimax_cn: boolean;
676679
moonshot: boolean;
677680
zai_coding_plan: boolean;
678681
}
@@ -695,6 +698,20 @@ export interface ProviderModelTestResponse {
695698
sample: string | null;
696699
}
697700

701+
export interface OpenAiOAuthBrowserStartResponse {
702+
success: boolean;
703+
message: string;
704+
authorization_url: string | null;
705+
state: string | null;
706+
}
707+
708+
export interface OpenAiOAuthBrowserStatusResponse {
709+
found: boolean;
710+
done: boolean;
711+
success: boolean;
712+
message: string | null;
713+
}
714+
698715
// -- Model Types --
699716

700717
export interface ModelInfo {
@@ -1153,6 +1170,28 @@ export const api = {
11531170
}
11541171
return response.json() as Promise<ProviderModelTestResponse>;
11551172
},
1173+
startOpenAiOAuthBrowser: async (params: {model: string}) => {
1174+
const response = await fetch(`${API_BASE}/providers/openai/oauth/browser/start`, {
1175+
method: "POST",
1176+
headers: { "Content-Type": "application/json" },
1177+
body: JSON.stringify({
1178+
model: params.model,
1179+
}),
1180+
});
1181+
if (!response.ok) {
1182+
throw new Error(`API error: ${response.status}`);
1183+
}
1184+
return response.json() as Promise<OpenAiOAuthBrowserStartResponse>;
1185+
},
1186+
openAiOAuthBrowserStatus: async (state: string) => {
1187+
const response = await fetch(
1188+
`${API_BASE}/providers/openai/oauth/browser/status?state=${encodeURIComponent(state)}`,
1189+
);
1190+
if (!response.ok) {
1191+
throw new Error(`API error: ${response.status}`);
1192+
}
1193+
return response.json() as Promise<OpenAiOAuthBrowserStatusResponse>;
1194+
},
11561195
removeProvider: async (provider: string) => {
11571196
const response = await fetch(`${API_BASE}/providers/${encodeURIComponent(provider)}`, {
11581197
method: "DELETE",

interface/src/components/ModelSelect.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const PROVIDER_LABELS: Record<string, string> = {
1616
anthropic: "Anthropic",
1717
openrouter: "OpenRouter",
1818
openai: "OpenAI",
19+
"openai-chatgpt": "ChatGPT Plus (OAuth)",
1920
deepseek: "DeepSeek",
2021
xai: "xAI",
2122
mistral: "Mistral",
@@ -129,6 +130,7 @@ export function ModelSelect({
129130
"openrouter",
130131
"anthropic",
131132
"openai",
133+
"openai-chatgpt",
132134
"ollama",
133135
"deepseek",
134136
"xai",

interface/src/components/WebChatPanel.tsx

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useEffect, useRef, useState } from "react";
2-
import { useWebChat, type ToolActivity } from "@/hooks/useWebChat";
2+
import { useWebChat, getPortalChatSessionId, type ToolActivity } from "@/hooks/useWebChat";
3+
import type { ActiveWorker } from "@/hooks/useChannelLiveState";
4+
import { useLiveContext } from "@/hooks/useLiveContext";
35
import { Markdown } from "@/components/Markdown";
46

57
interface WebChatPanelProps {
@@ -38,6 +40,33 @@ function ThinkingIndicator() {
3840
);
3941
}
4042

43+
function ActiveWorkersPanel({ workers }: { workers: ActiveWorker[] }) {
44+
if (workers.length === 0) return null;
45+
46+
return (
47+
<div className="rounded-lg border border-amber-500/25 bg-amber-500/5 px-3 py-2">
48+
<div className="mb-2 flex items-center gap-1.5 text-tiny text-amber-200">
49+
<div className="h-1.5 w-1.5 animate-pulse rounded-full bg-amber-400" />
50+
<span>
51+
{workers.length} active worker{workers.length !== 1 ? "s" : ""}
52+
</span>
53+
</div>
54+
<div className="flex flex-col gap-1.5">
55+
{workers.map((worker) => (
56+
<div key={worker.id} className="flex min-w-0 items-center gap-2 rounded-md bg-amber-500/10 px-2.5 py-1.5 text-tiny">
57+
<span className="font-medium text-amber-300">Worker</span>
58+
<span className="min-w-0 flex-1 truncate text-ink-dull">{worker.task}</span>
59+
<span className="shrink-0 text-ink-faint">{worker.status}</span>
60+
{worker.currentTool && (
61+
<span className="max-w-40 shrink-0 truncate text-amber-400/80">{worker.currentTool}</span>
62+
)}
63+
</div>
64+
))}
65+
</div>
66+
</div>
67+
);
68+
}
69+
4170
function FloatingChatInput({
4271
value,
4372
onChange,
@@ -125,12 +154,16 @@ function FloatingChatInput({
125154

126155
export function WebChatPanel({ agentId }: WebChatPanelProps) {
127156
const { messages, isStreaming, error, toolActivity, sendMessage } = useWebChat(agentId);
157+
const { liveStates } = useLiveContext();
128158
const [input, setInput] = useState("");
129159
const messagesEndRef = useRef<HTMLDivElement>(null);
160+
const sessionId = getPortalChatSessionId(agentId);
161+
const activeWorkers = Object.values(liveStates[sessionId]?.workers ?? {});
162+
const hasActiveWorkers = activeWorkers.length > 0;
130163

131164
useEffect(() => {
132165
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
133-
}, [messages.length, isStreaming, toolActivity.length]);
166+
}, [messages.length, isStreaming, toolActivity.length, activeWorkers.length]);
134167

135168
const handleSubmit = () => {
136169
const trimmed = input.trim();
@@ -144,6 +177,12 @@ export function WebChatPanel({ agentId }: WebChatPanelProps) {
144177
{/* Messages */}
145178
<div className="flex-1 overflow-y-auto">
146179
<div className="mx-auto flex max-w-2xl flex-col gap-6 px-4 py-6 pb-32">
180+
{hasActiveWorkers && (
181+
<div className="sticky top-0 z-10 bg-app/90 pb-2 pt-2 backdrop-blur-sm">
182+
<ActiveWorkersPanel workers={activeWorkers} />
183+
</div>
184+
)}
185+
147186
{messages.length === 0 && !isStreaming && (
148187
<div className="flex flex-col items-center justify-center py-24">
149188
<p className="text-sm text-ink-faint">

interface/src/hooks/useWebChat.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export interface WebChatMessage {
1212
content: string;
1313
}
1414

15+
export function getPortalChatSessionId(agentId: string) {
16+
return `portal:chat:${agentId}`;
17+
}
18+
1519
async function consumeSSE(
1620
response: Response,
1721
onEvent: (eventType: string, data: string) => void,
@@ -48,7 +52,7 @@ async function consumeSSE(
4852
}
4953

5054
export function useWebChat(agentId: string) {
51-
const sessionId = `portal:chat:${agentId}`;
55+
const sessionId = getPortalChatSessionId(agentId);
5256
const [messages, setMessages] = useState<WebChatMessage[]>([]);
5357
const [isStreaming, setIsStreaming] = useState(false);
5458
const [error, setError] = useState<string | null>(null);

interface/src/lib/providerIcons.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export function ProviderIcon({ provider, className = "text-ink-faint", size = 24
9999
const iconMap: Record<string, React.ComponentType<IconProps>> = {
100100
anthropic: Anthropic,
101101
openai: OpenAI,
102+
"openai-chatgpt": OpenAI,
102103
openrouter: OpenRouter,
103104
groq: Groq,
104105
mistral: Mistral,

interface/src/routes/ChannelDetail.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,16 @@ function LiveWorkerRunItem({ item, live, channelId }: { item: TimelineWorkerRun;
7878
</span>
7979
<div className="min-w-0 flex-1">
8080
<div className="rounded-md bg-amber-500/10 px-3 py-2">
81-
<div className="flex items-center gap-2">
81+
<div className="flex min-w-0 items-center gap-2 overflow-hidden">
8282
<div className="h-2 w-2 animate-pulse rounded-full bg-amber-400" />
8383
<span className="text-sm font-medium text-amber-300">Worker</span>
84-
<span className="truncate text-sm text-ink-dull">{item.task}</span>
84+
<span className="min-w-0 flex-1 truncate text-sm text-ink-dull">{item.task}</span>
8585
<CancelButton onClick={() => { api.cancelProcess(channelId, "worker", item.id).catch(console.warn); }} />
8686
</div>
87-
<div className="mt-1 flex items-center gap-3 pl-4 text-tiny text-ink-faint">
88-
<span>{live.status}</span>
87+
<div className="mt-1 flex min-w-0 items-center gap-3 overflow-hidden pl-4 text-tiny text-ink-faint">
88+
<span className="truncate">{live.status}</span>
8989
{live.currentTool && (
90-
<span className="text-amber-400/70">{live.currentTool}</span>
90+
<span className="truncate text-amber-400/70">{live.currentTool}</span>
9191
)}
9292
{live.toolCalls > 0 && (
9393
<span>{live.toolCalls} tool calls</span>
@@ -146,23 +146,22 @@ function WorkerRunItem({ item }: { item: TimelineWorkerRun }) {
146146
{formatTimestamp(new Date(item.started_at).getTime())}
147147
</span>
148148
<div className="min-w-0 flex-1">
149-
<Button
149+
<button
150150
type="button"
151151
onClick={() => setExpanded(!expanded)}
152-
variant="ghost"
153-
className="h-auto w-full justify-start rounded-md bg-amber-500/10 px-3 py-2 text-left hover:bg-amber-500/15"
152+
className="w-full rounded-md bg-amber-500/10 px-3 py-2 text-left transition-colors hover:bg-amber-500/15 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-400/60"
154153
>
155-
<div className="flex items-center gap-2">
154+
<div className="flex min-w-0 items-center gap-2 overflow-hidden">
156155
<div className="h-2 w-2 rounded-full bg-amber-400/50" />
157156
<span className="text-sm font-medium text-amber-300">Worker</span>
158-
<span className="truncate text-sm text-ink-dull">{item.task}</span>
157+
<span className="min-w-0 flex-1 truncate text-sm text-ink-dull">{item.task}</span>
159158
{item.result && (
160159
<span className="ml-auto text-tiny text-ink-faint">
161160
{expanded ? "▾" : "▸"}
162161
</span>
163162
)}
164163
</div>
165-
</Button>
164+
</button>
166165
{expanded && item.result && (
167166
<div className="mt-1 rounded-md border border-amber-500/10 bg-amber-500/5 px-3 py-2">
168167
<div className="text-sm text-ink-dull">

0 commit comments

Comments
 (0)