This commit is contained in:
ZacharyZhang-NY committed 2026-04-07 20:18:47 -04:00
1 parent f5d4f8b705
commit 9779c4d1ca
24 files changed
+2782 -130

No files matched your search

+3 -19
View File
@@ -247,25 +247,9 @@ export function executeAutomationAction(
}
} else {
// No manager found — agent is likely the CEO or top-level.
// Post as company-wide message so the board and other leaders see it.
const escalationResult = dispatchAgentMessage(
{ db, emitEvent, publishDomainChanged, wakeAgentIfPossible },
{
companyId: ctx.companyId,
fromAgentId: targetAgentId,
toAgentId: null,
channel: "company",
subject: `Escalation: ${taskTitle}`,
body: escalationBody,
priority: "urgent",
},
wakeTrigger,
);
if (!escalationResult.ok) {
logger.warn(
`[automation] Failed to escalate "${taskTitle}" to company channel: ${escalationResult.error.code} ${escalationResult.error.message}`,
);
}
// Skip posting to company channel to avoid flooding it with system noise.
// The task status change is already visible in the task list and activity log.
logger.info(`[automation] Skipping escalation for top-level agent "${agentName}" — no manager to escalate to.`);
}
break;
}
+1
View File
@@ -168,6 +168,7 @@ export class AppDatabaseBase {
canOpenPath(targetPath: string) { return this.settings.canOpenPath(targetPath); }
getDisabledModels(connectorId: string) { return this.settings.getDisabledModels(connectorId); }
setDisabledModels(connectorId: string, models: Array<{ id: string; label: string }>) { this.settings.setDisabledModels(connectorId, models); }
hasModelPoolSettings(connectorId: string) { return this.settings.hasModelPoolSettings(connectorId); }
getLastBudgetResetMonth(companyId: string) { return this.settings.getLastBudgetResetMonth(companyId); }
setLastBudgetResetMonth(companyId: string, month: string) { this.settings.setLastBudgetResetMonth(companyId, month); }
+76
View File
@@ -90,6 +90,53 @@ export function buildConnectorMethods(
const currentRows = readAll<Partial<ConnectorRecord>>(
"select id, label, description, status, command, version, auth_state as authState, last_checked_at as lastCheckedAt, last_error as lastError, model, env_binding_text as envBindingText, notes from connectors",
);
// Migrate first compat slot if its definition defaults have changed (edition upgrade).
// When the definition ships with preset notes that differ from the DB, relocate the
// old user config to the next empty compat slot so definition defaults take effect.
const firstCompat = currentRows.find((r) => r.id === "claude_compatible");
if (firstCompat?.envBindingText && firstCompat.notes) {
let firstDef: { notes: string } | undefined;
try { firstDef = getConnectorDefinition("claude_compatible"); } catch { /* skip */ }
if (firstDef?.notes && firstCompat.notes !== firstDef.notes) {
const migrationTargets: ConnectorId[] = [
"claude_compat_2", "claude_compat_3", "claude_compat_4", "claude_compat_5",
"claude_compat_6", "claude_compat_7", "claude_compat_8", "claude_compat_9", "claude_compat_10",
];
const occupied = new Set(
currentRows
.filter((r) => r.id !== "claude_compatible" && r.id?.startsWith("claude_compat") && r.envBindingText)
.map((r) => r.id),
);
const targetId = migrationTargets.find((id) => !occupied.has(id));
if (targetId) {
const migrated = {
envBindingText: firstCompat.envBindingText,
notes: firstCompat.notes,
model: firstCompat.model,
status: firstCompat.status,
authState: firstCompat.authState,
version: firstCompat.version,
lastCheckedAt: firstCompat.lastCheckedAt,
lastError: firstCompat.lastError,
};
const targetRow = currentRows.find((r) => r.id === targetId);
if (targetRow) {
Object.assign(targetRow, migrated);
} else {
currentRows.push({ id: targetId as ConnectorId, ...migrated });
}
firstCompat.envBindingText = undefined;
firstCompat.notes = undefined;
firstCompat.model = undefined;
firstCompat.status = undefined;
firstCompat.authState = undefined;
firstCompat.version = undefined;
firstCompat.lastCheckedAt = undefined;
firstCompat.lastError = undefined;
}
}
}
const merged = buildDefaultConnectorRows(currentRows);
const statement = getDb().prepare(
"insert or replace into connectors (id, label, description, status, command, version, auth_state, last_checked_at, last_error, model, env_binding_text, notes, updated_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
@@ -102,6 +149,35 @@ export function buildConnectorMethods(
row.lastError, row.model, row.envBindingText, row.notes, updatedAt,
);
}
// One-time migration: mark native connectors that are actively used by
// agents as model-pool-managed so they remain visible to the CEO after
// the opt-in policy change. Connectors not used by any agent stay
// unmanaged and will not appear in CEO prompts until the user explicitly
// enables models for them in Settings > Model Management.
const migrationKey = "migration:model_pool_opt_in_v1";
const migrated = readAll<{ value: string }>("SELECT value FROM settings WHERE key = ?", migrationKey);
if (migrated.length === 0) {
const usedIds = new Set(
readAll<{ connector_id: string }>(
"SELECT DISTINCT connector_id FROM agents WHERE status NOT IN ('terminated')",
).map((r) => r.connector_id),
);
const nativeIds = ["codex_local", "claude_local", "gemini_local"];
const upsert = getDb().prepare(
"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
);
for (const id of nativeIds) {
const hasSettings = readAll<{ key: string }>(
"SELECT key FROM settings WHERE key = ?",
`disabled_models:${id}`,
);
if (hasSettings.length === 0 && usedIds.has(id)) {
upsert.run(`disabled_models:${id}`, "[]");
}
}
upsert.run(migrationKey, "1");
}
},
};
}
+5 -5
View File
@@ -84,11 +84,11 @@ export function buildSettingsMethods(
},
setDisabledModels(connectorId: string, models: Array<{ id: string; label: string }>) {
if (models.length === 0) {
getDb().prepare("delete from settings where key = ?").run(`disabled_models:${connectorId}`);
} else {
setSetting(`disabled_models:${connectorId}`, JSON.stringify(models));
}
setSetting(`disabled_models:${connectorId}`, JSON.stringify(models));
},
hasModelPoolSettings(connectorId: string): boolean {
return getSetting(`disabled_models:${connectorId}`) !== null;
},
getLastBudgetResetMonth(companyId: string): string | null {
+10 -6
View File
@@ -237,12 +237,16 @@ export function buildRunPayload(params: RunDispatchParams, deps: TurnContextDeps
recentDocuments: snapshot.documents
.filter((document) => document.companyId === companyId && (document.authorAgentId === agent.id || document.reviewerAgentId === agent.id || document.status === "in_review"))
.slice(0, 5),
allConnectors: snapshot.connectors.map((c) => ({
id: c.id,
label: c.label,
status: c.status,
enabledModels: resolveEnabledConnectorModels(db, c),
})),
allConnectors: snapshot.connectors
.filter((c) => c.status === "ready")
.filter((c) => c.id.startsWith("claude_compat") || db.hasModelPoolSettings(c.id))
.map((c) => ({
id: c.id,
label: c.label,
status: c.status,
enabledModels: resolveEnabledConnectorModels(db, c),
}))
.filter((c) => c.enabledModels.length > 0),
sessionHandoff,
skillNames,
skillEntries: skills.map((s) => ({
+5 -1
View File
@@ -381,7 +381,11 @@ Always verify API responses before reporting — only report successful actions.
// Reinforce locale at the end of prompt (recency bias helps LLMs follow this)
if (localeInstruction) {
sections.push(`# 语言要求提醒\n${localeInstruction}\n请记住:你的所有输出必须使用中文。\n`);
if ((ctx.locale ?? "en") === "zh") {
sections.push(`# 语言要求提醒\n${localeInstruction}\n请记住:你的所有输出必须使用中文。\n`);
} else {
sections.push(`# Language Requirement Reminder\n${localeInstruction}\nRemember: ALL your output must be in English.\n`);
}
}
return sections.join("\n");
+7 -2
View File
@@ -3,7 +3,7 @@ import type { AppLocale } from "./locale";
/**
* Returns a locale-appropriate system prompt instruction for AI agents.
* When locale is "zh", the instruction tells the agent to respond entirely in Chinese.
* For "en" (or any other locale), returns an empty string (no additional instruction needed).
* When locale is "en", the instruction tells the agent to respond entirely in English.
*/
export function getLocalePromptInstruction(locale: AppLocale): string {
if (locale === "zh") {
@@ -14,5 +14,10 @@ export function getLocalePromptInstruction(locale: AppLocale): string {
"不要使用英文回复,除非是代码、变量名、API 端点等技术术语。",
].join("\n");
}
return "";
return [
"Important language requirement: You MUST communicate entirely in English.",
"This includes: task descriptions, comments, messages, reports, documents, approvals, notifications — ALL output must be in English.",
"Even if some existing content or context is in another language, all your responses and created content must be in English.",
"Do not respond in other languages, except for code, variable names, API endpoints, and other technical terms.",
].join("\n");
}