refactor: nuke dead exports, dead files, dead DB column, dead zero-value wrappers

Full-codebase ts-prune + manual audit sweep. Every change below is a pure
deletion (or a 2-line re-wire where a dead function was supposed to be
alive). No scope additions.

Dead wrappers / phantom features
  - shouldAutoApproveApproval: zero-value wrapper over shouldAutoApprove.
    Four call sites now import shouldAutoApprove from execution-policy
    directly.
  - evaluateRotation(session, connectorId, thresholdsOverride): the
    connectorId arg was never read (underscored), and thresholdsOverride
    was only exercised by the test suite — no production caller. Signature
    collapses to evaluateRotation(session). DEFAULT_THRESHOLDS /
    RotationThresholds exports and their tests go with it.
  - installLogRedactor: defensive console wrapper that was never wired
    from worker-entry. buildRedactor handles the known threat (env-value
    leaks); the pattern-based redactTokens stays for tests and future
    composition.

Dead files
  - resume-interrupted-tasks.ts: helper function never imported.
  - service-graph-orchestration-deps.ts: three factory closures
    superseded by the inline closures in create-orchestration-service.

Dead exports
  - listConnectorsByAuthKind (shared/connector-catalog)
  - getLocalePromptInstruction + whole localization.ts file
  - ImportantDocType
  - main-side ConnectorAuthKind (duplicated the shared one; not referenced)
  - CheckpointEngineService, RunHousekeepingService types
  - WorkspaceResolution type
  - cmdOrCtrlSymbol
  - AgentEventPush, CredentialPayload type aliases
  - parseApprovalPayloadJson, parseEnvValue, ProviderCard
  - getSOP — buildSOPSection reads the SOPS map directly; the individual
    getter had no callers. Deduped to a flat array.
  - renderWakeReason in wake-reasons/en.ts: byte-for-byte duplicate of the
    one in wake-reasons/index.ts.

Forgotten wiring (not cleanup, but adjacent)
  - registerAgentStandupRoute: the GET /api/agents/me/standup endpoint is
    advertised in every system prompt and wake-reason doc, but the route
    handler was never registered in api-server/index.ts. Agents have been
    hitting 404 since Pi Mono. Registered.

Dead DB column
  - v44 migration drops runs.context_snapshot_json. Column was added by
    v16-v25, never written or read, and v43's deliberate preservation
    was an interim measure. Same idempotent table-rebuild pattern as
    v41/v43.

Test tree
  - Phantom-feature threshold override tests removed (3). The rest of
    the rotation suite re-expressed against the real maxRuns=20 floor.

495/495 unit tests pass. typecheck + build clean.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-18 23:37:37 -04:00
1 parent e0e43d6127
commit f041336721
33 files changed
+143 -481

No files matched your search

