diff --git a/src/app/me/master-agent/takeover/page.tsx b/src/app/me/master-agent/takeover/page.tsx
index 2b77ba5..afeefa7 100644
--- a/src/app/me/master-agent/takeover/page.tsx
+++ b/src/app/me/master-agent/takeover/page.tsx
@@ -1,3 +1,4 @@
+import { RealtimeRefresh } from "@/components/app-runtime";
import { AppShell, PageNav, StatusBar } from "@/components/app-ui";
import { MasterAgentTakeoverClient } from "@/components/master-agent-takeover-client";
import { requirePageSession } from "@/lib/boss-auth";
@@ -12,6 +13,7 @@ export default async function MasterAgentTakeoverPage() {
return (
+
diff --git a/src/app/me/storage/page.tsx b/src/app/me/storage/page.tsx
index 17467f9..f97215f 100644
--- a/src/app/me/storage/page.tsx
+++ b/src/app/me/storage/page.tsx
@@ -1,3 +1,4 @@
+import { RealtimeRefresh } from "@/components/app-runtime";
import { AppShell, PageNav, StatusBar } from "@/components/app-ui";
import { AttachmentStorageClient } from "@/components/attachment-storage-client";
import { requirePageSession } from "@/lib/boss-auth";
@@ -20,6 +21,7 @@ export default async function StoragePage() {
return (
+
diff --git a/src/lib/boss-data.ts b/src/lib/boss-data.ts
index 7155b16..0412322 100644
--- a/src/lib/boss-data.ts
+++ b/src/lib/boss-data.ts
@@ -4322,7 +4322,7 @@ export async function updateProjectAgentControls(
throw new Error("MASTER_AGENT_TAKEOVER_SCOPE_RESTRICTED");
}
- return mutateStateIfChanged((state) => {
+ const result = await mutateStateIfChanged((state) => {
const project = state.projects.find((item) => item.id === projectId);
if (!project) throw new Error("PROJECT_NOT_FOUND");
@@ -4429,6 +4429,10 @@ export async function updateProjectAgentControls(
changed: true,
};
});
+ if (projectId === "master-agent") {
+ publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
+ }
+ return result;
}
function projectOrchestrationRequestedBackendId(project: Project): OrchestrationBackendId {
@@ -4529,7 +4533,7 @@ export async function getAttachmentStorageConfig(account: string) {
}
export async function upsertAttachmentStorageConfig(config: UserAttachmentStorageConfig) {
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const index = state.userAttachmentStorageConfigs.findIndex(
(item) => item.account === config.account,
);
@@ -4540,6 +4544,8 @@ export async function upsertAttachmentStorageConfig(config: UserAttachmentStorag
}
return config;
});
+ publishBossEvent("storage.updated");
+ return result;
}
export async function getMasterAgentPromptPolicy() {
@@ -4556,7 +4562,7 @@ export async function updateMasterAgentPromptPolicy(input: {
throw new Error("MASTER_AGENT_PROMPT_REQUIRED");
}
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const policy: MasterAgentPromptPolicy = {
globalPrompt,
updatedBy: input.updatedBy?.trim() || undefined,
@@ -4565,6 +4571,8 @@ export async function updateMasterAgentPromptPolicy(input: {
state.masterAgentPromptPolicy = policy;
return policy;
});
+ publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
+ return result;
}
export async function getUserMasterPrompt(account: string) {
@@ -4578,7 +4586,7 @@ export async function updateUserMasterPrompt(account: string, content: string) {
throw new Error("USER_MASTER_PROMPT_REQUIRED");
}
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const next: UserMasterPrompt = {
account,
content: trimmedContent,
@@ -4592,14 +4600,18 @@ export async function updateUserMasterPrompt(account: string, content: string) {
}
return next;
});
+ publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
+ return result;
}
export async function clearUserMasterPrompt(account: string) {
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const before = state.userMasterPrompts.length;
state.userMasterPrompts = state.userMasterPrompts.filter((item) => item.account !== account);
return { cleared: before !== state.userMasterPrompts.length };
});
+ publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
+ return result;
}
export async function listUserMasterMemories(
@@ -4647,7 +4659,7 @@ export async function createUserMasterMemory(input: {
throw new Error("USER_MASTER_MEMORY_PROJECT_ID_REQUIRED");
}
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const now = nowIso();
const memory: MasterAgentMemory = {
memoryId: randomToken("memory"),
@@ -4667,6 +4679,8 @@ export async function createUserMasterMemory(input: {
state.masterAgentMemories.unshift(memory);
return memory;
});
+ publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
+ return result;
}
export async function updateUserMasterMemory(
@@ -4679,7 +4693,7 @@ export async function updateUserMasterMemory(
>
>,
) {
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const memory = state.masterAgentMemories.find(
(item) => item.memoryId === memoryId && item.account === account,
);
@@ -4721,10 +4735,12 @@ export async function updateUserMasterMemory(
memory.updatedAt = nowIso();
return memory;
});
+ publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
+ return result;
}
export async function archiveUserMasterMemory(memoryId: string, account: string) {
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const memory = state.masterAgentMemories.find(
(item) => item.memoryId === memoryId && item.account === account,
);
@@ -4736,6 +4752,8 @@ export async function archiveUserMasterMemory(memoryId: string, account: string)
memory.updatedAt = nowIso();
return memory;
});
+ publishBossEvent("master_agent.settings.updated", { projectId: "master-agent" });
+ return result;
}
export async function touchUserMasterMemories(memoryIds: string[], account: string) {
@@ -5509,7 +5527,7 @@ export async function saveAiAccount(payload: {
setActive?: boolean;
loginStatusNote?: string;
}) {
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const existing = payload.accountId
? state.aiAccounts.find((item) => item.accountId === payload.accountId)
: null;
@@ -5580,13 +5598,15 @@ export async function saveAiAccount(payload: {
return buildAiAccountSummary(next);
});
+ publishBossEvent("ai_accounts.updated");
+ return result;
}
export async function deleteAiAccount(accountId: string) {
if (accountId === ENV_OPENAI_ACCOUNT_ID) {
throw new Error("ENV_AI_ACCOUNT_READ_ONLY");
}
- return mutateState((state) => {
+ const result = await mutateState((state) => {
const target = state.aiAccounts.find((item) => item.accountId === accountId);
if (!target) {
throw new Error("AI_ACCOUNT_NOT_FOUND");
@@ -5600,12 +5620,14 @@ export async function deleteAiAccount(accountId: string) {
}
return true;
});
+ publishBossEvent("ai_accounts.updated");
+ return result;
}
export async function activateAiAccount(accountId: string, reason: string) {
if (accountId === ENV_OPENAI_ACCOUNT_ID) {
const state = await readState();
- return {
+ const result = {
activeIdentity: {
...getMasterIdentitySummaryFromState(state),
accountId: ENV_OPENAI_ACCOUNT_ID,
@@ -5617,13 +5639,17 @@ export async function activateAiAccount(accountId: string, reason: string) {
isEnvironmentFallback: true,
},
};
+ publishBossEvent("ai_accounts.updated");
+ return result;
}
- return mutateState((state) => {
+ const result = await mutateState((state) => {
setActiveAiAccountInState(state, accountId, reason);
return {
activeIdentity: getMasterIdentitySummaryFromState(state),
};
});
+ publishBossEvent("ai_accounts.updated");
+ return result;
}
export async function updateAiAccountHealth(params: {
@@ -5661,6 +5687,7 @@ export async function updateAiAccountHealth(params: {
);
}
});
+ publishBossEvent("ai_accounts.updated");
}
export async function getMasterAgentRuntimeAccount() {
diff --git a/src/lib/boss-events.ts b/src/lib/boss-events.ts
index 796bc25..ee5fac3 100644
--- a/src/lib/boss-events.ts
+++ b/src/lib/boss-events.ts
@@ -6,8 +6,11 @@ export type BossEventName =
| "project.context_risk.updated"
| "app.logs.updated"
| "master_agent.task.updated"
+ | "master_agent.settings.updated"
+ | "ai_accounts.updated"
| "devices.updated"
| "devices.skills.updated"
+ | "storage.updated"
| "ota.updated";
export interface BossEventPayload {
diff --git a/tests/config-pages-realtime-refresh.test.ts b/tests/config-pages-realtime-refresh.test.ts
new file mode 100644
index 0000000..25339c1
--- /dev/null
+++ b/tests/config-pages-realtime-refresh.test.ts
@@ -0,0 +1,47 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import { readFile } from "node:fs/promises";
+
+async function readSource(relativePath: string) {
+ return readFile(new URL(`../${relativePath}`, import.meta.url), "utf8");
+}
+
+test("ai accounts page refreshes when AI account state changes", async () => {
+ const source = await readSource("src/app/me/ai-accounts/page.tsx");
+
+ assert.match(source, /import \{ RealtimeRefresh \}/, "expected ai accounts page to import RealtimeRefresh");
+ assert.match(source, / {
+ const source = await readSource("src/app/me/storage/page.tsx");
+
+ assert.match(source, /import \{ RealtimeRefresh \}/, "expected storage page to import RealtimeRefresh");
+ assert.match(source, / {
+ for (const relativePath of [
+ "src/app/me/master-agent/page.tsx",
+ "src/app/me/master-agent/takeover/page.tsx",
+ ]) {
+ const source = await readSource(relativePath);
+ assert.match(source, /import \{ RealtimeRefresh \}/, `expected ${relativePath} to import RealtimeRefresh`);
+ assert.match(source, / project.id === "master-agent");
+ if (masterAgentProject) {
+ masterAgentProject.agentControls = undefined;
+ }
+ await writeState(state);
+}
+
+test.beforeEach(async () => {
+ await setup();
+ await resetConfigState();
+});
+
+test.after(async () => {
+ if (runtimeRoot) {
+ await rm(runtimeRoot, { recursive: true, force: true });
+ }
+});
+
+test("saveAiAccount publishes ai account refresh event", async () => {
+ const events: Array<{ event: string }> = [];
+ const unsubscribe = subscribeBossEvents((event) => {
+ events.push({ event });
+ });
+
+ await saveAiAccount({
+ label: "主 GPT",
+ role: "primary",
+ provider: "openai_api",
+ displayName: "OpenAI 主账号",
+ apiKey: "sk-test-primary",
+ setActive: true,
+ });
+ unsubscribe();
+
+ assert.equal(events.at(-1)?.event, "ai_accounts.updated");
+});
+
+test("upsertAttachmentStorageConfig publishes storage refresh event", async () => {
+ const events: Array<{ event: string }> = [];
+ const unsubscribe = subscribeBossEvents((event) => {
+ events.push({ event });
+ });
+
+ await upsertAttachmentStorageConfig({
+ account: "17600003315",
+ mode: "server_file",
+ updatedAt: "2026-04-07T10:20:00.000Z",
+ });
+ unsubscribe();
+
+ assert.equal(events.at(-1)?.event, "storage.updated");
+});
+
+test("master agent prompt policy publishes master agent settings refresh event", async () => {
+ const events: Array<{ event: string; payload: { projectId?: string } }> = [];
+ const unsubscribe = subscribeBossEvents((event, payload) => {
+ events.push({ event, payload });
+ });
+
+ await updateMasterAgentPromptPolicy({
+ globalPrompt: "保持简洁并只输出有效内容。",
+ updatedBy: "17600003315",
+ });
+ unsubscribe();
+
+ assert.equal(events.at(-1)?.event, "master_agent.settings.updated");
+ assert.equal(events.at(-1)?.payload.projectId, "master-agent");
+});
+
+test("master agent memory writes publish master agent settings refresh event", async () => {
+ const events: Array<{ event: string; payload: { projectId?: string } }> = [];
+ const unsubscribe = subscribeBossEvents((event, payload) => {
+ events.push({ event, payload });
+ });
+
+ await createUserMasterMemory({
+ account: "17600003315",
+ scope: "global",
+ title: "用户偏好",
+ content: "群聊里默认一键通过。",
+ memoryType: "user_preference",
+ });
+ unsubscribe();
+
+ assert.equal(events.at(-1)?.event, "master_agent.settings.updated");
+ assert.equal(events.at(-1)?.payload.projectId, "master-agent");
+});
+
+test("master agent takeover changes publish master agent settings refresh event", async () => {
+ const events: Array<{ event: string; payload: { projectId?: string } }> = [];
+ const unsubscribe = subscribeBossEvents((event, payload) => {
+ events.push({ event, payload });
+ });
+
+ await updateProjectAgentControls(
+ "master-agent",
+ {
+ globalTakeoverEnabled: true,
+ },
+ "17600003315",
+ );
+ unsubscribe();
+
+ assert.equal(events.at(-1)?.event, "master_agent.settings.updated");
+ assert.equal(events.at(-1)?.payload.projectId, "master-agent");
+});