This commit is contained in:
ZacharyZhang-NY committed 2026-04-05 20:14:48 -04:00
1 parent 09971cabc2
commit 9145572422
163 files changed
+3348 -2874

No files matched your search

@@ -0,0 +1,232 @@
import { fileURLToPath } from "node:url";
import path from "node:path";
import fs from "node:fs/promises";
import type { ManagedSkillRef } from "./types.js";
import { asString, asStringArray, parseObject } from "./server-utils-parsers.js";
import { buildAgentCompanyEnv } from "./server-utils-parsers.js";
// ---------------------------------------------------------------------------
// hasNonEmptyEnvValue
// ---------------------------------------------------------------------------
export function hasNonEmptyEnvValue(env: Record<string, string>, key: string): boolean {
const raw = env[key];
return typeof raw === "string" && raw.trim().length > 0;
}
// ---------------------------------------------------------------------------
// Skills directory resolution
// ---------------------------------------------------------------------------
/**
* Build the standard two-candidate list for a given adapter package.
* `importMetaUrl` should be `import.meta.url` from the adapter's execute-helpers.ts.
* - published: <pkg>/dist/server/ -> <pkg>/skills/
* - dev: src/server/ -> repo root/skills/
*/
export function buildSkillsCandidates(importMetaUrl: string): string[] {
const moduleDir = path.dirname(fileURLToPath(importMetaUrl));
return [
path.resolve(moduleDir, "../../skills"),
path.resolve(moduleDir, "../../../../../skills"),
];
}
/**
* Resolve the first existing skills directory from the given candidates.
*/
export async function resolveSkillsDir(candidates: string[]): Promise<string | null> {
for (const candidate of candidates) {
const isDir = await fs.stat(candidate).then((s) => s.isDirectory()).catch(() => false);
if (isDir) return candidate;
}
return null;
}
// ---------------------------------------------------------------------------
// ManagedSkills type guard + parser
// ---------------------------------------------------------------------------
export function parseManagedSkills(config: Record<string, unknown>): ManagedSkillRef[] {
if (!Array.isArray(config.managedSkills)) return [];
return (config.managedSkills as ManagedSkillRef[]).filter(
(s) => typeof s.runtimeName === "string" && typeof s.sourcePath === "string",
);
}
// ---------------------------------------------------------------------------
// extraArgs parser
// ---------------------------------------------------------------------------
export function parseExtraArgs(config: Record<string, unknown>): string[] {
const fromExtraArgs = asStringArray(config.extraArgs);
if (fromExtraArgs.length > 0) return fromExtraArgs;
return asStringArray(config.args);
}
// ---------------------------------------------------------------------------
// Common env building
// ---------------------------------------------------------------------------
export interface CommonAgentCompanyEnvInput {
runId: string;
agent: { id: string; companyId: string };
config: Record<string, unknown>;
context: Record<string, unknown>;
authToken?: string;
}
export interface CommonAgentCompanyEnvResult {
env: Record<string, string>;
cwd: string;
effectiveWorkspaceCwd: string;
workspaceId: string | null;
workspaceRepoUrl: string | null;
workspaceRepoRef: string | null;
hasExplicitApiKey: boolean;
}
/**
* Build the AGENT_COMPANY_* environment block common to all local adapters.
* Returns the env map plus resolved workspace fields needed for session params.
*/
export async function buildCommonAgentCompanyEnv(
input: CommonAgentCompanyEnvInput,
opts: {
ensureAbsoluteDirectory: (dir: string, options: { createIfMissing: boolean }) => Promise<void>;
},
): Promise<CommonAgentCompanyEnvResult> {
const { runId, agent, config, context, authToken } = input;
const workspaceContext = parseObject(context.agentCompanyWorkspace);
const workspaceCwd = asString(workspaceContext.cwd, "");
const workspaceSource = asString(workspaceContext.source, "");
const workspaceStrategy = asString(workspaceContext.strategy, "");
const workspaceId = asString(workspaceContext.workspaceId, "") || null;
const workspaceRepoUrl = asString(workspaceContext.repoUrl, "") || null;
const workspaceRepoRef = asString(workspaceContext.repoRef, "") || null;
const workspaceBranch = asString(workspaceContext.branchName, "") || null;
const workspaceWorktreePath = asString(workspaceContext.worktreePath, "") || null;
const workspaceHints = Array.isArray(context.agentCompanyWorkspaces)
? context.agentCompanyWorkspaces.filter(
(value): value is Record<string, unknown> => typeof value === "object" && value !== null,
)
: [];
const runtimeServiceIntents = Array.isArray(context.agentCompanyRuntimeServiceIntents)
? context.agentCompanyRuntimeServiceIntents.filter(
(value): value is Record<string, unknown> => typeof value === "object" && value !== null,
)
: [];
const runtimeServices = Array.isArray(context.agentCompanyRuntimeServices)
? context.agentCompanyRuntimeServices.filter(
(value): value is Record<string, unknown> => typeof value === "object" && value !== null,
)
: [];
const runtimePrimaryUrl = asString(context.agentCompanyRuntimePrimaryUrl as unknown, "");
const configuredCwd = asString(config.cwd, "");
const useConfiguredInsteadOfAgentHome = workspaceSource === "agent_home" && configuredCwd.length > 0;
const effectiveWorkspaceCwd = useConfiguredInsteadOfAgentHome ? "" : workspaceCwd;
const cwd = effectiveWorkspaceCwd || configuredCwd || process.cwd();
await opts.ensureAbsoluteDirectory(cwd, { createIfMissing: true });
const envConfig = parseObject(config.env);
const hasExplicitApiKey =
typeof envConfig.AGENT_COMPANY_API_KEY === "string" && envConfig.AGENT_COMPANY_API_KEY.trim().length > 0;
const env: Record<string, string> = { ...buildAgentCompanyEnv(agent) };
env.AGENT_COMPANY_RUN_ID = runId;
const wakeTaskId =
(typeof context.taskId === "string" && context.taskId.trim().length > 0 && context.taskId.trim()) ||
(typeof context.issueId === "string" && context.issueId.trim().length > 0 && context.issueId.trim()) ||
null;
const wakeReason =
typeof context.wakeReason === "string" && context.wakeReason.trim().length > 0
? context.wakeReason.trim()
: null;
const wakeCommentId =
(typeof context.wakeCommentId === "string" && context.wakeCommentId.trim().length > 0 && context.wakeCommentId.trim()) ||
(typeof context.commentId === "string" && context.commentId.trim().length > 0 && context.commentId.trim()) ||
null;
const approvalId =
typeof context.approvalId === "string" && context.approvalId.trim().length > 0
? context.approvalId.trim()
: null;
const approvalStatus =
typeof context.approvalStatus === "string" && context.approvalStatus.trim().length > 0
? context.approvalStatus.trim()
: null;
const linkedIssueIds = Array.isArray(context.issueIds)
? context.issueIds.filter((value): value is string => typeof value === "string" && value.trim().length > 0)
: [];
if (wakeTaskId) env.AGENT_COMPANY_TASK_ID = wakeTaskId;
if (wakeReason) env.AGENT_COMPANY_WAKE_REASON = wakeReason;
if (wakeCommentId) env.AGENT_COMPANY_WAKE_COMMENT_ID = wakeCommentId;
if (approvalId) env.AGENT_COMPANY_APPROVAL_ID = approvalId;
if (approvalStatus) env.AGENT_COMPANY_APPROVAL_STATUS = approvalStatus;
if (linkedIssueIds.length > 0) env.AGENT_COMPANY_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
if (effectiveWorkspaceCwd) env.AGENT_COMPANY_WORKSPACE_CWD = effectiveWorkspaceCwd;
if (workspaceSource) env.AGENT_COMPANY_WORKSPACE_SOURCE = workspaceSource;
if (workspaceStrategy) env.AGENT_COMPANY_WORKSPACE_STRATEGY = workspaceStrategy;
if (workspaceId) env.AGENT_COMPANY_WORKSPACE_ID = workspaceId;
if (workspaceRepoUrl) env.AGENT_COMPANY_WORKSPACE_REPO_URL = workspaceRepoUrl;
if (workspaceRepoRef) env.AGENT_COMPANY_WORKSPACE_REPO_REF = workspaceRepoRef;
if (workspaceBranch) env.AGENT_COMPANY_WORKSPACE_BRANCH = workspaceBranch;
if (workspaceWorktreePath) env.AGENT_COMPANY_WORKSPACE_WORKTREE_PATH = workspaceWorktreePath;
if (workspaceHints.length > 0) env.AGENT_COMPANY_WORKSPACES_JSON = JSON.stringify(workspaceHints);
if (runtimeServiceIntents.length > 0) env.AGENT_COMPANY_RUNTIME_SERVICE_INTENTS_JSON = JSON.stringify(runtimeServiceIntents);
if (runtimeServices.length > 0) env.AGENT_COMPANY_RUNTIME_SERVICES_JSON = JSON.stringify(runtimeServices);
if (runtimePrimaryUrl) env.AGENT_COMPANY_RUNTIME_PRIMARY_URL = runtimePrimaryUrl;
// Merge caller-configured env overrides
for (const [key, value] of Object.entries(envConfig)) {
if (typeof value === "string") env[key] = value;
}
if (!hasExplicitApiKey && authToken) {
env.AGENT_COMPANY_API_KEY = authToken;
}
return {
env,
cwd,
effectiveWorkspaceCwd,
workspaceId,
workspaceRepoUrl,
workspaceRepoRef,
hasExplicitApiKey,
};
}
// ---------------------------------------------------------------------------
// Session resume validation
// ---------------------------------------------------------------------------
export interface SessionResumeState {
sessionId: string | null;
runtimeSessionId: string;
runtimeSessionCwd: string;
canResumeSession: boolean;
}
export function resolveSessionResumeState(
runtime: { sessionId?: string | null; sessionParams?: Record<string, unknown> | null },
cwd: string,
): SessionResumeState {
const runtimeSessionParams = parseObject(runtime.sessionParams);
const runtimeSessionId = asString(runtimeSessionParams.sessionId, runtime.sessionId ?? "");
const runtimeSessionCwd = asString(runtimeSessionParams.cwd, "");
const canResumeSession =
runtimeSessionId.length > 0 &&
(runtimeSessionCwd.length === 0 || path.resolve(runtimeSessionCwd) === path.resolve(cwd));
return {
sessionId: canResumeSession ? runtimeSessionId : null,
runtimeSessionId,
runtimeSessionCwd,
canResumeSession,
};
}
+14 -1
View File
@@ -33,5 +33,18 @@ export {
REDACTED_HOME_PATH_USER,
redactHomePathUserSegments,
redactHomePathUserSegmentsInValue,
redactTranscriptEntryPaths,
} from "./log-redaction.js";
export type {
CommonAgentCompanyEnvInput,
CommonAgentCompanyEnvResult,
SessionResumeState,
} from "./execute-helpers.js";
export {
hasNonEmptyEnvValue,
buildSkillsCandidates,
resolveSkillsDir,
parseManagedSkills,
parseExtraArgs,
buildCommonAgentCompanyEnv,
resolveSessionResumeState,
} from "./execute-helpers.js";
@@ -1,5 +1,3 @@
import type { TranscriptEntry } from "./types.js";
export const REDACTED_HOME_PATH_USER = "[]";
const HOME_PATH_PATTERNS = [
@@ -49,33 +47,3 @@ export function redactHomePathUserSegmentsInValue<T>(value: T): T {
return redacted as T;
}
export function redactTranscriptEntryPaths(entry: TranscriptEntry): TranscriptEntry {
switch (entry.kind) {
case "assistant":
case "thinking":
case "user":
case "stderr":
case "system":
case "stdout":
return { ...entry, text: redactHomePathUserSegments(entry.text) };
case "tool_call":
return { ...entry, name: redactHomePathUserSegments(entry.name), input: redactHomePathUserSegmentsInValue(entry.input) };
case "tool_result":
return { ...entry, content: redactHomePathUserSegments(entry.content) };
case "init":
return {
...entry,
model: redactHomePathUserSegments(entry.model),
sessionId: redactHomePathUserSegments(entry.sessionId),
};
case "result":
return {
...entry,
text: redactHomePathUserSegments(entry.text),
subtype: redactHomePathUserSegments(entry.subtype),
errors: entry.errors.map((error) => redactHomePathUserSegments(error)),
};
default:
return entry;
}
}
@@ -1,4 +1,4 @@
import { spawn, execSync, type ChildProcess } from "node:child_process";
import { spawn, spawnSync, type ChildProcess } from "node:child_process";
import { constants as fsConstants, promises as fs } from "node:fs";
import path from "node:path";
import { StringDecoder } from "node:string_decoder";
@@ -356,7 +356,7 @@ export async function runChildProcess(
function killProcessTree(pid: number): void {
if (process.platform === "win32") {
try {
execSync(`taskkill /T /F /PID ${pid}`, { stdio: "ignore", timeout: 5000 });
spawnSync("taskkill", ["/T", "/F", "/PID", String(pid)], { stdio: "ignore", timeout: 5000 });
} catch { /* process already exited */ }
} else {
try {
@@ -224,30 +224,6 @@ export function resolveAgentCompanyDesiredSkillNames(
return Array.from(new Set([...requiredSkills, ...desiredSkills]));
}
/**
* Write desired skills back to adapter config.
*/
export function writeAgentCompanySkillSyncPreference(
config: Record<string, unknown>,
desiredSkills: string[],
): Record<string, unknown> {
const next = { ...config };
const raw = next.agentCompanySkillSync;
const current =
typeof raw === "object" && raw !== null && !Array.isArray(raw)
? { ...(raw as Record<string, unknown>) }
: {};
current.desiredSkills = Array.from(
new Set(
desiredSkills
.map((value) => value.trim())
.filter(Boolean),
),
);
next.agentCompanySkillSync = current;
return next;
}
/**
* Create or repair a symlink from source to target skill directory.
* Returns the action taken: "created", "repaired", or "skipped".
-8
View File
@@ -35,11 +35,3 @@ export function summarizeProbeDetail(stdout: string, stderr: string, parsedError
return clean.length > max ? `${clean.slice(0, max - 1)}…` : clean;
}
export function normalizeEnv(input: unknown): Record<string, string> {
if (typeof input !== "object" || input === null || Array.isArray(input)) return {};
const env: Record<string, string> = {};
for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
if (typeof value === "string") env[key] = value;
}
return env;
}