diff --git a/packages/persona-kit/package.json b/packages/persona-kit/package.json index df62f75a..6355aaba 100644 --- a/packages/persona-kit/package.json +++ b/packages/persona-kit/package.json @@ -10,6 +10,10 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, + "./spec": { + "types": "./dist/spec.d.ts", + "default": "./dist/spec.js" + }, "./package.json": "./package.json", "./schemas/persona.schema.json": "./schemas/persona.schema.json", "./schemas/agent.schema.json": "./schemas/agent.schema.json" diff --git a/packages/persona-kit/src/spec.test.ts b/packages/persona-kit/src/spec.test.ts new file mode 100644 index 00000000..6e390350 --- /dev/null +++ b/packages/persona-kit/src/spec.test.ts @@ -0,0 +1,49 @@ +import assert from 'node:assert/strict'; +import { test } from 'node:test'; + +import { + HARNESS_VALUES, + isHarness, + isIntent, + parseAgentSpec, + parsePersonaSpec +} from './spec.js'; + +function validSpec(over: Record = {}): Record { + return { + id: 'p', + intent: 'documentation', + tags: ['documentation'], + description: 'd', + harness: 'claude', + model: 'anthropic/claude-3-5-sonnet', + systemPrompt: 'be helpful', + harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 }, + ...over + }; +} + +test('spec entrypoint re-exports the validation surface', () => { + assert.equal(typeof parsePersonaSpec, 'function'); + assert.equal(typeof parseAgentSpec, 'function'); + assert.equal(typeof isIntent, 'function'); + assert.equal(typeof isHarness, 'function'); + assert.ok(Array.isArray(HARNESS_VALUES)); +}); + +test('spec entrypoint validates a minimal persona spec', () => { + const spec = parsePersonaSpec(validSpec(), 'documentation'); + assert.equal(spec.id, 'p'); + assert.equal(spec.intent, 'documentation'); +}); + +test('spec entrypoint accepts the grok harness', () => { + assert.ok(HARNESS_VALUES.includes('grok')); + const spec = parsePersonaSpec(validSpec({ harness: 'grok' }), 'documentation'); + assert.equal(spec.harness, 'grok'); +}); + +test('spec entrypoint parses an agent spec', () => { + const agent = parseAgentSpec({}, 'agent'); + assert.equal(typeof agent, 'object'); +}); diff --git a/packages/persona-kit/src/spec.ts b/packages/persona-kit/src/spec.ts new file mode 100644 index 00000000..c3b5916c --- /dev/null +++ b/packages/persona-kit/src/spec.ts @@ -0,0 +1,115 @@ +/** + * Narrow, side-effect-free persona/agent spec VALIDATION surface. + * + * This entrypoint exists so server-side consumers that only need to + * validate a persona or agent spec (e.g. AgentWorkforce Cloud's deploy + * route, which routes persona validation through + * `@cloud/core/proactive-runtime/persona-spec.js`) can import the parsers + * and constants WITHOUT pulling in the orchestration modules the package + * barrel (`index.ts`) re-exports. + * + * The barrel re-exports `./mount.js`, `./execute.js`, `./plan.js`, + * `./skills.js`, `./skill-runner.js`, `./config-files.js`, etc. Importing + * the barrel therefore EVALUATES every one of those modules' top-level + * code — `node:child_process`, `node:fs`, and the deferred + * `@relayfile/local-mount` → `@parcel/watcher` edge — none of which a pure + * validation consumer needs. `./spec.js` transitively imports only + * `./parse.js`, `./constants.js`, and `./types.js`, which have ZERO + * external runtime dependencies (no `@relayfile/*`, no native bindings, + * no Node process/fs APIs at module-eval time). + * + * Keep this entrypoint in lockstep with the validation-related exports of + * `./index.js`; it is intentionally a strict subset, never a superset. + */ + +// Constants (intents, harnesses, permission modes, …) +export { + BUILT_IN_PERSONA_INTENTS, + CODEX_APPROVAL_POLICIES, + CODEX_SANDBOX_MODES, + HARNESS_SKILL_TARGETS, + HARNESS_VALUES, + PERMISSION_MODES, + PERSONA_INTENTS, + PERSONA_TAGS, + SIDECAR_MD_MODES, + SKILL_SOURCE_KINDS +} from './constants.js'; +export type { KnownPersonaTag } from './constants.js'; + +// Spec types +export type { + AgentSpec, + CapabilityValue, + CodexApprovalPolicy, + CodexSandboxMode, + Harness, + HarnessSettings, + HarnessSkillTarget, + IntegrationSource, + McpServerSpec, + PermissionMode, + PersonaInputPicker, + PersonaInputSpec, + PersonaIntegrationConfig, + PersonaIntegrationTrigger, + PersonaIntent, + PersonaAiMemoryConfig, + PersonaMemory, + PersonaMemoryConfig, + PersonaMemoryScope, + PersonaRelay, + PersonaRelayConfig, + PersonaTrajectoryConfig, + PersonaMount, + PersonaPermissions, + PersonaSchedule, + PersonaSelection, + PersonaSkill, + PersonaSpec, + PersonaTag, + ProactiveCapabilities, + SidecarMdMode, + SkillSourceKind, + WatchEvent, + WatchRule +} from './types.js'; + +// Parsers, type guards, and the sidecar resolver +export { + assertInputName, + assertSidecarPath, + deepFreeze, + INPUT_NAME_RE, + INTEGRATION_SOURCE_NAME_RE, + isHarness, + isIntent, + isObject, + isSidecarMode, + isTag, + parseAgentSpec, + parseCapabilities, + parseHarnessSettings, + parseInputs, + parseIntegrationConfig, + parseIntegrationSource, + parseIntegrationTrigger, + parseIntegrations, + parseMcpServers, + parseMemory, + parseMount, + parseOnEvent, + parseRelay, + parsePermissions, + parsePersonaSpec, + resolveAiMemory, + resolveTrajectoryRecording, + parseSchedules, + parseSkills, + parseStringList, + parseStringMap, + parseTags, + parseWatch, + resolveSidecar, + sidecarSelectionFields +} from './parse.js';