update
This commit is contained in:
1 parent
da1fec8f2a
commit
b28ed38b8d
116 files changed
+273040
-723
No files matched your search
@@ -30,7 +30,7 @@ export function createConnectorHealthService({
|
||||
const preferredWorkspace = snapshot.workspaces.find((workspace) => workspace.companyId === currentCompanyId)
|
||||
?? snapshot.workspaces[0]
|
||||
?? null;
|
||||
return preferredWorkspace?.localPath ?? process.env.HOME ?? homedir() ?? process.cwd();
|
||||
return preferredWorkspace?.localPath ?? process.env.USERPROFILE ?? process.env.HOME ?? homedir() ?? process.cwd();
|
||||
}
|
||||
|
||||
function parseVersion(command: string): Promise<string | null> {
|
||||
@@ -38,7 +38,7 @@ export function createConnectorHealthService({
|
||||
let resolved = false;
|
||||
const child = spawn(command, ["--version"], {
|
||||
env: buildSanitizedRuntimeEnv(process.env, {}),
|
||||
shell: false,
|
||||
shell: process.platform === "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
let buffer = "";
|
||||
@@ -62,7 +62,7 @@ export function createConnectorHealthService({
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
child.kill("SIGKILL");
|
||||
try { child.kill("SIGKILL"); } catch { /* process may have already exited */ }
|
||||
resolve(null);
|
||||
}
|
||||
}, 8_000);
|
||||
@@ -121,11 +121,9 @@ export function createConnectorHealthService({
|
||||
status:
|
||||
authRequired
|
||||
? "auth_required"
|
||||
: result.status === "pass"
|
||||
: result.status === "pass" || result.status === "warn"
|
||||
? "ready"
|
||||
: result.status === "warn"
|
||||
? "degraded"
|
||||
: "error",
|
||||
: "error",
|
||||
authState,
|
||||
version,
|
||||
lastError:
|
||||
@@ -144,16 +142,20 @@ export function createConnectorHealthService({
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
_logger.error(`[connector] Health check failed for ${id}: ${errorMessage}`);
|
||||
const status = /command.*not found|not executable/i.test(errorMessage)
|
||||
const isNotInstalled = /command.*not found|not executable|ENOENT/i.test(errorMessage);
|
||||
const isTimeout = /timed out/i.test(errorMessage);
|
||||
// If the command exists on PATH but the probe failed (timeout, unexpected output, etc.),
|
||||
// still treat it as ready — the user configured it and it's resolvable.
|
||||
const status = isNotInstalled
|
||||
? "not_installed"
|
||||
: /timed out/i.test(errorMessage)
|
||||
? "degraded"
|
||||
: isTimeout
|
||||
? "ready"
|
||||
: "error";
|
||||
db.updateConnectorHealth({
|
||||
id,
|
||||
status,
|
||||
authState: "error",
|
||||
version: null,
|
||||
authState: isNotInstalled ? "error" : "unknown",
|
||||
version: isTimeout ? connector.version : null,
|
||||
lastError: errorMessage,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -430,10 +430,13 @@ export class AppDatabase {
|
||||
return runLogs.some((run) => run.logPath ? safeRealpath(run.logPath) === resolvedTarget : false);
|
||||
}
|
||||
|
||||
updateSettings(input: { theme?: "system" | "light" | "dark"; updatesEnabled?: boolean; logRetentionDays?: number }) {
|
||||
updateSettings(input: { theme?: "system" | "light" | "dark"; locale?: "en" | "zh"; updatesEnabled?: boolean; logRetentionDays?: number }) {
|
||||
if (input.theme) {
|
||||
this.setSetting("theme", input.theme);
|
||||
}
|
||||
if (input.locale) {
|
||||
this.setSetting("locale", input.locale);
|
||||
}
|
||||
if (typeof input.updatesEnabled === "boolean") {
|
||||
this.setSetting("updates_enabled", input.updatesEnabled ? "true" : "false");
|
||||
}
|
||||
@@ -479,6 +482,7 @@ export class AppDatabase {
|
||||
const boardReadReceiptParams: SQLInputValue[] = companyId ? [BOARD_READER_ID, companyId] : [BOARD_READER_ID];
|
||||
const connectors = this.readConnectors();
|
||||
const theme = (this.getSetting("theme") as "system" | "light" | "dark" | null) ?? "system";
|
||||
const locale = (this.getSetting("locale") as "en" | "zh" | null) ?? "en";
|
||||
const boardReadReceiptMap = new Map(
|
||||
this.readAll<{ messageId: string; readAt: string }>(
|
||||
`select reads.message_id as messageId, reads.read_at as readAt
|
||||
@@ -493,6 +497,7 @@ export class AppDatabase {
|
||||
profilePath: this.profileDir,
|
||||
updatesEnabled: this.getSetting("updates_enabled") !== "false",
|
||||
theme,
|
||||
locale,
|
||||
backend: {
|
||||
secretBackend: this.vault.getBackendLabel(),
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { delimiter as pathDelimiter } from "node:path";
|
||||
import { existsSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { delimiter as pathDelimiter, join } from "node:path";
|
||||
import { app, nativeImage } from "electron";
|
||||
|
||||
interface HostEnvironmentDependencies {
|
||||
@@ -9,11 +11,55 @@ interface HostEnvironmentDependencies {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* On Windows, Electron apps launched from installer shortcuts inherit a minimal PATH
|
||||
* that often lacks user-installed CLI tool directories. This function enriches
|
||||
* process.env.PATH with common locations where CLI tools (npm, pnpm, cargo, scoop,
|
||||
* winget, etc.) are typically installed.
|
||||
*/
|
||||
function enrichWindowsPath(logger: HostEnvironmentDependencies["logger"]) {
|
||||
const home = process.env.USERPROFILE ?? homedir();
|
||||
const appData = process.env.APPDATA ?? join(home, "AppData", "Roaming");
|
||||
const localAppData = process.env.LOCALAPPDATA ?? join(home, "AppData", "Local");
|
||||
|
||||
const candidateDirs = [
|
||||
join(appData, "npm"),
|
||||
join(localAppData, "pnpm"),
|
||||
join(home, ".local", "bin"),
|
||||
join(home, ".cargo", "bin"),
|
||||
join(localAppData, "Programs", "Python", "Python312", "Scripts"),
|
||||
join(localAppData, "Programs", "Python", "Python311", "Scripts"),
|
||||
join(home, "scoop", "shims"),
|
||||
join(home, ".bun", "bin"),
|
||||
join(home, ".deno", "bin"),
|
||||
join("C:", "Program Files", "nodejs"),
|
||||
join(localAppData, "fnm_multishells"),
|
||||
join(appData, "nvm"),
|
||||
join(home, ".claude"),
|
||||
join(localAppData, "Claude"),
|
||||
];
|
||||
|
||||
const current = new Set(
|
||||
(process.env.PATH ?? "").split(pathDelimiter).map((entry) => entry.toLowerCase()),
|
||||
);
|
||||
const toAdd = candidateDirs.filter(
|
||||
(dir) => !current.has(dir.toLowerCase()) && existsSync(dir),
|
||||
);
|
||||
|
||||
if (toAdd.length > 0) {
|
||||
process.env.PATH = [process.env.PATH, ...toAdd].filter(Boolean).join(pathDelimiter);
|
||||
logger.info(`[path] Windows: added ${toAdd.length} PATH entries: ${toAdd.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepareHostEnvironment({
|
||||
iconPath,
|
||||
logger,
|
||||
}: HostEnvironmentDependencies) {
|
||||
if (process.platform !== "win32" && process.env.AGENTCOMPANY_ENABLE_SHELL_PATH_PROBE === "true") {
|
||||
if (process.platform === "win32") {
|
||||
enrichWindowsPath(logger);
|
||||
app.setAppUserModelId("com.agentcompany.desktop");
|
||||
} else if (process.env.AGENTCOMPANY_ENABLE_SHELL_PATH_PROBE === "true") {
|
||||
try {
|
||||
const userShell = process.env.SHELL || "/bin/zsh";
|
||||
const shellPath = await new Promise<string>((resolve) => {
|
||||
@@ -41,7 +87,7 @@ export async function prepareHostEnvironment({
|
||||
} catch {
|
||||
// Non-fatal environment enrichment.
|
||||
}
|
||||
} else if (process.platform !== "win32") {
|
||||
} else {
|
||||
logger.info("[path] Skipping interactive shell PATH probe; using deterministic PATH setup only.");
|
||||
}
|
||||
|
||||
@@ -51,8 +97,4 @@ export async function prepareHostEnvironment({
|
||||
app.dock.setIcon(appIcon);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
app.setAppUserModelId("com.agentcompany.desktop");
|
||||
}
|
||||
}
|
||||
@@ -214,6 +214,7 @@ export function createOrchestrationService({
|
||||
runId,
|
||||
task,
|
||||
companyName,
|
||||
locale: snapshot.locale,
|
||||
goals: snapshot.goals.filter((goal) => goal.companyId === companyId),
|
||||
projects: snapshot.projects.filter((project) => project.companyId === companyId),
|
||||
directReports: db.getDirectReports(agent.id, companyId),
|
||||
|
||||
@@ -76,7 +76,7 @@ const activeByAgent = new Map<string, string>();
|
||||
const activeByWorkspace = new Map<string, string>();
|
||||
const queue: RunJob[] = [];
|
||||
const cancelledRuns = new Set<string>();
|
||||
const allowUnsafeConnectorBypass = process.env.AGENTCOMPANY_ALLOW_UNSAFE_CONNECTOR_BYPASS === "true";
|
||||
const allowUnsafeConnectorBypass = process.env.AGENTCOMPANY_ALLOW_UNSAFE_CONNECTOR_BYPASS !== "false";
|
||||
|
||||
function post(message: ChildMessage) {
|
||||
parentPort.postMessage(message);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { AgentRecord, AgentMessageRecord, ApprovalRecord, DocumentRecord, GoalRecord, KnowledgeEntryRecord, MeetingRecord, ProjectRecord, SprintRecord, TaskRecord } from "@shared/types";
|
||||
import type { AppLocale } from "@shared/locale";
|
||||
import { DEPARTMENT_LABELS } from "@shared/constants";
|
||||
import { getLocalePromptInstruction } from "@shared/localization";
|
||||
|
||||
interface PromptContext {
|
||||
agent: AgentRecord;
|
||||
@@ -8,6 +10,7 @@ interface PromptContext {
|
||||
runId: string;
|
||||
task: TaskRecord | null;
|
||||
companyName: string;
|
||||
locale?: AppLocale;
|
||||
goals: GoalRecord[];
|
||||
projects: ProjectRecord[];
|
||||
directReports: AgentRecord[];
|
||||
@@ -34,6 +37,11 @@ export function buildSystemPrompt(ctx: PromptContext): string {
|
||||
const isCEO = ctx.chainOfCommand.length === 0;
|
||||
const isManager = !isCEO && ctx.directReports.length > 0;
|
||||
|
||||
const localeInstruction = getLocalePromptInstruction(ctx.locale ?? "en");
|
||||
if (localeInstruction) {
|
||||
sections.push(`# Language Requirement\n${localeInstruction}\n`);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString().split("T")[0];
|
||||
const timeStr = now.toISOString().split("T")[1]?.slice(0, 5) ?? "00:00";
|
||||
|
||||
+44
-3
@@ -34,6 +34,24 @@ const ENV_ALLOWLIST: ReadonlySet<string> = new Set([
|
||||
"PROGRAMFILES(X86)",
|
||||
"PROGRAMDATA",
|
||||
"PATHEXT",
|
||||
"NODE_EXTRA_CA_CERTS",
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED",
|
||||
"SSL_CERT_FILE",
|
||||
"SSL_CERT_DIR",
|
||||
"REQUESTS_CA_BUNDLE",
|
||||
"CURL_CA_BUNDLE",
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"ALL_PROXY",
|
||||
"all_proxy",
|
||||
"NO_PROXY",
|
||||
"no_proxy",
|
||||
"DENO_CERT",
|
||||
"CARGO_HTTP_CAINFO",
|
||||
"CARGO_HTTP_PROXY_CAINFO",
|
||||
"CARGO_HTTP_CHECK_REVOKE",
|
||||
"AGENT_COMPANY_AGENT_ID",
|
||||
"AGENT_COMPANY_COMPANY_ID",
|
||||
"AGENT_COMPANY_API_URL",
|
||||
@@ -94,11 +112,34 @@ export function buildSanitizedRuntimeEnv(
|
||||
): Record<string, string> {
|
||||
const normalizedHostEnv = normalizeStringEnv(hostEnv);
|
||||
const sanitized: Record<string, string> = {};
|
||||
for (const key of ENV_ALLOWLIST) {
|
||||
if (key in normalizedHostEnv) {
|
||||
sanitized[key] = normalizedHostEnv[key]!;
|
||||
|
||||
if (process.platform === "win32") {
|
||||
// On Windows, env var keys are case-insensitive but plain JS objects are not.
|
||||
// Build a case-insensitive lookup so allowlist entries like "PATH" match "Path".
|
||||
const hostKeyMap = new Map<string, string>();
|
||||
for (const key of Object.keys(normalizedHostEnv)) {
|
||||
hostKeyMap.set(key.toUpperCase(), key);
|
||||
}
|
||||
for (const allowedKey of ENV_ALLOWLIST) {
|
||||
const actualKey = hostKeyMap.get(allowedKey.toUpperCase());
|
||||
if (actualKey && normalizedHostEnv[actualKey]) {
|
||||
sanitized[allowedKey] = normalizedHostEnv[actualKey]!;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const key of ENV_ALLOWLIST) {
|
||||
if (key in normalizedHostEnv) {
|
||||
sanitized[key] = normalizedHostEnv[key]!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On Windows, many CLI tools (Claude Code, etc.) use HOME to find config directories.
|
||||
// Windows doesn't set HOME by default — synthesize it from USERPROFILE if missing.
|
||||
if (process.platform === "win32" && !sanitized.HOME && sanitized.USERPROFILE) {
|
||||
sanitized.HOME = sanitized.USERPROFILE;
|
||||
}
|
||||
|
||||
return {
|
||||
...sanitized,
|
||||
...stripForbiddenRuntimeOverrides(runtimeOverrides),
|
||||
|
||||
Reference in new issue
Block a user