holo-codex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/CONTRIBUTING.md +54 -0
- package/LICENSE +21 -0
- package/README.md +215 -0
- package/README.zh-CN.md +215 -0
- package/SECURITY.md +39 -0
- package/assets/brand/README.md +35 -0
- package/assets/brand/holo-codex-icon.svg +28 -0
- package/assets/brand/holo-codex-lockup.svg +49 -0
- package/assets/brand/holo-codex-mark.svg +33 -0
- package/assets/brand/holo-codex-plugin-card.png +0 -0
- package/assets/brand/holo-codex-plugin-card.svg +81 -0
- package/assets/brand/holo-codex-readme-hero.png +0 -0
- package/assets/brand/holo-codex-readme-hero.svg +140 -0
- package/assets/brand/holo-codex-social-preview.png +0 -0
- package/assets/brand/holo-codex-social-preview.svg +130 -0
- package/assets/brand/holo-codex-wordmark-options.svg +52 -0
- package/docs/checklists/agent-loop-first-delivery-audit.md +129 -0
- package/docs/examples/generic-loop-repo-hygiene.md +168 -0
- package/docs/install.md +190 -0
- package/docs/local-release-readiness.md +206 -0
- package/docs/release-checklist.md +144 -0
- package/docs/self-bootstrap.md +150 -0
- package/docs/trust-and-safety.md +45 -0
- package/package.json +83 -0
- package/plugins/autonomous-pr-loop/.codex-plugin/plugin.json +17 -0
- package/plugins/autonomous-pr-loop/.mcp.json +13 -0
- package/plugins/autonomous-pr-loop/bin/agent-loop.mjs +31 -0
- package/plugins/autonomous-pr-loop/core/artifacts.ts +164 -0
- package/plugins/autonomous-pr-loop/core/autonomy-policy.ts +206 -0
- package/plugins/autonomous-pr-loop/core/ci.ts +131 -0
- package/plugins/autonomous-pr-loop/core/cli-i18n.ts +123 -0
- package/plugins/autonomous-pr-loop/core/cli.ts +1413 -0
- package/plugins/autonomous-pr-loop/core/command-runner.ts +446 -0
- package/plugins/autonomous-pr-loop/core/command.ts +47 -0
- package/plugins/autonomous-pr-loop/core/config-editor.ts +140 -0
- package/plugins/autonomous-pr-loop/core/config.ts +293 -0
- package/plugins/autonomous-pr-loop/core/controller-host.ts +19 -0
- package/plugins/autonomous-pr-loop/core/dashboard-server.ts +536 -0
- package/plugins/autonomous-pr-loop/core/delivery-work-item.ts +217 -0
- package/plugins/autonomous-pr-loop/core/doctor.ts +335 -0
- package/plugins/autonomous-pr-loop/core/errors.ts +82 -0
- package/plugins/autonomous-pr-loop/core/gate-recovery.ts +176 -0
- package/plugins/autonomous-pr-loop/core/gates.ts +26 -0
- package/plugins/autonomous-pr-loop/core/generic-lifecycle.ts +399 -0
- package/plugins/autonomous-pr-loop/core/git.ts +213 -0
- package/plugins/autonomous-pr-loop/core/github.ts +269 -0
- package/plugins/autonomous-pr-loop/core/gitnexus.ts +90 -0
- package/plugins/autonomous-pr-loop/core/happy.ts +42 -0
- package/plugins/autonomous-pr-loop/core/hook-capture.ts +115 -0
- package/plugins/autonomous-pr-loop/core/hook-events.ts +22 -0
- package/plugins/autonomous-pr-loop/core/hook-installation.ts +85 -0
- package/plugins/autonomous-pr-loop/core/hook-observer.ts +84 -0
- package/plugins/autonomous-pr-loop/core/hook-policy.ts +423 -0
- package/plugins/autonomous-pr-loop/core/hook-router.ts +452 -0
- package/plugins/autonomous-pr-loop/core/index.ts +32 -0
- package/plugins/autonomous-pr-loop/core/local-install.ts +778 -0
- package/plugins/autonomous-pr-loop/core/locale.ts +60 -0
- package/plugins/autonomous-pr-loop/core/loop-shapes.ts +190 -0
- package/plugins/autonomous-pr-loop/core/mcp-controller.ts +1479 -0
- package/plugins/autonomous-pr-loop/core/notification-feed.ts +263 -0
- package/plugins/autonomous-pr-loop/core/plan-parser.ts +206 -0
- package/plugins/autonomous-pr-loop/core/plugin-paths.ts +32 -0
- package/plugins/autonomous-pr-loop/core/policy.ts +65 -0
- package/plugins/autonomous-pr-loop/core/pr-lifecycle.ts +464 -0
- package/plugins/autonomous-pr-loop/core/pr-selector.ts +284 -0
- package/plugins/autonomous-pr-loop/core/profiles.ts +439 -0
- package/plugins/autonomous-pr-loop/core/redaction.ts +17 -0
- package/plugins/autonomous-pr-loop/core/repo-root.ts +22 -0
- package/plugins/autonomous-pr-loop/core/review-comments.ts +77 -0
- package/plugins/autonomous-pr-loop/core/scope-guard.ts +179 -0
- package/plugins/autonomous-pr-loop/core/state-machine.ts +828 -0
- package/plugins/autonomous-pr-loop/core/state-types.ts +130 -0
- package/plugins/autonomous-pr-loop/core/storage.ts +2527 -0
- package/plugins/autonomous-pr-loop/core/types.ts +567 -0
- package/plugins/autonomous-pr-loop/core/worker-events.ts +412 -0
- package/plugins/autonomous-pr-loop/core/worker-policy.ts +72 -0
- package/plugins/autonomous-pr-loop/core/worker-prompts.ts +182 -0
- package/plugins/autonomous-pr-loop/core/worker.ts +809 -0
- package/plugins/autonomous-pr-loop/core/workflow-board.ts +1515 -0
- package/plugins/autonomous-pr-loop/hooks/dist/permission-request.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/post-compact.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/post-tool-use.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/pre-compact.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/pre-tool-use.js +3460 -0
- package/plugins/autonomous-pr-loop/hooks/dist/session-start.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/stop.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/dist/user-prompt-submit.js +2462 -0
- package/plugins/autonomous-pr-loop/hooks/hooks.json +106 -0
- package/plugins/autonomous-pr-loop/hooks/observe-runner.ts +25 -0
- package/plugins/autonomous-pr-loop/hooks/permission-request.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/post-compact.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/post-tool-use.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/pre-compact.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/pre-tool-use.ts +44 -0
- package/plugins/autonomous-pr-loop/hooks/session-start.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/stop.ts +4 -0
- package/plugins/autonomous-pr-loop/hooks/user-prompt-submit.ts +4 -0
- package/plugins/autonomous-pr-loop/mcp-server/src/index.ts +87 -0
- package/plugins/autonomous-pr-loop/mcp-server/src/tools.ts +205 -0
- package/plugins/autonomous-pr-loop/package.json +9 -0
- package/plugins/autonomous-pr-loop/schemas/config.schema.json +74 -0
- package/plugins/autonomous-pr-loop/schemas/marketplace.schema.json +46 -0
- package/plugins/autonomous-pr-loop/schemas/plugin.schema.json +32 -0
- package/plugins/autonomous-pr-loop/schemas/state.schema.json +19 -0
- package/plugins/autonomous-pr-loop/schemas/worker-event.schema.json +19 -0
- package/plugins/autonomous-pr-loop/schemas/worker-result.schema.json +58 -0
- package/plugins/autonomous-pr-loop/scripts/agent-loop.ts +44 -0
- package/plugins/autonomous-pr-loop/skills/autonomous-pr-loop/SKILL.md +26 -0
- package/plugins/autonomous-pr-loop/skills/autonomous-pr-loop/agents/openai.yaml +6 -0
- package/plugins/autonomous-pr-loop/ui/index.html +26 -0
- package/plugins/autonomous-pr-loop/ui/public/favicon.svg +7 -0
- package/plugins/autonomous-pr-loop/ui/src/api.ts +639 -0
- package/plugins/autonomous-pr-loop/ui/src/app.tsx +238 -0
- package/plugins/autonomous-pr-loop/ui/src/components/ActivityBadge.tsx +31 -0
- package/plugins/autonomous-pr-loop/ui/src/components/BrandMark.tsx +36 -0
- package/plugins/autonomous-pr-loop/ui/src/components/Collapsible.tsx +6 -0
- package/plugins/autonomous-pr-loop/ui/src/components/CommandPreview.tsx +15 -0
- package/plugins/autonomous-pr-loop/ui/src/components/ConfigEditor.tsx +389 -0
- package/plugins/autonomous-pr-loop/ui/src/components/EmptyState.tsx +10 -0
- package/plugins/autonomous-pr-loop/ui/src/components/ErrorState.tsx +12 -0
- package/plugins/autonomous-pr-loop/ui/src/components/List.tsx +7 -0
- package/plugins/autonomous-pr-loop/ui/src/components/MetricRow.tsx +6 -0
- package/plugins/autonomous-pr-loop/ui/src/components/ResponsiveTable.tsx +65 -0
- package/plugins/autonomous-pr-loop/ui/src/components/RiskBadge.tsx +10 -0
- package/plugins/autonomous-pr-loop/ui/src/components/StatusBadge.tsx +29 -0
- package/plugins/autonomous-pr-loop/ui/src/components/TopMetric.tsx +10 -0
- package/plugins/autonomous-pr-loop/ui/src/fixtures.ts +1152 -0
- package/plugins/autonomous-pr-loop/ui/src/i18n.ts +1105 -0
- package/plugins/autonomous-pr-loop/ui/src/main.tsx +14 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/CommandCenter.tsx +470 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/CommandCenterParts.tsx +276 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/agent-timeline/AgentTimelineView.tsx +73 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/artifact-viewer/ArtifactViewer.tsx +44 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/dry-run-preview/DryRunPreview.tsx +66 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/event-ledger/EventLedger.tsx +17 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/gate-center/GateCenter.tsx +34 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/mission-control/MissionControl.tsx +104 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/mission-control/WorkflowBoard.tsx +577 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/notifications/NotificationsView.tsx +30 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/plan-navigator/PlanNavigator.tsx +19 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/policy-config/PolicyConfig.tsx +22 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/pr-inbox/PrInbox.tsx +26 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/recovery-center/RecoveryCenter.tsx +125 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/scope-guard/ScopeGuard.tsx +16 -0
- package/plugins/autonomous-pr-loop/ui/src/pages/worker-runs/WorkerRuns.tsx +39 -0
- package/plugins/autonomous-pr-loop/ui/src/styles.css +2673 -0
- package/plugins/autonomous-pr-loop/ui/src/theme.ts +57 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"PreToolUse": [
|
|
3
|
+
{
|
|
4
|
+
"matcher": "*",
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "node ./hooks/dist/pre-tool-use.js",
|
|
9
|
+
"timeout": 1000,
|
|
10
|
+
"statusMessage": "Checking agent-loop command policy"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"PostToolUse": [
|
|
16
|
+
{
|
|
17
|
+
"matcher": "*",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "node ./hooks/dist/post-tool-use.js",
|
|
22
|
+
"timeout": 500,
|
|
23
|
+
"statusMessage": "Recording agent-loop PostToolUse event"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"UserPromptSubmit": [
|
|
29
|
+
{
|
|
30
|
+
"matcher": "*",
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "node ./hooks/dist/user-prompt-submit.js",
|
|
35
|
+
"timeout": 500,
|
|
36
|
+
"statusMessage": "Recording agent-loop UserPromptSubmit event"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"Stop": [
|
|
42
|
+
{
|
|
43
|
+
"matcher": "*",
|
|
44
|
+
"hooks": [
|
|
45
|
+
{
|
|
46
|
+
"type": "command",
|
|
47
|
+
"command": "node ./hooks/dist/stop.js",
|
|
48
|
+
"timeout": 500,
|
|
49
|
+
"statusMessage": "Recording agent-loop Stop event"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"SessionStart": [
|
|
55
|
+
{
|
|
56
|
+
"matcher": "*",
|
|
57
|
+
"hooks": [
|
|
58
|
+
{
|
|
59
|
+
"type": "command",
|
|
60
|
+
"command": "node ./hooks/dist/session-start.js",
|
|
61
|
+
"timeout": 500,
|
|
62
|
+
"statusMessage": "Recording agent-loop SessionStart event"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"PreCompact": [
|
|
68
|
+
{
|
|
69
|
+
"matcher": "*",
|
|
70
|
+
"hooks": [
|
|
71
|
+
{
|
|
72
|
+
"type": "command",
|
|
73
|
+
"command": "node ./hooks/dist/pre-compact.js",
|
|
74
|
+
"timeout": 500,
|
|
75
|
+
"statusMessage": "Recording agent-loop PreCompact event"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"PostCompact": [
|
|
81
|
+
{
|
|
82
|
+
"matcher": "*",
|
|
83
|
+
"hooks": [
|
|
84
|
+
{
|
|
85
|
+
"type": "command",
|
|
86
|
+
"command": "node ./hooks/dist/post-compact.js",
|
|
87
|
+
"timeout": 500,
|
|
88
|
+
"statusMessage": "Recording agent-loop PostCompact event"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"PermissionRequest": [
|
|
94
|
+
{
|
|
95
|
+
"matcher": "*",
|
|
96
|
+
"hooks": [
|
|
97
|
+
{
|
|
98
|
+
"type": "command",
|
|
99
|
+
"command": "node ./hooks/dist/permission-request.js",
|
|
100
|
+
"timeout": 500,
|
|
101
|
+
"statusMessage": "Recording agent-loop PermissionRequest event"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { observeCodexHook } from "../core/hook-observer.js";
|
|
3
|
+
import type { CodexHookEvent } from "../core/hook-events.js";
|
|
4
|
+
|
|
5
|
+
export function runObserveOnlyHook(event: CodexHookEvent): void {
|
|
6
|
+
const input = readStdinJson();
|
|
7
|
+
const repoRoot = process.env.AGENT_LOOP_REPO_ROOT;
|
|
8
|
+
const result = observeCodexHook(event, input, repoRoot);
|
|
9
|
+
if (result.error) {
|
|
10
|
+
process.stderr.write(`agent-loop ${event} observe failed: ${result.error}\n`);
|
|
11
|
+
}
|
|
12
|
+
process.stdout.write(`${JSON.stringify({ continue: true })}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readStdinJson(): unknown {
|
|
16
|
+
const text = readFileSync(0, "utf8");
|
|
17
|
+
if (text.trim().length === 0) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(text) as unknown;
|
|
22
|
+
} catch {
|
|
23
|
+
return { rawLength: text.length };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { evaluatePreToolUseHook, toCodexHookResponse, type HookPolicyDecision } from "../core/hook-policy.js";
|
|
4
|
+
|
|
5
|
+
const repoRoot = process.env.AGENT_LOOP_REPO_ROOT;
|
|
6
|
+
const input = readStdinJson();
|
|
7
|
+
const decision = safeEvaluate(input, repoRoot);
|
|
8
|
+
process.stdout.write(`${JSON.stringify(toCodexHookResponse(decision))}\n`);
|
|
9
|
+
|
|
10
|
+
function safeEvaluate(input: unknown, repoRoot: string | undefined): HookPolicyDecision {
|
|
11
|
+
try {
|
|
12
|
+
return evaluatePreToolUseHook(input, repoRoot);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return {
|
|
15
|
+
allow: false,
|
|
16
|
+
matchedPolicy: "hook_runner_error",
|
|
17
|
+
gate: "policy_violation",
|
|
18
|
+
blockedCommand: "<hook runner error>",
|
|
19
|
+
nextAction: "Run `agent-loop hooks doctor` and fix hook routing before retrying.",
|
|
20
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readStdinJson(): unknown {
|
|
26
|
+
const text = readFileSync(0, "utf8");
|
|
27
|
+
if (text.trim().length === 0) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(text) as unknown;
|
|
32
|
+
} catch {
|
|
33
|
+
const decision: HookPolicyDecision = {
|
|
34
|
+
allow: false,
|
|
35
|
+
matchedPolicy: "malformed_hook_payload",
|
|
36
|
+
gate: "policy_violation",
|
|
37
|
+
blockedCommand: "<unparseable hook payload>",
|
|
38
|
+
nextAction: "Retry the tool call with a valid PreToolUse payload.",
|
|
39
|
+
reason: "PreToolUse payload was not valid JSON."
|
|
40
|
+
};
|
|
41
|
+
process.stdout.write(`${JSON.stringify(toCodexHookResponse(decision))}\n`);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
import { resolveRepoRoot } from "../../core/repo-root.js";
|
|
4
|
+
import { callMcpTool, MCP_TOOLS } from "./tools.js";
|
|
5
|
+
|
|
6
|
+
interface JsonRpcRequest {
|
|
7
|
+
id?: string | number | null;
|
|
8
|
+
method?: string;
|
|
9
|
+
params?: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const repoRoot = resolveRepoRoot(process.env.AGENT_LOOP_REPO_ROOT ?? process.cwd());
|
|
13
|
+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
14
|
+
let initialized = false;
|
|
15
|
+
|
|
16
|
+
rl.on("line", (line) => {
|
|
17
|
+
void handleLine(line);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
async function handleLine(line: string): Promise<void> {
|
|
21
|
+
if (line.trim().length === 0) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let request: JsonRpcRequest;
|
|
25
|
+
try {
|
|
26
|
+
request = JSON.parse(line) as JsonRpcRequest;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
respond(null, undefined, errorPayload(-32700, "Parse error"));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (request.method === "initialize") {
|
|
34
|
+
initialized = true;
|
|
35
|
+
respond(request.id, {
|
|
36
|
+
protocolVersion: "2024-11-05",
|
|
37
|
+
capabilities: { tools: {} },
|
|
38
|
+
serverInfo: { name: "autonomous-pr-loop", version: "0.1.0" }
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (request.method === "ping") {
|
|
43
|
+
respond(request.id, {});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!initialized) {
|
|
47
|
+
respond(request.id, undefined, errorPayload(-32002, "MCP server is not initialized."));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (request.method === "tools/list") {
|
|
51
|
+
respond(request.id, { tools: MCP_TOOLS });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (request.method === "tools/call") {
|
|
55
|
+
const params = isRecord(request.params) ? request.params : {};
|
|
56
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
57
|
+
const args = isRecord(params.arguments) ? params.arguments : {};
|
|
58
|
+
const result = await callMcpTool(name, args, repoRoot);
|
|
59
|
+
respond(request.id, {
|
|
60
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
61
|
+
structuredContent: result
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (request.id !== undefined) {
|
|
66
|
+
respond(request.id, undefined, errorPayload(-32601, `Method not found: ${request.method ?? ""}`));
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
respond(request.id, undefined, errorPayload(-32000, error instanceof Error ? error.message : String(error)));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function respond(id: JsonRpcRequest["id"], result?: unknown, error?: unknown): void {
|
|
74
|
+
process.stdout.write(`${JSON.stringify({
|
|
75
|
+
jsonrpc: "2.0",
|
|
76
|
+
id: id ?? null,
|
|
77
|
+
...(error ? { error } : { result })
|
|
78
|
+
})}\n`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function errorPayload(code: number, message: string): { code: number; message: string } {
|
|
82
|
+
return { code, message };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
86
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
87
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { createControllerHost } from "../../core/controller-host.js";
|
|
2
|
+
import type { AgentTimelineSource, WorkerType } from "../../core/types.js";
|
|
3
|
+
|
|
4
|
+
export interface McpToolDefinition {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object";
|
|
9
|
+
properties: Record<string, unknown>;
|
|
10
|
+
required?: string[];
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const emptySchema = { type: "object" as const, properties: {} };
|
|
15
|
+
|
|
16
|
+
export const MCP_TOOLS: McpToolDefinition[] = [
|
|
17
|
+
tool("loop_status", "Return current loop status.", emptySchema),
|
|
18
|
+
tool("loop_next_action", "Return the next recommended loop action.", emptySchema),
|
|
19
|
+
tool("loop_run_until_gate", "Start a background run until the next gate.", tokenSchema()),
|
|
20
|
+
tool("loop_resume", "Resume one loop step.", tokenSchema()),
|
|
21
|
+
tool("loop_stop", "Stop the current run.", tokenSchema()),
|
|
22
|
+
tool("loop_step", "Advance one loop step.", tokenSchema()),
|
|
23
|
+
tool("loop_list_gates", "List gates newest-first.", emptySchema),
|
|
24
|
+
tool("loop_explain_gate", "Explain one gate.", stringIdSchema("gateId")),
|
|
25
|
+
tool("loop_approve_gate", "Approve one gate with an operator note.", noteSchema("gateId")),
|
|
26
|
+
tool("loop_reject_gate", "Reject one gate with an operator note.", noteSchema("gateId")),
|
|
27
|
+
tool("loop_list_runs", "List persisted runs.", {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: { limit: { type: "number" } }
|
|
30
|
+
}),
|
|
31
|
+
tool("loop_agent_timeline", "List normalized agent timeline entries.", {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
cursor: { type: "string" },
|
|
35
|
+
limit: { type: "number" },
|
|
36
|
+
sources: {
|
|
37
|
+
type: "array",
|
|
38
|
+
items: { type: "string", enum: ["event", "worker_event", "worker", "state", "gate", "artifact", "decision"] }
|
|
39
|
+
},
|
|
40
|
+
runId: { type: "string" },
|
|
41
|
+
workerId: { type: "string" }
|
|
42
|
+
}
|
|
43
|
+
}),
|
|
44
|
+
tool("loop_read_artifact", "Read a persisted artifact by id.", stringIdSchema("artifactId")),
|
|
45
|
+
tool("loop_get_pr_status", "Return stored PR status for current run.", emptySchema),
|
|
46
|
+
tool("loop_get_ci_status", "Return stored CI checks for current run.", emptySchema),
|
|
47
|
+
tool("loop_get_review_comments", "Return stored review comments for current run.", emptySchema),
|
|
48
|
+
tool("loop_spawn_worker", "Spawn or dry-run a delegated worker.", {
|
|
49
|
+
type: "object",
|
|
50
|
+
required: ["type", "token"],
|
|
51
|
+
properties: {
|
|
52
|
+
type: { type: "string", enum: ["planner", "implementation", "review-fix", "ci-fix", "reviewer"] },
|
|
53
|
+
dryRun: { type: "boolean" },
|
|
54
|
+
token: { type: "string" }
|
|
55
|
+
}
|
|
56
|
+
}),
|
|
57
|
+
tool("loop_open_dashboard", "Return dashboard URL or unavailable status.", emptySchema)
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/** Dispatch an MCP tool call to the shared controller. */
|
|
61
|
+
export async function callMcpTool(name: string, args: Record<string, unknown>, repoRoot: string): Promise<unknown> {
|
|
62
|
+
const host = getControllerHost(repoRoot);
|
|
63
|
+
const controller = host.getController();
|
|
64
|
+
if (name === "loop_status") return controller.loopStatus();
|
|
65
|
+
if (name === "loop_next_action") return controller.loopNextAction();
|
|
66
|
+
if (name === "loop_run_until_gate") return controller.loopRunUntilGate(optionalString(args, "token"));
|
|
67
|
+
if (name === "loop_resume") return await controller.loopResume(optionalString(args, "token"));
|
|
68
|
+
if (name === "loop_stop") return controller.loopStop(optionalString(args, "token"));
|
|
69
|
+
if (name === "loop_step") return await controller.loopStep(optionalString(args, "token"));
|
|
70
|
+
if (name === "loop_list_gates") return controller.loopListGates();
|
|
71
|
+
if (name === "loop_explain_gate") return controller.loopExplainGate(requiredString(args, "gateId"));
|
|
72
|
+
if (name === "loop_approve_gate") return controller.loopApproveGate(requiredString(args, "gateId"), gateDecisionArgs(args), optionalString(args, "token"));
|
|
73
|
+
if (name === "loop_reject_gate") return controller.loopRejectGate(requiredString(args, "gateId"), gateDecisionArgs(args), optionalString(args, "token"));
|
|
74
|
+
if (name === "loop_list_runs") return controller.loopListRuns(optionalNumber(args, "limit"));
|
|
75
|
+
if (name === "loop_agent_timeline") return controller.loopAgentTimeline({
|
|
76
|
+
...optionalStringObject(args, "cursor"),
|
|
77
|
+
...optionalNumberObject(args, "limit"),
|
|
78
|
+
...optionalStringObject(args, "runId"),
|
|
79
|
+
...optionalStringObject(args, "workerId"),
|
|
80
|
+
...optionalSourcesObject(args)
|
|
81
|
+
});
|
|
82
|
+
if (name === "loop_read_artifact") return controller.loopReadArtifact(requiredString(args, "artifactId"));
|
|
83
|
+
if (name === "loop_get_pr_status") return controller.loopGetPrStatus();
|
|
84
|
+
if (name === "loop_get_ci_status") return controller.loopGetCiStatus();
|
|
85
|
+
if (name === "loop_get_review_comments") return controller.loopGetReviewComments();
|
|
86
|
+
if (name === "loop_spawn_worker") return await controller.loopSpawnWorker(requiredWorkerType(args), optionalBoolean(args, "dryRun") ?? true, optionalString(args, "token"));
|
|
87
|
+
if (name === "loop_open_dashboard") return controller.loopOpenDashboard();
|
|
88
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const hosts = new Map<string, ReturnType<typeof createControllerHost>>();
|
|
92
|
+
const MAX_HOSTS = 16;
|
|
93
|
+
|
|
94
|
+
function getControllerHost(repoRoot: string): ReturnType<typeof createControllerHost> {
|
|
95
|
+
const existing = hosts.get(repoRoot);
|
|
96
|
+
if (existing) {
|
|
97
|
+
hosts.delete(repoRoot);
|
|
98
|
+
hosts.set(repoRoot, existing);
|
|
99
|
+
return existing;
|
|
100
|
+
}
|
|
101
|
+
if (hosts.size >= MAX_HOSTS) {
|
|
102
|
+
const oldestKey = hosts.keys().next().value as string | undefined;
|
|
103
|
+
if (oldestKey) {
|
|
104
|
+
hosts.get(oldestKey)?.dispose();
|
|
105
|
+
hosts.delete(oldestKey);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const host = createControllerHost({ repoRoot });
|
|
109
|
+
hosts.set(repoRoot, host);
|
|
110
|
+
return host;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function tool(name: string, description: string, inputSchema: McpToolDefinition["inputSchema"]): McpToolDefinition {
|
|
114
|
+
return { name, description, inputSchema };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function stringIdSchema(name: string): McpToolDefinition["inputSchema"] {
|
|
118
|
+
return {
|
|
119
|
+
type: "object",
|
|
120
|
+
required: [name],
|
|
121
|
+
properties: { [name]: { type: "string" } }
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function noteSchema(idName: string): McpToolDefinition["inputSchema"] {
|
|
126
|
+
return {
|
|
127
|
+
type: "object",
|
|
128
|
+
required: [idName, "note", "token"],
|
|
129
|
+
properties: {
|
|
130
|
+
[idName]: { type: "string" },
|
|
131
|
+
note: { type: "string" },
|
|
132
|
+
source: { type: "string", enum: ["cli", "api", "ui", "nl"] },
|
|
133
|
+
payload: { type: "object" },
|
|
134
|
+
token: { type: "string" }
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function tokenSchema(): McpToolDefinition["inputSchema"] {
|
|
140
|
+
return {
|
|
141
|
+
type: "object",
|
|
142
|
+
required: ["token"],
|
|
143
|
+
properties: { token: { type: "string" } }
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function requiredString(args: Record<string, unknown>, name: string): string {
|
|
148
|
+
const value = args[name];
|
|
149
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
150
|
+
throw new Error(`${name} is required`);
|
|
151
|
+
}
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function optionalNumber(args: Record<string, unknown>, name: string): number | undefined {
|
|
156
|
+
const value = args[name];
|
|
157
|
+
return typeof value === "number" ? value : undefined;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function optionalNumberObject(args: Record<string, unknown>, name: string): Record<string, number> {
|
|
161
|
+
const value = optionalNumber(args, name);
|
|
162
|
+
return value === undefined ? {} : { [name]: value };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function optionalBoolean(args: Record<string, unknown>, name: string): boolean | undefined {
|
|
166
|
+
const value = args[name];
|
|
167
|
+
return typeof value === "boolean" ? value : undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function optionalString(args: Record<string, unknown>, name: string): string | undefined {
|
|
171
|
+
const value = args[name];
|
|
172
|
+
return typeof value === "string" ? value : undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function optionalStringObject(args: Record<string, unknown>, name: string): Record<string, string> {
|
|
176
|
+
const value = optionalString(args, name);
|
|
177
|
+
return value === undefined ? {} : { [name]: value };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function gateDecisionArgs(args: Record<string, unknown>): { note: string; source?: "cli" | "api" | "ui" | "nl"; payload?: Record<string, unknown> } {
|
|
181
|
+
const source = optionalString(args, "source");
|
|
182
|
+
const payload = args.payload;
|
|
183
|
+
return {
|
|
184
|
+
note: requiredString(args, "note"),
|
|
185
|
+
source: source === "cli" || source === "api" || source === "ui" || source === "nl" ? source : "nl",
|
|
186
|
+
payload: typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload as Record<string, unknown> : {}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function optionalSourcesObject(args: Record<string, unknown>): { sources?: AgentTimelineSource[] } {
|
|
191
|
+
const value = args.sources;
|
|
192
|
+
if (!Array.isArray(value)) {
|
|
193
|
+
return {};
|
|
194
|
+
}
|
|
195
|
+
const sources = value.filter((item): item is AgentTimelineSource => typeof item === "string");
|
|
196
|
+
return sources.length ? { sources } : {};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function requiredWorkerType(args: Record<string, unknown>): WorkerType {
|
|
200
|
+
const value = requiredString(args, "type");
|
|
201
|
+
if (["planner", "implementation", "review-fix", "ci-fix", "reviewer"].includes(value)) {
|
|
202
|
+
return value as WorkerType;
|
|
203
|
+
}
|
|
204
|
+
throw new Error(`Unsupported worker type: ${value}`);
|
|
205
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://holo-codex.local/schemas/config.schema.json",
|
|
4
|
+
"title": "AgentLoopConfig",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["repoId"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"repoId": { "type": "string", "minLength": 1 },
|
|
10
|
+
"locale": { "enum": ["zh-CN", "en-US", "system"] },
|
|
11
|
+
"loopShape": { "enum": ["pr-loop", "generic-loop"] },
|
|
12
|
+
"workflowProfile": {
|
|
13
|
+
"enum": [
|
|
14
|
+
"default_pr_loop",
|
|
15
|
+
"docs_only_loop",
|
|
16
|
+
"review_fix_loop",
|
|
17
|
+
"release_ready_loop",
|
|
18
|
+
"research_report_loop",
|
|
19
|
+
"document_preparation_loop",
|
|
20
|
+
"repo_hygiene_loop",
|
|
21
|
+
"weekly_review_loop",
|
|
22
|
+
"data_extraction_loop"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"roleProfile": { "enum": ["default_pr_roles"] },
|
|
26
|
+
"baseBranch": { "type": "string", "minLength": 1 },
|
|
27
|
+
"branchPrefix": { "type": "string", "minLength": 1 },
|
|
28
|
+
"plansDir": { "type": "string", "minLength": 1 },
|
|
29
|
+
"lintCommand": { "type": "string", "minLength": 1 },
|
|
30
|
+
"testCommand": { "type": "string", "minLength": 1 },
|
|
31
|
+
"gitnexusRepo": { "type": "string", "minLength": 1 },
|
|
32
|
+
"gitnexusRequired": { "type": "boolean" },
|
|
33
|
+
"requiredChecks": { "type": "array", "items": { "type": "string" } },
|
|
34
|
+
"requireReviewApproval": { "type": "boolean" },
|
|
35
|
+
"autonomyMode": {
|
|
36
|
+
"enum": ["supervised", "autonomous_until_gate", "autonomous_until_terminal"]
|
|
37
|
+
},
|
|
38
|
+
"mergeMode": {
|
|
39
|
+
"enum": ["manual", "conditional", "disabled"]
|
|
40
|
+
},
|
|
41
|
+
"notifyMode": {
|
|
42
|
+
"enum": ["all_gates", "important_only", "blockers_only"]
|
|
43
|
+
},
|
|
44
|
+
"reviewHandling": {
|
|
45
|
+
"enum": ["fix_scoped_and_carry_forward", "ask_on_any_review", "require_zero_open_findings"]
|
|
46
|
+
},
|
|
47
|
+
"carryoverTarget": { "type": "string", "minLength": 1 },
|
|
48
|
+
"allowAutoMerge": { "type": "boolean" },
|
|
49
|
+
"maxReviewFixRounds": { "type": "integer", "minimum": 0 },
|
|
50
|
+
"maxTestFixRounds": { "type": "integer", "minimum": 0 },
|
|
51
|
+
"maxCiReruns": { "type": "integer", "minimum": 0 },
|
|
52
|
+
"commandTimeoutMs": { "type": "integer", "minimum": 1 },
|
|
53
|
+
"commandOutputLimitBytes": { "type": "integer", "minimum": 1 },
|
|
54
|
+
"githubRetryMaxAttempts": { "type": "integer", "minimum": 1 },
|
|
55
|
+
"githubRetryBaseDelayMs": { "type": "integer", "minimum": 1 },
|
|
56
|
+
"reviewCiPollIntervalMs": { "type": "integer", "minimum": 1 },
|
|
57
|
+
"reviewCiMaxWaitMs": { "type": "integer", "minimum": 1 },
|
|
58
|
+
"workerBackend": { "enum": ["codex-exec", "codex-app-server"] },
|
|
59
|
+
"workerTimeoutMs": { "type": "integer", "minimum": 1 },
|
|
60
|
+
"workerMaxRetries": { "type": "integer", "minimum": 0 },
|
|
61
|
+
"workerEphemeral": { "type": "boolean" },
|
|
62
|
+
"protectedPaths": { "type": "array", "items": { "type": "string" } },
|
|
63
|
+
"dashboard": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"required": ["enabled", "host"],
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"properties": {
|
|
68
|
+
"enabled": { "type": "boolean" },
|
|
69
|
+
"host": { "type": "string", "minLength": 1 },
|
|
70
|
+
"port": { "type": "integer", "minimum": 1, "maximum": 65535 }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://holo-codex.local/schemas/marketplace.schema.json",
|
|
4
|
+
"title": "CodexRepoMarketplace",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["name", "interface", "plugins"],
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": { "const": "codex-auto-pr-loop" },
|
|
10
|
+
"interface": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"required": ["displayName"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"displayName": { "const": "HOLO-Codex" }
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"plugins": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"minItems": 1,
|
|
20
|
+
"contains": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"required": ["name", "source", "policy", "category"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"name": { "const": "autonomous-pr-loop" },
|
|
25
|
+
"source": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"required": ["source", "path"],
|
|
28
|
+
"properties": {
|
|
29
|
+
"source": { "const": "local" },
|
|
30
|
+
"path": { "const": "./plugins/autonomous-pr-loop" }
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"policy": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"required": ["installation", "authentication"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"installation": { "const": "AVAILABLE" },
|
|
38
|
+
"authentication": { "const": "ON_INSTALL" }
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"category": { "const": "Engineering" }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://holo-codex.local/schemas/plugin.schema.json",
|
|
4
|
+
"title": "CodexPluginManifest",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["name", "version", "description", "skills", "interface"],
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": { "const": "autonomous-pr-loop" },
|
|
10
|
+
"version": { "type": "string", "minLength": 1 },
|
|
11
|
+
"description": { "type": "string", "minLength": 1 },
|
|
12
|
+
"skills": { "const": "./skills/" },
|
|
13
|
+
"interface": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["displayName", "shortDescription", "category", "capabilities", "defaultPrompt"],
|
|
16
|
+
"additionalProperties": true,
|
|
17
|
+
"properties": {
|
|
18
|
+
"displayName": { "const": "HOLO-Codex" },
|
|
19
|
+
"shortDescription": { "type": "string", "minLength": 1 },
|
|
20
|
+
"category": { "const": "Engineering" },
|
|
21
|
+
"capabilities": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"contains": { "const": "Write" }
|
|
24
|
+
},
|
|
25
|
+
"defaultPrompt": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"contains": { "const": "进入 HOLO-Codex,继续跑到 gate" }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|