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
1 change: 0 additions & 1 deletion .github/context/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ It builds `TaskContext`, launches agent cycles, persists agent output, manages r
### Review runtime — `src/review/`

- `reviewOrchestrator.ts` drives `REVIEW_PENDING → ... → REVIEW_DONE/REVIEW_FAILED`; the agent runs in the workspace container via `workspaceRunner.runReviewInDocker()` (`REVIEW_MODE=1`, prompt read from `USER_PROMPT_FILE`)
- `copilotReviewAgent.ts` (host-side SDK client) is **legacy** — never instantiated by the main bootstrap
- `reviewPromptBuilder.ts` and `reviewResultParser.ts` build/parse the review prompt contract

`src/index.ts` wires the Docker review path through `buildReviewBundle()` / `buildReviewTrigger()`, resolving the active review integration's `createReviewer()` descriptor hook.
Expand Down
2 changes: 1 addition & 1 deletion .github/context/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ tests/
| Agents / Copilot | `copilotAdapter` (+ `.promptInjection`), `copilotConnectionValidator`, `copilotOAuthService`, `copilotModelsService`, `providerAuthService`, `mockAgentAdapter`, `agentEventTypes` (+ `.normalization`), `workerCommitProtocol`, `workerNetworkGuard`, `workerSkills`, `workerLocalSkills` |
| Agents / Claude | `claudeAdapter`, `claudeConnectionValidator`, `claudeModelsService` |
| Agents / Aider | `aiderAdapter`, `aiderDescriptor`, `aiderConnectionValidator`, `aiderModelsService`, `aiderWorker` |
| Review runtime | `copilotReviewAgent`, `reviewOrchestrator`, `reviewPromptBuilder`, `reviewResultParser`, `reviewLiveLogs`, `commentHash`, `commentSeverity`, `revisionPatchset` |
| Review runtime | `reviewOrchestrator`, `reviewPromptBuilder`, `reviewResultParser`, `reviewLiveLogs`, `commentHash`, `commentSeverity`, `revisionPatchset` |
| Cost tracking | `cycleCost`, `stateStore.cost`, `adminCostRoutes` |
| Plugins / runtime wiring | `pluginManager` (+ `.multiInstance`), `registry`, `runtimeBootstrap` (historical name; covers bootstrap wiring in `src/index.ts`), `integrationStreamEvents` |
| Webhooks | `webhookServer`, `webhookHandlerRegistry` (+ the per-provider handlers listed above) |
Expand Down
8 changes: 4 additions & 4 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ src/
plugins/ # registry, pluginManager, init, descriptors/{index,github,
# gitlab,gerrit,redmine,copilot,claude,aider,mock}.ts (unified
# provider descriptors; githubOAuth/gitlabOAuth helpers)
review/ # reviewOrchestrator, copilotReviewAgent,
review/ # reviewOrchestrator,
# reviewBootstrap (bundle + trigger factory),
# reviewPromptBuilder, reviewResultParser,
# commentFilter, commentHash, commentSeverity,
Expand Down Expand Up @@ -214,12 +214,12 @@ Empty strings in env are treated as `undefined` (helpful for env overrides).
## Copilot Execution

1. **Worker-local headless CLI** — code-generation containers always spawn `copilot --headless` inside the container and connect the SDK to that local CLI server.
2. **Docker review execution** — review tasks also run in the agent container (`REVIEW_MODE=1` via `workspaceRunner.runReviewInDocker`); the worker reads the prompt from `USER_PROMPT_FILE` (`/ve-home/user-prompt.txt`) and returns raw LLM text for the host to parse. `src/review/copilotReviewAgent.ts` (host-side SDK client) is **legacy** — never instantiated in `src/`.
2. **Docker review execution** — review tasks also run in the agent container (`REVIEW_MODE=1` via `workspaceRunner.runReviewInDocker`); the worker reads the prompt from `USER_PROMPT_FILE` (`/ve-home/user-prompt.txt`) and returns raw LLM text for the host to parse.
3. **Container validation fallback** — when the local Node runtime lacks `node:sqlite`, `copilotConnectionValidator` runs the validation script inside `AGENT_CONTAINER_IMAGE`, which also starts a local headless CLI in-container.

