Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions packages/agent-bundle/src/adapters/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ import {
type TargetArtifactPlan,
} from './types.ts';
import { withInstallSurface } from '../install/surface.ts';
import { deepFreeze } from '../core/freeze.ts';


/**
* One Claude Code plugin LSP server. The binary is never vendored: Claude
Expand Down Expand Up @@ -146,21 +148,21 @@ const metadata = Object.freeze({
});
const evidence = capabilityEvidence(claudeName, metadata);

const artifactValidation = Object.freeze({
documents: Object.freeze([
const artifactValidation = deepFreeze({
documents: [
Object.freeze({ path: 'hooks/hooks.json', required: false, schema: 'hooks' }),
Object.freeze({ path: claudeArtifactPaths.lsp, required: false, schema: 'lsp' }),
Object.freeze({ path: '.claude-plugin/marketplace.json', required: false, schema: 'marketplace' }),
Object.freeze({ path: '.mcp.json', required: false, schema: 'mcp' }),
Object.freeze({ path: '.claude-plugin/plugin.json', required: true, schema: 'plugin' }),
]),
schemas: Object.freeze([
],
schemas: [
Object.freeze({ name: 'hooks', validate: validateJsonSchemaDocument(validateHooks) }),
Object.freeze({ name: 'lsp', validate: validateJsonSchemaDocument(validateLsp) }),
Object.freeze({ name: 'marketplace', validate: validateJsonSchemaDocument(validateMarketplace) }),
Object.freeze({ name: 'mcp', validate: validateModernMcpDocument(validateJsonSchemaDocument(validateMcp)) }),
Object.freeze({ name: 'plugin', validate: validateJsonSchemaDocument(validatePlugin) }),
]),
],
});

const mcpRuntime = createTargetMcpRuntime({
Expand Down Expand Up @@ -364,9 +366,9 @@ interface ClaudeLspPlan {
readonly sourceInputs: readonly string[];
}

const noLspPlan: ClaudeLspPlan = Object.freeze({
diagnostics: Object.freeze([]),
sourceInputs: Object.freeze([]),
const noLspPlan: ClaudeLspPlan = deepFreeze({
diagnostics: [],
sourceInputs: [],
});

/**
Expand Down
12 changes: 7 additions & 5 deletions packages/agent-bundle/src/adapters/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import {
type TargetArtifactPlan,
} from './types.ts';
import { withInstallSurface } from '../install/surface.ts';
import { deepFreeze } from '../core/freeze.ts';


export interface CodexConfigExtension {
codex?: AgentBundleHostConfig;
Expand Down Expand Up @@ -128,19 +130,19 @@ const metadata = Object.freeze({
});
const evidence = capabilityEvidence(codexName, metadata);

const artifactValidation = Object.freeze({
documents: Object.freeze([
const artifactValidation = deepFreeze({
documents: [
Object.freeze({ path: 'hooks/hooks.json', required: false, schema: 'hooks' }),
Object.freeze({ path: '.agents/plugins/marketplace.json', required: false, schema: 'marketplace' }),
Object.freeze({ path: '.mcp.json', required: false, schema: 'mcp' }),
Object.freeze({ path: '.codex-plugin/plugin.json', required: true, schema: 'plugin' }),
]),
schemas: Object.freeze([
],
schemas: [
Object.freeze({ name: 'hooks', validate: validateJsonSchemaDocument(validateHooks) }),
Object.freeze({ name: 'marketplace', validate: validateJsonSchemaDocument(validateMarketplace) }),
Object.freeze({ name: 'mcp', validate: validateJsonSchemaDocument(validateMcp) }),
Object.freeze({ name: 'plugin', validate: validateJsonSchemaDocument(validatePlugin) }),
]),
],
});

const mcpRuntime = createTargetMcpRuntime({
Expand Down
44 changes: 23 additions & 21 deletions packages/agent-bundle/src/adapters/hook-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type {
NormalizedNativeHook,
NormalizedPlugin,
} from '../core/types.ts';
import { deepFreeze } from '../core/freeze.ts';


export interface TargetHookWrapper {
readonly event: CanonicalHookEvent;
Expand Down Expand Up @@ -172,26 +174,26 @@ export const readCursorNativeHookCommands = (document: unknown): TargetNativeHoo
return Object.freeze({ commands: Object.freeze(commands), status: 'found' });
};

const nativeHookInputFields = Object.freeze([
Object.freeze({ canonical: 'agentId', native: 'agent_id' }),
Object.freeze({ canonical: 'agentTranscriptPath', native: 'agent_transcript_path' }),
Object.freeze({ canonical: 'agentType', native: 'agent_type' }),
Object.freeze({ canonical: 'cwd', native: 'cwd' }),
Object.freeze({ canonical: 'effort', native: 'effort' }),
Object.freeze({ canonical: 'hookEventName', native: 'hook_event_name' }),
Object.freeze({ canonical: 'lastAssistantMessage', native: 'last_assistant_message' }),
Object.freeze({ canonical: 'model', native: 'model' }),
Object.freeze({ canonical: 'permissionMode', native: 'permission_mode' }),
Object.freeze({ canonical: 'promptId', native: 'prompt_id' }),
Object.freeze({ canonical: 'sessionId', native: 'session_id' }),
Object.freeze({ canonical: 'source', native: 'source' }),
Object.freeze({ canonical: 'stopHookActive', native: 'stop_hook_active' }),
Object.freeze({ canonical: 'toolInput', native: 'tool_input' }),
Object.freeze({ canonical: 'toolName', native: 'tool_name' }),
Object.freeze({ canonical: 'toolResponse', native: 'tool_response' }),
Object.freeze({ canonical: 'toolUseId', native: 'tool_use_id' }),
Object.freeze({ canonical: 'transcriptPath', native: 'transcript_path' }),
Object.freeze({ canonical: 'turnId', native: 'turn_id' }),
const nativeHookInputFields = deepFreeze([
{ canonical: 'agentId', native: 'agent_id' },
{ canonical: 'agentTranscriptPath', native: 'agent_transcript_path' },
{ canonical: 'agentType', native: 'agent_type' },
{ canonical: 'cwd', native: 'cwd' },
{ canonical: 'effort', native: 'effort' },
{ canonical: 'hookEventName', native: 'hook_event_name' },
{ canonical: 'lastAssistantMessage', native: 'last_assistant_message' },
{ canonical: 'model', native: 'model' },
{ canonical: 'permissionMode', native: 'permission_mode' },
{ canonical: 'promptId', native: 'prompt_id' },
{ canonical: 'sessionId', native: 'session_id' },
{ canonical: 'source', native: 'source' },
{ canonical: 'stopHookActive', native: 'stop_hook_active' },
{ canonical: 'toolInput', native: 'tool_input' },
{ canonical: 'toolName', native: 'tool_name' },
{ canonical: 'toolResponse', native: 'tool_response' },
{ canonical: 'toolUseId', native: 'tool_use_id' },
{ canonical: 'transcriptPath', native: 'transcript_path' },
{ canonical: 'turnId', native: 'turn_id' },
]);

const defined = (value: Record<string, unknown>): Record<string, unknown> =>
Expand Down Expand Up @@ -719,7 +721,7 @@ export const planHooks = (
return eventComparison !== 0 ? eventComparison : left.id.localeCompare(right.id);
});
if (selected.length === 0) {
return Object.freeze({ diagnostics: Object.freeze(diagnostics), hookEntries: Object.freeze([]) });
return deepFreeze({ diagnostics: diagnostics, hookEntries: [] });
}

const groups: Record<string, unknown[]> = Object.create(null) as Record<string, unknown[]>;
Expand Down
20 changes: 11 additions & 9 deletions packages/agent-bundle/src/adapters/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import {
type TargetArtifactPlan,
type TargetHookEntry,
} from './types.ts';
import { deepFreeze } from '../core/freeze.ts';


const pluginName = 'plugin';

Expand Down Expand Up @@ -149,8 +151,8 @@ const hostValidation = (adapter: TargetAdapter, name: string) => {
const claudeValidation = hostValidation(claudeAdapter, 'Claude');
const codexValidation = hostValidation(codexAdapter, 'Codex');

const artifactValidation = Object.freeze({
documents: Object.freeze([
const artifactValidation = deepFreeze({
documents: [
// One shared Claude-format hook document serves both hosts; the pinned
// Codex hooks schema is byte-identical apart from its $id.
Object.freeze({ path: bundleHookContract.manifestPath, required: false, schema: 'claude-hooks' }),
Expand All @@ -165,8 +167,8 @@ const artifactValidation = Object.freeze({
Object.freeze({ path: cursorPaths.marketplace, required: false, schema: 'cursor-marketplace' }),
Object.freeze({ path: cursorPaths.mcp, required: false, schema: 'cursor-mcp' }),
Object.freeze({ path: cursorPaths.plugin, required: false, schema: 'cursor-plugin' }),
]),
schemas: Object.freeze([
],
schemas: [
...prefixedSchemas('claude', claudeValidation.schemas),
...prefixedSchemas('codex', codexValidation.schemas, 'plugin').filter((schema) => schema.name !== 'codex-hooks'),
// The bundle's Codex manifest points at the relocated MCP document, so its
Expand All @@ -176,7 +178,7 @@ const artifactValidation = Object.freeze({
Object.freeze({ name: 'cursor-marketplace', validate: validateJsonSchemaDocument(cursorMarketplaceValidator) }),
Object.freeze({ name: 'cursor-mcp', validate: validateJsonSchemaDocument(cursorMcpValidator) }),
Object.freeze({ name: 'cursor-plugin', validate: validateJsonSchemaDocument(cursorPluginValidator) }),
]),
],
});

const metadata = Object.freeze({
Expand Down Expand Up @@ -497,10 +499,10 @@ const plan = (model: NormalizedPlugin): TargetArtifactPlan => {
});
};

const eventCapabilityTables = Object.freeze([
Object.freeze({ name: 'Claude', routes: claudeCapabilityTable.hooks.eventRoutes }),
Object.freeze({ name: 'Codex', routes: codexCapabilityTable.hooks.eventRoutes }),
Object.freeze({ name: 'Cursor', routes: cursorCapabilityTable.hooks.eventRoutes }),
const eventCapabilityTables = deepFreeze([
{ name: 'Claude', routes: claudeCapabilityTable.hooks.eventRoutes },
{ name: 'Codex', routes: codexCapabilityTable.hooks.eventRoutes },
{ name: 'Cursor', routes: cursorCapabilityTable.hooks.eventRoutes },
]);
const compositeEventNames = new Set(eventCapabilityTables.flatMap(({ routes }) => Object.keys(routes)));
for (const event of compositeEventNames) {
Expand Down
20 changes: 11 additions & 9 deletions packages/agent-bundle/src/adapters/portable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
type TargetArtifactPlan,
} from './types.ts';
import { withInstallSurface } from '../install/surface.ts';
import { deepFreeze } from '../core/freeze.ts';


export interface PortableConfigExtension {
portable?: AgentBundlePortableConfig;
Expand Down Expand Up @@ -64,15 +66,15 @@ const metadata = Object.freeze({
});
const evidence = capabilityEvidence(portableName, metadata);

const artifactValidation = Object.freeze({
documents: Object.freeze([
const artifactValidation = deepFreeze({
documents: [
Object.freeze({ path: 'mcp.json', required: false, schema: 'mcp' }),
Object.freeze({ path: 'plugin.json', required: true, schema: 'plugin' }),
]),
schemas: Object.freeze([
],
schemas: [
Object.freeze({ name: 'mcp', validate: validateModernMcpDocument(validateJsonSchemaDocument(validateMcp)) }),
Object.freeze({ name: 'plugin', validate: validateJsonSchemaDocument(validatePlugin) }),
]),
],
});

const mcpRuntime = createTargetMcpRuntime({
Expand Down Expand Up @@ -311,10 +313,10 @@ const plan = (model: NormalizedPlugin): TargetArtifactPlan => {
}
}

return withInstallSurface(Object.freeze({
diagnostics: Object.freeze(diagnostics),
entries: Object.freeze(entries),
hookEntries: Object.freeze([]),
return withInstallSurface(deepFreeze({
diagnostics: diagnostics,
entries: entries,
hookEntries: [],
}), model, 'portable');
};

Expand Down
14 changes: 8 additions & 6 deletions packages/agent-bundle/src/adapters/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ import type {
TargetSchemaDescriptor,
} from './types.ts';
import type { TargetMcpRuntimeContract } from '../services/mcp-runtime.ts';
import { deepFreeze } from '../core/freeze.ts';


const sha256Pattern = /^[0-9a-f]{64}$/;
type NativeHookSource = NonNullable<TargetAdapter['nativeHookSource']>;

const emptyArtifactValidation: TargetArtifactValidationContract = Object.freeze({
documents: Object.freeze([]),
schemas: Object.freeze([]),
const emptyArtifactValidation: TargetArtifactValidationContract = deepFreeze({
documents: [],
schemas: [],
});

const emptyArtifactLayout: TargetArtifactLayout = Object.freeze({});
Expand Down Expand Up @@ -297,9 +299,9 @@ const snapshotArtifactValidation = (
if ([...schemaNames].some((name) => !referencedSchemas.has(name))) {
throw new Error(`Target adapter "${adapter.name}" must assign every artifact schema contract to a document.`);
}
return Object.freeze({
documents: Object.freeze(documents.sort((left, right) => left.path.localeCompare(right.path))),
schemas: Object.freeze(schemas.sort((left, right) => left.name.localeCompare(right.name))),
return deepFreeze({
documents: documents.sort((left, right) => left.path.localeCompare(right.path)),
schemas: schemas.sort((left, right) => left.name.localeCompare(right.name)),
});
};

Expand Down
6 changes: 4 additions & 2 deletions packages/agent-bundle/src/adapters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
} from '../core/types.ts';
import type { TargetHookContract, TargetHookEntry } from './hook-contract.ts';
import type { TargetMcpRuntimeContract } from '../services/mcp-runtime.ts';
import { deepFreeze } from '../core/freeze.ts';


export type { TargetHookEntry, TargetHookWrapper } from './hook-contract.ts';

Expand Down Expand Up @@ -397,10 +399,10 @@ export interface TargetArtifactOutputLayout {
}

const noArtifactDocumentIssues: readonly TargetArtifactDocumentIssue[] = Object.freeze([]);
const invalidMcpDocumentIssues: readonly TargetArtifactDocumentIssue[] = Object.freeze([Object.freeze({
const invalidMcpDocumentIssues: readonly TargetArtifactDocumentIssue[] = deepFreeze([{
instancePath: '',
message: 'MCP document must be a detached finite JSON value.',
})]);
}]);

/**
* Target-owned compiler namespaces, separate from target-native schema documents.
Expand Down
40 changes: 21 additions & 19 deletions packages/agent-bundle/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { isInsideOrEqual } from './core/paths.ts';
import { emptyCompiledRouteGraph } from './routes/graph.ts';
import { inspectRouteGraph, type RouteGraphInspection } from './routes/inspect.ts';
import { mcpServerStateDirectory, runMcpForeground } from './services/mcp-run.ts';
import { deepFreeze } from './core/freeze.ts';

export { compileRouteGraph, emptyCompiledRouteGraph, isEmptyRouteGraph } from './routes/graph.ts';
export { canonicalAgentEvents } from './routes/public.ts';
export type {
Expand Down Expand Up @@ -651,43 +653,43 @@ export const build = async (options: BuildOptions): Promise<BuildProjectResult>
const evalDiagnostics: Readonly<Record<EvalServiceErrorCode, Readonly<{
readonly code: string;
readonly recovery: string;
}>>> = Object.freeze({
EVAL_ARTIFACT_NOT_FOUND: Object.freeze({
}>>> = deepFreeze({
EVAL_ARTIFACT_NOT_FOUND: {
code: 'AB9009',
recovery: 'Select raw evidence that the recorded eval trial persisted.',
}),
EVAL_ARTIFACT_UNAVAILABLE: Object.freeze({
},
EVAL_ARTIFACT_UNAVAILABLE: {
code: 'AB9010',
recovery: 'Regenerate the recorded eval run before reading its raw evidence.',
}),
EVAL_EVENTS_CURSOR_INVALID: Object.freeze({
},
EVAL_EVENTS_CURSOR_INVALID: {
code: 'AB9011',
recovery: 'Reconnect from a non-negative cursor no later than the durable event sequence.',
}),
EVAL_HARNESS_UNSUPPORTED: Object.freeze({
},
EVAL_HARNESS_UNSUPPORTED: {
code: 'AB9001',
recovery: 'Use deterministic, claude, or codex, or correct an unknown harness name.',
}),
EVAL_RUN_NOT_FOUND: Object.freeze({
},
EVAL_RUN_NOT_FOUND: {
code: 'AB9003',
recovery: 'Read a run that this project recorded, or start a new one.',
}),
EVAL_SELECTION_EMPTY: Object.freeze({
},
EVAL_SELECTION_EMPTY: {
code: 'AB9002',
recovery: 'Select a suite or case that "agent-bundle eval --json" reports as discovered.',
}),
EVAL_SEMANTIC_GRADER_UNSUPPORTED: Object.freeze({
},
EVAL_SEMANTIC_GRADER_UNSUPPORTED: {
code: 'AB9008',
recovery: 'Run the configured semantic grader with "--harness claude" and a Claude-pinned eval case.',
}),
EVAL_TARGET_MISSING: Object.freeze({
},
EVAL_TARGET_MISSING: {
code: 'AB9004',
recovery: 'Select the targets the pinned eval hosts name, then evaluate again.',
}),
EVAL_TRIALS_INVALID: Object.freeze({
},
EVAL_TRIALS_INVALID: {
code: 'AB9005',
recovery: 'Request an integer trial count between 1 and 100.',
}),
},
});

const evalDiagnostic = (error: EvalServiceError): Diagnostic => {
Expand Down
6 changes: 4 additions & 2 deletions packages/agent-bundle/src/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import {
type ArtifactOutputProvenance,
} from './provenance.ts';
import { validateArtifact, validateArtifactFiles } from './validate-artifact.ts';
import { deepFreeze } from '../core/freeze.ts';


export interface BuildResult {
readonly compiledEntries: readonly CompiledEntry[];
Expand Down Expand Up @@ -380,8 +382,8 @@ export const build = async (options: BuildOptions): Promise<BuildResult> => {
...tools,
})));
}
const publishedCompiledEntries = Object.freeze(compiledEntries.map((entry) =>
Object.freeze({
const publishedCompiledEntries = deepFreeze(compiledEntries.map((entry) =>
({
...entry,
output: publishedOutput(entry),
}),
Expand Down
Loading
Loading