Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
24 changes: 24 additions & 0 deletions .github/workflows/dsh-plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
frozen-configuration:
name: dsh-pnpm-frozen (${{ matrix.pnpm }})
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
pnpm: ["9.15.9", "10.33.0", "12.10.1"]
defaults:
run:
working-directory: packages/dsh-loopx-plugin
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: "24"
- uses: pnpm/action-setup@v4
with:
version: ${{ matrix.pnpm }}
- name: Qualify the frozen override configuration without dependency scripts
run: pnpm install --frozen-lockfile --lockfile-only --ignore-scripts
- name: Keep the qualified dependency graph unchanged
run: git diff --exit-code -- pnpm-lock.yaml

distribution:
name: dsh-plugin (${{ matrix.os }})
runs-on: ${{ matrix.os }}
Expand Down
9 changes: 5 additions & 4 deletions apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2303,9 +2303,10 @@ const larkAppsSchema = z.object({
})),
});

export async function fetchLarkApps() {
export async function fetchLarkApps(options?: {verify: boolean; appRef?: string; signal?: AbortSignal}) {
const query = options ? new URLSearchParams({verify: String(options.verify), ...(options.appRef ? {app_ref: options.appRef} : {})}) : null;
return larkAppsSchema.parse(
await requestJson<unknown>("/api/chat/lark/apps"),
await requestJson<unknown>(`/api/chat/lark/apps${query ? `?${query}` : ""}`, {signal: options?.signal}),
).apps;
}

Expand Down Expand Up @@ -2464,9 +2465,9 @@ const larkConnectionsSchema = z.object({
})),
});

