mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-08-28 02:44:59 +00:00
feat: add codex cloud delegation (#315)
This commit is contained in:
parent
42fb759cca
commit
7bf8df6666
24 changed files with 490 additions and 19 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [4.2.2] - 2026-05-06
|
||||
|
||||
### Added
|
||||
|
||||
- Codex Cloud delegation through GitHub issue, issue-comment, and PR-comment workflows.
|
||||
- `POST /api/github/codex/delegate` for creating scoped `@codex` GitHub delegation prompts from Veritas tasks.
|
||||
- Disabled-by-default `codex-cloud` agent profile and `codex-cloud` provider metadata support.
|
||||
- Cloud delegation attempt metadata with `cloudUrl` and `cloudTarget` fields.
|
||||
- Task comments that link successful Codex Cloud delegations back to the GitHub artifact.
|
||||
- Frontend API hook for Codex Cloud delegation.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated workspace package versions and README badge from `4.2.1` to `4.2.2`.
|
||||
- Documented Codex Cloud delegation as implemented while workflow steps, review actions, and richer Settings checks remain next in the v4.2 patch train.
|
||||
|
||||
## [4.2.1] - 2026-05-06
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Built for developers who want a visual Kanban board that works with autonomous c
|
|||
|
||||
[](https://github.com/BradGroux/veritas-kanban/actions/workflows/ci.yml)
|
||||
[](LICENSE)
|
||||
[](CHANGELOG.md)
|
||||
[](CHANGELOG.md)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](CONTRIBUTING.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@veritas-kanban/cli",
|
||||
"version": "4.2.1",
|
||||
"version": "4.2.2",
|
||||
"description": "CLI for Veritas Kanban task management",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Release target: **Veritas Kanban v4.2**
|
||||
|
||||
This roadmap tracks the first-class OpenAI Codex integration work for Veritas Kanban. v4.2 now includes local `codex exec` execution and SDK-backed local Codex sessions. The remaining work expands that foundation into cloud delegation, workflow-engine execution, review automation, and deeper Settings health checks.
|
||||
This roadmap tracks the first-class OpenAI Codex integration work for Veritas Kanban. v4.2 now includes local `codex exec` execution, SDK-backed local Codex sessions, and GitHub-native Codex Cloud delegation. The remaining work expands that foundation into workflow-engine execution, review automation, and deeper Settings health checks.
|
||||
|
||||
Companion docs:
|
||||
|
||||
|
|
@ -51,11 +51,25 @@ const thread = codex.startThread({
|
|||
|
||||
### Codex Cloud Delegation
|
||||
|
||||
Cloud delegation should start through GitHub-native workflows: Veritas can create or comment on GitHub issues/PRs with scoped `@codex` prompts, then sync links and outcomes back into the task. If official cloud APIs become available, they can be added behind the same provider boundary.
|
||||
Cloud delegation starts through GitHub-native workflows: Veritas can create or comment on GitHub issues/PRs with scoped `@codex` prompts, then sync links and outcomes back into the task. If official cloud APIs become available, they can be added behind the same provider boundary.
|
||||
|
||||
API shape:
|
||||
|
||||
```http
|
||||
POST /api/github/codex/delegate
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"taskId": "task_...",
|
||||
"target": "issue",
|
||||
"model": "gpt-5.5"
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture Direction
|
||||
|
||||
v4.2 starts with a focused provider seam inside the existing agent service instead of a full rewrite. `codex` agents resolve to the local Codex CLI runner, `codex-sdk` agents resolve to the SDK session runner, and existing agents keep the OpenClaw request-file behavior. A fuller provider abstraction is still tracked for workflow and review execution.
|
||||
v4.2 starts with a focused provider seam inside the existing agent service instead of a full rewrite. `codex` agents resolve to the local Codex CLI runner, `codex-sdk` agents resolve to the SDK session runner, `codex-cloud` uses GitHub-native delegation, and existing agents keep the OpenClaw request-file behavior. A fuller provider abstraction is still tracked for workflow and review execution.
|
||||
|
||||
Expected long-term provider capabilities:
|
||||
|
||||
|
|
@ -73,6 +87,7 @@ The provider abstraction should support:
|
|||
- OpenClaw compatibility through an OpenClaw provider adapter.
|
||||
- Codex CLI through a local process provider.
|
||||
- Codex SDK through the implemented thread/session provider.
|
||||
- Codex Cloud through GitHub issue/PR delegation.
|
||||
- Future providers without route-level branching.
|
||||
|
||||
## Telemetry And Logs
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ Expected behavior:
|
|||
- provider mode `codex-cloud`
|
||||
- delegation timestamp
|
||||
- sync status
|
||||
- cloud attempt metadata (`cloudTarget`, `cloudUrl`)
|
||||
|
||||
5. Human reviews the Codex Cloud PR and links it back to the Veritas task.
|
||||
|
||||
|
|
|
|||
|
|
@ -295,18 +295,18 @@ First-class support for autonomous coding agents.
|
|||
|
||||
## OpenAI Codex Integration (v4.2)
|
||||
|
||||
v4.2 adds first-class OpenAI Codex support: local `codex exec` attempts plus SDK-backed Codex sessions that run in task worktrees and report status, logs, completion, telemetry, and durable thread IDs through the existing Veritas agent lifecycle.
|
||||
v4.2 adds first-class OpenAI Codex support: local `codex exec` attempts, SDK-backed Codex sessions, and GitHub-native Codex Cloud delegation through the existing Veritas task lifecycle.
|
||||
|
||||
Implemented:
|
||||
|
||||
- **Codex CLI provider** — Runs `codex exec --json` in the task worktree, maps JSONL/stdout/stderr into Veritas attempt logs, records final summaries, and emits run/token telemetry when available.
|
||||
- **Codex agent defaults** — Adds a disabled-by-default OpenAI Codex agent profile with `codex exec --sandbox workspace-write --json`.
|
||||
- **Codex SDK provider** — Uses `@openai/codex-sdk` to start durable local Codex threads, stream SDK events into attempt logs, persist `threadId` on attempts, and emit token telemetry from completed turns.
|
||||
- **Codex Cloud delegation** — Creates scoped `@codex` GitHub issue/PR prompts, records cloud attempt metadata, and links the GitHub artifact back to the Veritas task.
|
||||
- **Config migration** — Existing configs receive the missing built-in Codex agent without overwriting customized agents.
|
||||
|
||||
Still planned:
|
||||
|
||||
- **Codex Cloud delegation** — Creates GitHub issue/PR delegation prompts for cloud Codex work and links GitHub artifacts back to Veritas tasks.
|
||||
- **Codex review actions** — Uses Codex to review task branches, PR diffs, and failed changes.
|
||||
- **Workflow Codex steps** — Executes workflow-engine agent steps through the same provider abstraction used by local task starts.
|
||||
- **Richer Settings health checks** — Detects Codex install, auth, SDK availability, and profile readiness from Settings.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# SOP: OpenAI Codex Integration
|
||||
|
||||
Use this playbook when Veritas Kanban delegates work to OpenAI Codex. v4.2 includes local Codex CLI execution through `codex exec` and SDK-backed local Codex sessions; Cloud delegation, workflow-engine execution, review actions, and richer Settings checks continue through the v4.2 patch train.
|
||||
Use this playbook when Veritas Kanban delegates work to OpenAI Codex. v4.2 includes local Codex CLI execution through `codex exec`, SDK-backed local Codex sessions, and GitHub-native Codex Cloud delegation; workflow-engine execution, review actions, and richer Settings checks continue through the v4.2 patch train.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -133,6 +133,14 @@ Veritas persists the Codex thread ID in attempt metadata:
|
|||
|
||||
Use cloud delegation when the desired output is a GitHub issue/PR workflow rather than direct local worktree execution.
|
||||
|
||||
Veritas endpoint:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3001/api/github/codex/delegate \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"taskId":"task_123","target":"issue"}'
|
||||
```
|
||||
|
||||
Recommended prompt pattern:
|
||||
|
||||
```text
|
||||
|
|
@ -153,7 +161,19 @@ Veritas context:
|
|||
Please open a PR and include a concise implementation summary, tests run, and any follow-up risks.
|
||||
```
|
||||
|
||||
Veritas should link the GitHub artifact back to the task and track cloud delegation as a provider attempt, even if execution happens outside the local runtime.
|
||||
Veritas links the GitHub artifact back to the task and tracks cloud delegation as a provider attempt, even though execution happens outside the local runtime.
|
||||
|
||||
Attempt metadata:
|
||||
|
||||
```json
|
||||
{
|
||||
"agent": "codex-cloud",
|
||||
"provider": "codex-cloud",
|
||||
"status": "pending",
|
||||
"cloudTarget": "issue",
|
||||
"cloudUrl": "https://github.com/owner/repo/issues/123"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@veritas-kanban/mcp",
|
||||
"version": "4.2.1",
|
||||
"version": "4.2.2",
|
||||
"description": "MCP server for Veritas Kanban",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "veritas-kanban",
|
||||
"version": "4.2.1",
|
||||
"version": "4.2.2",
|
||||
"private": true,
|
||||
"description": "Local-first task management and AI agent orchestration platform",
|
||||
"author": "Brad Groux <brad@digitalmeld.io>",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@veritas-kanban/server",
|
||||
"version": "4.2.1",
|
||||
"version": "4.2.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
|
|
|||
|
|
@ -57,6 +57,13 @@ describe('ConfigService', () => {
|
|||
provider: 'codex-sdk',
|
||||
enabled: false,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'codex-cloud',
|
||||
name: 'OpenAI Codex Cloud',
|
||||
command: 'gh',
|
||||
provider: 'codex-cloud',
|
||||
enabled: false,
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
|
@ -116,6 +123,11 @@ describe('ConfigService', () => {
|
|||
command: 'codex',
|
||||
provider: 'codex-sdk',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: 'codex-cloud',
|
||||
command: 'gh',
|
||||
provider: 'codex-cloud',
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -202,6 +202,15 @@ describe('Config Routes (actual module)', () => {
|
|||
provider: 'codex-sdk',
|
||||
model: 'gpt-5.5',
|
||||
},
|
||||
{
|
||||
type: 'codex-cloud',
|
||||
name: 'OpenAI Codex Cloud',
|
||||
command: 'gh',
|
||||
args: [],
|
||||
enabled: true,
|
||||
provider: 'codex-cloud',
|
||||
model: 'gpt-5.5',
|
||||
},
|
||||
];
|
||||
mockConfigService.updateAgents.mockResolvedValue({ agents });
|
||||
const res = await request(app).put('/api/config/agents').send(agents);
|
||||
|
|
|
|||
78
server/src/__tests__/routes/github-codex-cloud.test.ts
Normal file
78
server/src/__tests__/routes/github-codex-cloud.test.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { errorHandler } from '../../middleware/error-handler.js';
|
||||
|
||||
const { mockGithubService } = vi.hoisted(() => ({
|
||||
mockGithubService: {
|
||||
checkGhCli: vi.fn(),
|
||||
createPR: vi.fn(),
|
||||
openPRInBrowser: vi.fn(),
|
||||
delegateToCodexCloud: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../services/github-service.js', () => ({
|
||||
GitHubService: function () {
|
||||
return mockGithubService;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../services/github-sync-service.js', () => ({
|
||||
getGitHubSyncService: () => ({
|
||||
sync: vi.fn(),
|
||||
getSyncState: vi.fn(),
|
||||
getConfig: vi.fn(),
|
||||
updateConfig: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
import githubRoutes from '../../routes/github.js';
|
||||
|
||||
describe('GitHub Codex Cloud routes', () => {
|
||||
let app: express.Express;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
app = express();
|
||||
app.use(express.json());
|
||||
app.use('/api/github', githubRoutes);
|
||||
app.use(errorHandler);
|
||||
});
|
||||
|
||||
it('delegates a task to Codex Cloud through GitHub', async () => {
|
||||
mockGithubService.delegateToCodexCloud.mockResolvedValue({
|
||||
taskId: 'task_123',
|
||||
attemptId: 'attempt_cloud',
|
||||
target: 'issue',
|
||||
url: 'https://github.com/owner/repo/issues/42',
|
||||
number: 42,
|
||||
repo: 'owner/repo',
|
||||
prompt: '@codex Please work on this task.',
|
||||
});
|
||||
|
||||
const response = await request(app).post('/api/github/codex/delegate').send({
|
||||
taskId: 'task_123',
|
||||
target: 'issue',
|
||||
model: 'gpt-5.5',
|
||||
});
|
||||
|
||||
expect(response.status).toBe(201);
|
||||
expect(response.body.url).toBe('https://github.com/owner/repo/issues/42');
|
||||
expect(mockGithubService.delegateToCodexCloud).toHaveBeenCalledWith({
|
||||
taskId: 'task_123',
|
||||
target: 'issue',
|
||||
model: 'gpt-5.5',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects invalid Codex Cloud targets', async () => {
|
||||
const response = await request(app).post('/api/github/codex/delegate').send({
|
||||
taskId: 'task_123',
|
||||
target: 'discussion',
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(mockGithubService.delegateToCodexCloud).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -26,7 +26,7 @@ const agentSchema = z.object({
|
|||
command: z.string().min(1),
|
||||
args: z.array(z.string()),
|
||||
enabled: z.boolean(),
|
||||
provider: z.enum(['openclaw', 'codex-cli', 'codex-sdk', 'custom']).optional(),
|
||||
provider: z.enum(['openclaw', 'codex-cli', 'codex-sdk', 'codex-cloud', 'custom']).optional(),
|
||||
model: z.string().optional(),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,14 @@ const createPRSchema = z.object({
|
|||
draft: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const codexCloudDelegateSchema = z.object({
|
||||
taskId: z.string().min(1),
|
||||
target: z.enum(['issue', 'issue-comment', 'pr-comment']).optional(),
|
||||
title: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
});
|
||||
|
||||
const syncConfigSchema = z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
repo: z.string().min(1).optional(),
|
||||
|
|
@ -64,6 +72,25 @@ router.post(
|
|||
})
|
||||
);
|
||||
|
||||
// POST /api/github/codex/delegate - Delegate a task to Codex Cloud through GitHub
|
||||
router.post(
|
||||
'/codex/delegate',
|
||||
asyncHandler(async (req, res) => {
|
||||
let input;
|
||||
try {
|
||||
input = codexCloudDelegateSchema.parse(req.body);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
throw new ValidationError('Validation failed', error.issues);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
const delegation = await githubService.delegateToCodexCloud(input);
|
||||
res.status(201).json(delegation);
|
||||
})
|
||||
);
|
||||
|
||||
// ── GitHub Issues Sync routes ───────────────────────────────
|
||||
|
||||
// POST /api/github/sync - Trigger a manual sync
|
||||
|
|
|
|||
|
|
@ -70,6 +70,14 @@ const DEFAULT_CONFIG: AppConfig = {
|
|||
enabled: false,
|
||||
provider: 'codex-sdk',
|
||||
},
|
||||
{
|
||||
type: 'codex-cloud',
|
||||
name: 'OpenAI Codex Cloud',
|
||||
command: 'gh',
|
||||
args: [],
|
||||
enabled: false,
|
||||
provider: 'codex-cloud',
|
||||
},
|
||||
],
|
||||
defaultAgent: 'claude-code',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { exec, execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { ConfigService } from './config-service.js';
|
||||
import { TaskService } from './task-service.js';
|
||||
import { getBreaker } from './circuit-registry.js';
|
||||
import type { AgentType, Task } from '@veritas-kanban/shared';
|
||||
import { createLogger } from '../lib/logger.js';
|
||||
const log = createLogger('github-service');
|
||||
|
||||
|
|
@ -27,6 +29,26 @@ export interface PRInfo {
|
|||
baseBranch: string;
|
||||
}
|
||||
|
||||
export type CodexCloudTarget = 'issue' | 'issue-comment' | 'pr-comment';
|
||||
|
||||
export interface CodexCloudDelegationInput {
|
||||
taskId: string;
|
||||
target?: CodexCloudTarget;
|
||||
title?: string;
|
||||
prompt?: string;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface CodexCloudDelegationResult {
|
||||
taskId: string;
|
||||
attemptId: string;
|
||||
target: CodexCloudTarget;
|
||||
url: string;
|
||||
number?: number;
|
||||
repo: string;
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
export class GitHubService {
|
||||
private configService: ConfigService;
|
||||
private taskService: TaskService;
|
||||
|
|
@ -115,6 +137,115 @@ export class GitHubService {
|
|||
}
|
||||
}
|
||||
|
||||
async delegateToCodexCloud(
|
||||
input: CodexCloudDelegationInput
|
||||
): Promise<CodexCloudDelegationResult> {
|
||||
const task = await this.taskService.getTask(input.taskId);
|
||||
if (!task) {
|
||||
throw new Error('Task not found');
|
||||
}
|
||||
|
||||
if (!task.git?.repo) {
|
||||
throw new Error('Task must have a repository configured for Codex Cloud delegation');
|
||||
}
|
||||
|
||||
const config = await this.configService.getConfig();
|
||||
const repoConfig = config.repos.find((r) => r.name === task.git!.repo);
|
||||
if (!repoConfig) {
|
||||
throw new Error(`Repository "${task.git.repo}" not found in config`);
|
||||
}
|
||||
|
||||
const ghStatus = await this.checkGhCli();
|
||||
if (!ghStatus.installed) {
|
||||
throw new Error('GitHub CLI (gh) is not installed. Install it with: brew install gh');
|
||||
}
|
||||
if (!ghStatus.authenticated) {
|
||||
throw new Error('GitHub CLI is not authenticated. Run: gh auth login');
|
||||
}
|
||||
|
||||
const repoPath = this.expandPath(repoConfig.path);
|
||||
const repo = await this.getRepoNameWithOwner(repoPath);
|
||||
const target = input.target || (task.git.prNumber || task.git.prUrl ? 'pr-comment' : 'issue');
|
||||
const prompt = input.prompt || this.buildCodexCloudPrompt(task, repo);
|
||||
const title = input.title || `Codex: ${task.title}`;
|
||||
const ghBreaker = getBreaker('github');
|
||||
|
||||
let url = '';
|
||||
let number: number | undefined;
|
||||
|
||||
if (target === 'issue') {
|
||||
const { stdout } = await ghBreaker.execute(() =>
|
||||
execFileAsync('gh', ['issue', 'create', '--title', title, '--body', prompt], {
|
||||
cwd: repoPath,
|
||||
})
|
||||
);
|
||||
url = stdout.trim();
|
||||
number = this.extractIssueNumber(url);
|
||||
if (number) {
|
||||
await this.taskService.updateTask(input.taskId, {
|
||||
github: { issueNumber: number, repo, url },
|
||||
});
|
||||
}
|
||||
} else if (target === 'issue-comment') {
|
||||
if (!task.github?.issueNumber) {
|
||||
throw new Error('Task must be linked to a GitHub issue for issue-comment delegation');
|
||||
}
|
||||
const issueRef = String(task.github.issueNumber);
|
||||
const { stdout } = await ghBreaker.execute(() =>
|
||||
execFileAsync('gh', ['issue', 'comment', issueRef, '--body', prompt], { cwd: repoPath })
|
||||
);
|
||||
const issue = await this.getIssueInfo(repoPath, issueRef);
|
||||
url = stdout.trim() || issue.url;
|
||||
number = issue.number;
|
||||
} else {
|
||||
const prRef = task.git.prNumber ? String(task.git.prNumber) : task.git.prUrl;
|
||||
if (!prRef) {
|
||||
throw new Error('Task must have a GitHub PR for pr-comment delegation');
|
||||
}
|
||||
const { stdout } = await ghBreaker.execute(() =>
|
||||
execFileAsync('gh', ['pr', 'comment', prRef, '--body', prompt], { cwd: repoPath })
|
||||
);
|
||||
const pr = await this.getPRInfo(repoPath, prRef);
|
||||
url = stdout.trim() || pr.url;
|
||||
number = pr.number;
|
||||
}
|
||||
|
||||
const attemptId = `attempt_${nanoid(8)}`;
|
||||
const now = new Date().toISOString();
|
||||
const cloudComment = {
|
||||
id: `comment_${nanoid(8)}`,
|
||||
author: 'veritas',
|
||||
text: `Delegated to Codex Cloud via GitHub ${target}: ${url}`,
|
||||
timestamp: now,
|
||||
};
|
||||
|
||||
await this.taskService.updateTask(input.taskId, {
|
||||
status: 'in-progress',
|
||||
agent: 'codex-cloud',
|
||||
attempt: {
|
||||
id: attemptId,
|
||||
agent: 'codex-cloud' as AgentType,
|
||||
status: 'pending',
|
||||
started: now,
|
||||
provider: 'codex-cloud',
|
||||
model: input.model,
|
||||
cloudUrl: url,
|
||||
cloudTarget: target,
|
||||
},
|
||||
comments: [...(task.comments || []), cloudComment],
|
||||
});
|
||||
|
||||
return {
|
||||
taskId: input.taskId,
|
||||
attemptId,
|
||||
target,
|
||||
url,
|
||||
number,
|
||||
repo,
|
||||
prompt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GitHub PR for a task
|
||||
*/
|
||||
|
|
@ -264,6 +395,87 @@ export class GitHubService {
|
|||
return lines.join('\n');
|
||||
}
|
||||
|
||||
private buildCodexCloudPrompt(task: Task, repo: string): string {
|
||||
const lines = [
|
||||
'@codex Please work on this Veritas Kanban task.',
|
||||
'',
|
||||
`Task: ${task.id} - ${task.title}`,
|
||||
`Repository: ${repo}`,
|
||||
`Type: ${task.type}`,
|
||||
`Priority: ${task.priority}`,
|
||||
];
|
||||
|
||||
if (task.project) lines.push(`Project: ${task.project}`);
|
||||
if (task.sprint) lines.push(`Sprint: ${task.sprint}`);
|
||||
if (task.git?.baseBranch) lines.push(`Base branch: ${task.git.baseBranch}`);
|
||||
if (task.git?.branch) lines.push(`Working branch: ${task.git.branch}`);
|
||||
|
||||
lines.push('', 'Task description:', task.description || 'No description provided.', '');
|
||||
lines.push('Acceptance criteria:');
|
||||
if (task.verificationSteps?.length) {
|
||||
for (const step of task.verificationSteps) lines.push(`- ${step.description}`);
|
||||
} else {
|
||||
lines.push('- Implement the task as described.');
|
||||
lines.push('- Run relevant checks and summarize the result.');
|
||||
lines.push('- Open or update a GitHub PR with the completed work.');
|
||||
}
|
||||
|
||||
lines.push('', 'When finished:');
|
||||
lines.push('- Link the PR or final artifact back to this GitHub thread.');
|
||||
lines.push('- Summarize changed files, validation performed, and any follow-up risks.');
|
||||
lines.push('- Keep Veritas Kanban as the source of truth for task status.');
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
private async getRepoNameWithOwner(repoPath: string): Promise<string> {
|
||||
try {
|
||||
const { stdout } = await execFileAsync('gh', ['repo', 'view', '--json', 'nameWithOwner'], {
|
||||
cwd: repoPath,
|
||||
});
|
||||
const parsed = JSON.parse(stdout) as { nameWithOwner?: string };
|
||||
if (parsed.nameWithOwner) return parsed.nameWithOwner;
|
||||
} catch {
|
||||
// Fall back to the origin remote when gh cannot resolve repo metadata.
|
||||
}
|
||||
|
||||
const remote = await this.getGitHubRemote(repoPath);
|
||||
const match = remote?.match(/github\.com[:/](.+?\/.+?)(?:\.git)?$/);
|
||||
if (match?.[1]) return match[1];
|
||||
throw new Error('Could not determine GitHub owner/repo for Codex Cloud delegation');
|
||||
}
|
||||
|
||||
private async getIssueInfo(
|
||||
repoPath: string,
|
||||
issueRef: string
|
||||
): Promise<{ url: string; number?: number }> {
|
||||
const { stdout } = await execFileAsync(
|
||||
'gh',
|
||||
['issue', 'view', issueRef, '--json', 'url,number'],
|
||||
{
|
||||
cwd: repoPath,
|
||||
}
|
||||
);
|
||||
const parsed = JSON.parse(stdout) as { url: string; number?: number };
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private async getPRInfo(
|
||||
repoPath: string,
|
||||
prRef: string
|
||||
): Promise<{ url: string; number?: number }> {
|
||||
const { stdout } = await execFileAsync('gh', ['pr', 'view', prRef, '--json', 'url,number'], {
|
||||
cwd: repoPath,
|
||||
});
|
||||
const parsed = JSON.parse(stdout) as { url: string; number?: number };
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private extractIssueNumber(url: string): number | undefined {
|
||||
const match = url.match(/\/issues\/(\d+)/);
|
||||
return match ? Number(match[1]) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open PR in browser
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@veritas-kanban/shared",
|
||||
"version": "4.2.1",
|
||||
"version": "4.2.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export interface AgentConfig {
|
|||
command: string;
|
||||
args: string[];
|
||||
enabled: boolean;
|
||||
provider?: 'openclaw' | 'codex-cli' | 'codex-sdk' | 'custom';
|
||||
provider?: 'openclaw' | 'codex-cli' | 'codex-sdk' | 'codex-cloud' | 'custom';
|
||||
model?: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,14 @@ export interface QaGateState {
|
|||
passedBy?: string; // Who approved (e.g. 'human', agent slug, user name)
|
||||
}
|
||||
/** Built-in agent types. Custom agents use any string slug. */
|
||||
export type BuiltInAgentType = 'claude-code' | 'amp' | 'copilot' | 'gemini' | 'codex' | 'veritas';
|
||||
export type BuiltInAgentType =
|
||||
| 'claude-code'
|
||||
| 'amp'
|
||||
| 'copilot'
|
||||
| 'gemini'
|
||||
| 'codex'
|
||||
| 'codex-cloud'
|
||||
| 'veritas';
|
||||
export type AgentType = BuiltInAgentType | (string & {});
|
||||
export type AttemptStatus = 'pending' | 'running' | 'complete' | 'failed';
|
||||
export type BlockedCategory = 'waiting-on-feedback' | 'technical-snag' | 'prerequisite' | 'other';
|
||||
|
|
@ -43,6 +50,8 @@ export interface TaskAttempt {
|
|||
provider?: string;
|
||||
model?: string;
|
||||
threadId?: string;
|
||||
cloudUrl?: string;
|
||||
cloudTarget?: string;
|
||||
}
|
||||
|
||||
export interface Subtask {
|
||||
|
|
@ -165,6 +174,7 @@ export const ALLOWED_MIME_TYPES = [
|
|||
export interface TaskGitHub {
|
||||
issueNumber: number;
|
||||
repo: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@veritas-kanban/web",
|
||||
"version": "4.2.1",
|
||||
"version": "4.2.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { api, type GitHubStatus, type PRInfo, type CreatePRInput } from '../lib/api';
|
||||
import {
|
||||
api,
|
||||
type GitHubStatus,
|
||||
type PRInfo,
|
||||
type CreatePRInput,
|
||||
type CodexCloudDelegationInput,
|
||||
type CodexCloudDelegationResult,
|
||||
} from '../lib/api';
|
||||
|
||||
export type { GitHubStatus, PRInfo, CreatePRInput };
|
||||
export type {
|
||||
GitHubStatus,
|
||||
PRInfo,
|
||||
CreatePRInput,
|
||||
CodexCloudDelegationInput,
|
||||
CodexCloudDelegationResult,
|
||||
};
|
||||
|
||||
/**
|
||||
* Check GitHub CLI status
|
||||
|
|
@ -38,3 +51,18 @@ export function useOpenPR() {
|
|||
mutationFn: (taskId) => api.github.openPR(taskId),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate a task to Codex Cloud via GitHub issue/PR workflows.
|
||||
*/
|
||||
export function useDelegateCodexCloud() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<CodexCloudDelegationResult, Error, CodexCloudDelegationInput>({
|
||||
mutationFn: (input) => api.github.delegateCodexCloud(input),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['tasks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['task', variables.taskId] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,18 @@ export const githubApi = {
|
|||
return handleResponse<PRInfo>(response);
|
||||
},
|
||||
|
||||
delegateCodexCloud: async (
|
||||
input: CodexCloudDelegationInput
|
||||
): Promise<CodexCloudDelegationResult> => {
|
||||
const response = await fetch(`${API_BASE}/github/codex/delegate`, {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
return handleResponse<CodexCloudDelegationResult>(response);
|
||||
},
|
||||
|
||||
openPR: async (taskId: string): Promise<void> => {
|
||||
const response = await fetch(`${API_BASE}/github/pr/${taskId}/open`, {
|
||||
credentials: 'include',
|
||||
|
|
@ -194,3 +206,23 @@ export interface CreatePRInput {
|
|||
targetBranch?: string;
|
||||
draft?: boolean;
|
||||
}
|
||||
|
||||
export type CodexCloudTarget = 'issue' | 'issue-comment' | 'pr-comment';
|
||||
|
||||
export interface CodexCloudDelegationInput {
|
||||
taskId: string;
|
||||
target?: CodexCloudTarget;
|
||||
title?: string;
|
||||
prompt?: string;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface CodexCloudDelegationResult {
|
||||
taskId: string;
|
||||
attemptId: string;
|
||||
target: CodexCloudTarget;
|
||||
url: string;
|
||||
number?: number;
|
||||
repo: string;
|
||||
prompt: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ export type {
|
|||
GitHubStatus,
|
||||
PRInfo,
|
||||
CreatePRInput,
|
||||
CodexCloudTarget,
|
||||
CodexCloudDelegationInput,
|
||||
CodexCloudDelegationResult,
|
||||
} from './diff';
|
||||
|
||||
export type {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue