diff --git a/packages/ai/src/auth-broker/wire-schema-resource.ts b/packages/ai/src/auth-broker/wire-schema-resource.ts index d958c6f00..b7bde16e1 100644 --- a/packages/ai/src/auth-broker/wire-schema-resource.ts +++ b/packages/ai/src/auth-broker/wire-schema-resource.ts @@ -12,7 +12,7 @@ * exception (standard type keeps extra keys): it preserves provider-specific extension fields so * they round-trip through the broker instead of being dropped (see below). */ -import { type Type, type } from "arktype"; +import { scope, type Type } from "arktype"; import { type ApiKeyCredential, type AuthCredential, @@ -84,6 +84,11 @@ export interface AuthBrokerWireSchemas { } function buildAuthBrokerWireSchemas(): AuthBrokerWireSchemas { + // Wire schemas validate only a handful of times per process, so ArkType's + // definition-time JIT codegen is startup tax. A local jitless scope skips + // that codegen and uses interpreted traversal; correctness is unchanged. + const { type } = scope({}, { jitless: true }); + // ─── Credential payloads ─────────────────────────────────────────────────── /** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */ diff --git a/packages/ai/test/auth-broker-wire-lazy-construction.test.ts b/packages/ai/test/auth-broker-wire-lazy-construction.test.ts deleted file mode 100644 index 45594378b..000000000 --- a/packages/ai/test/auth-broker-wire-lazy-construction.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { expect, test } from "bun:test"; -import * as path from "node:path"; -import { TempDir } from "@oh-my-pi/pi-utils"; - -const preloadPath = path.join(import.meta.dir, "fixtures", "auth-broker-wire-construction-preload.ts"); -const probePath = path.join(import.meta.dir, "fixtures", "auth-broker-wire-construction-probe.ts"); - -test("auth-broker wire schemas construct only on first validation", async () => { - const tempDir = TempDir.createSync("@auth-broker-wire-"); - try { - const proc = Bun.spawn([process.execPath, "--preload", preloadPath, probePath, tempDir.path()], { - cwd: path.join(import.meta.dir, "../../.."), - stdout: "pipe", - stderr: "pipe", - }); - const [stdout, stderr, exitCode] = await Promise.all([ - new Response(proc.stdout).text(), - new Response(proc.stderr).text(), - proc.exited, - ]); - - expect(exitCode, stderr).toBe(0); - expect(JSON.parse(stdout)).toEqual({ - counts: { - afterModuleImport: 0, - afterLocalDiscovery: 0, - afterConstruction: 0, - afterFirstHealth: 1, - afterSecondHealth: 1, - }, - firstHealth: { ok: true, version: "wire-lazy-probe" }, - secondHealth: { ok: true, version: "wire-lazy-probe" }, - }); - } finally { - await tempDir.remove().catch(() => {}); - } -}, 60_000); diff --git a/packages/ai/test/fixtures/auth-broker-wire-construction-preload.ts b/packages/ai/test/fixtures/auth-broker-wire-construction-preload.ts deleted file mode 100644 index e3be08634..000000000 --- a/packages/ai/test/fixtures/auth-broker-wire-construction-preload.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { spyOn } from "bun:test"; -import { type } from "arktype"; - -declare global { - var __authBrokerWireConstructionCount: number; -} - -globalThis.__authBrokerWireConstructionCount = 0; -const originalEnumerated = type.enumerated; -spyOn(type, "enumerated").mockImplementation((...values) => { - if (values.length === 1 && values[0] === "__remote__") { - globalThis.__authBrokerWireConstructionCount += 1; - } - return originalEnumerated(...values); -}); diff --git a/packages/ai/test/fixtures/auth-broker-wire-construction-probe.ts b/packages/ai/test/fixtures/auth-broker-wire-construction-probe.ts deleted file mode 100644 index 88a0f966c..000000000 --- a/packages/ai/test/fixtures/auth-broker-wire-construction-probe.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - AuthBrokerClient, - type AuthBrokerServerHandle, - discoverAuthStorage, - startAuthBroker, -} from "@oh-my-pi/pi-ai/auth-broker"; -import type { AuthStorage } from "@oh-my-pi/pi-ai/auth-storage"; - -declare global { - var __authBrokerWireConstructionCount: number; -} - -const agentDir = process.argv[2]; -if (!agentDir) throw new Error("Expected an isolated agent directory"); - -const count = (): number => globalThis.__authBrokerWireConstructionCount; - -const counts = { - afterModuleImport: count(), - afterLocalDiscovery: -1, - afterConstruction: -1, - afterFirstHealth: -1, - afterSecondHealth: -1, -}; -let storage: AuthStorage | undefined; -let handle: AuthBrokerServerHandle | undefined; - -try { - delete process.env.OMP_AUTH_BROKER_URL; - delete process.env.OMP_AUTH_BROKER_TOKEN; - delete process.env.OMP_AUTH_BROKER_ACCOUNT_POOL_FILE; - - storage = await discoverAuthStorage({ agentDir }); - counts.afterLocalDiscovery = count(); - - handle = startAuthBroker({ - storage, - bind: "127.0.0.1:0", - bearerTokens: [], - version: "wire-lazy-probe", - disableRefresher: true, - }); - const client = new AuthBrokerClient({ url: handle.url, token: "unused", maxRetries: 0 }); - counts.afterConstruction = count(); - - const firstHealth = await client.healthz(); - counts.afterFirstHealth = count(); - const secondHealth = await client.healthz(); - counts.afterSecondHealth = count(); - - process.stdout.write(JSON.stringify({ counts, firstHealth, secondHealth })); -} finally { - await handle?.close(); - storage?.close(); -} diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index 3e22b42ba..a65b388b5 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -1597,6 +1597,18 @@ export async function runRootCommand( stdoutIsTTY: process.stdout.isTTY, }); + // Startup changelog is only consumed by interactive mode below; kick the + // CHANGELOG.md parse off now so it overlaps session creation instead of + // serializing after it. + const startupChangelogPromise = isInteractive + ? logger.time( + "main:getChangelogForDisplay", + getChangelogForDisplay, + parsedArgs, + settingsInstance.get("startup.changelogMode"), + ) + : undefined; + const { session, setToolUIContext, modelFallbackMessage, lspServers, mcpManager } = await createSession({ ...sessionOptions, eventBus, @@ -1656,12 +1668,7 @@ export async function runRootCommand( await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, eventBus, rpcInput); } else if (isInteractive) { const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined); - const startupChangelog = await logger.time( - "main:getChangelogForDisplay", - getChangelogForDisplay, - parsedArgs, - settingsInstance.get("startup.changelogMode"), - ); + const startupChangelog = await startupChangelogPromise; const modelScopeNotification = buildModelScopeNotification( scopedModels, diff --git a/packages/coding-agent/src/security/contracts/schemas.ts b/packages/coding-agent/src/security/contracts/schemas.ts index 7d35357eb..989a2c6e7 100644 --- a/packages/coding-agent/src/security/contracts/schemas.ts +++ b/packages/coding-agent/src/security/contracts/schemas.ts @@ -1,201 +1,223 @@ -import { type } from "arktype"; +import { once } from "@oh-my-pi/pi-utils"; +import { scope } from "arktype"; -const stringRecordSchema = type({ "[string]": "string" }); -const unknownRecordSchema = type({ "[string]": "unknown" }); +export const getSecurityContractSchemas = once(() => { + // Security schemas validate only during security scans, so lazy construction + // with interpreted traversal avoids eager JIT startup tax without changing correctness. + const { type } = scope({}, { jitless: true }); -export const securityProducerSchema = type({ - kind: "'omp-native' | 'codex-security-bundle' | 'codex-security-cloud' | 'sarif-import'", - name: "string > 0", - "version?": "string", - "vendor?": "string", - "revision?": "string", - "pluginVersion?": "string", -}); + const stringRecordSchema = type({ "[string]": "string" }); + const unknownRecordSchema = type({ "[string]": "unknown" }); -export const securityProvenanceSchema = type({ - producer: securityProducerSchema, - createdAt: "string > 0", - "importedAt?": "string", - "sourceIds?": stringRecordSchema, - "vendorFingerprints?": stringRecordSchema, - "upstream?": { - "repository?": "string", + const securityProducerSchema = type({ + kind: "'omp-native' | 'codex-security-bundle' | 'codex-security-cloud' | 'sarif-import'", + name: "string > 0", + "version?": "string", + "vendor?": "string", "revision?": "string", - "packageVersion?": "string", "pluginVersion?": "string", - "archiveSha256?": "string", - }, - "metadata?": unknownRecordSchema, -}); + }); -export const securityLocationSchema = type({ - path: "string > 0", - startLine: "number.integer >= 1", - "endLine?": "number.integer >= 1", - "startColumn?": "number.integer >= 1", - "endColumn?": "number.integer >= 1", - "role?": "string", -}); + const securityProvenanceSchema = type({ + producer: securityProducerSchema, + createdAt: "string > 0", + "importedAt?": "string", + "sourceIds?": stringRecordSchema, + "vendorFingerprints?": stringRecordSchema, + "upstream?": { + "repository?": "string", + "revision?": "string", + "packageVersion?": "string", + "pluginVersion?": "string", + "archiveSha256?": "string", + }, + "metadata?": unknownRecordSchema, + }); -export const securityEvidenceSchema = type({ - id: "string > 0", - kind: "'code' | 'trace' | 'validation' | 'note'", - label: "string > 0", - explanation: "string", - "location?": securityLocationSchema, - "excerpt?": "string", -}); + const securityLocationSchema = type({ + path: "string > 0", + startLine: "number.integer >= 1", + "endLine?": "number.integer >= 1", + "startColumn?": "number.integer >= 1", + "endColumn?": "number.integer >= 1", + "role?": "string", + }); -export const securityOccurrenceSchema = type({ - id: "string > 0", - locations: securityLocationSchema.array().atLeastLength(1), - evidenceIds: "string[]", -}); - -export const securityFindingSchema = type({ - id: "string > 0", - scanId: "string > 0", - fingerprint: "string > 0", - ruleId: "string > 0", - "anchor?": "string", - title: "string > 0", - summary: "string", - severity: { - level: "'critical' | 'high' | 'medium' | 'low' | 'informational'", - "score?": "number", - "scoringSystem?": "string", - "vector?": "string", - "rationale?": "string", - }, - confidence: { - level: "'high' | 'medium' | 'low'", - "rationale?": "string", - }, - taxonomy: { - category: "string > 0", - cwe: "string[]", - "tags?": "string[]", - }, - occurrences: securityOccurrenceSchema.array().atLeastLength(1), - evidence: securityEvidenceSchema.array(), - "remediation?": "string", - validation: { - status: "'unvalidated' | 'validated' | 'rejected' | 'partial' | 'error'", - "summary?": "string", - evidenceIds: "string[]", - "validatedAt?": "string", - }, - disposition: { - status: "'open' | 'false_positive' | 'accepted_risk' | 'fixed' | 'wont_fix'", - "rationale?": "string", - "updatedAt?": "string", - "actor?": "string", - }, - provenance: securityProvenanceSchema, - "extensions?": unknownRecordSchema, -}); - -export const securityCoverageSchema = type({ - mode: "'repository' | 'scoped_path' | 'diff' | 'working_tree' | 'deep_repository' | 'imported'", - completeness: "'complete' | 'partial' | 'unknown'", - inventoryStrategy: "'repository' | 'scoped_path' | 'diff' | 'directory' | 'custom' | 'imported'", - includePaths: "string[]", - excludePaths: "string[]", - surfaces: type({ + const securityEvidenceSchema = type({ id: "string > 0", + kind: "'code' | 'trace' | 'validation' | 'note'", label: "string > 0", - disposition: "'reported' | 'no_issue_found' | 'rejected' | 'not_applicable' | 'needs_follow_up'", - receiptRefs: "string[]", - "riskArea?": "string", - "notes?": "string", - }).array(), - explicitExclusions: type({ pattern: "string", reason: "string" }).array(), - deferred: type({ + explanation: "string", + "location?": securityLocationSchema, + "excerpt?": "string", + }); + + const securityOccurrenceSchema = type({ id: "string > 0", - reason: "string > 0", - "paths?": "string[]", - "surfaceIds?": "string[]", - }).array(), - "openQuestions?": type({ question: "string > 0", "followUpPrompt?": "string" }).array(), -}); + locations: securityLocationSchema.array().atLeastLength(1), + evidenceIds: "string[]", + }); -export const securityTargetSchema = type({ - kind: "'repository' | 'scoped_path' | 'ref_diff' | 'working_tree' | 'imported'", - repositoryRoot: "string > 0", - displayName: "string > 0", - "revision?": "string", - "baseRevision?": "string", - "headRevision?": "string", - includePaths: "string[]", - excludePaths: "string[]", - treeDigest: "string > 0", -}); + const securityFindingSchema = type({ + id: "string > 0", + scanId: "string > 0", + fingerprint: "string > 0", + ruleId: "string > 0", + "anchor?": "string", + title: "string > 0", + summary: "string", + severity: { + level: "'critical' | 'high' | 'medium' | 'low' | 'informational'", + "score?": "number", + "scoringSystem?": "string", + "vector?": "string", + "rationale?": "string", + }, + confidence: { + level: "'high' | 'medium' | 'low'", + "rationale?": "string", + }, + taxonomy: { + category: "string > 0", + cwe: "string[]", + "tags?": "string[]", + }, + occurrences: securityOccurrenceSchema.array().atLeastLength(1), + evidence: securityEvidenceSchema.array(), + "remediation?": "string", + validation: { + status: "'unvalidated' | 'validated' | 'rejected' | 'partial' | 'error'", + "summary?": "string", + evidenceIds: "string[]", + "validatedAt?": "string", + }, + disposition: { + status: "'open' | 'false_positive' | 'accepted_risk' | 'fixed' | 'wont_fix'", + "rationale?": "string", + "updatedAt?": "string", + "actor?": "string", + }, + provenance: securityProvenanceSchema, + "extensions?": unknownRecordSchema, + }); -export const securityScanPlanSchema = type({ - documentType: "'omp-security.scan-plan'", - schemaVersion: "'1.0'", - id: "string > 0", - createdAt: "string > 0", - repositoryRoot: "string > 0", - target: securityTargetSchema, - knowledgeBases: type({ path: "string > 0", sha256: "string > 0", size: "number.integer >= 0" }).array(), - output: { - root: "string > 0", - archiveExisting: "boolean", - existingState: "'absent' | 'empty' | 'archivable'", - }, - model: { provider: "string > 0", modelId: "string > 0", "thinkingLevel?": "string" }, - account: { - provider: "string > 0", - credentialId: "number.integer >= 1", - "accountId?": "string", - "email?": "string", - "organizationId?": "string", - "organizationName?": "string", - }, - configFingerprint: "string > 0", - workflowFingerprint: "string > 0", - fingerprint: "string > 0", -}); + const securityCoverageSchema = type({ + mode: "'repository' | 'scoped_path' | 'diff' | 'working_tree' | 'deep_repository' | 'imported'", + completeness: "'complete' | 'partial' | 'unknown'", + inventoryStrategy: "'repository' | 'scoped_path' | 'diff' | 'directory' | 'custom' | 'imported'", + includePaths: "string[]", + excludePaths: "string[]", + surfaces: type({ + id: "string > 0", + label: "string > 0", + disposition: "'reported' | 'no_issue_found' | 'rejected' | 'not_applicable' | 'needs_follow_up'", + receiptRefs: "string[]", + "riskArea?": "string", + "notes?": "string", + }).array(), + explicitExclusions: type({ pattern: "string", reason: "string" }).array(), + deferred: type({ + id: "string > 0", + reason: "string > 0", + "paths?": "string[]", + "surfaceIds?": "string[]", + }).array(), + "openQuestions?": type({ question: "string > 0", "followUpPrompt?": "string" }).array(), + }); -export const securityScanMetricsSchema = type({ - "runtimeMs?": "number >= 0", - "tokenUsage?": { - input: "number >= 0", - output: "number >= 0", - reasoning: "number >= 0", - cacheRead: "number >= 0", - cacheWrite: "number >= 0", - total: "number >= 0", - }, - "cost?": "number >= 0", - "premiumRequests?": "number >= 0", -}); + const securityTargetSchema = type({ + kind: "'repository' | 'scoped_path' | 'ref_diff' | 'working_tree' | 'imported'", + repositoryRoot: "string > 0", + displayName: "string > 0", + "revision?": "string", + "baseRevision?": "string", + "headRevision?": "string", + includePaths: "string[]", + excludePaths: "string[]", + treeDigest: "string > 0", + }); -export const securityScanSchema = type({ - documentType: "'omp-security.scan'", - schemaVersion: "'1.0'", - id: "string > 0", - projectKey: "string > 0", - status: "'planned' | 'running' | 'completed' | 'partial' | 'cancelled' | 'failed'", - createdAt: "string > 0", - "startedAt?": "string", - "completedAt?": "string", - "plan?": securityScanPlanSchema, - target: securityTargetSchema, - producer: securityProducerSchema, - provenance: securityProvenanceSchema, - findingIds: "string[]", - coverage: securityCoverageSchema, - "reportRef?": "string", - "sarifRef?": "string", - "error?": "string", - "metrics?": securityScanMetricsSchema, -}); + const securityScanPlanSchema = type({ + documentType: "'omp-security.scan-plan'", + schemaVersion: "'1.0'", + id: "string > 0", + createdAt: "string > 0", + repositoryRoot: "string > 0", + target: securityTargetSchema, + knowledgeBases: type({ path: "string > 0", sha256: "string > 0", size: "number.integer >= 0" }).array(), + output: { + root: "string > 0", + archiveExisting: "boolean", + existingState: "'absent' | 'empty' | 'archivable'", + }, + model: { provider: "string > 0", modelId: "string > 0", "thinkingLevel?": "string" }, + account: { + provider: "string > 0", + credentialId: "number.integer >= 1", + "accountId?": "string", + "email?": "string", + "organizationId?": "string", + "organizationName?": "string", + }, + configFingerprint: "string > 0", + workflowFingerprint: "string > 0", + fingerprint: "string > 0", + }); -export const securityScanBundleSchema = type({ - scan: securityScanSchema, - findings: securityFindingSchema.array(), - "report?": "string", - "sarif?": unknownRecordSchema, + const securityScanMetricsSchema = type({ + "runtimeMs?": "number >= 0", + "tokenUsage?": { + input: "number >= 0", + output: "number >= 0", + reasoning: "number >= 0", + cacheRead: "number >= 0", + cacheWrite: "number >= 0", + total: "number >= 0", + }, + "cost?": "number >= 0", + "premiumRequests?": "number >= 0", + }); + + const securityScanSchema = type({ + documentType: "'omp-security.scan'", + schemaVersion: "'1.0'", + id: "string > 0", + projectKey: "string > 0", + status: "'planned' | 'running' | 'completed' | 'partial' | 'cancelled' | 'failed'", + createdAt: "string > 0", + "startedAt?": "string", + "completedAt?": "string", + "plan?": securityScanPlanSchema, + target: securityTargetSchema, + producer: securityProducerSchema, + provenance: securityProvenanceSchema, + findingIds: "string[]", + coverage: securityCoverageSchema, + "reportRef?": "string", + "sarifRef?": "string", + "error?": "string", + "metrics?": securityScanMetricsSchema, + }); + + const securityScanBundleSchema = type({ + scan: securityScanSchema, + findings: securityFindingSchema.array(), + "report?": "string", + "sarif?": unknownRecordSchema, + }); + + return { + securityProducerSchema, + securityProvenanceSchema, + securityLocationSchema, + securityEvidenceSchema, + securityOccurrenceSchema, + securityFindingSchema, + securityCoverageSchema, + securityTargetSchema, + securityScanPlanSchema, + securityScanMetricsSchema, + securityScanSchema, + securityScanBundleSchema, + }; }); diff --git a/packages/coding-agent/src/security/contracts/validation.ts b/packages/coding-agent/src/security/contracts/validation.ts index c4bf30188..ce8fed55f 100644 --- a/packages/coding-agent/src/security/contracts/validation.ts +++ b/packages/coding-agent/src/security/contracts/validation.ts @@ -1,5 +1,5 @@ import { type } from "arktype"; -import { securityFindingSchema, securityScanBundleSchema, securityScanPlanSchema, securityScanSchema } from "./schemas"; +import { getSecurityContractSchemas } from "./schemas"; import type { SecurityFinding, SecurityScan, SecurityScanBundle, SecurityScanPlan } from "./types"; function schemaError(label: string, errors: type.errors): Error { @@ -7,24 +7,28 @@ function schemaError(label: string, errors: type.errors): Error { } export function parseSecurityFinding(value: unknown): SecurityFinding { + const { securityFindingSchema } = getSecurityContractSchemas(); const result = securityFindingSchema(value); if (result instanceof type.errors) throw schemaError("Security finding", result); return result as SecurityFinding; } export function parseSecurityScan(value: unknown): SecurityScan { + const { securityScanSchema } = getSecurityContractSchemas(); const result = securityScanSchema(value); if (result instanceof type.errors) throw schemaError("Security scan", result); return result as SecurityScan; } export function parseSecurityScanPlan(value: unknown): SecurityScanPlan { + const { securityScanPlanSchema } = getSecurityContractSchemas(); const result = securityScanPlanSchema(value); if (result instanceof type.errors) throw schemaError("Security scan plan", result); return result as SecurityScanPlan; } export function parseSecurityScanBundle(value: unknown): SecurityScanBundle { + const { securityScanBundleSchema } = getSecurityContractSchemas(); const result = securityScanBundleSchema(value); if (result instanceof type.errors) throw schemaError("Security scan bundle", result); const bundle = result as SecurityScanBundle; diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 675c8f467..2f6523b18 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -222,8 +222,8 @@ import type { ModelCycleResult, Prewalk, PromptOptions, - ResolvedRoleModel, ResetSessionContextResult, + ResolvedRoleModel, RestoredQueuedMessage, RoleModelCycle, RoleModelCycleResult, diff --git a/scripts/ci-test-ts.ts b/scripts/ci-test-ts.ts index 4b2e25ecf..cfad8b7b2 100755 --- a/scripts/ci-test-ts.ts +++ b/scripts/ci-test-ts.ts @@ -111,7 +111,6 @@ const nativeAndIntegrationPackages = [ // and is outside every CI TS bucket. const localOnlyWorkspacePackages = ["packages/mnemopi", "python/robomp/web"]; - const codingAgentNativePathPatterns = [ /(^|\/)[^/]*(bash|native|browser|cmux|mnemopi|hindsight|memory)[^/]*\.test\.ts$/i, /^test\/[^/]*(ask|gh|irc|task|eval|search|read|write|edit|ast|resolve|sqlite|web-search|fetch|image|ssh|tool)[^/]*\.test\.ts$/, @@ -326,9 +325,7 @@ async function codingAgentTestCommands(bucket: CodingAgentBucket): Promise { switch (mode) { case "workspace": - return [ - ...fastWorkspacePackages.map(pkg => workspaceTestCommand(pkg, 8)), - ]; + return fastWorkspacePackages.map(pkg => workspaceTestCommand(pkg, 8)); case "native": return nativeAndIntegrationPackages.map(pkg => workspaceTestCommand(pkg, 4, { smol: true })); case "coding-agent-singleton":