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");