Worker `sendAndWait` timeout ≈ 540s. Host agent timeout = `AGENT_TIMEOUT_MS` (default 60 min).

Implementation: `src/agents/copilotAdapter.ts`, `src/agents/copilotOAuthService.ts`, `src/agents/copilotModelsService.ts`, `src/agents/copilotConnectionValidator.ts`, `src/review/copilotReviewAgent.ts`, `agent-worker/src/index.ts`.
Implementation: `src/agents/copilotAdapter.ts`, `src/agents/copilotOAuthService.ts`, `src/agents/copilotModelsService.ts`, `src/agents/copilotConnectionValidator.ts`, `agent-worker/src/index.ts`.

## Claude Execution (`agent_execution` alternative to Copilot)

Expand Down Expand Up @@ -286,7 +286,7 @@ Body lines ≤72 chars. See `typescript-standard` skill.
- **One provider, many capabilities**: there is no longer a `github-issue` vs `github-pull-request` (or `gitlab-issue` vs `gitlab-merge-request`) split. A single `github` / `gitlab` provider descriptor exposes multiple domain capabilities; resolve runtime dependencies by capability (`getConnectorForCapability`, `getActiveIntegrationsByCapability`) rather than by an integration type/role.
- **Ticket-source uniqueness is app-enforced**: there is no DB unique index across projects for the issue_tracking binding. `projectStore` throws when a second project binds the same `(integrationId, ticketProjectKey)`; keep that check in application code.
- **Multi-instance plugins**: all enabled integrations stay active in memory, including multiple rows of the same provider. Resolve runtime dependencies by `integrationId`, capability, or explicit integration lists; do not add new logic that assumes a single active integration per provider.
- **Copilot execution path**: no host/external CLI server support remains. Containers and validation scripts always boot a local headless CLI; reviews run in the agent container with `REVIEW_MODE=1` (`CopilotReviewAgent` is legacy, unused).
- **Copilot execution path**: no host/external CLI server support remains. Containers and validation scripts always boot a local headless CLI; reviews run in the agent container with `REVIEW_MODE=1`.
- **Descriptor-driven event streams**: stream-capable integrations are reconciled through `descriptor.streamEvents` plus `PluginManager.getActiveIntegrations()`. Gerrit is the current stream-backed implementation, but the bootstrap is no longer Gerrit-specific.
- **Descriptor-driven review backends**: generic review routing resolves active review integrations through `descriptor.createReviewer`; keep provider-specific clone/setup logic in the descriptor and out of `src/index.ts` / `src/review/reviewOrchestrator.ts`.
- **Review tasks are integration-scoped**: webhook-triggered review flows must resolve the exact review integration by `integrationId`, and code-review tasks should preserve that integration in `ticketSourceLabel` / derived `ticketId` to avoid collisions between multiple active Gerrit instances.
Expand Down
2 changes: 0 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,6 @@ src/

