diff --git a/.trajectories/completed/2026-05/traj_rdhqus1o0ci8.json b/.trajectories/completed/2026-05/traj_rdhqus1o0ci8.json new file mode 100644 index 00000000..2f4509fd --- /dev/null +++ b/.trajectories/completed/2026-05/traj_rdhqus1o0ci8.json @@ -0,0 +1,53 @@ +{ + "id": "traj_rdhqus1o0ci8", + "version": 1, + "task": { + "title": "Add typed ProactiveCapabilities to persona-kit PersonaSpec" + }, + "status": "completed", + "startedAt": "2026-05-31T10:29:26.181Z", + "completedAt": "2026-05-31T10:32:51.807Z", + "agents": [ + { + "name": "default", + "role": "lead", + "joinedAt": "2026-05-31T10:29:31.307Z" + } + ], + "chapters": [ + { + "id": "chap_19bcnrc6w4th", + "title": "Work", + "agentName": "default", + "startedAt": "2026-05-31T10:29:31.307Z", + "endedAt": "2026-05-31T10:32:51.807Z", + "events": [ + { + "ts": 1780223371308, + "type": "decision", + "content": "Preserve capabilities in parsePersonaSpec: Preserve capabilities in parsePersonaSpec", + "raw": { + "question": "Preserve capabilities in parsePersonaSpec", + "chosen": "Preserve capabilities in parsePersonaSpec", + "alternatives": [], + "reasoning": "parsePersonaSpec reconstructs PersonaSpec and drops unknown top-level fields, so the new optional field needs parser support for define/parse round-trip" + }, + "significance": "high" + } + ] + } + ], + "retrospective": { + "summary": "Added optional typed proactive capabilities to persona-kit PersonaSpec, exported the canonical types, preserved capabilities through define/parse, regenerated schema, bumped package version to 3.0.34, and validated build/typecheck/test.", + "approach": "Standard approach", + "confidence": 0.9 + }, + "commits": [], + "filesChanged": [], + "projectId": "/private/tmp/wt-pk", + "tags": [], + "_trace": { + "startRef": "a380c74cc90bbe0d9f1bc897b1cbb36808586eea", + "endRef": "a380c74cc90bbe0d9f1bc897b1cbb36808586eea" + } +} \ No newline at end of file diff --git a/.trajectories/completed/2026-05/traj_rdhqus1o0ci8.md b/.trajectories/completed/2026-05/traj_rdhqus1o0ci8.md new file mode 100644 index 00000000..40e01080 --- /dev/null +++ b/.trajectories/completed/2026-05/traj_rdhqus1o0ci8.md @@ -0,0 +1,31 @@ +# Trajectory: Add typed ProactiveCapabilities to persona-kit PersonaSpec + +> **Status:** ✅ Completed +> **Confidence:** 90% +> **Started:** May 31, 2026 at 12:29 PM +> **Completed:** May 31, 2026 at 12:32 PM + +--- + +## Summary + +Added optional typed proactive capabilities to persona-kit PersonaSpec, exported the canonical types, preserved capabilities through define/parse, regenerated schema, bumped package version to 3.0.34, and validated build/typecheck/test. + +**Approach:** Standard approach + +--- + +## Key Decisions + +### Preserve capabilities in parsePersonaSpec +- **Chose:** Preserve capabilities in parsePersonaSpec +- **Reasoning:** parsePersonaSpec reconstructs PersonaSpec and drops unknown top-level fields, so the new optional field needs parser support for define/parse round-trip + +--- + +## Chapters + +### 1. Work +*Agent: default* + +- Preserve capabilities in parsePersonaSpec: Preserve capabilities in parsePersonaSpec diff --git a/.trajectories/index.json b/.trajectories/index.json index 45e2f4d8..8891d9ee 100644 --- a/.trajectories/index.json +++ b/.trajectories/index.json @@ -1,6 +1,6 @@ { "version": 1, - "lastUpdated": "2026-05-28T07:47:24.572Z", + "lastUpdated": "2026-05-31T10:32:51.951Z", "trajectories": { "traj_1775734701264_ba65c69b": { "title": "finish-npm-provenance-persona-workflow", @@ -43,6 +43,13 @@ "startedAt": "2026-05-28T07:46:09.716Z", "completedAt": "2026-05-28T07:47:24.475Z", "path": "/Users/khaliqgant/Projects/AgentWorkforce/workforce-issue-154/.trajectories/completed/2026-05/traj_61qz5tibb2lm.json" + }, + "traj_rdhqus1o0ci8": { + "title": "Add typed ProactiveCapabilities to persona-kit PersonaSpec", + "status": "completed", + "startedAt": "2026-05-31T10:29:26.181Z", + "completedAt": "2026-05-31T10:32:51.807Z", + "path": "/private/tmp/wt-pk/.trajectories/completed/2026-05/traj_rdhqus1o0ci8.json" } } } \ No newline at end of file diff --git a/packages/persona-kit/CHANGELOG.md b/packages/persona-kit/CHANGELOG.md index a7ad9400..2ea64979 100644 --- a/packages/persona-kit/CHANGELOG.md +++ b/packages/persona-kit/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.34] - 2026-05-31 + +### Added + +- Add typed proactive capabilities to `PersonaSpec`. + ## [3.0.33] - 2026-05-29 ### Fixed diff --git a/packages/persona-kit/package.json b/packages/persona-kit/package.json index f57bd068..2c5e4110 100644 --- a/packages/persona-kit/package.json +++ b/packages/persona-kit/package.json @@ -1,6 +1,6 @@ { "name": "@agentworkforce/persona-kit", - "version": "3.0.33", + "version": "3.0.34", "private": false, "type": "module", "main": "dist/index.js", diff --git a/packages/persona-kit/schemas/persona.schema.json b/packages/persona-kit/schemas/persona.schema.json index e61795f2..3e716f23 100644 --- a/packages/persona-kit/schemas/persona.schema.json +++ b/packages/persona-kit/schemas/persona.schema.json @@ -131,6 +131,10 @@ }, "description": "Relayfile-change listeners for proactive cloud personas." }, + "capabilities": { + "$ref": "#/definitions/ProactiveCapabilities", + "description": "Portable proactive capability declarations. A capability is enabled when its value is `true` or an object whose `.enabled !== false` (`{}` is enabled; `{ enabled: false }` is disabled). `pullRequest` is the legacy alias of `review`; both map to the review capability." + }, "memory": { "$ref": "#/definitions/PersonaMemory", "description": "Memory subsystem opt-in. Wires the agent-assistant memory adapter at runtime; the persona spec only declares intent, not implementation details (api keys, adapter type, etc. come from workforce env)." @@ -561,6 +565,44 @@ "deleted" ] }, + "ProactiveCapabilities": { + "type": "object", + "properties": { + "review": { + "$ref": "#/definitions/CapabilityValue", + "description": "Review a PR/MR diff and post a review." + }, + "issueClaim": { + "$ref": "#/definitions/CapabilityValue", + "description": "Claim an issue and open a change request." + }, + "conflictAutofix": { + "$ref": "#/definitions/CapabilityValue", + "description": "Rebase a dirty PR/MR." + }, + "pullRequest": { + "$ref": "#/definitions/CapabilityValue", + "description": "Legacy alias of `review`." + } + }, + "description": "Portable proactive capability declarations.\n\nA capability is enabled when its value is `true` or an object whose `.enabled !== false` (`{}` is enabled; `{ enabled: false }` is disabled). `pullRequest` is the legacy alias of `review`; both map to the review capability." + }, + "CapabilityValue": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + }, + "additionalProperties": {} + } + ] + }, "PersonaMemory": { "anyOf": [ { diff --git a/packages/persona-kit/src/__fixtures__/personas/full.json b/packages/persona-kit/src/__fixtures__/personas/full.json index 3d60d78d..9b77a6a2 100644 --- a/packages/persona-kit/src/__fixtures__/personas/full.json +++ b/packages/persona-kit/src/__fixtures__/personas/full.json @@ -42,6 +42,12 @@ } }, "schedules": [{ "name": "daily-review", "cron": "0 14 * * 1-5", "tz": "UTC" }], + "capabilities": { + "review": true, + "issueClaim": {}, + "conflictAutofix": { "enabled": false }, + "pullRequest": { "enabled": true } + }, "memory": { "enabled": true, "scopes": ["workspace", "user", "global"], diff --git a/packages/persona-kit/src/define.test.ts b/packages/persona-kit/src/define.test.ts index 8dfa7140..fc2a1bca 100644 --- a/packages/persona-kit/src/define.test.ts +++ b/packages/persona-kit/src/define.test.ts @@ -28,6 +28,10 @@ test('definePersona returns authored specs that parse successfully', () => { jira: { triggers: [{ on: 'comment.created' }] }, customProvider: { triggers: [{ on: 'custom.event' }] } }, + capabilities: { + review: true, + conflictAutofix: { enabled: false } + }, onEvent: './agent.ts', harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 } }); @@ -42,6 +46,10 @@ test('definePersona returns authored specs that parse successfully', () => { 'off_registry.github_event' ); assert.equal(parsed.integrations?.customProvider.triggers?.[0].on, 'custom.event'); + assert.deepEqual(parsed.capabilities, { + review: true, + conflictAutofix: { enabled: false } + }); }); test('definePersona types tags against the closed PersonaTag vocabulary', () => { diff --git a/packages/persona-kit/src/define.ts b/packages/persona-kit/src/define.ts index 433c9146..6d599d0c 100644 --- a/packages/persona-kit/src/define.ts +++ b/packages/persona-kit/src/define.ts @@ -1,4 +1,5 @@ import type { + ProactiveCapabilities, Harness, HarnessSettings, IntegrationSource, @@ -62,6 +63,7 @@ export interface PersonaDefinitionBase { integrations?: TypedIntegrations; schedules?: readonly PersonaSchedule[]; watch?: readonly WatchRule[]; + capabilities?: ProactiveCapabilities; memory?: PersonaMemory; } diff --git a/packages/persona-kit/src/index.ts b/packages/persona-kit/src/index.ts index ef18b2d7..a4049ca0 100644 --- a/packages/persona-kit/src/index.ts +++ b/packages/persona-kit/src/index.ts @@ -15,6 +15,7 @@ export type { KnownPersonaTag } from './constants.js'; // Types export type { + CapabilityValue, CodexApprovalPolicy, CodexSandboxMode, Harness, @@ -40,6 +41,7 @@ export type { PersonaSkill, PersonaSpec, PersonaTag, + ProactiveCapabilities, SidecarMdMode, SkillInstall, SkillMaterializationOptions, @@ -71,6 +73,7 @@ export { isObject, isSidecarMode, isTag, + parseCapabilities, parseHarnessSettings, parseInputs, parseIntegrationConfig, diff --git a/packages/persona-kit/src/parse.ts b/packages/persona-kit/src/parse.ts index 3d7600b1..b116670f 100644 --- a/packages/persona-kit/src/parse.ts +++ b/packages/persona-kit/src/parse.ts @@ -8,6 +8,7 @@ import { SIDECAR_MD_MODES } from './constants.js'; import type { + CapabilityValue, CodexApprovalPolicy, CodexSandboxMode, Harness, @@ -29,6 +30,7 @@ import type { PersonaSkill, PersonaSpec, PersonaTag, + ProactiveCapabilities, SidecarMdMode, WatchEvent, WatchRule @@ -851,6 +853,44 @@ export function parseMemory(value: unknown, context: string): PersonaMemory | un return out; } +function parseCapabilityValue(value: unknown, context: string): CapabilityValue { + if (typeof value === 'boolean') return value; + if (!isObject(value)) { + throw new Error(`${context} must be a boolean or object if provided`); + } + if (value.enabled !== undefined && typeof value.enabled !== 'boolean') { + throw new Error(`${context}.enabled must be a boolean if provided`); + } + return { ...value } as CapabilityValue; +} + +export function parseCapabilities( + value: unknown, + context: string +): ProactiveCapabilities | undefined { + if (value === undefined) return undefined; + if (!isObject(value)) { + throw new Error(`${context} must be an object if provided`); + } + + const out: ProactiveCapabilities = {}; + const { review, issueClaim, conflictAutofix, pullRequest } = value; + if (review !== undefined) { + out.review = parseCapabilityValue(review, `${context}.review`); + } + if (issueClaim !== undefined) { + out.issueClaim = parseCapabilityValue(issueClaim, `${context}.issueClaim`); + } + if (conflictAutofix !== undefined) { + out.conflictAutofix = parseCapabilityValue(conflictAutofix, `${context}.conflictAutofix`); + } + if (pullRequest !== undefined) { + out.pullRequest = parseCapabilityValue(pullRequest, `${context}.pullRequest`); + } + + return Object.keys(out).length > 0 ? out : undefined; +} + export function parseOnEvent(value: unknown, context: string): string | undefined { if (value === undefined) return undefined; return assertOnEventPath(value, context); @@ -893,6 +933,7 @@ export function parsePersonaSpec(value: unknown, expectedIntent: PersonaIntent): integrations, schedules, watch, + capabilities, memory, onEvent } = value; @@ -998,6 +1039,10 @@ export function parsePersonaSpec(value: unknown, expectedIntent: PersonaIntent): ); const parsedSchedules = parseSchedules(schedules, `persona[${expectedIntent}].schedules`); const parsedWatch = parseWatch(watch, `persona[${expectedIntent}].watch`); + const parsedCapabilities = parseCapabilities( + capabilities, + `persona[${expectedIntent}].capabilities` + ); const parsedMemory = parseMemory(memory, `persona[${expectedIntent}].memory`); const parsedOnEvent = parseOnEvent(onEvent, `persona[${expectedIntent}].onEvent`); const parsedSandbox = parseSandbox(sandbox, `persona[${expectedIntent}].sandbox`); @@ -1029,6 +1074,7 @@ export function parsePersonaSpec(value: unknown, expectedIntent: PersonaIntent): ...(parsedIntegrations ? { integrations: parsedIntegrations } : {}), ...(parsedSchedules ? { schedules: parsedSchedules } : {}), ...(parsedWatch ? { watch: parsedWatch } : {}), + ...(parsedCapabilities ? { capabilities: parsedCapabilities } : {}), ...(parsedMemory !== undefined ? { memory: parsedMemory } : {}), ...(parsedOnEvent !== undefined ? { onEvent: parsedOnEvent } : {}) }; diff --git a/packages/persona-kit/src/types.ts b/packages/persona-kit/src/types.ts index 797ef8a9..c64d6416 100644 --- a/packages/persona-kit/src/types.ts +++ b/packages/persona-kit/src/types.ts @@ -283,6 +283,29 @@ export interface PersonaMemoryConfig { export type PersonaMemory = boolean | PersonaMemoryConfig; +export type CapabilityValue = + | boolean + | { enabled?: boolean; [k: string]: unknown }; + +/** + * Portable proactive capability declarations. + * + * A capability is enabled when its value is `true` or an object whose + * `.enabled !== false` (`{}` is enabled; `{ enabled: false }` is disabled). + * `pullRequest` is the legacy alias of `review`; both map to the review + * capability. + */ +export interface ProactiveCapabilities { + /** Review a PR/MR diff and post a review. */ + review?: CapabilityValue; + /** Claim an issue and open a change request. */ + issueClaim?: CapabilityValue; + /** Rebase a dirty PR/MR. */ + conflictAutofix?: CapabilityValue; + /** Legacy alias of `review`. */ + pullRequest?: CapabilityValue; +} + /** * A persona listens for events. Three listener kinds: clock (cron schedules * through `schedules[]`), radio (RelayFile integration events through @@ -421,6 +444,13 @@ export interface PersonaSpec { schedules?: PersonaSchedule[]; /** Relayfile-change listeners for proactive cloud personas. */ watch?: WatchRule[]; + /** + * Portable proactive capability declarations. A capability is enabled when + * its value is `true` or an object whose `.enabled !== false` (`{}` is + * enabled; `{ enabled: false }` is disabled). `pullRequest` is the legacy + * alias of `review`; both map to the review capability. + */ + capabilities?: ProactiveCapabilities; /** * Memory subsystem opt-in. Wires the agent-assistant memory adapter at * runtime; the persona spec only declares intent, not implementation