export async function fetchLarkConnections() {
export async function fetchLarkConnections(signal?: AbortSignal) {
return larkConnectionsSchema.parse(
await requestJson<unknown>("/api/chat/lark/connections"),
await requestJson<unknown>("/api/chat/lark/connections", {signal}),
).connections;
}

Expand Down
47 changes: 47 additions & 0 deletions apps/presentation/dashboard/src/data/use-lark-apps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {useCallback, useEffect, useRef, useState} from "react";
import {fetchLarkApps, type LarkApp} from "./chat";

/** One settings mount owns one read, shared by its connection/setup panels.
* Observed readiness is presentation only; operations revalidate at Core.
*/
export function useLarkApps() {
const [apps, setApps] = useState<LarkApp[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<unknown>(null);
const current = useRef<AbortController | null>(null);
const refresh = useCallback(async () => {
current.current?.abort();
const controller = new AbortController();
current.current = controller;
setApps([]); setError(null); setLoading(true);
const active = () => !controller.signal.aborted;
try {
const profiles = await fetchLarkApps({verify: false,
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(7000)])});
if (!active()) return;
setApps(profiles);
let next = 0;
// A slow profile neither holds the catalog nor other profile results.
const worker = async () => {
while (active() && next < profiles.length) {
const profile = profiles[next++];
let observed: LarkApp;
try {
const rows = await fetchLarkApps({verify: true, appRef: profile.app_ref,
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(7000)])});
if (rows.length !== 1 || rows[0].app_ref !== profile.app_ref) throw new Error("Lark profile changed");
observed = rows[0].health_error_code === "lark_app_unverified"
? {...rows[0], health_error_code: "lark_app_verification_failed"} : rows[0];
} catch {
observed = {...profile, ready: false, reply_ready: false, health_error_code: "lark_app_verification_failed"};
}
if (active()) setApps(items => items.map(item => item.app_ref === profile.app_ref ? observed : item));
}
};
void Promise.all(Array.from({length: Math.min(4, profiles.length)}, worker));
} catch (cause) {if (active()) setError(cause);}
finally {if (active()) setLoading(false);}
}, []);
useEffect(() => {void refresh(); return () => current.current?.abort();}, [refresh]);
return {apps, loading, error, refresh};
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Job = {status: string; attempt?: number; analysis_wall_seconds?: number | n
type Pack = ResearchProgressPack & {source_count: number; analysis_key: string; method_version: string;
config: {model: string; effort: string; service_tier: string}; availability: string; reason_code: string | null;
objective?: string; episodes?: TrajectoryEpisode[]; job?: Job | null};
type Snapshot = {ok: true; catalog: {availability: string; reason_code: string | null; default_pack_id: string | null; packs: Pack[]}; pack: Pack | null};
type Snapshot = {ok: true; execution_enabled?: boolean; catalog: {availability: string; reason_code: string | null; default_pack_id: string | null; packs: Pack[]}; pack: Pack | null};
type Citation = AssessmentCitation & {artifact: string; text: string; applicability: string};
const errorText = (error: unknown) => error instanceof Error ? error.message : String(error);
const locationFor = (goal: string) => {
Expand Down Expand Up @@ -117,23 +117,23 @@ export function useResearchAnalysis(goalId: string, active: boolean, zh: boolean
requestAnimationFrame(() => {panel.current?.focus({preventScroll: true}); panel.current?.scrollIntoView({block: "start"});});
};
const execute = async (retry = false) => {
if (readSourceUrl) return;
if (readSourceUrl && snapshot?.execution_enabled !== true) return;
if (!pack || pack.availability !== "available" || submitting.current) return;
show(); submitting.current = true; setPending(true); setError("");
const current = generation.current;
try {
await requestJson(endpoint, {method: "POST", body: JSON.stringify({goal_id: goalId, pack_id: pack.pack_id, analysis_key: pack.analysis_key, retry})});
await requestJson(sourceReadApiUrl(readSourceUrl, endpoint), {method: "POST", body: JSON.stringify({goal_id: goalId, pack_id: pack.pack_id, analysis_key: pack.analysis_key, retry})});
if (current === generation.current) await read(pack.pack_id);
} catch (e) {if (current === generation.current) {setError(errorText(e)); void read(pack.pack_id).catch(() => undefined);}}
finally {if (current === generation.current) {submitting.current = false; setPending(false);}}
};
const cancel = async () => {
if (readSourceUrl) return;
if (readSourceUrl && snapshot?.execution_enabled !== true) return;
if (!pack || cancelPending) return;
const current = generation.current;
setCancelPending(true);
try {
await requestJson(`${endpoint}/cancel`, {method: "POST", body: JSON.stringify({goal_id: goalId, pack_id: pack.pack_id, analysis_key: pack.analysis_key})});
await requestJson(sourceReadApiUrl(readSourceUrl, `${endpoint}/cancel`), {method: "POST", body: JSON.stringify({goal_id: goalId, pack_id: pack.pack_id, analysis_key: pack.analysis_key})});
if (current === generation.current) await read(pack.pack_id);
} catch (e) {if (current === generation.current) setError(errorText(e));}
finally {if (current === generation.current) setCancelPending(false);}
Expand All @@ -150,7 +150,7 @@ export function useResearchAnalysis(goalId: string, active: boolean, zh: boolean
finally {if (current === citeGeneration.current) setCitationBusy(false);}
};
const closeCitation = () => {citeGeneration.current++; setCitation(null); setCitationError(""); setCitationBusy(false); requestAnimationFrame(() => sourceTrigger.current?.focus());};
return {readOnly: Boolean(readSourceUrl), citation, citationBusy, citationError, sourcePanel, readCitation, closeCitation, goalId, zh, location, snapshot, pack, readError, error: error || readError, loading, pending, cancelPending, recordedRunning, panel, trigger, read, show, execute, cancel,
return {readOnly: Boolean(readSourceUrl) && snapshot?.execution_enabled !== true, citation, citationBusy, citationError, sourcePanel, readCitation, closeCitation, goalId, zh, location, snapshot, pack, readError, error: error || readError, loading, pending, cancelPending, recordedRunning, panel, trigger, read, show, execute, cancel,
close: () => {citeGeneration.current++; setCitation(null); setCitationError(""); setCitationBusy(false); navigate(false); requestAnimationFrame(() => trigger.current?.focus());}, select: (id: string) => navigate(location.open, id, true)};
}
type Controller = ReturnType<typeof useResearchAnalysis>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,9 @@ const en = {
"lark.mentionsOnly": "Mentions only",
"lark.needsMessagePermissions": "Needs message permissions",
"lark.needsSetup": "Needs setup",
"lark.verificationPending": "Checking…",
"lark.verificationFailed": "Not verified",
"lark.retryVerification": "Refresh profiles and retry verification",
"lark.newApp": "New Lark App",
"lark.noAgentConfigured": "No Agent configured",
"lark.noConnections": "No matching connections.",
Expand Down Expand Up @@ -2431,6 +2434,9 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"lark.mentionsOnly": "仅提及消息",
"lark.needsMessagePermissions": "需要消息权限",
"lark.needsSetup": "需要设置",
"lark.verificationPending": "正在核验…",
"lark.verificationFailed": "尚未核验",
"lark.retryVerification": "刷新配置并重试核验",
"lark.newApp": "新建 Lark App",
"lark.noAgentConfigured": "未配置 Agent",
"lark.noConnections": "暂无匹配的连接。",
Expand Down
Loading
Loading