review/ # Code-review workflow
reviewOrchestrator.ts # REVIEW_PENDING → REVIEW_DONE lifecycle
copilotReviewAgent.ts # LEGACY host-side Copilot SDK client (unused; reviews
# run in-container via REVIEW_MODE=1, see below)
reviewPromptBuilder.ts
reviewResultParser.ts
commentFilter.ts # Filters comments to lines present in the diff
Expand Down
40 changes: 0 additions & 40 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@
"devDependencies": {
"@fontsource/ibm-plex-mono": "^5.2.7",
"@fontsource/ibm-plex-sans": "^5.2.8",
"@fontsource/material-icons": "^5.2.7",
"@material-icons/svg": "^1.0.33",
"@types/better-sqlite3": "^7.6.13",
Comment thread
fshehadeh-sfl marked this conversation as resolved.
"@types/node": "^20.19.41",
"@types/react": "^19.2.17",
Expand All @@ -54,7 +52,6 @@
"drizzle-kit": "^0.31.10",
"eslint": "^9.5.0",
"eslint-plugin-react-hooks": "^7.1.1",
"simple-icons": "^16.23.0",
"tsx": "^4.15.6",
"typescript": "^5.4.5",
"vite": "^8.0.16",
Expand Down
12 changes: 0 additions & 12 deletions src/admin/defaultSkillSources.ts

This file was deleted.

8 changes: 0 additions & 8 deletions src/admin/ui/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,19 +186,11 @@ export function getMe(): Promise<ApiMe> {

export interface AgentKey { publicKey: string; keyType: string; comment: string }

export function generateSshKey(integrationId: string): Promise<{ publicKey: string }> {
return request<{ publicKey: string }>("POST", `/api/admin/integrations/${integrationId}/ssh-key/generate`);
}

/** Generate a key pair without requiring an existing integration (returns both values for in-form state). */
export function generateSshKeyPair(provider: string, sshUser?: string): Promise<{ sshPrivateKeyEnc: string; sshPublicKey: string }> {
return request<{ sshPrivateKeyEnc: string; sshPublicKey: string }>("POST", "/api/admin/ssh-key/generate", { provider, sshUser });
}

export function getSshPublicKey(integrationId: string): Promise<{ publicKey: string | null }> {
return request<{ publicKey: string | null }>("GET", `/api/admin/integrations/${integrationId}/ssh-key/public`);
}

export function listAgentKeys(): Promise<{ keys: AgentKey[]; agentAvailable: boolean }> {
return request<{ keys: AgentKey[]; agentAvailable: boolean }>("GET", "/api/admin/ssh-agent/keys");
}
Expand Down
4 changes: 2 additions & 2 deletions src/bootstrap/runtimeBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ export function parseIntegrationConfig(integration: Integration | null): Record<
}

/** Return all active integrations of `provider`, sorted newest-first. */
export function getActiveIntegrationsByType(pluginManager: PluginManager, provider: ProviderId): Integration[] {
function getActiveIntegrationsByType(pluginManager: PluginManager, provider: ProviderId): Integration[] {
return pluginManager
.getActiveIntegrationsByProvider(provider)
.slice()
.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());
}

/** Return the most-recently-updated active integration of `provider`, or null. */
export function getPrimaryActiveIntegration(pluginManager: PluginManager, provider: ProviderId): Integration | null {
function getPrimaryActiveIntegration(pluginManager: PluginManager, provider: ProviderId): Integration | null {
return getActiveIntegrationsByType(pluginManager, provider)[0] ?? null;
}

Expand Down
54 changes: 4 additions & 50 deletions src/orchestrator/orchestrator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pRetry from "p-retry";
import { randomUUID, createHash } from "crypto";
import { formatTicketFooter, hasTicketFooter } from "../utils/ticketFooterFormatter.js";
import { formatTicketFooter } from "../utils/ticketFooterFormatter.js";
import type {
AgentAdapter,
CommitDescriptor,
Expand Down Expand Up @@ -319,11 +319,6 @@ export class Orchestrator {
await this.checkReviewProgress(task);
}

/** Gerrit-flavoured alias for handleReviewEvent. */
async handleGerritEvent(changeId: ExternalChangeId): Promise<void> {
await this.handleReviewEvent(changeId);
}

/**
* Webhook entry points — look up the task for a review-system change id and
* apply the appropriate lifecycle step. All three are no-ops for unknown or terminal tasks;
Expand Down Expand Up @@ -886,7 +881,6 @@ export class Orchestrator {
handle,
projectPushTargets,
commitMessage,
context.ticketUrl ?? "",
agentResult.commits,
projectRecord.gerritTopicOverride
);
Expand Down Expand Up @@ -1465,7 +1459,6 @@ export class Orchestrator {
handle: WorkspaceHandle,
pushTargets: import("../interfaces.js").ProjectPushTargetRecord[],
fallbackCommitMessage: string,
ticketUrl: string,
agentCommits: CommitDescriptor[] | undefined = undefined,
topicOverride: string | null = null
): Promise<void> {
Expand Down Expand Up @@ -1537,15 +1530,12 @@ export class Orchestrator {

const volumeOpts = { volumeName: handle.volumeName, image: handle.containerImage, subPath: target.localPath };
try {
const commitMsg = this.appendTicketFooter(fallbackCommitMessage, task.ticketId, ticketUrl, task.ticketSourceLabel);
const subjectHash = createHash("sha1").update(fallbackCommitMessage.split("\n")[0] ?? "").digest("hex");

let pushResult;
if (vcsConnector.pushDirect) {
pushResult = await vcsConnector.pushDirect(handle.hostWorkspacePath, ref, topic, volumeOpts);
} else {
pushResult = await vcsConnector.push(handle.hostWorkspacePath, ref, commitMsg, undefined, volumeOpts);
if (!vcsConnector.pushDirect) {
throw new Error(`VCS connector for ${reviewSystemLabel} does not implement pushDirect`);
}
const pushResult = await vcsConnector.pushDirect(handle.hostWorkspacePath, ref, topic, volumeOpts);
Comment on lines 1533 to +1538

// Use Change-Ids from agent commits when available — this is the source of truth
// for multi-commit pushes where pushResult.changeId only reflects HEAD (the last commit).
Expand Down Expand Up @@ -1825,42 +1815,6 @@ export class Orchestrator {
return `feat: ${subject}`;
}

/**
* Appends a ticket reference footer to a conventional commit message.
*
* The footer is formatted using the modular ticketFooterFormatter utility,
* which supports any configured ticketing system in ID format: "System: #ticketId"
*
* Footer is skipped if the message already contains an existing footer
* (idempotent — safe to call multiple times).
*/
private appendTicketFooter(message: string, ticketId: string, ticketUrl: string, ticketSourceLabel?: string): string {
if (hasTicketFooter(message, ticketSourceLabel)) {
return message;
}

const footer = this.buildTicketFooter(ticketId, ticketUrl, ticketSourceLabel);
if (!footer) return message;

return `${message.trimEnd()}\n\n${footer}\n`;
}

/**
* Builds the footer line using the modular ticketFooterFormatter utility.
* Returns null if no footer is applicable (unknown system or missing data).
*
* All supported systems use ID format: "System: #ticketId"
* This is simple, consistent, and works across all review systems (GitLab, Gerrit, etc.)
* and is future-proof for new ticketing systems.
*
* To add support for a new ticketing system:
* 1. Add configuration to TICKET_SYSTEM_CONFIG in ticketFooterFormatter.ts
* 2. No changes needed here — automatically supported.
*/
private buildTicketFooter(ticketId: string, ticketUrl: string, ticketSourceLabel?: string): string | null {
return formatTicketFooter(ticketId, ticketUrl, ticketSourceLabel);
}

/** Extract acceptance-criteria lines (checklist or numbered items) from a ticket description. */
private extractAcceptanceCriteria(description: string): string[] {
return description
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ export function getCapabilityIntake(
}

/** Return the technical (non-domain) capabilities derived from descriptor hooks. */
export function getProviderTechnicalCapabilities(descriptor: ProviderDescriptor): TechnicalCapability[] {
function getProviderTechnicalCapabilities(descriptor: ProviderDescriptor): TechnicalCapability[] {
const technical: TechnicalCapability[] = [];
if (descriptor.oauth) technical.push("oauth");
if (descriptor.discoverResources) technical.push("discovery");
Expand Down
Loading
Loading