+2
View File
@@ -1,6 +1,7 @@
import { AgentApiServer, type ApiServerEventCallbacks } from "./server-core";
import { registerAgentRoutes } from "./routes-agents";
import { registerAgentControlRoutes } from "./routes-agent-control";
import { registerAgentStandupRoute } from "./routes-agents-standup";
import { registerTaskRoutes } from "./routes-tasks";
import { registerWorkGraphRoutes } from "./routes-work-graph";
import { registerMessageRoutes } from "./routes-messages";
@@ -28,6 +29,7 @@ export function createApiServer(
const server = new AgentApiServer(db, onDomainChanged, onWakeAgent, callbacks, allowedOrigins);
registerAgentRoutes(server);
registerAgentControlRoutes(server);
registerAgentStandupRoute(server);
registerTaskRoutes(server);
registerWorkGraphRoutes(server);
registerMessageRoutes(server);
+3 -2
View File
@@ -8,7 +8,8 @@ import {
getHireRoutingValidationError,
resolveHireReportsTo,
} from "../agent-permissions";
import { resolveApprovalDecision, shouldAutoApproveApproval } from "../approval-policy";
import { resolveApprovalDecision } from "../approval-policy";
import { shouldAutoApprove } from "../execution-policy";
import { requireParam, type AgentApiServer } from "./server-core";
export function registerAgentRoutes(server: AgentApiServer) {
@@ -365,7 +366,7 @@ export function registerAgentRoutes(server: AgentApiServer) {
});
let autoApproved = false;
if (shouldAutoApproveApproval(db, companyId, "hire_agent")) {
if (shouldAutoApprove(db, companyId, "hire_agent")) {
resolveApprovalDecision({
db,
handleApprovalResolved: callbacks.onApprovalResolved,
+3 -2
View File
@@ -1,7 +1,8 @@
import { z } from "zod";
import log from "electron-log/main.js";
import { approvalInputSchema } from "@shared/contracts";
import { resolveApprovalDecision, shouldAutoApproveApproval } from "../approval-policy";
import { resolveApprovalDecision } from "../approval-policy";
import { shouldAutoApprove } from "../execution-policy";
import { requireParam, type AgentApiServer } from "./server-core";
const approvalListQuerySchema = z.object({
@@ -69,7 +70,7 @@ export function registerApprovalRoutes(server: AgentApiServer) {
// Notify thread integration about the new approval
callbacks.onApprovalCreated(id, companyId, input.relatedTaskId ?? null, input.type, input.payloadSummary ?? "");
if (shouldAutoApproveApproval(db, companyId, input.type)) {
if (shouldAutoApprove(db, companyId, input.type)) {
resolveApprovalDecision({
db,
handleApprovalResolved: callbacks.onApprovalResolved,
-9
View File
@@ -1,13 +1,4 @@
import type { AppDatabase } from "./database";
import { shouldAutoApprove } from "./execution-policy";
export function shouldAutoApproveApproval(
db: AppDatabase,
companyId: string,
approvalType: string,
): boolean {
return shouldAutoApprove(db, companyId, approvalType);
}
export function resolveApprovalDecision({
db,
-2
View File
@@ -148,5 +148,3 @@ export function createCheckpointEngine(deps: CheckpointDeps) {
},
};
}
export type CheckpointEngineService = ReturnType<typeof createCheckpointEngine>;
-2
View File
@@ -39,8 +39,6 @@ export type PiOAuthProviderId =
| "google-gemini-cli"
| "google-antigravity";
export type ConnectorAuthKind = "oauth" | "api_key" | "aws" | "azure" | "gcp_adc";
export interface ConnectorAuthOAuth {
type: "oauth";
oauthId: PiOAuthProviderId;
+4 -4
View File
@@ -82,10 +82,10 @@ export function createMigrationsV43V43(deps: MigrationDeps): Migration[] {
}
if (runsHasDeadCol) {
// v16-v25 added `context_snapshot_json`; it is unreferenced by
// production code today but the column exists on real databases
// and carries no policy against preservation. Keep it so this
// migration's blast radius stays exactly one column.
// `context_snapshot_json` rides through this rebuild untouched
// so the single-concern invariant holds: v43 drops only
// `session_display_id`. v44 is the one that drops the snapshot
// column.
db.exec(`
CREATE TABLE runs_new (
id TEXT PRIMARY KEY,
+83
View File
@@ -0,0 +1,83 @@
import log from "electron-log/main.js";
import type { Migration, MigrationDeps } from "./types";
import { columnExists, tableExists } from "./types";
/**
* Drop `runs.context_snapshot_json` — dead since its introduction.
*
* v16-v25 added the column with the intent to snapshot the turn context
* for post-run forensics. Nothing ever wrote to it, nothing ever read it,
* and the Pi Mono migration removed the last plausible use case (the
* turn snapshot now lives in `task_sessions` / `session_entries`).
*
* Same table-rebuild pattern as v43 — `runs_new` without the column,
* copy the real data, swap, reindex. Idempotent: if the column is
* already absent the migration is a no-op.
*/
export function createMigrationsV44V44(deps: MigrationDeps): Migration[] {
const { db } = deps;
return [
{
version: 44,
name: "drop_runs_context_snapshot_json",
fn: () => {
if (!tableExists(db, "runs") || !columnExists(db, "runs", "context_snapshot_json")) {
log.info("[migration v44] runs.context_snapshot_json already absent; skipping");
return;
}
db.exec("PRAGMA foreign_keys = OFF");
try {
db.exec(`
CREATE TABLE runs_new (
id TEXT PRIMARY KEY,
company_id TEXT NOT NULL,
task_id TEXT,
agent_id TEXT NOT NULL,
workspace_id TEXT,
connector_id TEXT NOT NULL,
status TEXT NOT NULL,
summary TEXT NOT NULL,
error_message TEXT,
exit_code INTEGER,
signal TEXT,
model TEXT,
cost_usd REAL,
unattributed_cost INTEGER NOT NULL DEFAULT 0,
actor_type TEXT,
actor_id TEXT,
started_at TEXT,
finished_at TEXT,
log_path TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
INSERT INTO runs_new (
id, company_id, task_id, agent_id, workspace_id, connector_id,
status, summary, error_message, exit_code, signal, model,
cost_usd, unattributed_cost, actor_type, actor_id,
started_at, finished_at, log_path, created_at, updated_at
)
SELECT
id, company_id, task_id, agent_id, workspace_id, connector_id,
status, summary, error_message, exit_code, signal, model,
cost_usd, unattributed_cost, actor_type, actor_id,
started_at, finished_at, log_path, created_at, updated_at
FROM runs;
DROP TABLE runs;
ALTER TABLE runs_new RENAME TO runs;
CREATE INDEX IF NOT EXISTS idx_runs_company ON runs(company_id);
CREATE INDEX IF NOT EXISTS idx_runs_task ON runs(task_id);
CREATE INDEX IF NOT EXISTS idx_runs_agent ON runs(agent_id);
CREATE INDEX IF NOT EXISTS idx_runs_company_status ON runs(company_id, status);
CREATE INDEX IF NOT EXISTS idx_runs_company_created_at ON runs(company_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_runs_agent_created_at ON runs(agent_id, created_at DESC);
`);
log.info("[migration v44] rebuilt runs without context_snapshot_json");
} finally {
db.exec("PRAGMA foreign_keys = ON");
}
},
},
];
}
+2
View File
@@ -8,6 +8,7 @@ import { createMigrationsV40V40 } from "./migrations/v40-v40";
import { createMigrationsV41V41 } from "./migrations/v41-v41";
import { createMigrationsV42V42 } from "./migrations/v42-v42";
import { createMigrationsV43V43 } from "./migrations/v43-v43";
import { createMigrationsV44V44 } from "./migrations/v44-v44";
import {
columnExists,
nowIso,
@@ -182,6 +183,7 @@ export function runMigrations(
...createMigrationsV41V41(deps),
...createMigrationsV42V42(deps),
...createMigrationsV43V43(deps),
...createMigrationsV44V44(deps),
];
for (const migration of migrations) {
+3 -2
View File
@@ -6,7 +6,8 @@ import {
workspaceInputSchema,
} from "@shared/contracts";
import { getConnectorExecutionReadinessIssue } from "@shared/connector-policy";
import { resolveApprovalDecision, shouldAutoApproveApproval } from "./approval-policy";
import { resolveApprovalDecision } from "./approval-policy";
import { shouldAutoApprove } from "./execution-policy";
import { getHireNameValidationError, getHireRoutingValidationError, resolveHireReportsTo } from "./agent-permissions";
import { validateConnectorModelSelection } from "./connector-model-policy";
import { fail, ok, type RegisterCoreHandlersDependencies } from "./ipc-core-common";
@@ -246,7 +247,7 @@ export function registerCoreAgentGraphHandlers({
});
let autoApproved = false;
if (shouldAutoApproveApproval(db, parsed.companyId, "hire_agent")) {
if (shouldAutoApprove(db, parsed.companyId, "hire_agent")) {
resolveApprovalDecision({
db,
handleApprovalResolved,
+3 -2
View File
@@ -10,7 +10,8 @@ import {
startRunSchema,
triggerHeartbeatSchema,
} from "@shared/contracts";
import { resolveApprovalDecision, shouldAutoApproveApproval } from "./approval-policy";
import { resolveApprovalDecision } from "./approval-policy";
import { shouldAutoApprove } from "./execution-policy";
import { fail, ok, type RegisterCoreHandlersDependencies } from "./ipc-core-common";
export function registerCoreOperationHandlers({
@@ -71,7 +72,7 @@ export function registerCoreOperationHandlers({
// Notify thread integration about the new approval
handleApprovalCreated(id, parsed.companyId, parsed.relatedTaskId ?? null, parsed.type, parsed.payloadSummary ?? "");
if (shouldAutoApproveApproval(db, parsed.companyId, parsed.type)) {
if (shouldAutoApprove(db, parsed.companyId, parsed.type)) {
resolveApprovalDecision({
db,
handleApprovalResolved,
-6
View File
@@ -6,12 +6,6 @@ import type { ProjectedSkill } from "../skill-types";
export type WorkspaceLockMode = "exclusive" | "shared" | "none";
export interface WorkspaceResolution {
workspace: ReturnType<AppDatabase["getWorkspace"]>;
lockMode: WorkspaceLockMode;
source: "task" | "agent" | "project" | "company_fallback" | "none";
}
export interface AutomationDispatch {
companyId: string;
taskId?: string;
+6 -23
View File
@@ -14,32 +14,15 @@ import sop013En from "./sops/sop-013-incident.en.md?raw";
import sop014En from "./sops/sop-014-hiring-pipeline.en.md?raw";
import sop015En from "./sops/sop-015-knowledge.en.md?raw";
export type SOPId =
| "sop-001" | "sop-002" | "sop-003" | "sop-004" | "sop-005"
| "sop-006" | "sop-007" | "sop-008" | "sop-009" | "sop-010"
| "sop-011" | "sop-012" | "sop-013" | "sop-014" | "sop-015";
const SOPS: Record<SOPId, string> = {
"sop-001": sop001En, "sop-002": sop002En, "sop-003": sop003En,
"sop-004": sop004En, "sop-005": sop005En, "sop-006": sop006En,
"sop-007": sop007En, "sop-008": sop008En, "sop-009": sop009En,
"sop-010": sop010En, "sop-011": sop011En, "sop-012": sop012En,
"sop-013": sop013En, "sop-014": sop014En, "sop-015": sop015En,
};
const SOPS_IN_ORDER: string[] = [
sop001En, sop002En, sop003En, sop004En, sop005En,
sop006En, sop007En, sop008En, sop009En, sop010En,
sop011En, sop012En, sop013En, sop014En, sop015En,
];
const SOP_HEADING = "# Standard Operating Procedures";
const SOP_IDS: SOPId[] = [
"sop-001", "sop-002", "sop-003", "sop-004", "sop-005",
"sop-006", "sop-007", "sop-008", "sop-009", "sop-010",
"sop-011", "sop-012", "sop-013", "sop-014", "sop-015",
];
export function getSOP(id: SOPId): string {
return SOPS[id].trimEnd();
}
export function buildSOPSection(): string {
const body = SOP_IDS.map((id) => SOPS[id].trimEnd()).join("\n\n");
const body = SOPS_IN_ORDER.map((text) => text.trimEnd()).join("\n\n");
return `${SOP_HEADING}\n\n${body}\n`;
}
@@ -132,17 +132,3 @@ export const WAKE_REASONS: Record<WakeReason, WakeReasonEntry> = {
],
},
};
export function renderWakeReason(
reason: string,
vars: WakeReasonVars,
): string {
const entry = WAKE_REASONS[reason as WakeReason];
if (!entry) {
return `Trigger: ${reason}\nAction required: Check your standup briefing and messages for context on why you were woken.`;
}
const steps = entry.steps(vars)
.map((step, i) => `${i + 1}. ${step}`)
.join("\n");
return `${entry.title}\nAction required:\n${steps}`;
}
-67
View File
@@ -1,67 +0,0 @@
import type log from "electron-log/main.js";
import type { AppDatabase } from "./database";
interface ResumeInterruptedTasksDeps {
db: AppDatabase;
logger: typeof log;
wakeAgentIfPossible: (agentId: string, companyId: string, trigger: string) => string | null;
publishDomainChanged: () => void;
}
/**
* Resume tasks that were left behind by a clean shutdown.
*
* The worker-service marks running/queued runs as "interrupted" with a fixed
* summary string on shutdown, but does not reset the owning task. This helper
* rolls those tasks back to "todo", releases their run locks, and re-wakes the
* assigned agents so heartbeat can pick them up again.
*/
export function resumeInterruptedTasks({
db,
logger,
wakeAgentIfPossible,
publishDomainChanged,
}: ResumeInterruptedTasksDeps): void {
try {
const snapshot = db.listSnapshot();
const shutdownRuns = snapshot.runs.filter(
(r) =>
r.status === "interrupted" &&
(r.summary === "Application shut down." || r.summary === "Worker process crashed."),
);
if (shutdownRuns.length === 0) return;
const resumedAgents = new Set<string>();
for (const run of shutdownRuns) {
try {
if (run.taskId) {
const task = db.getTask(run.taskId);
if (task.status === "in_progress" || task.status === "blocked") {
db.advanceTaskStatus(run.taskId, "todo", "Auto-resumed after app restart.");
db.releaseTaskLock(run.taskId, run.id);
}
}
if (!resumedAgents.has(run.agentId)) {
resumedAgents.add(run.agentId);
wakeAgentIfPossible(run.agentId, run.companyId, "app_restart_resume");
}
} catch (error) {
logger.warn(
`[bootstrap] Skipped shutdown recovery for run ${run.id} (task/agent may have been deleted):`,
error,
);
}
}
if (resumedAgents.size > 0) {
logger.info(
`[bootstrap] Resumed ${shutdownRuns.length} interrupted task(s) across ${resumedAgents.size} agent(s) after clean shutdown recovery.`,
);
publishDomainChanged();
}
} catch (err) {
logger.warn(
`[bootstrap] Shutdown recovery failed: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
-2
View File
@@ -136,5 +136,3 @@ export function createRunHousekeeping({ db, logger, threadIntegration }: RunHous
},
};
}
export type RunHousekeepingService = ReturnType<typeof createRunHousekeeping>;
@@ -1,67 +0,0 @@
/**
* Helpers for assembling orchestration-service dependencies in service-graph.ts.
* Extracted to keep service-graph.ts under the 400-line cap.
*/
import type { AppDatabase } from "./database";
import { createSkillService } from "./skill-service";
import type { createSessionManagementService } from "./session-management-service";
import type { ProjectedSkill } from "./skill-types";
interface Logger {
info: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
}
export function makeListRuntimeSkillEntries(deps: { db: AppDatabase; logger: Logger }) {
let cache: ReturnType<typeof createSkillService> | null = null;
return (companyId: string, agentId: string, connectorId: string): ProjectedSkill[] => {
try {
if (!cache) cache = createSkillService({ db: deps.db, profileDir: deps.db.profileDir, logger: deps.logger });
return cache.listRuntimeSkillEntries(companyId, agentId, connectorId);
} catch (error) {
deps.logger.warn(`[service-graph] listRuntimeSkillEntries failed for agent ${agentId}:`, error);
return [];
}
};
}
export function makeSessionHandoffLookup(
deps: { logger: Logger },
getSessionService: () => ReturnType<typeof createSessionManagementService>,
) {
return (agentId: string): string | null => {
try {
return getSessionService().getHandoffForAgent(agentId);
} catch (error) {
deps.logger.warn(`[service-graph] getSessionHandoff failed for agent ${agentId}:`, error);
return null;
}
};
}
export function makePreRunRotationEvaluator(
deps: { db: AppDatabase; logger: Logger },
getSessionService: () => ReturnType<typeof createSessionManagementService>,
) {
return (agentId: string, connectorId: string): { rotated: boolean; handoff: string | null; sessionId: string | null } => {
try {
const sessionSvc = getSessionService();
const agent = deps.db.getAgent(agentId);
if (!agent.sessionId) return { rotated: false, handoff: null, sessionId: null };
const session = deps.db.getSessionById(agent.sessionId);
if (!session) return { rotated: false, handoff: null, sessionId: agent.sessionId };
const result = sessionSvc.evaluateRotation(session, connectorId);
if (result.shouldRotate) {
const newSessionId = sessionSvc.rotateSession(session);
const handoff = sessionSvc.getHandoffForAgent(agentId);
return { rotated: true, handoff, sessionId: newSessionId };
}
return { rotated: false, handoff: null, sessionId: agent.sessionId };
} catch (error) {
deps.logger.warn(`[service-graph] evaluatePreRunSessionRotation failed for agent ${agentId}:`, error);
return { rotated: false, handoff: null, sessionId: null };
}
};
}
@@ -97,7 +97,7 @@ export function buildOrchestrationService({
if (!agent.sessionId) return { rotated: false, handoff: null, sessionId: null };
const session = db.getSessionById(agent.sessionId);
if (!session) return { rotated: false, handoff: null, sessionId: agent.sessionId };
const result = sessionService.evaluateRotation(session, connectorId);
const result = sessionService.evaluateRotation(session);
if (result.shouldRotate) {
const newSessionId = sessionService.rotateSession(session);
const handoff = sessionService.getHandoffForAgent(agentId);
+8 -19
View File
@@ -6,16 +6,13 @@ interface SessionManagementDependencies {
logger: { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void };
}
// D-01/D-02: Exported for testing and external override. Conservative defaults.
export const DEFAULT_THRESHOLDS = {
// Rotation thresholds. Any-of semantics — first trigger wins.
const ROTATION_THRESHOLDS = {
maxRuns: 20,
maxTokens: 500_000,
maxAgeHours: 72,
};
// D-01: Type for per-adapter or company-level threshold overrides
export type RotationThresholds = Partial<typeof DEFAULT_THRESHOLDS>;
// D-04: Handoff size cap to prevent prompt bloat.
// 2000 characters ≈ 500 tokens — enough to resume context without crowding
// out the system prompt + user message in an 8k-ish model window.
@@ -31,32 +28,24 @@ export function createSessionManagementService({ db, logger }: SessionManagement
db.incrementSessionUsage(sessionId, inputTokens, outputTokens, runId);
}
// D-01: Any-of thresholds with optional overrides. D-04: Check nativeContextManagement.
// Per D-01: "Configurable per-adapter with company-level overrides" -- accepts optional
// thresholds parameter that merges with defaults. Callers can pass per-adapter or
// company-level overrides; omitted fields fall back to DEFAULT_THRESHOLDS.
// Any-of thresholds — first trigger wins. Pi-mono delegates context
// management to pi-agent-core compaction hooks; AgentCompany applies its
// own rotation thresholds on top of that.
function evaluateRotation(
session: TaskSessionRecord,
_connectorId: string,
thresholdsOverride?: RotationThresholds,
): { shouldRotate: boolean; reason: string | null } {
// Pi-mono delegates context management to pi-agent-core compaction hooks;
// AgentCompany always applies its own rotation thresholds.
const thresholds = { ...DEFAULT_THRESHOLDS, ...thresholdsOverride };
// D-01: Any-of thresholds - first trigger wins
if (session.runCount >= thresholds.maxRuns) {
if (session.runCount >= ROTATION_THRESHOLDS.maxRuns) {
return { shouldRotate: true, reason: "max_runs" };
}
const totalTokens = session.totalInputTokens + session.totalOutputTokens;
if (totalTokens >= thresholds.maxTokens) {
if (totalTokens >= ROTATION_THRESHOLDS.maxTokens) {
return { shouldRotate: true, reason: "max_tokens" };
}
const ageMs = Date.now() - new Date(session.createdAt).getTime();
const ageHours = ageMs / (1000 * 60 * 60);
if (ageHours >= thresholds.maxAgeHours) {
if (ageHours >= ROTATION_THRESHOLDS.maxAgeHours) {
return { shouldRotate: true, reason: "max_age_hours" };
}
@@ -116,7 +116,7 @@ export function accumulateAndRotateSession(
event.outputTokens ?? 0,
event.runId,
);
const rotationResult = sessionService.evaluateRotation(session, run.connectorId);
const rotationResult = sessionService.evaluateRotation(session);
if (rotationResult.shouldRotate) {
// Re-read to get updated counts after accumulation.
const updatedSession = db.getSessionById(agentRecord.sessionId);
+4 -58
View File
@@ -1,13 +1,9 @@
/**
* Defensive token redactor for worker process logs.
* Pattern-based token redactor.
*
* Pi-mono's HTTP clients are not known to log credentials, but stack traces
* from upstream libraries (or future pi versions) could include header
* fragments containing `Authorization: Bearer ...` or `?access_token=...`.
* To prevent any such fragment from reaching the parent's stderr stream,
* `installLogRedactor()` wraps `console.error`, `console.warn`, `console.log`,
* `process.stdout.write`, and `process.stderr.write` so that every string
* argument is filtered through `redactTokens()` before transmission.
* Complements `buildRedactor` (env-value redaction) by scrubbing common
* credential shapes out of arbitrary strings before they reach the parent
* process — Bearer tokens, provider API keys, OAuth query params.
*/
const PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [
@@ -29,53 +25,3 @@ export function redactTokens(text: string): string {
}
return result;
}
function redactArg(arg: unknown): unknown {
if (typeof arg === "string") return redactTokens(arg);
if (arg instanceof Error) {
const redacted = new Error(redactTokens(arg.message));
redacted.name = arg.name;
if (arg.stack) redacted.stack = redactTokens(arg.stack);
return redacted;
}
return arg;
}
interface InstallTarget {
console: { log: typeof console.log; warn: typeof console.warn; error: typeof console.error; info: typeof console.info };
stdoutWrite: typeof process.stdout.write;
stderrWrite: typeof process.stderr.write;
}
export function installLogRedactor(target?: Partial<InstallTarget>): () => void {
const cons = target?.console ?? console;
const origLog = cons.log.bind(cons);
const origWarn = cons.warn.bind(cons);
const origError = cons.error.bind(cons);
const origInfo = cons.info.bind(cons);
cons.log = (...args: unknown[]) => origLog(...args.map(redactArg));
cons.warn = (...args: unknown[]) => origWarn(...args.map(redactArg));
cons.error = (...args: unknown[]) => origError(...args.map(redactArg));
cons.info = (...args: unknown[]) => origInfo(...args.map(redactArg));
const stdoutWriteOrig = (target?.stdoutWrite ?? process.stdout.write).bind(process.stdout);
const stderrWriteOrig = (target?.stderrWrite ?? process.stderr.write).bind(process.stderr);
process.stdout.write = ((chunk: unknown, ...rest: unknown[]) => {
if (typeof chunk === "string") return stdoutWriteOrig(redactTokens(chunk), ...rest as never[]);
return stdoutWriteOrig(chunk as never, ...rest as never[]);
}) as typeof process.stdout.write;
process.stderr.write = ((chunk: unknown, ...rest: unknown[]) => {
if (typeof chunk === "string") return stderrWriteOrig(redactTokens(chunk), ...rest as never[]);
return stderrWriteOrig(chunk as never, ...rest as never[]);
}) as typeof process.stderr.write;
return () => {
cons.log = origLog;
cons.warn = origWarn;
cons.error = origError;
cons.info = origInfo;
process.stdout.write = stdoutWriteOrig;
process.stderr.write = stderrWriteOrig;
};
}
@@ -1,4 +1,3 @@
import { CaretDown, CaretRight } from "../ui/icons";
import type { ProfileSnapshot } from "@shared/types";
import { Input } from "../ui";
import { useT } from "../../i18n";
@@ -17,11 +16,6 @@ export interface ModelEntry {
export type ConnectorStatus = ProfileSnapshot["connectors"][number]["status"];
export function parseEnvValue(text: string, key: string): string {
const line = text.split(/\r?\n/).find((l) => l.startsWith(`${key}=`));
return line ? line.slice(key.length + 1) : "";
}
/* ------------------------------------------------------------------ */
/* StatusBadge */
/* ------------------------------------------------------------------ */
@@ -68,45 +62,6 @@ export function StatusBadge({ status }: { status: ConnectorStatus }) {
);
}
/* ------------------------------------------------------------------ */
/* ProviderCard */
/* ------------------------------------------------------------------ */
export function ProviderCard({
title,
badge,
expanded,
onToggle,
children,
}: {
title: string;
badge: React.ReactNode;
expanded: boolean;
onToggle: () => void;
children: React.ReactNode;
}) {
return (
<div className="overflow-hidden rounded-lg border border-border bg-card">
<button
type="button"
onClick={onToggle}
className="flex w-full cursor-pointer items-center justify-between px-4 py-3 transition hover:bg-muted/50"
>
<div className="flex items-center gap-2.5">
{expanded ? (
<CaretDown size={14} className="text-muted-foreground" />
) : (
<CaretRight size={14} className="text-muted-foreground" />
)}
<span className="text-[13px] font-semibold text-foreground">{title}</span>
</div>
{badge}
</button>
{expanded && <div className="space-y-3 px-4 pb-4">{children}</div>}
</div>
);
}
/* ------------------------------------------------------------------ */
/* Toggle switch */
/* ------------------------------------------------------------------ */
@@ -1,20 +1,11 @@
import type { ApprovalRecord } from "@shared/types"
import {
Robot, CheckSquare, Play, ShieldCheck, Gear, FolderOpen, Key, Target, Briefcase, Buildings,
} from "../ui/icons"
import { safeJson } from "../../lib/safe-json"
export function describeActionError(error: unknown) {
return error instanceof Error ? error.message : "The action could not be completed."
}
export function parseApprovalPayloadJson(approval: ApprovalRecord): Record<string, unknown> | null {
if (!approval.payloadJson) return null
const parsed = safeJson<unknown>(approval.payloadJson, null)
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed as Record<string, unknown>
return null
}
export const ENTITY_ICONS: Record<string, typeof Robot> = {
agent: Robot, task: CheckSquare, run: Play, approval: ShieldCheck, connector: Gear,
workspace: FolderOpen, secret: Key, goal: Target, project: Briefcase, company: Buildings,
-4
View File
@@ -5,7 +5,3 @@ export function isMac(): boolean {
export function cmdOrCtrlLabel(): string {
return isMac() ? "Cmd" : "Ctrl";
}
export function cmdOrCtrlSymbol(): string {
return isMac() ? "⌘" : "Ctrl+";
}
-6
View File
@@ -286,9 +286,3 @@ export function findConnectorCatalogEntry(id: string): ConnectorCatalogEntry | u
export function listFeaturedOnboardingConnectors(): ConnectorCatalogEntry[] {
return CONNECTOR_CATALOG.filter((entry) => entry.onboardingFeatured === true);
}
export function listConnectorsByAuthKind(
kind: ConnectorAuthKind,
): ConnectorCatalogEntry[] {
return CONNECTOR_CATALOG.filter((entry) => entry.auth.type === kind);
}
@@ -17,5 +17,3 @@ export const agentEventPushSchema = z.object({
type: z.string(),
}).passthrough(),
});
export type AgentEventPush = z.infer<typeof agentEventPushSchema>;
@@ -81,7 +81,6 @@ export const credentialSummarySchema = z.object({
});
export type CredentialSummary = z.infer<typeof credentialSummarySchema>;
export type CredentialPayload = z.infer<typeof credentialPayloadSchema>;
export type SaveCredentialInput = z.infer<typeof saveCredentialInputSchema>;
export type AwsCredential = z.infer<typeof awsCredentialSchema>;
export type AzureCredential = z.infer<typeof azureCredentialSchema>;
-2
View File
@@ -17,6 +17,4 @@ export const IMPORTANT_DOC_TYPES = [
"sop",
] as const;
export type ImportantDocType = (typeof IMPORTANT_DOC_TYPES)[number];
export const IMPORTANT_DOC_TYPE_SET: ReadonlySet<string> = new Set(IMPORTANT_DOC_TYPES);
-23
View File
@@ -1,23 +0,0 @@
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.
* When locale is "en", the instruction tells the agent to respond entirely in English.
*/
export function getLocalePromptInstruction(locale: AppLocale): string {
if (locale === "zh") {
return [
"重要语言要求:你必须全程使用中文进行所有沟通。",
"这包括:任务描述、评论、消息、报告、文档、审批、通知 — 所有输出必须是中文。",
"即使系统提示和 API 文档是英文的,你的所有回复和创建的内容必须是中文。",
"不要使用英文回复,除非是代码、变量名、API 端点等技术术语。",
].join("\n");
}
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");
}
+5 -10
View File
@@ -446,25 +446,20 @@ describe("Process Recovery -- Context Preservation (D-02)", () => {
adapterType: "gemini-cli",
});
// Add token usage to simulate real work
db.incrementSessionUsage(sessionId, 5000, 2000, runId);
// Drive the session past the maxRuns=20 threshold so rotation fires.
for (let i = 0; i < 20; i++) {
db.incrementSessionUsage(sessionId, 1000, 500, `${runId}-${i}`);
}
// Create session management service to test rotation
const sessionService = createSessionManagementService({
db,
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
});
// Get the session and evaluate rotation with maxRuns:1 override
// (so it triggers rotation since we did 1 run)
const session = db.getSessionById(sessionId);
expect(session).not.toBeNull();
const rotationResult = sessionService.evaluateRotation(
session!,
"gemini-cli",
{ maxRuns: 1 },
);
const rotationResult = sessionService.evaluateRotation(session!);
expect(rotationResult.shouldRotate).toBe(true);
expect(rotationResult.reason).toBe("max_runs");
-1
View File
@@ -2,7 +2,6 @@ import { describe, expect, it } from "vitest";
import {
inferTaskType,
getConnectorStrengths,
ROLE_DEPARTMENT_HINTS as _ROLE_DEPARTMENT_HINTS,
} from "../../src/main/connector-matching";
describe("connector-matching", () => {
+9 -10
View File
@@ -13,7 +13,7 @@ vi.mock("electron", () => ({
}));
import { AppDatabase } from "../../src/main/database";
import { createSessionManagementService, DEFAULT_THRESHOLDS } from "../../src/main/session-management-service";
import { createSessionManagementService } from "../../src/main/session-management-service";
import type { ConnectorId } from "@shared/types";
// ---------------------------------------------------------------------------
@@ -121,17 +121,17 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
});
// Simulate runs until we reach the maxRuns threshold (20)
for (let i = 0; i < DEFAULT_THRESHOLDS.maxRuns; i++) {
for (let i = 0; i < 20; i++) {
db.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`);
}
// Get the updated session from DB
const session = db.getSessionById(sessionId);
expect(session).not.toBeNull();
expect(session!.runCount).toBe(DEFAULT_THRESHOLDS.maxRuns);
expect(session!.runCount).toBe(20); // matches maxRuns threshold in session-management-service
// Evaluate rotation -- should trigger
const result = sessionService.evaluateRotation(session!, "gemini-cli");
const result = sessionService.evaluateRotation(session!);
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_runs");
});
@@ -146,8 +146,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
adapterType: "gemini-cli",
});
// Accumulate tokens past the threshold in one big call
// DEFAULT_THRESHOLDS.maxTokens = 500_000 (total input + output)
// Accumulate tokens past the 500_000 maxTokens threshold in one big call
db.incrementSessionUsage(sessionId, 300000, 250000, "big-run");
const session = db.getSessionById(sessionId);
@@ -155,7 +154,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const totalTokens = session!.totalInputTokens + session!.totalOutputTokens;
expect(totalTokens).toBe(550000);
const result = sessionService.evaluateRotation(session!, "gemini-cli");
const result = sessionService.evaluateRotation(session!);
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_tokens");
});
@@ -171,7 +170,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
});
// Accumulate enough to trigger rotation
for (let i = 0; i < DEFAULT_THRESHOLDS.maxRuns; i++) {
for (let i = 0; i < 20; i++) {
db.incrementSessionUsage(sessionId, 1000, 500, `run-${i}`);
}
@@ -266,7 +265,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const session = db.getSessionById(sessionId)!;
// Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly.
const result = sessionService.evaluateRotation(session, "claude-pro");
const result = sessionService.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
expect(result.reason).not.toBeNull();
});
@@ -288,7 +287,7 @@ describe("Edge Case: Session Rotation During Execution (D-05.3)", () => {
const session = db.getSessionById(sessionId)!;
// Pi-mono delegates compaction to pi-agent-core; AgentCompany still applies rotation uniformly.
const result = sessionService.evaluateRotation(session, "openai-codex");
const result = sessionService.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
expect(result.reason).not.toBeNull();
});
+6 -85
View File
@@ -14,7 +14,7 @@ vi.mock("electron", () => ({
import { AppDatabase } from "../../src/main/database";
import { getConnectorDefinition } from "../../src/main/connectors";
import { createSessionManagementService, DEFAULT_THRESHOLDS } from "../../src/main/session-management-service";
import { createSessionManagementService } from "../../src/main/session-management-service";
import type { ConnectorId, TaskSessionRecord } from "@shared/types";
// ---------------------------------------------------------------------------
@@ -291,7 +291,7 @@ describe("session management service", () => {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const result = svc.evaluateRotation(session, "gemini-cli");
const result = svc.evaluateRotation(session);
expect(result.shouldRotate).toBe(false);
expect(result.reason).toBeNull();
});
@@ -314,7 +314,7 @@ describe("session management service", () => {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const result = svc.evaluateRotation(session, "gemini-cli");
const result = svc.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_runs");
});
@@ -337,7 +337,7 @@ describe("session management service", () => {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const result = svc.evaluateRotation(session, "gemini-cli");
const result = svc.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_tokens");
});
@@ -361,7 +361,7 @@ describe("session management service", () => {
createdAt: oldDate,
updatedAt: oldDate,
};
const result = svc.evaluateRotation(session, "gemini-cli");
const result = svc.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_age_hours");
});
@@ -389,91 +389,12 @@ describe("session management service", () => {
createdAt: new Date(Date.now() - 200 * 60 * 60 * 1000).toISOString(), // Way old
updatedAt: new Date().toISOString(),
};
const result = svc.evaluateRotation(session, "claude-pro");
const result = svc.evaluateRotation(session);
expect(result.shouldRotate).toBe(true);
expect(result.reason).not.toBeNull();
});
});
// -------------------------------------------------------------------------
// configurable thresholds (D-01)
// -------------------------------------------------------------------------
describe("configurable thresholds (D-01)", () => {
it("evaluateRotation with custom thresholds override { maxRuns: 5 } triggers at 5 runs", () => {
const svc = createSessionManagementService({ db, logger: mockLogger });
const session: TaskSessionRecord = {
id: "sess-1",
companyId: "co-1",
agentId: "agent-1",
adapterType: "gemini-cli",
lastRunId: null,
lastError: null,
runCount: 5,
totalInputTokens: 100,
totalOutputTokens: 100,
rotatedAt: null,
rotationReason: null,
handoffMarkdown: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const result = svc.evaluateRotation(session, "gemini-cli", { maxRuns: 5 });
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_runs");
});
it("partial override { maxTokens: 100000 } uses override for tokens but defaults for runs and age", () => {
const svc = createSessionManagementService({ db, logger: mockLogger });
const session: TaskSessionRecord = {
id: "sess-1",
companyId: "co-1",
agentId: "agent-1",
adapterType: "gemini-cli",
lastRunId: null,
lastError: null,
runCount: 10, // Below default 20
totalInputTokens: 60000,
totalOutputTokens: 40000, // Total 100000 = meets override threshold
rotatedAt: null,
rotationReason: null,
handoffMarkdown: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const result = svc.evaluateRotation(session, "gemini-cli", { maxTokens: 100000 });
expect(result.shouldRotate).toBe(true);
expect(result.reason).toBe("max_tokens");
});
it("omitting override uses DEFAULT_THRESHOLDS", () => {
const svc = createSessionManagementService({ db, logger: mockLogger });
const session: TaskSessionRecord = {
id: "sess-1",
companyId: "co-1",
agentId: "agent-1",
adapterType: "gemini-cli",
lastRunId: null,
lastError: null,
runCount: 19, // Just below default 20
totalInputTokens: 200000,
totalOutputTokens: 200000, // Total 400000, below default 500000
rotatedAt: null,
rotationReason: null,
handoffMarkdown: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const result = svc.evaluateRotation(session, "gemini-cli");
expect(result.shouldRotate).toBe(false);
expect(result.reason).toBeNull();
// Verify DEFAULT_THRESHOLDS values
expect(DEFAULT_THRESHOLDS.maxRuns).toBe(20);
expect(DEFAULT_THRESHOLDS.maxTokens).toBe(500_000);
expect(DEFAULT_THRESHOLDS.maxAgeHours).toBe(72);
});
});
// -------------------------------------------------------------------------
// token accumulation
// -------------------------------------------------------------------------